NỘI DUNG BÀI HỌC

☑️ Dùng setChecked() cho checkbox/radio (role=checkbox) và click() cho element "giả".

⬇️ Phân biệt selectOption() (cho ) và click() (cho custom dropdowns).

⚠️ Xử lý alert, confirm của trình duyệt bằng page.on('dialog') (lắng nghe trước).

🖥️ Tương tác với HTML Modals (React/AntD) bằng locator (ví dụ: getByRole('dialog')).

🔄 Dùng page.once để xử lý nhiều dialogs (OK/Cancel) trong cùng một test.



Phần 1: Lựa chọn Trạng thái - Checkboxes và Radio Buttons ☑️🔘

Tương tác với checkboxes (ô đánh dấu) và radio buttons (nút chọn) là một phần cơ bản của automation. Playwright cung cấp các phương thức "thông minh" (check(), uncheck(), setChecked()) an toàn và ổn định hơn nhiều so với việc chỉ dùng click().

Vấn đề của click(): Lật (Toggle) vs. Đảm bảo (Ensure)

Tránh dùng click() trên checkboxes, vì nó là một hành động lật trạng thái (toggle).

Nếu ô đang chưa check -> click() -> ô được check.

Nếu ô đang được check -> click() -> ô bị bỏ check.

Điều này làm test của bạn mong manh (flaky). Nếu test của bạn chạy lại và ô đó vô tình đã được check từ trước, cú click() của bạn sẽ làm hỏng test.


Các Phương thức "Thông minh" (Idempotent)

Playwright cung cấp các hành động "nhất quán" (idempotent). Chúng đảm bảo một trạng thái cuối cùng, bất kể trạng thái ban đầu là gì.

locator.check(): Đảm bảo ô được check. (Nếu đã check, nó không làm gì cả).

locator.uncheck(): Đảm bảo ô bị bỏ check. (Nếu đã bỏ check, nó không làm gì cả).

locator.setChecked(boolean): Phương thức "tất cả trong một", linh hoạt và được khuyên dùng nhất.


Tương tác với Checkboxes (Ô đánh dấu) ☑️

Checkboxes cho phép bạn chọn nhiều tùy chọn. Chúng hỗ trợ đầy đủ cả hai trạng thái true và false.

await locator.setChecked(true);

Tương đương check(). Đảm bảo ô được chọn.

await locator.setChecked(false);

Tương đương uncheck(). Đảm bảo ô bị bỏ chọn.

Hành động này cực kỳ hữu ích khi bạn cần set trạng thái dựa trên một biến:

const shouldBeChecked = true; // Lấy từ file data

await page.getByLabel('Đồng ý').setChecked(shouldBeChecked);

Tương tác với Radio Buttons (Nút chọn) 🔘

Radio buttons chỉ cho phép bạn chọn một tùy chọn trong nhóm. Đây là điểm khác biệt mấu chốt.

await locator.setChecked(true); (Hoặc locator.check())

HOẠT ĐỘNG.

Đây là cách dùng chính, nó sẽ chọn (check) radio button đó.

await locator.setChecked(false);

SẼ GÂY LỖI (nếu radio đó đang được chọn).

Đây là hành vi đúng của Playwright. Nó mô phỏng chính xác hành vi người dùng. Người dùng không thể "bỏ check" (uncheck) một radio button (bạn chỉ có thể chọn một cái khác trong nhóm).

Vì Playwright không thể thực hiện hành động "bất khả thi" này, nó sẽ báo lỗi.

Kết luận: Để chọn một radio, dùng check() hoặc setChecked(true).

// Chọn tùy chọn "Medium"

await page.getByLabel('Medium').check();

// Kiểm tra "Medium" đã được chọn

await expect(page.getByLabel('Medium')).toBeChecked();

// (Quan trọng) Kiểm tra các tùy chọn khác KHÔNG được chọn

await expect(page.getByLabel('Low')).not.toBeChecked();

🎯 Các Element "Được Hỗ trợ"

Các phương thức "thông minh" (check, uncheck, setChecked) chỉ hoạt động trên các element mà Playwright hiểu là có "trạng thái được chọn".

input[type="checkbox"]: Thẻ checkbox HTML tiêu chuẩn.

input[type="radio"]: Thẻ radio button HTML tiêu chuẩn.

[role="checkbox"]: Các element tùy chỉnh (custom) được xây dựng (ví dụ: bằng thẻ <div>) nhưng tuân thủ tiêu chuẩn ARIA. Playwright đủ thông minh để click và chờ thuộc tính aria-checked thay đổi.

❌ Các Element KHÔNG Được Hỗ trợ (Giải pháp: Dùng .click())

Điều gì xảy ra nếu lập trình viên tạo một "checkbox giả" mà KHÔNG tuân thủ ARIA (tức là không có role="checkbox")?

Ví dụ (HTML xấu):

<div class="my-custom-checkbox" data-checked="false"></div>

Playwright sẽ không hiểu đây là checkbox.

Sẽ Thất Bại: await page.locator('.my-custom-checkbox').setChecked(true);

Playwright sẽ báo lỗi: Error: Element is not a checkbox or radio button.

Giải pháp (Bắt buộc): Bạn phải quay về dùng .click() và tự mình kiểm tra kết quả.

const fakeCheckbox = page.locator('.my-custom-checkbox');

// 1. Phải dùng .click()

await fakeCheckbox.click();

// 2. Phải tự kiểm tra trạng thái

// (Vì .toBeChecked() cũng sẽ không hoạt động)

await expect(fakeCheckbox).toHaveAttribute('data-checked', 'true');

Chiến lược tìm kiếm (Locating) và Kiểm tra (Verification)

Kiểm tra: Luôn dùng expect(locator).toBeChecked() để kiểm tra trạng thái. Nó sẽ tự động chờ.

Chiến lược tìm (Theo thứ tự ưu tiên):

page.getByLabel('Text liên kết'): Tốt nhất, nếu có <label>.

page.getByRole('checkbox' | 'radio', { name: 'Text' }): Tốt nhì.

page.getByTestId('id-test-duy-nhat'): Ổn định nhất (nếu bạn thêm được test ID).

page.locator('selector:has-text("Text")'): Linh hoạt, khi text và input nằm gần nhau.

page.locator('CSS hoặc XPath trực tiếp'): Dùng làm giải pháp cuối cùng.

 

Phần 2: Làm chủ Dropdowns (Select & Custom) ⬇️

Dropdowns là một trong những element dễ gây nhầm lẫn nhất, vì chúng có hai loại hoàn toàn khác nhau: <select> (HTML gốc) và "Custom Dropdowns" (làm bằng <div> và JavaScript).

Loại 1: Dropdown HTML Gốc (<select>)

Đây là loại dropdown "cổ điển" của trình duyệt. Chúng rất dễ tự động hóa vì Playwright cung cấp một hàm chuyên dụng: locator.selectOption().

Cách nhận biết: Bạn sẽ thấy thẻ <select> và <option> trong DOM.

 

<select id="select-color">

  <option value="red">Màu Đỏ</option>

  <option value="green">Màu Xanh</option>

  <option value="blue">Màu Xanh Dương</option>

</select>

Cách chọn (Cách tốt nhất)

Bạn không cần click(). selectOption() sẽ tự động tìm và chọn.

const colorSelect = page.locator('#select-color');

// Cách 1: Chọn bằng Text (Khuyên dùng)

// Rõ ràng nhất, mô phỏng cách người dùng nghĩ

await colorSelect.selectOption('Màu Xanh');


// Cách 2: Chọn bằng Value (Ổn định nhất)

// Tốt nếu text thay đổi, nhưng value ("red") thì không

await colorSelect.selectOption({ value: 'red' });


// Cách 3: Chọn bằng Index (Không khuyến khích)

// Rất mong manh, chỉ dùng khi không còn cách nào khác

await colorSelect.selectOption({ index: 2 }); // Sẽ chọn "Màu Xanh Dương"

 

Chọn nhiều giá trị (Multi-Select)

Nếu thẻ <select> có thuộc tính multiple, bạn chỉ cần truyền một mảng:

await colorSelect.selectOption(['Màu Đỏ', 'Màu Xanh Dương']);

 

Loại 2: "Custom Dropdowns" (React, AntD, react-select)

Đây là loại dropdown phổ biến nhất hiện nay. Chúng không dùng thẻ <select>. Chúng được tạo bằng <div>, <span>, <input> và rất nhiều JavaScript.

Cách nhận biết: Bạn sẽ thấy các div, input (thường bị ẩn), và khi bạn click, một div (menu) khác sẽ xuất hiện ở cuối <body>.

Với loại này, bạn bắt buộc phải click() để mở.

Chiến lược 1: Click và Chọn (Hành động trực tiếp)

Đây là cách làm hiệu quả nhất, mô phỏng chính xác người dùng. Bạn không "lấy hết giá trị".

Tìm và Click để Mở: Tìm đúng element "có thể click" (trigger) để mở menu.

Chờ và Chọn: Click vào lựa chọn (option) mong muốn.

const countrySelect = page.locator('.ant-select-selector');

// 1. Click để mở

await countrySelect.click();

// 2. Chờ và Chọn

// Playwright sẽ tự động chờ cho đến khi 'Vietnam' xuất hiện

await page.getByRole('option', { name: 'Vietnam' }).click();

 

Chiến lược 2: Gõ và Nhấn Enter (Nếu hỗ trợ tìm kiếm)

Nếu dropdown cho phép gõ, đây là cách nhanh nhất.

// 1. Tìm input (thường là input ẩn)

const countryInput = page.locator('#country-input');

// 2. Gõ để lọc

await countryInput.fill('Vietnam');

// 3. Nhấn Enter để chọn

await countryInput.press('Enter');

 

Chiến lược 3: "Lấy hết giá trị" (Chỉ để kiểm tra dữ liệu)

Như bạn đã hỏi, đây là "thuật toán" để lấy tất cả các giá trị. Bạn chỉ nên dùng cách này để KIỂM TRA (verify) xem dropdown có đủ các lựa chọn hay không, không nên dùng nó để chọn một giá trị.

test('Kiểm tra các lựa chọn trong dropdown', async ({ page }) => {

  // 1. Click để mở

  await page.locator('.ant-select-selector').click();

 // 2. Chờ menu (list) xuất hiện

  // Tìm cái list cha (ví dụ: cái list có id là '..._list')

  const dropdownMenu = page.locator('.ant-select-dropdown');

  await expect(dropdownMenu).toBeVisible();

  // 3. Lấy TẤT CẢ các lựa chọn bên trong nó

  // dùng .all() để lấy một mảng Locator

  const options = dropdownMenu.locator('[role="option"]');

  // 4. Lấy text của tất cả

  // (Đây là cách Playwright 1.44+ khuyến nghị thay vì .allTextContents())

  const allOptionTexts = await options.evaluateAll(elements =>

    elements.map(el => el.textContent)

  );

  // allOptionTexts sẽ là: ['USA', 'Vietnam', 'Japan', ...]

  // 5. Kiểm tra (Assert)

  expect(allOptionTexts).toContain('Vietnam');

  expect(allOptionTexts.length).toBe(250); // Kiểm tra tổng số quốc gia

});

Chiến lược 4: Xử lý Scroll (Khi có quá nhiều lựa chọn)

Đôi khi, dropdown có "infinite scroll" (cuộn vô hạn) hoặc "virtualization" (ảo hóa) - nó chỉ tải 20 lựa chọn đầu tiên. Nếu bạn tìm "Vietnam" (ở cuối danh sách), nó sẽ thất bại.

Lúc này, bạn phải mô phỏng hành vi cuộn.

// 1. Click để mở

await page.locator('.ant-select-selector').click();


// 2. Định nghĩa locator của lựa chọn bạn muốn

const targetOption = page.getByRole('option', { name: 'Vietnam' });

const dropdownMenu = page.locator('.ant-select-dropdown');


// 3. "Thuật toán" cuộn (Scroll)

// Lặp tối đa 10 lần cuộn

for (let i = 0; i < 10; i++) {

  // 3a. Kiểm tra xem lựa chọn đã hiển thị chưa

  if (await targetOption.isVisible()) {

    break; // Nếu thấy, thoát khỏi vòng lặp

  }

  // 3b. Nếu chưa thấy, cuộn xuống

  // Gửi sự kiện 'wheel' (lăn chuột) xuống cuối menu

  await dropdownMenu.dispatchEvent('wheel', { deltaY: 500 }); // deltaY > 0 là cuộn xuống

  await page.waitForTimeout(200); // Chờ (một chút) để DOM render

}


// 4. Click

// (Sau vòng lặp, element chắc chắn đã visible)

await targetOption.click();


Phần 3: Xử lý ⚠️ Alerts, Modals & Dialogs

 

Browser Dialogs (Cảnh báo trình duyệt): Là các hộp thoại alert, confirm, prompt. Chúng không phải là HTML và chặn (block) toàn bộ trang web.

HTML Modals (Modal của UI Framework): Là các pop-up của React, AntD, Vue... Chúng chỉ là HTML (các thẻ <div>) được style (CSS) để trông giống pop-up.

Cách xử lý Browser Dialogs (alert, confirm, prompt)

Vì đây là các sự kiện JavaScript bên ngoài DOM, bạn không thể dùng locator (như getByText) để tìm chúng.

Bạn bắt buộc phải dùng trình lắng nghe sự kiện page.on('dialog', ...)

💡 Quy tắc VÀNG: Lắng nghe trước, Hành động sau

Đây là điều quan trọng nhất. Vì các dialog này "chặn" (block) trang, bạn phải cài đặt trình lắng nghe (page.on) TRƯỚC khi bạn click vào nút gây ra dialog.

Xử lý window.alert (Chỉ có "OK")

Alert là đơn giản nhất, bạn chỉ có thể accept() (nhấn OK).

test('Xử lý Alert', async ({ page }) => {

  // 1. Lắng nghe TRƯỚC: Cài đặt trình xử lý

  page.on('dialog', async dialog => {

    console.log('Loại Dialog:', dialog.type()); // Sẽ in ra: 'alert'

    console.log('Nội dung:', dialog.message()); // Lấy text của alert

    // 2. Hành động: Nhấn "OK"

    await dialog.accept();

  });

  // 3. Hành động SAU: Click vào nút gây ra alert

  await page.getByRole('button', { name: 'Hiện Alert' }).click();

  // 4. (Optional) Kiểm tra kết quả

  await expect(page.locator('#alert-result')).toHaveText('Đã nhấn OK');

});

 

Xử lý window.confirm ("OK" và "Cancel")

Giờ bạn có 2 lựa chọn: dialog.accept() (OK) hoặc dialog.dismiss() (Hủy).


test('Xử lý Confirm (Nhấn Hủy)', async ({ page }) => {

  // 1. Lắng nghe TRƯỚC:

  page.on('dialog', async dialog => {

    console.log('Loại Dialog:', dialog.type()); // 'confirm'

  
    // 2. Hành động: Nhấn "Hủy"

    await dialog.dismiss();

  });

  // 3. Hành động SAU:

  await page.getByRole('button', { name: 'Hiện Confirm' }).click();

  // 4. Kiểm tra kết quả

  await expect(page.locator('#confirm-result')).toHaveText('Đã nhấn Hủy!');

});

 

Lưu ý: Nếu bạn muốn test cả 2 trường hợp (OK và Hủy) trong cùng 1 file, hãy dùng page.once('dialog', ...) để nó chỉ lắng nghe 1 lần cho mỗi hành động.


Xử lý window.prompt (Nhập văn bản)

Prompt cho phép bạn accept() (OK) với một giá trị văn bản, hoặc dismiss() (Hủy).

test('Xử lý Prompt', async ({ page }) => {

  // 1. Lắng nghe TRƯỚC:

  page.on('dialog', async dialog => {

    console.log('Loại Dialog:', dialog.type()); // 'prompt'

    // 2. Hành động: Nhấn "OK" và điền text

    await dialog.accept('Tên của tôi');

  });

  // 3. Hành động SAU:

  await page.getByRole('button', { name: 'Hiện Prompt' }).click();

  // 4. Kiểm tra kết quả

  await expect(page.locator('#prompt-result')).toHaveText('Chào, Tên của tôi');

});

 

 

Cách xử lý HTML Modals (React, AntD, Bootstrap...)

Đây là loại pop-up phổ biến nhất. Chúng chỉ là các thẻ <div> nằm trong DOM.

Tuyệt đối KHÔNG DÙNG page.on('dialog') cho loại này.

Bạn phải xử lý chúng như mọi element HTML khác: Dùng Locators.

"Thuật toán" xử lý Modal

Click vào nút để mở Modal.

Dùng expect(modal).toBeVisible() để chờ Modal xuất hiện. (Đây là bước chờ quan trọng).

Tương tác với các element bên trong Modal (dùng modal.getByRole(...)).

Click nút "Đóng" hoặc "OK" (bên trong Modal).

Dùng expect(modal).not.toBeVisible() để chờ Modal biến mất. (Quan trọng để đảm bảo ổn định).

Ví dụ thực tế

test('Xử lý HTML Modal', async ({ page }) => {

  // 1. Click để mở

  await page.getByRole('button', { name: 'Mở Modal' }).click();

  // 2. Tìm và chờ Modal xuất hiện

  // Cách TỐT NHẤT là dùng role="dialog" (chuẩn ARIA)

  const modal = page.getByRole('dialog');

  await expect(modal).toBeVisible();

  // 3. Tương tác BÊN TRONG Modal

  // Dùng 'modal.locator(...)' để chỉ tìm bên trong modal

  await expect(modal).toContainText('Đây là nội dung modal');

  await modal.locator('#username-in-modal').fill('Tên đăng nhập');

  // 4. Đóng Modal

  await modal.getByRole('button', { name: 'Đồng ý' }).click();


  // 5. Chờ Modal biến mất

  await expect(modal).not.toBeVisible();

});

 


Tóm tắt: Browser Dialog vs. HTML Modal

Tính chất

Browser Dialog (alert, confirm)

HTML Modal (AntD, React...)

Bản chất

Sự kiện JavaScript (Ngoài DOM)

Element HTML (Trong DOM)

Cách xử lý

page.on('dialog', ...)

page.locator(...) (vd: getByRole('dialog'))

Hành vi

Chặn (Block) toàn bộ trang

Không chặn, chỉ che phủ (overlay)

Quy tắc

Lắng nghe TRƯỚC, hành động SAU

Hành động TRƯỚC, chờ (expect) SAU




Teacher

Teacher

Nguyên Hoàng

Automation Engineer

With 7+ years of hands-on experience across multiple languages and frameworks. I'm here to share knowledge, helping you turn complex processes into simple and effective solutions.

Cộng đồng Automation Testing Việt Nam:

🌱 Telegram Automation Testing:   Cộng đồng Automation Testing
🌱 
Facebook Group Automation: Cộng đồng Automation Testing Việt Nam
🌱 
Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium
🌱 Telegram
Manual Testing:   Cộng đồng Manual Testing
🌱 
Facebook Group Manual: Cộng đồng Manual Testing Việt Nam

Chia sẻ khóa học lên trang

Bạn có thể đăng khóa học của chính bạn lên trang Anh Tester để kiếm tiền

Danh sách bài học