Ngừng làm lỗi Production: Hướng dẫn thực tế về Playwright, TypeScript và CI/CD

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

Thảm họa Deploy lúc 4 giờ chiều thứ Sáu

Bây giờ là chiều muộn thứ Sáu. Nhóm của bạn vừa đẩy một bản cập nhật giao diện tưởng chừng như vô hại lên production. Mười phút sau, các ticket hỗ trợ bắt đầu đổ về. Nút đăng nhập bị “liệt”, và luồng thanh toán thì vắng tanh không một bóng người. Một thay đổi nhỏ trong CSS selector vừa phá hỏng toàn bộ hành trình người dùng, và giờ đây kế hoạch cuối tuần của bạn được thay thế bằng một đợt rollback khẩn cấp.

Kịch bản này xảy ra vì việc kiểm thử thủ công không thể theo kịp tốc độ phát triển hiện đại. Khi ứng dụng của bạn phát triển, số lượng các đường dẫn người dùng có thể đi sẽ bùng nổ. Việc kiểm tra thủ công mọi biểu mẫu, nút bấm và chuyên hướng trước mỗi lần release là một cuộc chơi nắm chắc phần thua. Nó dẫn đến “nỗi lo sợ release”, nơi các đội ngũ trở nên kinh hãi khi deploy code vì họ đơn giản là không biết cái gì sẽ hỏng tiếp theo.

Tại sao các công cụ đời cũ gặp khó khăn với Framework hiện đại

Kiểm thử hồi quy (regression testing) thủ công thất bại vì con người thường cảm thấy nhàm chán và bỏ qua các bước kiểm tra “hiển nhiên”. Nhưng ngay cả những công cụ tự động hóa đời đầu cũng có những khuyết điểm. Nếu bạn có 50 màn hình khác nhau cần xác minh, một người có thể mất ba giờ để click qua tất cả. Một script tự động có thể làm điều đó trong chưa đầy ba phút, nhưng chỉ khi công cụ đó thực sự đáng tin cậy.

Trong một thời gian dài, Selenium là lựa chọn thực tế duy nhất. Tuy nhiên, nó bị mang tiếng là “flaky” (không ổn định). Các bài test thường thất bại vì trình duyệt chưa render xong một phần tử trước khi script cố gắng click vào đó. Các lập trình viên dành nhiều thời gian để sửa các bài test bị hỏng hơn là viết các tính năng mới. Việc quản lý các driver trình duyệt phức tạp và các vấn đề đồng bộ hóa cuối cùng đã khiến E2E testing có cảm giác mang lại nhiều rắc rối hơn là giá trị.

Bộ công cụ hiện đại: Tại sao Playwright chiến thắng

Các ứng dụng Single Page (SPA) ngày nay được xây dựng bằng React hoặc Next.js cần một công cụ kiểm thử thực sự hiểu cách web hiện đại vận hành. Dưới đây là cái nhìn toàn cảnh hiện tại:

  • Selenium: Linh hoạt nhưng chậm. Nó yêu cầu thời gian chờ thủ công và quản lý driver nặng nề, tạo cảm giác cồng kềnh cho các trang web sử dụng nhiều JavaScript.
  • Cypress: Mang lại trải nghiệm lập trình viên tuyệt vời nhưng chạy bên trong trình duyệt. Điều này gây khó khăn khi xử lý nhiều tab hoặc kiểm thử chéo domain (cross-domain) mà không có các giải pháp thay thế phức tạp.
  • Playwright: Được xây dựng bởi Microsoft, công cụ này hiện là tiêu chuẩn vàng. Nó hỗ trợ Chromium, WebKit (Safari) và Firefox ngay khi cài đặt. Tính năng “Auto-wait” của nó hầu như loại bỏ hoàn toàn sự không ổn định từng gây khó khăn cho các công cụ cũ bằng cách chờ các phần tử sẵn sàng trước khi tương tác.

Để tiến xa từ một lập trình viên junior lên senior, bạn cần xây dựng phần mềm mà mọi người có thể tin tưởng. Kết hợp Playwright với TypeScript mang lại cho bạn một bộ test type-safe, nhanh như chớp, giúp bắt lỗi trước khi người dùng kịp nhìn thấy chúng.

Xây dựng Framework kiểm thử của bạn

Một bộ test tốt phải dễ viết và thậm chí còn dễ đọc hơn đối với đồng đội của bạn. Hãy cùng thiết lập một môi trường chuyên nghiệp từ đầu.

1. Bắt đầu

Mở terminal tại thư mục gốc của dự án và chạy lệnh sau:

npm init playwright@latest

Chọn TypeScript khi được hỏi. Trình cài đặt sẽ tạo một thư mục tests và thậm chí đề nghị thiết lập workflow cho GitHub Actions. Hãy chọn “Yes” cho workflow—nó sẽ giúp bạn tiết kiệm thời gian thiết lập thủ công sau này.

2. Viết một bài Test bền bỉ

Hãy viết script cho luồng đăng nhập. Thay vì nhắm mục tiêu vào các class CSS dễ vỡ như .btn-blue, chúng ta sẽ sử dụng các locator mô phỏng cách một người thật tương tác với trang web. Tạo file tests/auth.spec.ts:

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

test('người dùng có thể đăng nhập với thông tin hợp lệ', async ({ page }) => {
  await page.goto('https://example.com/login');

  // Sử dụng các locator dễ tiếp cận giúp bài test ổn định hơn 90%
  await page.getByLabel('Tên đăng nhập').fill('testuser');
  await page.getByLabel('Mật khẩu').fill('password123');
  
  await page.getByRole('button', { name: 'Đăng nhập' }).click();

  // Xác minh kết quả
  await expect(page).toHaveURL(/.*dashboard/);
  await expect(page.getByText('Chào mừng, testuser')).toBeVisible();
});

Bằng cách sử dụng getByRole, bài test của bạn sẽ không bận tâm nếu bạn thay đổi màu nút từ xanh sang đỏ. Miễn là nó vẫn là một cái nút có nhãn “Đăng nhập”, bài test sẽ vượt qua. Cách tiếp cận này cũng đảm bảo ứng dụng của bạn thân thiện với các trình đọc màn hình (screen readers).

3. Mở rộng với Page Object Model (POM)

Nếu bạn có mười bài test khác nhau đều bắt đầu bằng việc đăng nhập, và URL đăng nhập của bạn thay đổi, bạn không nên phải cập nhật mười file khác nhau. Page Object Model (POM) tập trung hóa logic UI của bạn. Tạo file pages/LoginPage.ts:

import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.getByLabel('Tên đăng nhập');
    this.passwordInput = page.getByLabel('Mật khẩu');
    this.loginButton = page.getByRole('button', { name: 'Đăng nhập' });
  }

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

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

Giờ đây, file test thực tế của bạn sẽ rất sạch sẽ và dễ đọc:

test('bài test đăng nhập đã được tối ưu', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.navigate();
  await loginPage.login('testuser', 'password123');
  await expect(page).toHaveURL(/.*dashboard/);
});

Tự động hóa lưới an toàn với GitHub Actions

Một bộ test chỉ thực sự hữu ích nếu nó chạy mỗi khi code thay đổi. Bạn không thể dựa vào trí nhớ. Bạn cần hệ thống thực thi các quy tắc. File .github/workflows/playwright.yml của bạn sẽ trông như thế này:

name: Playwright Tests
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
    - name: Cài đặt các package phụ thuộc
      run: npm ci
    - name: Cài đặt Browser
      run: npx playwright install --with-deps
    - name: Chạy các bài test
      run: npx playwright test
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: playwright-report
        path: playwright-report/

Giờ đây, mỗi Pull Request sẽ kích hoạt một lượt chạy test mới trên một máy ảo. Nếu một lập trình viên vô tình làm hỏng luồng thanh toán, pipeline CI/CD sẽ báo đỏ, và nút “Merge” sẽ bị vô hiệu hóa. Sự tự động hóa đơn giản này ngăn chặn lỗi tiếp cận đến khách hàng của bạn.

Lời kết

Chuyển sang kiểm thử tự động với Playwright thay đổi toàn bộ quy trình phát triển của bạn. Nó thay thế “sự hy vọng” bằng dữ liệu thực tế. Hãy bắt đầu bằng cách tự động hóa luồng quan trọng nhất—thường là đăng nhập hoặc thanh toán—và mở rộng từ đó. Với TypeScript và Page Object Model, bạn không chỉ viết script; bạn đang xây dựng một chính sách bảo hiểm bền vững cho mã nguồn của mình.

Share: