[Playwright] Hướng dẫn tương tác với checkbox

Checkbox là một trong những element phổ biến nhất trong web testing, nhưng không phải lúc nào việc tương tác với chúng cũng đơn giản như ta tưởng. Bài viết này sẽ hướng dẫn chi tiết cách làm việc với checkbox trong Playwright TypeScript, từ cơ bản đến nâng cao.
Các phương thức cơ bản để làm việc với Checkbox
Playwright cung cấp ba phương thức chính để tương tác với checkbox. Tất cả các ví dụ dưới đây sử dụng page.locator() – cách được khuyến khích trong Playwright hiện đại.
1. Phương thức check()
Phương thức check() được sử dụng để đánh dấu checkbox nếu nó chưa được chọn. Nếu checkbox đã được chọn, phương thức này không thực hiện hành động gì.
import { test, expect } from '@playwright/test';
test('Check a checkbox', async ({ page }) => {
await page.goto('https://example.com/form');
// Check checkbox bằng id
await page.locator('#terms-checkbox').check();
// Check checkbox bằng label text
await page.locator('text=I agree to terms and conditions').check();
// Check checkbox bằng CSS selector
await page.locator('input[type="checkbox"][name="newsletter"]').check();
// Check checkbox bằng role
await page.locator('role=checkbox[name="Subscribe to newsletter"]').check();
// Verify checkbox đã được check
await expect(page.locator('#terms-checkbox')).toBeChecked();
});
2. Phương thức uncheck()
Phương thức uncheck() dùng để bỏ chọn checkbox nếu nó đang được chọn. Tương tự như check(), nếu checkbox chưa được chọn, phương thức này không làm gì.
test('Uncheck a checkbox', async ({ page }) => {
await page.goto('https://example.com/form');
const newsletterCheckbox = page.locator('#newsletter-checkbox');
// Giả sử checkbox ban đầu đã được check
await newsletterCheckbox.check();
// Uncheck checkbox
await newsletterCheckbox.uncheck();
// Verify checkbox đã được uncheck
await expect(newsletterCheckbox).not.toBeChecked();
});
3. Phương thức setChecked()
Phương thức setChecked() là phương thức linh hoạt nhất, cho phép bạn đặt trạng thái của checkbox một cách rõ ràng thông qua tham số boolean.
test('Set checkbox state with setChecked', async ({ page }) => {
await page.goto('https://example.com/form');
const notificationCheckbox = page.locator('#notification-checkbox');
// Set checkbox thành checked
await notificationCheckbox.setChecked(true);
// Set checkbox thành unchecked
await notificationCheckbox.setChecked(false);
// Toggle checkbox state
const currentState = await notificationCheckbox.isChecked();
await notificationCheckbox.setChecked(!currentState);
// Verify với assertion
await expect(notificationCheckbox).toBeChecked();
});
Options chi tiết cho check(), uncheck() và setChecked()
Cả ba phương thức đều hỗ trợ các options giống nhau để xử lý các tình huống phức tạp.
Option: force
Option force cho phép bỏ qua các kiểm tra actionability. Hữu ích khi element bị che khuất hoặc có custom styling phức tạp.
test('Using force option', async ({ page }) => {
await page.goto('https://example.com/custom-form');
// Checkbox bị overlay che phủ
const hiddenCheckbox = page.locator('#hidden-checkbox');
// Sẽ fail nếu checkbox bị che
try {
await hiddenCheckbox.check({ timeout: 3000 });
} catch (error) {
console.log('Cannot check hidden checkbox normally');
}
// Force check bỏ qua visibility checks
await hiddenCheckbox.check({ force: true });
// Force uncheck
await hiddenCheckbox.uncheck({ force: true });
// Force với setChecked
await hiddenCheckbox.setChecked(true, { force: true });
// Verify
await expect(hiddenCheckbox).toBeChecked();
});
Option: position
Option position cho phép click vào vị trí cụ thể là vị trí tương đối tới góc trái trên của element padding box.
test('Using position option', async ({ page }) => {
await page.goto('https://example.com/large-checkbox');
const largeCheckbox = page.locator('#large-custom-checkbox');
// Click vào vị trí cụ thể của checkbox
await largeCheckbox.check({
position: { x: 10, y: 10 }
});
// Useful cho custom checkbox với clickable area lớn
await page.locator('.checkbox-container').check({
position: { x: 20, y: 15 }
});
// Uncheck với position
await largeCheckbox.uncheck({
position: { x: 10, y: 10 }
});
// setChecked với position
await largeCheckbox.setChecked(false, {
position: { x: 5, y: 5 }
});
});
Option: timeout
Option timeout đặt thời gian chờ tối đa cho action. Mặc định là 30 giây (có thể config trong playwright.config.ts).
test('Using timeout option', async ({ page }) => {
await page.goto('https://example.com/slow-loading');
// Checkbox load chậm
const slowCheckbox = page.locator('#slow-checkbox');
// Tăng timeout cho element load chậm
await slowCheckbox.check({ timeout: 60_000 }); // 60 seconds
// Giảm timeout cho fast fail
try {
await page.locator('#non-existent').check({ timeout: 1000 });
} catch (error) {
console.log('Element not found within 1 second');
}
// Timeout với uncheck
await slowCheckbox.uncheck({ timeout: 45000 });
// Thêm "_" để phân tách phần nghìn cho dễ nhìn. Vẫn đúng syntax
await slowCheckbox.uncheck({ timeout: 45_000 });
// Timeout với setChecked
await slowCheckbox.setChecked(true, { timeout: 30000 });
});
Option: trial
Option trial chỉ thực hiện actionability checks mà không thực sự perform action. Hữu ích để verify element có thể tương tác được.
test('Using trial option', async ({ page }) => {
await page.goto('https://example.com/form');
const checkbox = page.locator('#terms-checkbox');
// Kiểm tra xem có thể check được không (dry run)
await checkbox.check({ trial: true });
console.log('Checkbox is checkable');
// Checkbox vẫn chưa được check thực sự
expect(await checkbox.isChecked()).toBeFalsy();
// Bây giờ thực sự check
await checkbox.check();
expect(await checkbox.isChecked()).toBeTruthy();
// Trial với uncheck
await checkbox.uncheck({ trial: true });
// Checkbox vẫn còn checked
expect(await checkbox.isChecked()).toBeTruthy();
// Trial với setChecked
await checkbox.setChecked(false, { trial: true });
// State không thay đổi
expect(await checkbox.isChecked()).toBeTruthy();
// Thực sự uncheck
await checkbox.uncheck();
});
Kết hợp nhiều options
test('Combining multiple options', async ({ page }) => {
await page.goto('https://example.com/complex-form');
const complexCheckbox = page.locator('#complex-checkbox');
// Kết hợp force và timeout
await complexCheckbox.check({
force: true,
timeout: 45000
});
// Kết hợp position và timeout
await complexCheckbox.uncheck({
position: { x: 15, y: 15 },
timeout: 30000
});
// Trial run với timeout ngắn
try {
await complexCheckbox.check({
trial: true,
timeout: 5000
});
console.log('Element is actionable');
} catch (error) {
console.log('Element not actionable within 5 seconds');
}
// setChecked với multiple options
await complexCheckbox.setChecked(true, {
force: true,
position: { x: 10, y: 10 },
timeout: 20000
});
});
Xử lý các tình huống phức tạp với Options
Custom checkbox với overlay
test('Handle checkbox with overlay using options', async ({ page }) => {
await page.goto('https://example.com/modal-form');
// Modal overlay che checkbox
await page.locator('#open-modal').click();
const modalCheckbox = page.locator('.modal-content #agreement');
// Thử check normal - có thể fail
try {
await modalCheckbox.check({ timeout: 3000 });
} catch {
// Nếu fail, dùng force
await modalCheckbox.check({ force: true });
}
// Hoặc check với specific position để avoid overlay
await modalCheckbox.check({
position: { x: 5, y: 5 },
timeout: 10000
});
});
Dynamic loading checkboxes
test('Handle dynamically loaded checkboxes', async ({ page }) => {
await page.goto('https://example.com/lazy-load');
// Trigger lazy loading
await page.locator('#load-more').click();
// Wait và check với extended timeout
const dynamicCheckboxes = page.locator('.dynamic-item input[type="checkbox"]');
// Check all với different timeouts
const count = await dynamicCheckboxes.count();
for (let i = 0; i < count; i++) {
await dynamicCheckboxes.nth(i).check({
timeout: 20000 // Extended timeout cho mỗi checkbox
});
}
// Verify với trial option trước
const criticalCheckbox = page.locator('#critical-option');
await criticalCheckbox.check({ trial: true, timeout: 30000 });
// Nếu trial pass, thực sự check
await criticalCheckbox.check({ timeout: 30000 });
});
Checkbox trong scrollable container
test('Handle checkbox in scrollable area', async ({ page }) => {
await page.goto('https://example.com/long-form');
const checkboxInScroll = page.locator('#checkbox-at-bottom');
// Auto scroll và check
await checkboxInScroll.check(); // Playwright auto scrolls
// Hoặc explicit scroll và check với position
await checkboxInScroll.scrollIntoViewIfNeeded();
await checkboxInScroll.check({
position: { x: 10, y: 10 },
timeout: 15000
});
// Force check nếu scroll có vấn đề
await checkboxInScroll.check({
force: true,
timeout: 20000
});
});
Best Practices với Options
1. Progressive enhancement strategy
test('Progressive enhancement with options', async ({ page }) => {
await page.goto('https://example.com/form');
const checkbox = page.locator('#important-checkbox');
// Bước 1: Thử normal check
try {
await checkbox.check({ timeout: 5000 });
} catch {
// Bước 2: Thử với extended timeout
try {
await checkbox.check({ timeout: 15000 });
} catch {
// Bước 3: Force as last resort
await checkbox.check({ force: true });
console.warn('Had to use force option for checkbox');
}
}
});
2. Validation before action
test('Validate before action using trial', async ({ page }) => {
await page.goto('https://example.com/form');
async function safeCheck(locator: Locator) {
// Validate với trial first
try {
await locator.check({ trial: true, timeout: 5000 });
// Nếu trial pass, proceed với actual check
await locator.check();
return true;
} catch (error) {
console.error(`Cannot check element: ${error.message}`);
return false;
}
}
const checkbox1 = page.locator('#checkbox1');
const checkbox2 = page.locator('#checkbox2');
await safeCheck(checkbox1);
await safeCheck(checkbox2);
});
3. Config-driven timeouts
// test-config.ts
export const TIMEOUTS = {
fast: 5000,
normal: 15000,
slow: 30000,
verySlow: 60000
};
// test file
import { TIMEOUTS } from './test-config';
test('Use configured timeouts', async ({ page }) => {
await page.goto('https://example.com/form');
// Fast elements
await page.locator('#quick-checkbox').check({
timeout: TIMEOUTS.fast
});
// Normal elements
await page.locator('#normal-checkbox').check({
timeout: TIMEOUTS.normal
});
// Slow loading elements
await page.locator('#slow-checkbox').check({
timeout: TIMEOUTS.slow
});
// Very slow or problematic elements
await page.locator('#problematic-checkbox').setChecked(true, {
timeout: TIMEOUTS.verySlow,
force: true
});
});
4. Helper functions với default options
// helpers/checkbox-helper.ts
import { Locator } from '@playwright/test';
export class CheckboxHelper {
static async safeCheck(
locator: Locator,
options = { timeout: 15000, force: false }
) {
try {
await locator.check(options);
} catch (error) {
console.warn(`Check failed with options, trying force: ${error.message}`);
await locator.check({ ...options, force: true });
}
}
static async checkWithRetry(
locator: Locator,
maxRetries = 3,
options = { timeout: 10000 }
) {
for (let i = 0; i < maxRetries; i++) {
try {
await locator.check(options);
return;
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`Retry ${i + 1}/${maxRetries}`);
await locator.page().waitForTimeout(1000);
}
}
}
static async checkAtPosition(
locator: Locator,
x: number,
y: number,
timeout = 15000
) {
await locator.check({
position: { x, y },
timeout
});
}
}
// Usage in test
test('Use checkbox helper', async ({ page }) => {
await page.goto('https://example.com/form');
const checkbox = page.locator('#my-checkbox');
await CheckboxHelper.safeCheck(checkbox);
await CheckboxHelper.checkWithRetry(checkbox, 5);
await CheckboxHelper.checkAtPosition(checkbox, 10, 10);
});
