Trang chủ
QA Learning Hub
Chương 13 · Test Automation
Chương 13 Trung Cấp

Test Automation

Từ automation strategy đến Playwright implementation, Page Object Model và CI/CD integration — con đường lên Senior QA.

🎯 Automation Strategy
Nên Automate ✅Không nên Automate ❌
Regression tests chạy mỗi sprintTest chỉ chạy 1 lần (one-time)
Smoke tests sau mỗi deployExploratory và usability testing
Data-driven tests (nhiều bộ data)UI thay đổi rất thường xuyên
Performance / load testingNew features đang development
API contract testingTính năng không stable
Cross-browser compatibilityKhi cost maintain > value gained

Test Pyramid — Kim tự tháp kiểm thử:

  • Unit Tests (70%): Dev viết, chạy nhanh (<1ms/test), hàng nghìn tests. Feedback ngay lập tức.
  • Integration/API Tests (20%): QA + Dev, chạy vừa (<1s/test), test tích hợp giữa services.
  • E2E UI Tests (10%): QA, chạy chậm (5-30s/test), chỉ critical user flows. Ít nhưng có giá trị cao.
⚠️ Ice Cream Cone Anti-pattern
Nhiều E2E tests (chậm, flaky, đắt) + ít unit tests = "Ice Cream Cone." Ngược với Test Pyramid. Kết quả: CI chậm, nhiều false failures, team mất niềm tin vào tests. Recognize và fix pattern này.
🎭 Playwright — Hướng Dẫn Từng Bước
# 1. Setup project npm init playwright@latest # Chọn: TypeScript, tests/ folder, GitHub Actions # 2. First test file: tests/login.spec.ts
import { test, expect } from '@playwright/test'; test.describe('Login', () => { test('successful login redirects to dashboard', async ({ page }) => { await page.goto('/login'); // Fill form using semantic locators (preferred) await page.getByLabel('Email').fill('user@test.com'); await page.getByLabel('Password').fill('Password123!'); await page.getByRole('button', { name: 'Sign in' }).click(); // Assertions await expect(page).toHaveURL('/dashboard'); await expect(page.getByText('Welcome back')).toBeVisible(); }); test('shows error for invalid credentials', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('wrong@test.com'); await page.getByLabel('Password').fill('wrongpass'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page.getByText('Invalid email or password')).toBeVisible(); await expect(page).toHaveURL('/login'); // stayed on login }); });

Locator Priority (best → worst):

  1. getByRole('button', {name: 'Submit'}) — Accessible role + name. Most resilient.
  2. getByLabel('Email address') — Form label. Good for inputs.
  3. getByText('Click here') — Visible text. Breaks if text changes.
  4. getByPlaceholder('Enter email') — Placeholder text.
  5. getByTestId('submit-btn') — data-testid attribute. Requires dev cooperation.
  6. locator('.btn-primary') — CSS selector. Last resort, fragile.
  7. locator('//button[1]') — XPath. Avoid unless necessary.
🏗️ Page Object Model (POM)
// pages/LoginPage.ts import { Page, expect } from '@playwright/test'; export class LoginPage { constructor(private page: Page) {} // Locators as getters — lazy evaluated get emailInput() { return this.page.getByLabel('Email'); } get passwordInput() { return this.page.getByLabel('Password'); } get signInButton() { return this.page.getByRole('button', {name: 'Sign in'}); } get errorMessage() { return this.page.getByRole('alert'); } // Action methods async navigate() { await this.page.goto('/login'); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.signInButton.click(); } } // tests/login.spec.ts — Clean test using POM import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; test('login success', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.navigate(); await loginPage.login('user@test.com', 'Password123!'); await expect(page).toHaveURL('/dashboard'); });
🚀 GitHub Actions CI Integration
# .github/workflows/playwright.yml name: Playwright Tests on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: timeout-minutes: 30 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Install Playwright browsers run: npx playwright install --with-deps chromium - name: Run Playwright tests run: npx playwright test env: BASE_URL: ${{ secrets.STAGING_URL }} - name: Upload test report uses: actions/upload-artifact@v4 if: always() # upload even on failure with: name: playwright-report path: playwright-report/ retention-days: 14
⚠️ Flaky Test Management

Nguyên nhân phổ biến và cách fix:

  • Race conditionClick button trước khi load xong. Fix: dùng await expect(element).toBeVisible() trước khi interact. Playwright auto-waits nhưng check custom conditions.
  • Hard-coded waitsawait page.waitForTimeout(3000) — fragile. Fix: dùng await page.waitForResponse('/api/data') hoặc wait for element state change.
  • Shared test dataTests dùng chung data, can thiệp nhau khi chạy parallel. Fix: mỗi test tạo data riêng, cleanup sau.
  • Order dependencyTest B cần kết quả của Test A. Fix: tests phải independent. Dùng beforeEach để setup state.
  • Dynamic contentTimestamp, random IDs trong assertions. Fix: dùng regex hoặc check existence, không check exact value.
  • Environment issuesTest pass locally, fail CI. Thường do: timezone, locale, network conditions. Fix: consistent Docker environment.
🏆 Senior Insight: Automation ROI

Senior QA không chỉ viết automation — họ biết tính ROI. Nếu 1 test case mất 2 giờ viết + 30 phút maintain mỗi sprint, nhưng manual testing chỉ mất 5 phút × 20 sprints = 100 phút = 1.7 giờ → automation KHÔNG có ROI ở đây. Focus automation vào high-frequency, time-consuming, repetitive tests.

⚖️ Framework Comparison
PlaywrightCypressSelenium WebDriver
LanguageJS/TS/Python/Java/C#JavaScript/TypeScriptJava/Python/C#/Ruby/JS
BrowsersChromium, Firefox, WebKit (Safari)Chrome, Edge, FirefoxAll major browsers
SpeedFast (parallel native)Fast but single tabSlow (HTTP protocol overhead)
SetupVery easyEasyComplex (WebDriver setup)
Auto-waitYes (smart)YesNo (manual waits)
Multi-tabYesLimitedYes
Best forModern teams, cross-browserFrontend-focused teamsLegacy projects, Java shops
Job marketGrowing fastPopularLarge existing base
✏️ Bài Tập
📝 Bài Tập: Page Object Model

Tạo POM và test cho Registration form có các fields: First Name, Last Name, Email, Password, Confirm Password, Agree to Terms checkbox, Submit button.

  1. Viết RegistrationPage.ts với tất cả locators và action methods
  2. Viết 5 test cases: successful registration, duplicate email, password mismatch, missing required fields, terms not checked
  3. Thêm beforeEach để navigate tới page trước mỗi test
  4. Viết helper function để generate unique test email cho mỗi test run