NỘI DUNG BÀI HỌC

🏗️ Phần 1: Nền tảng - Cấu trúc một File Test Chuyên nghiệp

🚀 Phần 2: Tăng tốc - Sử dụng Playwright Codegen



Phần 1: Codegen là gì?

Hãy tưởng tượng bạn có một người trợ lý cần mẫn ngồi cạnh, quan sát mọi hành động bạn làm trên trình duyệt (click, gõ phím, chọn dropdown), và tự động viết code Playwright tương ứng cho những hành động đó. Đó chính xác là những gì Codegen (Code Generator - Trình tạo mã) làm.

Nó là một công cụ giúp biến các tương tác thủ công của bạn trên trang web thành một kịch bản test Playwright hoàn chỉnh.

Hướng Dẫn Sử Dụng Codegen Từng Bước

Chúng ta sẽ thực hành ghi lại kịch bản đăng nhập vào trang https://hrm.anhtester.com/.

Bước 1: Khởi động Codegen

Đảm bảo bạn đã ở trong thư mục dự án Playwright của mình. Mở terminal và chạy lệnh sau:

npx playwright codegen https://hrm.anhtester.com/
  • npx playwright codegen: Là lệnh để gọi công cụ Codegen.

  • https://hrm.anhtester.com/: Là URL của trang web bạn muốn bắt đầu ghi.

Bước 2: Khám phá Giao diện Codegen

Sau khi chạy lệnh, hai cửa sổ sẽ tự động bật lên:

Cửa sổ Trình duyệt: Đây là một cửa sổ trình duyệt Chromium mới, nơi bạn sẽ thực hiện các hành động thủ công.

Cửa sổ Playwright Inspector (Codegen): Đây là "bộ não" của trợ lý. Nó hiển thị code được tạo ra trong thời gian thực khi bạn tương tác với cửa sổ trình duyệt.

Bước 3: Bắt đầu Ghi kịch bản (Recording)

Bây giờ, hãy thực hiện các hành động sau trên Cửa sổ Trình duyệt:


Click vào ô "Username": Bạn sẽ thấy cửa sổ Codegen ngay lập tức thêm vào một dòng code như await page.locator('#iusername').click();.

Gõ "admin_example" vào ô Username: Cửa sổ Codegen sẽ thêm dòng await page.locator('#iusername').fill('admin_example');.

Click và gõ "123456" vào ô Password.

Click vào nút "Login".

Bước 4: Thêm Khẳng định (Assertion) - Bước Cực kỳ Quan trọng!

Một kịch bản test không chỉ là một chuỗi hành động, nó cần phải xác minh kết quả. Codegen hỗ trợ bạn làm việc này.

Sau khi đăng nhập thành công, bạn sẽ thấy trang Dashboard.

Trên cửa sổ Playwright Inspector (Codegen), tìm đến thanh công cụ ở trên cùng và click vào nút "Assert Visibility" (biểu tượng con mắt).

Bây giờ, di chuột của bạn qua Cửa sổ Trình duyệt. Bạn sẽ thấy các element được highlight.

Click vào dòng chữ "Welcome back, Admin!".

Cửa sổ Codegen sẽ tự động thêm một dòng khẳng định: await expect(page.locator('.page-header h4')).toBeVisible();. Dòng này kiểm tra xem element đó có thực sự hiển thị sau khi đăng nhập hay không.

Bước 5: Sao chép và Lưu Code

Đến đây, bạn đã có một kịch bản test hoàn chỉnh. Code trong cửa sổ Codegen của bạn sẽ trông tương tự như sau:

import { test, expect } from '@playwright/test';

test('test', async ({ page }) => {
  await page.goto('https://hrm.anhtester.com/');
  await page.locator('#iusername').click();
  await page.locator('#iusername').fill('admin_example');
  await page.locator('#ipassword').click();
  await page.locator('#ipassword').fill('123456');
  await page.getByRole('button', { name: 'Login' }).click();
  await expect(page.locator('.page-header h4')).toBeVisible();
});



Bạn có thể click vào nút "Copy" để sao chép code này, sau đó tạo một file mới (ví dụ: hrm-login.spec.ts) trong thư mục tests/ và dán nó vào.

Bước 6: Chạy lại Kịch bản Vừa Ghi

Đóng các cửa sổ Codegen lại và chạy file test mới của bạn từ terminal:

npx playwright test tests/hrm-login.spec.ts

Playwright sẽ tự động thực hiện lại chính xác các bước bạn vừa làm.


Phân Tích Ưu Điểm và Nhược Điểm của Codegen

Codegen là một công cụ tuyệt vời, nhưng điều quan trọng là phải hiểu rõ điểm mạnh và điểm yếu của nó để sử dụng một cách thông minh.

✅ Ưu điểm (Advantages)

Tốc độ và Hiệu quả: Đây là cách nhanh nhất để tạo ra một kịch bản test brouillon. Thay vì phải tự mò mẫm DevTools để tìm locator và viết từng dòng code, bạn chỉ cần click và gõ.

Công cụ học tập tuyệt vời: Đối với người mới, Codegen là một người thầy. Nó dạy bạn cú pháp của Playwright (.fill(), .click(), getByRole()) và cách Playwright "suy nghĩ" về các locator.

Dễ dàng khám phá Locator: Khi bạn không chắc nên dùng locator nào cho một element phức tạp, chỉ cần khởi động Codegen, di chuột qua nó và xem Playwright đề xuất locator nào.

Tái hiện Bug nhanh chóng: Khi một tester thủ công tìm thấy bug, họ có thể dùng Codegen để ghi lại chính xác các bước gây ra bug và gửi file code cho lập trình viên, giúp việc sửa lỗi nhanh hơn rất nhiều.

 

❌ Nhược điểm (Disadvantages)

Code được tạo ra thường "bẩn" và khó bảo trì: Codegen tạo ra một kịch bản tuyến tính, không theo các mẫu thiết kế tốt như Page Object Model (POM). Nó lặp lại các locator và không sử dụng biến hay hàm, làm cho việc bảo trì khi giao diện thay đổi trở nên rất khó khăn.

Locator không phải lúc nào cũng tối ưu: Mặc dù Playwright cố gắng chọn locator tốt nhất (user-facing), đôi khi nó có thể chọn một locator dài, phức tạp và không ổn định (brittle). Người viết test vẫn cần có kinh nghiệm để xem lại và chọn locator tốt hơn nếu cần.

Thiếu Logic Phức tạp: Codegen chỉ ghi lại được các hành động của người dùng. Nó không thể tự tạo ra các vòng lặp (for), các câu lệnh điều kiện (if/else), các lệnh gọi API, hay xử lý dữ liệu phức tạp.

Nguy cơ Lười tư duy: Nếu quá phụ thuộc vào Codegen, bạn có thể sẽ không học được cách tự phân tích DOM, tự viết locator và tự cấu trúc một bộ test tốt. Điều này sẽ cản trở bạn trở thành một kỹ sư kiểm thử tự động giỏi.

Kết Luận: Codegen là Trợ Thủ, không phải là Người Thay Thế

Cách sử dụng Codegen thông minh và chuyên nghiệp nhất là coi nó như một trợ thủ đắc lực chứ không phải một công cụ làm thay hoàn toàn công việc của bạn.

Quy trình làm việc được khuyến khích:

Ghi (Record): Dùng Codegen để nhanh chóng ghi lại luồng cơ bản của một kịch bản và lấy các locator ban đầu.

Tái cấu trúc (Refactor): Đây là bước quan trọng nhất. Sao chép code đã tạo và tái cấu trúc nó lại theo các mẫu thiết kế tốt (như POM). Dọn dẹp các locator, đặt tên biến rõ ràng, và nhóm các bước vào các hàm có thể tái sử dụng.

Nâng cao (Enhance): Thêm vào các logic phức tạp (vòng lặp, điều kiện), các khẳng định chi tiết hơn, và tích hợp với các nguồn dữ liệu bên ngoài mà Codegen không thể làm được.Bằng cách này, bạn sẽ tận dụng được tốc độ của Codegen mà vẫn đảm bảo được chất lượng, sự ổn định và khả năng bảo trì của bộ test trong dài hạn.


Phần 2: Cách Tổ Chức Một File Test Playwright Hiệu Quả

Khi bắt đầu viết test, một câu hỏi thường gặp là: "Tôi nên cấu trúc các test case trong một file như thế nào? Cứ viết một loạt test() hay dùng test.describe()? Lợi và hại của mỗi cách là gì?"

🚀  Cách Tiếp Cận Đơn Giản Nhất - Một "Danh Sách Phẳng" các test()

Đây là cách cơ bản nhất: bạn mở một file .spec.ts và viết một loạt các hàm test() nối tiếp nhau.

Ví dụ: playwright-homepage.spec.ts

import { test, expect } from '@playwright/test';

test('Trang chủ nên có tiêu đề đúng', async ({ page }) => {

  await page.goto('https://playwright.dev/');

  await expect(page).toHaveTitle(/Playwright/);

});


test('Nút Search nên hoạt động', async ({ page }) => {

  await page.goto('https://playwright.dev/');

  await page.getByRole('button', { name: 'Search' }).click();

  await expect(page.getByPlaceholder('Search docs')).toBeVisible();

});

test('Chức năng đổi màu Sáng/Tối nên hoạt động', async ({ page }) => {

  await page.goto('https://playwright.dev/');

  const htmlElement = page.locator('html');

  // Lấy màu nền ban đầu

  const initialTheme = await htmlElement.getAttribute('data-theme');
  // Click vào nút đổi màu

  await page.getByLabel('Switch between dark and light mode').click();

  // Kiểm tra màu nền đã thay đổi

  const newTheme = await htmlElement.getAttribute('data-theme');

  expect(newTheme).not.toBe(initialTheme);

});


Phân Tích:

Ưu điểm (Pros):

Đơn giản, nhanh chóng: Rất dễ để bắt đầu, không cần suy nghĩ nhiều về cấu trúc.

Dễ hiểu: Với một file có 2-3 test case, cách này rất trực quan.

Nhược điểm (Cons):

Khó bảo trì khi số lượng test tăng: Khi file có 10, 20 test case, nó sẽ trở thành một mớ hỗn độn, khó tìm kiếm và quản lý.

Không có sự nhóm gộp logic: Các test case về "Search" và "Theme" bị đặt ngang hàng nhau, không có sự phân cấp rõ ràng.

Lặp lại code (Repetitive Code): Bạn có thấy await page.goto('https://playwright.dev/'); bị lặp lại ở đầu mỗi test không? Đây là một dấu hiệu của việc cấu trúc chưa tốt.

➡️ Khi nào nên dùng: Khi bạn đang viết một file test rất nhỏ (dưới 5 test case), các test không liên quan nhiều đến nhau, hoặc khi bạn đang làm một kịch bản thử nghiệm nhanh (proof-of-concept).

🏆  Cách Tiếp Cận Chuyên Nghiệp - Nhóm Gộp với test.describe()

Đây là cách làm được khuyến khích và là tiêu chuẩn trong hầu hết các dự án. test.describe() cho phép bạn tạo ra một "bộ test" (test suite) để nhóm các test() có liên quan lại với nhau.

Ví dụ: playwright-homepage.spec.ts được cấu trúc lại

import { test, expect } from '@playwright/test';


// Nhóm tất cả các test liên quan đến trang chủ

test.describe('Trang chủ Playwright.dev', () => {

  // Chạy trước mỗi test trong nhóm này

  test.beforeEach(async ({ page }) => {

    // Chỉ cần viết goto một lần ở đây!

    await page.goto('https://playwright.dev/');

  });

  test('Nên có tiêu đề đúng', async ({ page }) => {

    await expect(page).toHaveTitle(/Playwright/);

  });


  // Có thể tạo nhóm con để rõ ràng hơn nữa

  test.describe('Chức năng Tìm kiếm', () => {

    test('Click vào nút Search sẽ mở ra ô nhập liệu', async ({ page }) => {

      await page.getByRole('button', { name: 'Search' }).click();

      await expect(page.getByPlaceholder('Search docs')).toBeVisible();

    });

    // Thêm các test khác cho Search ở đây...

  });


  test.describe('Chức năng Đổi màu Sáng/Tối', () => {

    test('Click vào nút đổi màu sẽ thay đổi theme của trang', async ({ page }) => {

      const htmlElement = page.locator('html');

      const initialTheme = await htmlElement.getAttribute('data-theme');
      await page.getByLabel('Switch between dark and light mode').click();
      const newTheme = await htmlElement.getAttribute('data-theme');

      expect(newTheme).not.toBe(initialTheme);

    });

  });

});


Phân Tích Lợi Ích Vượt Trội:

Tổ Chức Logic Rõ Ràng:

File test của bạn giờ đây giống như một cuốn sách có các chương (describe) và các mục con (test).

Báo cáo kết quả test (test report) cũng sẽ hiển thị cấu trúc nhóm này, giúp bạn dễ dàng xác định khu vực nào đang bị lỗi.

Sử Dụng Hooks (beforeEach, afterEach, beforeAll, afterAll):

Đây là lợi ích lớn nhất. Bạn có thể chạy các đoạn code thiết lập (setup) hoặc dọn dẹp (teardown) cho toàn bộ nhóm.

Trong ví dụ trên, test.beforeEach giúp chúng ta điều hướng đến trang playwright.dev chỉ một lần duy nhất trong code, áp dụng cho tất cả các test bên trong. Điều này tuân thủ nguyên tắc DRY (Don't Repeat Yourself - Đừng Lặp Lại Code), làm cho code sạch hơn và dễ thay đổi hơn.


Nhóm Lồng Nhau (Nested Suites):

Bạn có thể đặt các test.describe lồng vào nhau để tạo ra các cấp độ phân loại chi tiết hơn, giúp file test cực lớn vẫn giữ được sự gọn gàng.

➡️ Khi nào nên dùng: Hầu như trong mọi trường hợp. Đây nên là cách tiếp cận mặc định của bạn khi viết test. Bất cứ khi nào bạn có từ 2 test case trở lên liên quan đến cùng một tính năng hoặc một trang, hãy nhóm chúng vào một test.describe.

🌐 Khi Nào Nên Tách Ra Nhiều File?

Ngay cả khi đã dùng test.describe, một file có thể trở nên quá lớn (ví dụ: hơn 300-400 dòng). Đó là lúc bạn nên nghĩ đến việc tách ra thành nhiều file.

Quy tắc chung: Một file cho một trang hoặc một tính năng lớn.

Ví dụ, với trang hrm.anhtester.com, bạn nên có các file riêng biệt:

login.spec.ts: Chứa một test.describe('Login Functionality', ...) với các test case đăng nhập thành công, thất bại, quên mật khẩu...

dashboard.spec.ts: Chứa các test case liên quan đến trang dashboard sau khi đăng nhập.

employees.spec.ts: Chứa các test case cho riêng tính năng quản lý nhân viên.

Lợi ích:

Quản lý dễ dàng: Dễ dàng tìm thấy các test bạn cần.

Phân công công việc: Mỗi người trong nhóm có thể phụ trách một file/tính năng.

Tận dụng tối đa việc chạy song song: Playwright có thể chạy các file này trên các "worker" khác nhau cùng một lúc, tăng tốc độ thực thi bộ test.

Tổng Kết và Lời Khuyên

Bắt đầu đơn giản: Nếu chỉ viết 1-2 test nhanh, một danh sách phẳng các test() là đủ.

Sử dụng test.describe() làm mặc định: Ngay khi có 2 test liên quan, hãy nhóm chúng lại. Nó giúp code của bạn sạch sẽ, có tổ chức và dễ bảo trì ngay từ đầu.

Tận dụng beforeEach: Đây là "người bạn thân" của test.describe. Dùng nó để thực hiện các bước thiết lập lặp đi lặp lại.

Đặt tên rõ ràng: Tên của test.describe và test nên mô tả rõ ràng mục đích của chúng (ví dụ: test.describe('Login with invalid credentials', ...)).

Biết khi nào cần tách file: Đừng để một file test trở nên quá lớn. Chia nhỏ theo trang hoặc tính năng để dự án của bạn có thể phát triển bền vững.


Tại Sao Playwright Lại Tìm File *.spec.ts?


Khi bạn chạy lệnh npx playwright test, Playwright không quét tất cả các file trong dự án của bạn. Thay vào đó, nó tìm các file khớp với một mẫu (pattern) đã được cấu hình sẵn.

Theo mặc định, Playwright sẽ tìm tất cả các file bên trong thư mục tests/ (hoặc thư mục gốc) có tên kết thúc bằng:

*.spec.ts (hoặc .js)

*.test.ts (hoặc .js)

Đây là lý do tại sao khi bạn khởi tạo dự án, file test mẫu được tạo ra có tên là example.spec.ts.

"Quy Tắc" Này Đến Từ Đâu?

Quy ước đặt tên này không phải do Playwright tự nghĩ ra. Nó là một tiêu chuẩn cộng đồng đã tồn tại từ lâu trong thế giới JavaScript, bắt nguồn từ các framework kiểm thử nổi tiếng khác như Jest, Jasmine, Mocha.

Lợi ích của việc tuân thủ quy ước này:

Rõ ràng (Clarity): Bất kỳ ai nhìn vào cấu trúc thư mục của bạn cũng ngay lập tức nhận ra "À, đây là một file test". Nó giúp phân biệt rõ ràng giữa mã nguồn của ứng dụng và mã nguồn của kiểm thử.

Tương thích với Công cụ (Tooling): Rất nhiều công cụ (trình soạn thảo code, hệ thống CI/CD, các thư viện khác) được cấu hình sẵn để nhận diện các mẫu *.spec.ts và *.test.ts. Tuân theo quy ước giúp mọi thứ hoạt động trơn tru.


Làm Thế Nào Nếu Tôi Muốn Dùng Tên Khác?

Bạn hoàn toàn có thể thay đổi quy tắc này. Playwright rất linh hoạt.

Bạn chỉ cần mở file cấu hình playwright.config.ts và thêm hoặc chỉnh sửa thuộc tính testMatch.

Ví dụ: Giả sử bạn muốn tất cả các file test của mình phải có đuôi là *.kiemthu.ts.

Bạn sẽ cấu hình file playwright.config.ts như sau:

import { defineConfig } from '@playwright/test';

export default defineConfig({

  // ... các cấu hình khác của bạn

  // Ghi đè lên quy tắc tìm kiếm file test mặc định

  testMatch: '**/*.kiemthu.ts',

  // ... các cấu hình khác

});

**/* có nghĩa là "tìm trong bất kỳ thư mục con nào".

*.kiemthu.ts là mẫu tên file bạn muốn tìm.

Sau khi lưu lại, lệnh npx playwright test sẽ chỉ chạy các file có đuôi *.kiemthu.ts và bỏ qua các file *.spec.ts.

Bạn cũng có thể cung cấp một mảng các mẫu:

testMatch: ['/tests/login/**/*.spec.ts', '/tests/smoke-tests/**/*.test.ts'],

Lời Khuyên: "Just because you can, doesn't mean you should"

Mặc dù bạn có thể thay đổi, lời khuyên chân thành là hãy giữ nguyên quy ước mặc định (*.spec.ts hoặc *.test.ts) trừ khi dự án của bạn có một lý do đặc biệt và rất thuyết phục để làm khác đi.

Việc tuân theo tiêu chuẩn cộng đồng sẽ giúp dự án của bạn dễ dàng hơn cho người mới tham gia, dễ bảo trì và tương thích tốt hơn với hệ sinh thái công cụ xung quanh.

Câu trả lời nằm ở chính ý nghĩa của từ spec.

"Spec" là viết tắt của "Specification"

Spec là viết tắt của từ Specification trong tiếng Anh, có nghĩa là "bản đặc tả" hoặc "bản mô tả chi tiết yêu cầu".

Hãy nghĩ về nó như thế này: Trước khi xây một ngôi nhà, kiến trúc sư phải tạo ra một bản thiết kế chi tiết (specification). Bản thiết kế này mô tả ngôi nhà phải trông như thế nào, kích thước ra sao, có bao nhiêu phòng, cửa sổ ở đâu, v.v.

Trong phát triển phần mềm cũng vậy. Trước khi viết một tính năng, chúng ta cần một bản đặc tả mô tả tính năng đó phải hoạt động như thế nào.

Ví dụ, với tính năng đăng nhập, bản đặc tả sẽ bao gồm:

Đặc tả 1: Khi người dùng nhập đúng username và password, hệ thống phải chuyển hướng họ đến trang dashboard.

Đặc tả 2: Khi người dùng nhập sai password, hệ thống phải hiển thị thông báo lỗi "Sai mật khẩu".

Đặc tả 3: Khi người dùng bỏ trống username, hệ thống phải hiển thị thông báo lỗi "Vui lòng nhập tên người dùng".


File Test chính là một "Bản Đặc Tả Có Thể Chạy Được"

Bây giờ, hãy kết nối ý tưởng trên với file test.

Một file login.spec.ts không chỉ là một file để "kiểm tra lỗi". Nó được xem là một "bản đặc tả có thể thực thi" (runnable specification) cho tính năng đăng nhập.

Mỗi khối test() bên trong file này tương ứng với một điểm trong bản đặc tả.

test('should redirect to dashboard with valid credentials', ...) chính là việc bạn biến "Đặc tả 1" ở trên thành code.

Khi bạn chạy file test, bạn không chỉ đang "tìm lỗi", mà bạn đang xác minh rằng mã nguồn của bạn tuân thủ đúng theo bản đặc tả đã đề ra.

Khi tất cả các test trong file login.spec.ts đều pass, bạn có thể tự tin nói rằng: "Tính năng đăng nhập đang hoạt động chính xác theo đúng đặc tả yêu cầu."


Nguồn Gốc Lịch Sử: Behavior-Driven Development (BDD)

Tư duy coi file test là một "bản đặc tả" bắt nguồn từ một phương pháp luận gọi là Behavior-Driven Development (Phát triển Hướng Hành vi - BDD).

BDD khuyến khích lập trình viên và tester suy nghĩ về hành vi (behavior) của ứng dụng theo cách mà người dùng hoặc các bên liên quan có thể hiểu được.

Một trong những framework tiên phong và có ảnh hưởng nhất cho BDD là RSpec trong thế giới Ruby. Cái tên RSpec có nghĩa là "Ruby Specification". Chính nó đã phổ biến hóa việc đặt tên file test là _spec.rb.

Khi các tư tưởng này lan sang cộng đồng JavaScript, các framework như Jasmine, Jest, và Mocha đã học hỏi và áp dụng theo. Họ mang đến cấu trúc describe/it (hoặc describe/test) và quy ước đặt tên file *.spec.js. Playwright, là một framework hiện đại, cũng đi theo tiêu chuẩn cộng đồng đã được chấp nhận rộng rãi này.

spec vs. test - Có gì khác biệt?

Bạn cũng sẽ thấy đuôi *.test.ts. Về mặt kỹ thuật trong Playwright, chúng không có gì khác biệt. Tuy nhiên, về mặt triết lý:

spec.ts: Thường ngụ ý rằng file này đang mô tả hành vi của một module (theo tư tưởng BDD).

test.ts: Thường mang ý nghĩa trực tiếp hơn là file này đang kiểm thử một cái gì đó.

Ngày nay, hai thuật ngữ này thường được dùng thay thế cho nhau, nhưng spec mang một ý nghĩa lịch sử sâu sắc hơn về việc viết test như là viết một tài liệu sống, một bản đặc tả chi tiết cho phần mềm của bạn.

Tóm lại: Tên file có đuôi spec.ts là một lời nhắc nhở rằng bạn không chỉ đang tìm lỗi, mà bạn đang định nghĩa và xác minh hành vi đúng đắn của ứng dụng theo một bản đặc tả rõ ràng.

 

Phần 3 : Sử dụng test.step()


test.step() là gì? - Tổ Chức Bên Trong Một Test Case

Hãy tưởng tượng cấu trúc file test của bạn như một cuốn sách:

test.describe(): Giống như một Chương (ví dụ: "Chương 5: Chức năng Đăng nhập").

test(): Giống như một Mục lớn trong chương đó (ví dụ: "5.1: Kịch bản đăng nhập thành công").

Vậy test.step() là gì? Nó giống như các đoạn văn hoặc các gạch đầu dòng bên trong mục lớn đó. Nó giúp bạn chia một test() dài thành các bước logic nhỏ hơn, có tên rõ ràng.

Mục đích chính: Làm cho Báo cáo Test (Test Report) của bạn trở nên chi tiết, dễ đọc và dễ gỡ lỗi hơn rất nhiều.

Ví dụ về sự khác biệt trong báo cáo:

Không dùng test.step():

✓ Kịch bản đăng nhập và tạo bài viết mới (10.2s)

Nếu test này thất bại, bạn chỉ biết rằng có lỗi ở đâu đó trong 10.2 giây đó.

Có dùng test.step():

✓ Kịch bản đăng nhập và tạo bài viết mới (10.3s)

  ✓ Bước 1: Đăng nhập vào hệ thống (2.5s)

  ✓ Bước 2: Điều hướng đến trang tạo bài viết (1.1s)

  ✓ Bước 3: Điền tiêu đề và nội dung (4.2s)

  ✓ Bước 4: Xác thực bài viết đã được tạo thành công (2.5s)

Nếu test này thất bại ở "Bước 3", bạn biết chính xác vấn đề nằm ở đâu và chỉ cần tập trung gỡ lỗi cho bước đó.


Ví dụ Thực Tế Hoàn Chỉnh

Hãy viết một kịch bản hoàn chỉnh sử dụng test.step và step.skip để bạn thấy rõ hơn.

import { test, expect } from '@playwright/test';

test.describe('Trang nhân sự Anh Tester', () => {

  test('Kịch bản đăng nhập và kiểm tra widget', async ({ page, browserName }) => {

    await test.step('Bước 1: Điều hướng và đăng nhập', async () => {

      await page.goto('https://hrm.anhtester.com/');

      await page.locator('#iusername').fill('admin_example');

      await page.locator('#ipassword').fill('password_example');

      await page.getByRole('button', { name: 'Login' }).click();

      await expect(page.locator('.page-header h4')).toHaveText('Welcome back, Admin!');

    });


    await test.step('Bước 2: Kiểm tra các widget cơ bản trên Dashboard', async () => {

      await expect(page.getByText('Employees')).toBeVisible();

      await expect(page.getByText('Projects')).toBeVisible();

    });


    await test.step('Bước 3: Kiểm tra widget đặc biệt (chỉ có trên Chrome)', async (step) => {

      // Bỏ qua bước này nếu trình duyệt không phải là Chromium

      step.skip(browserName !== 'chromium', 'Widget này chỉ được thiết kế cho trình duyệt Chrome.');

     
      console.log(`Đang chạy trên ${browserName}, tiếp tục kiểm tra widget đặc biệt...`);

      // Giả sử có một widget chỉ hiển thị trên Chrome

      await expect(page.locator('#chrome-special-widget')).toBeVisible();

    });


    await test.step('Bước 4: Đăng xuất', async () => {

      await page.getByRole('link', { name: 'Logout' }).click();

      await expect(page).toHaveURL(/.*login/);

    });

  });

});


Khi bạn chạy test này:

Trên Chrome (chromium): Tất cả 4 bước sẽ chạy và được kiểm tra.

Trên Firefox hoặc WebKit: Test sẽ chạy, "Bước 1", "Bước 2", "Bước 4" sẽ được thực thi, nhưng "Bước 3" sẽ được đánh dấu là skipped (bỏ qua) trong báo cáo, và test vẫn được coi là PASS.

Tóm lại, test.step() là công cụ tuyệt vời để:

Tăng tính dễ đọc và dễ gỡ lỗi cho báo cáo.

Chia nhỏ một test case phức tạp thành các phần logic.

Thực hiện các hành động có điều kiện (như skip) trên một phần của test case thay vì toàn bộ.

 

Phần 3: Hướng Dẫn Toàn Diện về Annotations cho testtest.step

Annotations là gì?

Trong Playwright, Annotations không phải là các lệnh để tương tác với trang web. Thay vào đó, chúng là các "chú thích" hay "nhãn" mà bạn gắn vào các test case hoặc các bộ test (test.describe) để ra lệnh cho Test Runner (trình chạy test) phải xử lý chúng như thế nào.

Hãy nghĩ chúng như những ghi chú bạn dán lên tài liệu: "Bỏ qua", "Cần xem lại", "Sẽ thất bại", "Ưu tiên chạy cái này". Chúng cung cấp siêu dữ liệu (metadata) và thay đổi cách bộ test được thực thi và báo cáo.

Chúng ta sẽ chia Annotations thành 2 cấp độ:

Test-Level Annotations: Áp dụng cho toàn bộ test() hoặc test.describe().

Step-Level Annotations: Áp dụng cho một test.step() cụ thể bên trong một test.


Phần 1: Test-Level Annotations (Dành cho
test()test.describe())

Đây là nhóm annotations phổ biến và đa dạng nhất.


test.skip(description) hoặc
test.skip(condition, description)

Mục đích: Bỏ qua, không chạy một test case hoặc một nhóm test.

Trường hợp sử dụng:

Tính năng tương ứng chưa được phát triển.

Test đang bị lỗi tạm thời do môi trường (ví dụ: server test đang bảo trì).

Bạn muốn tạm thời vô hiệu hóa một test để tập trung vào các test khác.

Bỏ qua có điều kiện (ví dụ: chỉ chạy test này trên Chrome, bỏ qua trên Firefox).

Ví dụ:

 

// Bỏ qua vô điều kiện

test.skip('Tính năng upload file nâng cao chưa hoàn thiện', async ({ page }) => {

  // Code trong này sẽ không bao giờ được chạy

});


test.describe.skip('Toàn bộ nhóm test cho tính năng Admin V2', () => {

  // Tất cả test trong này sẽ bị bỏ qua

});


// Bỏ qua có điều kiện

test('Test giao diện chỉ dành cho WebKit', async ({ page, browserName }) => {

  // Nếu không phải WebKit, bỏ qua test này

  test.skip(browserName !== 'webkit', 'Giao diện này chỉ được tối ưu cho WebKit');


  // ... code test cho WebKit

});

test.fail(description) hoặc
test.fail(condition, description)

Mục đích: Đánh dấu một test case là "dự kiến sẽ thất bại". Playwright vẫn sẽ chạy test này.

Trường hợp sử dụng: Đây là công cụ tuyệt vời để quản lý bug!

Bạn phát hiện một bug trong ứng dụng.

Bạn viết một test case để tái hiện bug đó (test này chắc chắn sẽ fail).

Bạn đánh dấu nó với test.fail(). Khi chạy, dù test này fail, Playwright sẽ coi đó là thành công (vì đúng như kỳ vọng của bạn).

Khi lập trình viên sửa bug, test này sẽ đột nhiên pass. Lúc này, Playwright sẽ báo lỗi "unexpected pass" (pass ngoài mong đợi), nhắc nhở bạn rằng bug đã được sửa và đã đến lúc gỡ bỏ test.fail().

Ví dụ:

 

// Test này sẽ được chạy, và được coi là "pass" nếu nó thất bại.

test.fail('Test này đang thất bại do bug #123 trong logic tính toán', async ({ page }) => {

  // ... code tái hiện bug và gây ra lỗi

  await page.locator('#calculate').click();

  await expect(page.locator('#result')).toHaveText('100'); // Giả sử kết quả thực tế là '99'

});

test.fixme(description)

Mục đích: Tương tự như test.skip, nhưng mang ý nghĩa mạnh mẽ hơn. Nó đánh dấu một test đang bị hỏng và cần phải được sửa.

Trường hợp sử dụng: Khi bạn đang refactor code và làm hỏng một test, nhưng cần commit code gấp. Bạn đánh dấu test.fixme() như một lời nhắc nhở cho bản thân hoặc đồng nghiệp rằng test này cần được ưu tiên sửa chữa. Trong báo cáo, nó sẽ được liệt kê ở một mục riêng là "Fixme".

Ví dụ:

test.fixme('Test này cần được cập nhật sau khi refactor API', async ({ page }) => {

  // ... code test đã cũ và không còn hoạt động

});

test.slow(description)

Mục đích: Đánh dấu một test case là chạy chậm. Playwright sẽ tự động tăng gấp 3 lần thời gian timeout mặc định cho test đó.

Trường hợp sử dụng: Khi bạn có một test case đặc thù cần nhiều thời gian hơn bình thường (ví dụ: xử lý một file lớn, chờ một tiến trình backend chạy lâu).

Ví dụ:

// Nếu timeout mặc định là 30s, test này sẽ có timeout là 90s.

test.slow('Test xuất báo cáo tài chính cả năm', async ({ page }) => {

  // ... các bước tạo và tải báo cáo rất lâu

});

test.only(description)

Mục đích: Chỉ chạy duy nhất test case (hoặc nhóm describe) được đánh dấu only. Tất cả các test khác trong toàn bộ dự án sẽ bị bỏ qua.

Trường hợp sử dụng: Công cụ gỡ lỗi (debug) quan trọng nhất! Khi bạn đang làm việc với một test case cụ thể và muốn chạy chỉ mình nó để kiểm tra nhanh mà không cần chờ cả trăm test khác chạy.

Ví dụ:

// Khi chạy, chỉ có test này được thực thi

test.only('Tập trung debug test này', async ({ page }) => {

    // ...

});

 

⚠️ CẢNH BÁO CỰC KỲ QUAN TRỌNG: Tuyệt đối không bao giờ commit code có chứa test.only() vào nhánh chính (main/master). Nếu không, khi chạy trên CI/CD, nó sẽ bỏ qua tất cả các test khác và tạo ra một lỗ hổng lớn trong quy trình kiểm thử của bạn.


Step-Level Annotations (Dành cho
test.step())

Phạm vi ở cấp độ step hẹp hơn nhiều. step là một hành động mệnh lệnh, không phải là một lời khai báo về kết quả. Do đó, nó chỉ có một annotation duy nhất:

step.skip(condition, description)

Mục đích: Bỏ qua một bước cụ thể bên trong một test case đang chạy nếu một điều kiện được đáp ứng.

Trường hợp sử dụng: Khi một phần của một quy trình chỉ áp dụng cho một trình duyệt hoặc một kịch bản nhất định.

Ví dụ:

test('Quy trình thanh toán', async ({ page, browserName }) => {

  await test.step('Bước 1: Thêm hàng vào giỏ', async () => { /* ... */ });

  await test.step('Bước 2: Điền thông tin giao hàng', async () => { /* ... */ });

  await test.step('Bước 3: Xác thực qua vân tay (chỉ trên Chrome)', async (step) => {

    // Bỏ qua bước này nếu không phải là Chrome

    step.skip(browserName !== 'chromium', 'Xác thực vân tay chỉ hỗ trợ trên Chrome');

    // ... code test xác thực vân tay

  });

  await test.step('Bước 4: Hoàn tất thanh toán', async () => { /* ... */ });

});

Bảng Tóm Tắt

Chú thích (Annotation)

Phạm vi

Ý nghĩa

skip

test & step

Bỏ qua, không chạy.

fail

test

Chạy test và kỳ vọng nó sẽ thất bại.

fixme

test

Bỏ qua test và đánh dấu là "cần sửa".

slow

test

Tăng timeout lên 3 lần.

only

test

Chỉ chạy test này, bỏ qua tất cả các test khác.

 

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