Tài liệu nội bộ · Hướng dẫn cho người mới

Test Automation Demo

Một dự án automation test mẫu dùng Playwright + TypeScript, kiểm thử cả giao diện web lẫn API. Tài liệu này giải thích những gì đã được dựng, tại sao dựng như vậy, và dẫn từng bước để một Tester mới có thể đọc hiểu, chạy được, rồi tự mở rộng thêm test của riêng mình.

Stack: Playwright · TypeScript 17 test case (mặc định) Phạm vi: Web UI + API CI: GitHub Actions
01

Tổng quan dự án

Project nằm tại d:\Code\automation. Đây là bản demo để minh hoạ cách một bộ automation test thực tế thường được tổ chức — không phải một vài file test rời rạc, mà có cấu trúc, có dữ liệu test tách riêng, chạy được cả trên máy cá nhân lẫn trên CI, và xuất ra báo cáo xem lại được.

Project test hai thứ độc lập nhau trong cùng một bộ khung:

  • Web UI — trang demo bán hàng công khai saucedemo.com, mô phỏng luồng đăng nhập → thêm giỏ hàng → thanh toán.
  • API — dịch vụ REST công khai dummyjson.com, mô phỏng luồng quản lý người dùng và đăng nhập bằng token.

Cả hai đều là hệ thống demo công khai, không cần server hay tài khoản riêng — mục đích để bất kỳ ai clone project về cũng chạy được ngay, tập trung học cách automation hoạt động thay vì loay hoay dựng môi trường.

02

Vì sao chọn Playwright

Ba công cụ phổ biến nhất mà Tester hay gặp là Selenium, Cypress và Playwright. Bảng dưới so sánh nhanh những điểm ảnh hưởng trực tiếp đến người mới học:

Tiêu chíSeleniumCypressPlaywright
Test API trong cùng frameworkKhông — cần thư viện khácHạn chếCó sẵn (request fixture)
Tự chờ phần tử sẵn sàngPhải tự cấu hình waitCó, mặc định
Chạy đa trình duyệtGiới hạn hơnChromium, Firefox, WebKit
Báo cáo HTML tích hợp sẵnKhôngCó (bản trả phí đầy đủ hơn)Có, miễn phí, có trace/video

Vì project cần test cả UI lẫn API và muốn có report ngay mà không cài thêm gì, Playwright là lựa chọn tốn ít công cụ phụ trợ nhất.

03

Kiến trúc thư mục

Toàn bộ code test nằm trong tests/, tách rõ ba việc: trang UI, kịch bản test, và dữ liệu test.

automation/ ├─ docs/ │ └─ index.html # chính trang bạn đang đọc ├─ tests/ │ ├─ ui/ │ │ ├─ pages/ # Page Object — 1 class / 1 màn hình │ │ │ ├─ LoginPage.ts │ │ │ ├─ InventoryPage.ts │ │ │ ├─ CartPage.ts │ │ │ └─ CheckoutPage.ts │ │ ├─ login.spec.ts # 4 test đăng nhập │ │ └─ cart-checkout.spec.ts # 3 test giỏ hàng & thanh toán │ ├─ api/ │ │ ├─ users.spec.ts # 6 test CRUD user │ │ └─ auth.spec.ts # 4 test đăng nhập / token │ └─ fixtures/ │ └─ test-data.ts # tài khoản, dữ liệu dùng chung ├─ playwright.config.ts # cấu hình test runner ├─ tsconfig.json ├─ package.json ├─ .github/workflows/playwright.yml # CI └─ README.md

Sơ đồ dữ liệu chảy khi chạy một test, cho cả hai nhánh:

Bước 1
File *.spec.ts
Kịch bản test, đọc như một câu chuyện
Bước 2 (UI)
Page Object
Chứa locator + hành động của 1 màn hình
Bước 3
Trình duyệt / API thật
saucedemo.com hoặc dummyjson.com
04

Khái niệm cần biết trước khi đọc code

Năm khái niệm này xuất hiện lặp lại trong toàn bộ project. Hiểu chúng trước sẽ giúp đọc code nhanh hơn nhiều.

Locator

Cách Playwright "trỏ" tới một phần tử trên trang, ví dụ page.locator('#user-name'). Locator không tìm phần tử ngay lúc khai báo — nó chỉ thật sự tìm và tự động chờ phần tử sẵn sàng (hiện ra, không bị che, có thể click) vào lúc thao tác được gọi. Nhờ vậy code test gần như không bao giờ cần viết sleep().

Assertion (expect)

Câu khẳng định để kiểm tra kết quả có đúng như mong đợi không, ví dụ await expect(page).toHaveURL(...). Cũng tự động thử lại đến khi đúng hoặc hết thời gian chờ (mặc định 5 giây, cấu hình trong playwright.config.ts) rồi mới báo fail.

Page Object Model (POM)

Thay vì viết locator trực tiếp trong file test, project gom chúng vào một class riêng đại diện cho một màn hình — ví dụ LoginPage chứa 3 locator (ô tài khoản, ô mật khẩu, nút đăng nhập) và 1 hành động (login()). Lợi ích: khi giao diện đổi, chỉ cần sửa một chỗ trong Page Object thay vì lục tìm trong hàng chục file test.

Project (trong playwright.config.ts)

Một nhóm test chạy chung một cấu hình (trình duyệt, baseURL, headless hay không...). Config khai báo 3 project:

ProjectChạy gìCó trong npm test?
ui-chromium7 test UI, headless, dùng hằng ngày
api10 test API, không cần trình duyệt
ui-slow7 test UI y hệt ui-chromium, nhưng headed + chậm 800ms/thao tác để quan sátKhông — chỉ chạy qua npm run test:slow
Một điều dễ nhầm khi thêm project mới

Mặc định, Playwright chạy tất cả project khai báo trong config nếu không lọc bằng --project. Khi ui-slow được thêm vào, script "test": "playwright test" ban đầu vô tình chạy luôn cả 24 test — kể cả bật trình duyệt chậm mà không ai yêu cầu. Đã sửa lại thành "playwright test --project=ui-chromium --project=api" để npm test chỉ chạy đúng 2 project mặc định. Nhớ kiểm tra lại điều này mỗi khi thêm project mới.

Test data / fixture

Dữ liệu dùng lại ở nhiều test — tài khoản đăng nhập, thông tin thanh toán — được gom vào tests/fixtures/test-data.ts một lần, thay vì gõ tay rải rác trong từng file.

05

Bước 1 — Cài đặt môi trường

Bốn lệnh đã được chạy để dựng project từ số 0. Làm theo đúng thứ tự này khi setup trên một máy mới.

  1. 1

    Kiểm tra Node.js

    Playwright cần Node.js 18 trở lên. Kiểm tra bằng:

    $ node -v
    $ npm -v
  2. 2

    Khởi tạo project Node

    Tạo package.json — nơi khai báo tên project, script, và các thư viện sẽ cài.

    $ npm init -y
  3. 3

    Cài Playwright Test và TypeScript

    Cài như dev dependency (-D) vì đây là công cụ phục vụ việc test, không phải thứ ứng dụng thật cần lúc chạy.

    $ npm install -D @playwright/test typescript @types/node
  4. 4

    Tải trình duyệt Playwright điều khiển

    Khác với npm install — lệnh này tải một bản Chromium thật (~190MB) về máy, cộng thư viện hệ thống cần thiết (--with-deps). Không có bước này, test UI sẽ báo lỗi ngay khi mở trình duyệt.

    $ npx playwright install --with-deps chromium
Lưu ý

Test API không cần trình duyệt, nhưng bước 4 vẫn cần chạy một lần vì project chung một lệnh cài đặt cho cả hai loại test.

06

Bước 2 — Chạy test lần đầu

Chạy 17 test mặc định (project ui-chromium + api) bằng một lệnh duy nhất:

$ npm test

Mặc định test UI chạy headless — trình duyệt chạy ngầm, không hiện cửa sổ, để nhanh nhất có thể. Kết quả rút gọn từ lần chạy thật:

terminaloutput
Running 17 tests using 6 workers

  ok  1 [ui-chromium] › login.spec.ts:7:7 › Login › logs in successfully with valid credentials (1.1s)
  ok  8 [api] › auth.spec.ts:4:7 › Auth API › POST /auth/login succeeds with valid credentials (467ms)
  ...
  17 passed (3.9s)

Vài biến thể hay dùng khi làm việc hằng ngày:

LệnhKhi nào dùng
npm run test:uiChỉ muốn chạy 7 test UI, bỏ qua API
npm run test:apiChỉ muốn chạy 10 test API, nhanh hơn vì không mở trình duyệt
npm run test:headedMuốn nhìn thấy trình duyệt chạy thật, tốc độ bình thường
npm run test:slowMuốn quan sát từng bước — trình duyệt hiện lên, mỗi thao tác chờ 800ms, chạy tuần tự 1 test/lần (--workers=1)
npm run test:debugMở Playwright Inspector, chạy từng bước một để soi lỗi

Muốn chậm hơn hay nhanh hơn nữa với test:slow, chỉnh số slowMo: 800 (đơn vị mili-giây) trong project ui-slowplaywright.config.ts.

07

Bước 3 — Đọc báo cáo HTML

Sau mỗi lần chạy, Playwright ghi report vào thư mục playwright-report/. Mở bằng:

$ npm run report

Report mở trên trình duyệt, liệt kê toàn bộ test theo từng project, kèm thời gian chạy. Riêng với test fail, report còn kèm thêm ba thứ hữu ích để tìm nguyên nhân mà không cần chạy lại:

  • Screenshot tại đúng thời điểm fail (screenshot: 'only-on-failure')
  • Video toàn bộ quá trình chạy test đó (video: 'retain-on-failure')
  • Trace — dòng thời gian từng bước, từng network request, có thể tua qua lại như xem lại màn hình (trace: 'on-first-retry')

Ba dòng cấu hình này nằm trong playwright.config.ts, mục use.

08

Đọc hiểu một test UI

Lấy ví dụ test đầu tiên trong tests/ui/login.spec.ts:

tests/ui/login.spec.tsTypeScript
test('logs in successfully with valid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  const inventoryPage = new InventoryPage(page);

  await loginPage.goto();
  await loginPage.login(users.standard.username, users.standard.password);
  await inventoryPage.expectLoaded();
});

File test không hề chứa một dòng locator nào (không có #user-name, không có #login-button). Toàn bộ nằm trong tests/ui/pages/LoginPage.ts:

tests/ui/pages/LoginPage.tsTypeScript
export class LoginPage {
  // locator được khai báo 1 lần trong constructor
  constructor(page) {
    this.usernameInput = page.locator('#user-name');
    this.passwordInput = page.locator('#password');
    this.loginButton  = page.locator('#login-button');
  }

  async login(username, password) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
}

Đọc theo đúng thứ tự: test mô tả kịch bản người dùng ("đăng nhập rồi vào được trang sản phẩm") — Page Object mô tả màn hình đó có gì và làm được gì. Nếu SauceDemo đổi id nút đăng nhập ngày mai, chỉ sửa một dòng trong LoginPage.ts, không đụng vào 4 test đang dùng nó.

09

Đọc hiểu test API

Test API khác hẳn test UI: không mở trình duyệt, không tìm phần tử — chỉ gọi thẳng HTTP request rồi kiểm tra response trả về. Đó là lý do trong report test API chạy dưới nửa giây, còn test UI mất cả giây.

request đến từ đâu

Tham số { request } trong mỗi test là một fixture Playwright tự tạo sẵn (APIRequestContext). Nó biết gọi tới đâu nhờ baseURL khai báo trong project api của playwright.config.ts, nên trong test chỉ cần viết đường dẫn tương đối:

playwright.config.tsTypeScript
{
  name: 'api',
  testMatch: 'tests/api/**/*.spec.ts',
  use: { baseURL: 'https://dummyjson.com' },
}

6 test CRUD trong users.spec.ts

Đủ 4 thao tác cơ bản của một API quản lý dữ liệu, kèm cả một trường hợp lỗi — không chỉ test đường "happy path":

TestMethodKiểm tra gì
GET /users?limit=5GETstatus 200, mảng đúng 5 phần tử, có field email
GET /users/2GETstatus 200, id đúng bằng 2
GET /users/999999GETstatus 404 — id không tồn tại
POST /users/addPOSTstatus 201 (Created), body đúng dữ liệu vừa gửi
PUT /users/2PUTstatus 200, field đã đổi đúng giá trị mới
DELETE /users/2DELETEstatus 200, body.isDeleted === true

2 tầng kiểm tra trong mọi test API

Chỉ check status code là chưa đủ — server có thể trả 200 nhưng dữ liệu sai hoặc thiếu field. Đây là lỗi rất hay gặp ở người mới viết API test. Mọi test trong project đều kiểm tra đủ 2 tầng:

ví dụTypeScript
expect(response.status()).toBe(200);   // tầng 1: hợp đồng HTTP đúng không
const body = await response.json();
expect(body.id).toBe(2);               // tầng 2: dữ liệu trả về đúng không

Nối request: đăng nhập lấy token rồi gọi API cần xác thực

Test khó nhất trong tests/api/auth.spec.ts gọi 2 API liên tiếp — pattern rất phổ biến khi test bất kỳ hệ thống nào có xác thực:

tests/api/auth.spec.tsTypeScript
test('GET /auth/me returns current user with valid token', async ({ request }) => {
  // Bước 1 — đăng nhập để lấy accessToken
  const loginResponse = await request.post('/auth/login', {
    data: { username: 'emilys', password: 'emilyspass' },
  });
  const { accessToken } = await loginResponse.json();

  // Bước 2 — dùng token đó để gọi API cần xác thực
  const meResponse = await request.get('/auth/me', {
    headers: { Authorization: `Bearer ${accessToken}` },
  });

  expect(meResponse.status()).toBe(200);
});

Đây là cách automation mô phỏng luồng "đăng nhập rồi thao tác" mà không cần giữ session cookie như trình duyệt — mỗi request tự mang theo bằng chứng đã đăng nhập (token) trong header.

10

Thực hành — tự viết một test mới

Cách học automation nhanh nhất là tự viết một test còn thiếu. Bài tập: viết test kiểm tra API GET /products/1 của dummyjson.com trả về đúng sản phẩm có id = 1.

  1. 1

    Tạo file mới

    Trong tests/api/, tạo file products.spec.ts — mọi file nằm trong tests/api/ tự động chạy trong project api nhờ dòng testMatch: 'tests/api/**/*.spec.ts' trong config.

  2. 2

    Import và viết khung test

    Bắt đầu bằng import { test, expect } from '@playwright/test', sau đó một khối test('...', async ({ request }) => { ... }) — giống cấu trúc đã thấy ở mục 09.

  3. 3

    Gọi API và assert đủ 2 tầng

    Gọi request.get('/products/1'), kiểm tra status code trước, rồi kiểm tra body.id đúng bằng 1.

  4. 4

    Chạy riêng file vừa viết

    $ npx playwright test products

    Playwright tự khớp tên file theo từ khoá, không cần gõ đường dẫn đầy đủ.

Xem đáp án gợi ý
tests/api/products.spec.tsTypeScript
import { test, expect } from '@playwright/test';

test('GET /products/1 returns the matching product', async ({ request }) => {
  const response = await request.get('/products/1');
  expect(response.status()).toBe(200);

  const body = await response.json();
  expect(body.id).toBe(1);
  expect(body).toHaveProperty('title');
});
11

CI/CD — chạy test tự động

File .github/workflows/playwright.yml khai báo một GitHub Actions workflow, kích hoạt mỗi khi có push hoặc pull request vào nhánh main. Các bước chạy tuần tự trên một máy ảo Ubuntu sạch:

  1. 1

    Checkout code

    Lấy đúng phiên bản code vừa push về máy ảo.

  2. 2

    Setup Node 22

    Cài đúng phiên bản Node đã dùng khi dựng project.

  3. 3

    npm ci

    Cài dependency đúng y hệt phiên bản khoá trong package-lock.json — khác npm install ở chỗ không tự ý nâng version, đảm bảo CI luôn tái lập được kết quả giống máy dev.

  4. 4

    Cài trình duyệt

    npx playwright install --with-deps chromium — máy ảo CI là máy sạch, chưa từng có Chromium.

  5. 5

    npm test

    Chạy đúng 17 test mặc định — không chạy project ui-slow vì script test đã giới hạn rõ --project.

  6. 6

    Upload report

    Đính kèm toàn bộ playwright-report/ làm artifact, giữ 14 ngày — chạy cả khi test fail (if: always()) để vẫn xem lại được nguyên nhân.

Xem report của CI

Vào tab Actions trên GitHub → chọn lần chạy → mục Artifacts ở cuối trang → tải playwright-report về và mở index.html.

12

Cheatsheet lệnh

LệnhTác dụng
npm testChạy 17 test mặc định (UI headless + API)
npm run test:uiChỉ chạy 7 test UI (project ui-chromium)
npm run test:apiChỉ chạy 10 test API (project api)
npm run test:headedChạy UI test, hiện trình duyệt thật, tốc độ bình thường
npm run test:slowChạy UI test chậm lại (headed + slowMo 800ms) để quan sát
npm run test:debugMở Playwright Inspector, chạy từng bước
npm run reportMở báo cáo HTML lần chạy gần nhất
npx playwright test loginChạy test có tên file/khớp từ khoá "login"
npx playwright codegen saucedemo.comGhi thao tác chuột thành code locator tự động
13

Lỗi thường gặp khi mới bắt đầu

browserType.launch: Executable doesn't exist

Chưa chạy npx playwright install --with-deps chromium, hoặc chạy trên máy mới mà quên bước này. npm install chỉ tải code, không tải trình duyệt.

Test UI timeout ngay ở bước đầu

Thường do mạng chậm khi tải saucedemo.com lần đầu. Chạy lại, hoặc tăng timeout trong playwright.config.ts nếu mạng công ty chậm thường xuyên.

Locator không tìm thấy phần tử (element not found)

Trang demo có thể đổi cấu trúc HTML theo thời gian. Dùng npm run test:debug để dừng lại đúng bước lỗi và soi DOM thật, hoặc npx playwright codegen để lấy locator mới bằng cách click trực tiếp.

npm test tự nhiên chạy nhiều test hơn / bật trình duyệt ngoài ý muốn

Kiểm tra script test trong package.json có đang giới hạn đúng --project hay không. Playwright mặc định chạy toàn bộ project khai báo trong config nếu không lọc — dễ quên nhất là sau khi thêm một project mới.

Bài học từ chính project này

API demo reqres.in ban đầu được chọn cho phần test API, nhưng giữa chừng họ đổi chính sách — bắt buộc phải có API key đăng ký riêng, request không key trả về 401. Bài học: API miễn phí có thể đổi luật bất cứ lúc nào, nên luôn kiểm tra response thực tế bằng curl trước khi viết assertion, và cân nhắc mock lại API quan trọng thay vì phụ thuộc dịch vụ ngoài. Project đã chuyển sang dummyjson.com vì không yêu cầu đăng ký.

14

Bước tiếp theo

Khi đã quen với project này, vài hướng mở rộng tự nhiên để học sâu hơn:

  • Thêm project firefox / webkit trong playwright.config.ts để test UI trên nhiều trình duyệt cùng lúc.
  • Dùng test.each hoặc for loop để viết data-driven test — 1 kịch bản, nhiều bộ dữ liệu đầu vào.
  • Thêm test cho viewport mobile bằng devices['iPhone 13'].
  • Tích hợp report nâng cao hơn (Allure) nếu cần chia sẻ báo cáo cho nhiều team.
  • Cho CI gửi thông báo (Slack/email) khi test fail trên nhánh main.