0

Playwright TypeScript cho người mới: từ cài đặt đến Page Object, fixture và test data-driven

Nếu bạn là tester manual muốn chuyển sang automation, hoặc dev muốn viết E2E test cho dự án, Playwright là lựa chọn rất dễ bắt đầu: cài một lệnh, tự chờ phần tử, có trace để debug, chạy được Chromium/Firefox/WebKit. Bài này đi từ con số 0 đến một bộ test có cấu trúc gọn gàng: Page Object, fixture, test data-driven và chạy trên CI.

Toàn bộ code trong bài nằm trong repo miễn phí, clone về là chạy được: https://github.com/qakit-vn/playwright-starter-vn-lite

1. Cài đặt

Cần Node.js 18 trở lên.

mkdir pw-demo && cd pw-demo
npm init playwright@latest

Trình cài đặt sẽ hỏi vài câu: chọn TypeScript, thư mục test tests, có thể bỏ qua GitHub Actions lúc đầu. Sau đó cài trình duyệt (nếu chưa):

npx playwright install chromium

Hoặc dùng luôn repo mẫu:

git clone https://github.com/qakit-vn/playwright-starter-vn-lite.git
cd playwright-starter-vn-lite
npm install
npx playwright install chromium
npm test

Kết quả mong đợi là 4 test pass trong vài giây trên trang demo saucedemo.com.

2. Test đầu tiên

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

test('đăng nhập thành công', async ({ page }) => {
  await page.goto('https://www.saucedemo.com');
  await page.getByPlaceholder('Username').fill('standard_user');
  await page.getByPlaceholder('Password').fill('secret_sauce');
  await page.getByRole('button', { name: 'Login' }).click();
  await expect(page).toHaveURL(/inventory/);
});

Ba điều đáng chú ý:

  • Không có sleep. fill, click và expect tự chờ phần tử sẵn sàng (auto-wait). Nếu bạn thấy mình cần waitForTimeout, gần như chắc chắn có cách tốt hơn.
  • Locator theo cách người dùng nhìn thấy: getByRole, getByPlaceholder, getByLabel, getByText. Ổn định hơn CSS/XPath dài vì ít bị vỡ khi đổi giao diện.
  • expect là web-first assertion: toHaveURL, toBeVisible, toContainText sẽ thử lại cho đến khi đúng hoặc hết timeout.

Mẹo: npx playwright codegen https://www.saucedemo.com mở trình duyệt, bạn thao tác đến đâu nó sinh code đến đó. Rất hữu ích để học cách chọn locator.

3. Cấu hình chung: playwright.config.ts

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

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: process.env.CI ? 2 : 0,
  reporter: [['html', { open: 'never' }], ['list']],
  use: {
    baseURL: process.env.BASE_URL ?? 'https://www.saucedemo.com',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
  • baseURL giúp viết page.goto('/') thay vì URL đầy đủ, và đổi môi trường bằng biến: BASE_URL=https://staging.example.com npm test.
  • retries chỉ bật trên CI, để ở máy local bạn thấy ngay test flaky thay vì bị che đi.
  • trace: 'on-first-retry' ghi lại toàn bộ thao tác, DOM, network khi test chạy lại. Mở bằng npx playwright show-trace hoặc trong HTML report. Đây là công cụ debug mạnh nhất của Playwright.

4. Page Object Model: gom locator vào một chỗ

Khi có 20 test cùng đăng nhập, bạn không muốn sửa 20 chỗ khi nút Login đổi tên. Page Object gom locator và hành động của một trang vào một class:

// pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';

export class LoginPage {
  readonly username: Locator;
  readonly password: Locator;
  readonly loginButton: Locator;
  readonly error: Locator;

  constructor(private readonly page: Page) {
    this.username = page.getByPlaceholder('Username');
    this.password = page.getByPlaceholder('Password');
    this.loginButton = page.getByRole('button', { name: 'Login' });
    this.error = page.locator('[data-test="error"]');
  }

  async goto() { await this.page.goto('/'); }

  async login(user: string, pass: string) {
    await this.username.fill(user);
    await this.password.fill(pass);
    await this.loginButton.click();
  }

  async expectError(text: string) { await expect(this.error).toContainText(text); }
}

Nguyên tắc nhỏ: Page Object chứa cách thao tác, còn test chứa kỳ vọng nghiệp vụ. Đừng nhồi quá nhiều logic vào page object.

5. Fixture: khỏi new LoginPage(page) ở mọi test

Fixture là tính năng hay bị bỏ qua của Playwright. Thay vì khởi tạo page object trong từng test, ta mở rộng test:

// tests/fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { InventoryPage } from '../pages/InventoryPage';

type Pages = { loginPage: LoginPage; inventoryPage: InventoryPage };

export const test = base.extend<Pages>({
  loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
  inventoryPage: async ({ page }, use) => { await use(new InventoryPage(page)); },
});
export { expect } from '@playwright/test';

Giờ test chỉ cần khai báo thứ mình cần trong tham số, Playwright tự tạo. Sau này bạn có thể thêm fixture như loggedInPage (đã đăng nhập sẵn) hay apiClient theo cùng cách.

6. Test data-driven: một khung, nhiều bộ dữ liệu

Các case đăng nhập sai chỉ khác nhau ở input và thông báo lỗi, nên gom thành mảng dữ liệu:

// tests/ui/login.spec.ts
import { test } from '../fixtures';

const invalidCases = [
  { id: 'TC-LOGIN-02', user: 'locked_out_user', pass: 'secret_sauce', msg: 'locked out' },
  { id: 'TC-LOGIN-03', user: 'standard_user', pass: 'sai_mat_khau', msg: 'do not match' },
  { id: 'TC-LOGIN-04', user: '', pass: '', msg: 'Username is required' },
];

test.describe('Đăng nhập', () => {
  test.beforeEach(async ({ loginPage }) => { await loginPage.goto(); });

  test('TC-LOGIN-01 đăng nhập thành công', async ({ loginPage, inventoryPage }) => {
    await loginPage.login('standard_user', 'secret_sauce');
    await inventoryPage.expectLoaded();
  });

  for (const c of invalidCases) {
    test(`${c.id} báo lỗi: ${c.msg}`, async ({ loginPage }) => {
      await loginPage.login(c.user, c.pass);
      await loginPage.expectError(c.msg);
    });
  }
});

Đặt mã TC-LOGIN-xx trong tên test để đối chiếu với file test case manual (Excel, TestRail...). Khi test fail, report cho biết ngay case nào trong tài liệu bị ảnh hưởng.

7. Chạy, xem report, debug

npx playwright test                 # chạy tất cả
npx playwright test login           # lọc theo tên file
npx playwright test -g "TC-LOGIN-03" # lọc theo tên test
npx playwright test --headed        # xem trình duyệt chạy
npx playwright test --ui            # UI mode: chạy từng test, xem từng bước
npx playwright show-report          # mở HTML report

Khi test fail, thứ tự debug mình hay làm: đọc thông báo lỗi trong terminal → mở HTML report xem screenshot → mở trace để xem DOM tại thời điểm fail. --ui mode rất hợp khi đang viết test mới.

8. Chạy trên GitHub Actions

Repo có sẵn file mẫu docs/playwright.yml. Chép vào .github/workflows/playwright.yml trong repo của bạn:

name: Playwright
on: [push, pull_request, workflow_dispatch]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm install
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: playwright-report, path: playwright-report, retention-days: 7 }

if: always() để report vẫn được upload khi test fail, đúng lúc bạn cần nó nhất.

9. Những lỗi người mới hay gặp

  • Dùng waitForTimeout(3000): làm test chậm và vẫn flaky. Hãy chờ điều kiện cụ thể bằng expect(...).toBeVisible().
  • Locator quá dài kiểu div > div:nth-child(3) > span: đổi sang getByRole/getByTestId, và đề nghị dev thêm data-testid nếu cần.
  • Test phụ thuộc nhau (test B cần test A chạy trước): mỗi test phải tự chuẩn bị dữ liệu, vì Playwright chạy song song.
  • Quên await: TypeScript + ESLint rule @typescript-eslint/no-floating-promises sẽ bắt giúp bạn.
  • Hard-code URL, tài khoản: đưa vào baseURL và biến môi trường ngay từ đầu.

10. Bước tiếp theo

Khi đã quen với phần trên, bạn có thể tự luyện thêm:

  1. Viết InventoryPage.addToCart() và test giỏ hàng.
  2. Thêm fixture loggedInPage dùng storageState để không phải đăng nhập lại ở mỗi test.
  3. Thử test API bằng fixture request có sẵn của Playwright.
  4. Thêm project firefox, webkit trong config để chạy đa trình duyệt.

Repo lite để bạn fork và luyện: https://github.com/qakit-vn/playwright-starter-vn-lite. Có chỗ nào khó hiểu hoặc chạy lỗi, cứ mở Issue hoặc comment bên dưới.


Công khai: mình (Siêng Studio) là người làm QA Kit tiếng Việt, bản trả phí có thêm test giỏ hàng, test API, checklist 110 mục và file Excel test case. Repo trong bài là miễn phí (MIT) và đủ để học các khái niệm ở trên. Bài gốc: Playwright TypeScript cho người mới.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí