NỘI DUNG BÀI HỌC

✅ Hiểu tại sao phải kết hợp API + UI trong automation
✅ Biết flow chuẩn: Setup data bằng API → test UI → cleanup bằng API
✅ Tổ chức framework tách biệt UI / API rõ ràng
✅ Viết test UI phụ thuộc dữ liệu API (không phụ thuộc môi trường)
✅ Chạy toàn bộ test suite (API + UI) bằng 1 lệnh
✅ Tích hợp CI/CD với GitHub Actions + Report

1. Tại sao phải kết hợp UI + API?

Trong thực tế, nếu bạn chỉ test UI thuần túy, bạn sẽ gặp các vấn đề:

  • Chậm: Phải click từng bước để tạo dữ liệu, login.

  • Thiếu ổn định (Flaky): UI thay đổi hoặc load chậm làm test fail.

  • Khó cleanup: Sau khi tạo dữ liệu test, việc xóa bằng UI rất mất thời gian.

Mô hình Hybrid (Kết hợp):

  1. Setup: Gọi API tạo User/Dữ liệu (mất 0.5s).

  2. Test UI: Mở trình duyệt vào đúng trang cần test (mất 2s).

  3. Cleanup: Gọi API xóa dữ liệu vừa tạo (mất 0.5s).


2. Tổ chức cấu trúc Framework (Refactoring)

Để dự án mở rộng được, chúng ta cần tách biệt các thành phần:

project/
├── apis/               # Chứa các class gọi API (Logic backend)
│   └── posts_api.py
├── pages/              # Chứa các Page Object (Logic UI)
│   └── dashboard_page.py
├── tests/              # Chứa các file test
│   ├── api/
│   └── ui/
│       └── test_post_lifecycle.py
├── conftest.py         # Chứa Fixtures dùng chung
├── .github/
│   └── workflows/
│       └── playwright.yml # Cấu hình CI/CD
└── requirements.txt


3. Thực hành: Setup dữ liệu qua API cho UI Test

Giả sử ta test chức năng: Add User trên UI. Thay vì điền form tạo bài viết bằng UI, ta tạo bằng API.




Bước 1: Tạo Fixture: APIRequestContext + random account

import pytest
import random
import string
from playwright.sync_api import sync_playwright

def random_email():
    suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
    return f"lanqa_{suffix}@mailinator.com"

def random_password():
    return "Test@123456"  # demo; có thể đổi theo rule của hệ thống

@pytest.fixture(scope="session")
def api_context():
    with sync_playwright() as p:
        ctx = p.request.new_context(
            base_url="https://book.anhtester.com",
            extra_http_headers={
                "Accept": "application/json",
                "Content-Type": "application/json",
            },
        )
        yield ctx
        ctx.dispose()

@pytest.fixture
def new_user_data():
    return {
        "email": random_email(),
        "name": "LanHa",
        "password": random_password(),
        "phone": "0900000000",
        "address": "HCM",
        "avatarUrl": ""
    }


Bước 2: Helper: gọi API register (có “fallback” nếu backend yêu cầu bọc fields)

Có hệ thống (như Strapi/custom) sẽ bắt payload dạng {"fields": {...}}.
Swagger của bạn nhìn giống body phẳng, nhưng để chắc ăn mình làm 2 bước: gửi phẳng trước, fail thì gửi bọc fields.

api/auth_api.py

def register_user(api_context, payload: dict):
    # Try 1: body phẳng như swagger
    res = api_context.post("/api/register", data=payload)
    if res.ok:
        return res.json()

    # Try 2: fallback - bọc "fields" (nếu backend yêu cầu schema kiểu này)
    res2 = api_context.post("/api/register", data={"fields": payload})
    if res2.ok:
        return res2.json()

    raise AssertionError(
        f"Register failed.\n"
        f"Try1: {res.status} - {res.text()}\n"
        f"Try2: {res2.status} - {res2.text()}"
    )

 

Bước 3: Page Object UI: Login page

class LoginPage:
    EMAIL_INPUT = 'input[placeholder="Email address *"]'
    PASSWORD_INPUT = 'input[placeholder="Password *"]'
    LOGIN_BUTTON = 'button:has-text("Login account")'

    def __init__(self, page):
        self.page = page

    def open(self):
        self.page.goto("https://book.anhtester.com/sign-in")

    def login(self, email: str, password: str):
        self.page.fill(self.EMAIL_INPUT, email)
        self.page.fill(self.PASSWORD_INPUT, password)
        self.page.click(self.LOGIN_BUTTON)

 

Bước 4: Viết Test kết hợp UI + API (tests/ui/test_posts.py)

from api.auth_api import register_user
from pages.login_page import LoginPage

def test_register_by_api_then_login_ui(api_context, page, new_user_data):
    # 1) API: Register
    register_res = register_user(api_context, new_user_data)

    # assert tối thiểu (tuỳ response thực tế)
    assert register_res is not None

    # 2) UI: Login bằng account vừa tạo
    login = LoginPage(page)
    login.open()
    login.login(new_user_data["email"], new_user_data["password"])

    # Verify login successfully
    # User -> search user vừa tạo

4. Tăng tốc Login bằng storage_state

Thay vì bài test nào cũng điền Username/Password, ta login 1 lần bằng API và lưu lại "trạng thái" (Cookies/Token).

Tại conftest.py:

@pytest.fixture(scope="session")
def login_and_save_state(browser):
    context = browser.new_context()
    page = context.new_page()
    
    # Thực hiện login qua API hoặc UI một lần duy nhất
    page.goto("https://example.com/login")
    page.fill("#user", "admin")
    page.fill("#pass", "123456")
    page.click("#login-btn")
    
    # Lưu trạng thái vào file JSON
    context.storage_state(path="auth.json")
    yield "auth.json"
    context.close()

@pytest.fixture
def logged_in_page(browser, login_and_save_state):
    # Tạo context mới từ file auth.json đã có session
    context = browser.new_context(storage_state=login_and_save_state)
    page = context.new_page()
    yield page
    context.close()

5. Cấu hình CI/CD với GitHub Actions

5.1 ) Từng bước tạo YAML file 

Cách 1: Tạo ngay trong GitHub UI

  1. Vào repo trên GitHub

  2. Tab Actions

  3. GitHub gợi ý workflow → chọn set up a workflow yourself

  4. Đặt tên file: playwright.yml

  5. Paste nội dung YAML

  6. Commit trực tiếp lên branch (main hoặc branch mới)

Cách 2: Tạo trong local (khuyến khích khi làm dự án)

  1. Trong project local, tạo thư mục:

    mkdir -p .github/workflows
 
  1. Tạo file:

    touch .github/workflows/playwright.yml
 
  1. Dán YAML vào file

    name: Playwright Tests
    
    on:
      push:
        branches: [ "main" ]
      pull_request:
        branches: [ "main" ]
    
    
    jobs:
      test:
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v4
    
          - name: Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: "3.11"
    
          - name: Install dependencies
            run: |
              pip install -r requirements.txt
              playwright install --with-deps
    
          - name: Run tests
            run: pytest -q --html=report.html --self-contained-html
    
          - name: Upload report
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: playwright-report
              path: report.html
    


    File ghi chú:

    name: Playwright Tests        # ❌ Không bắt buộc – chỉ để hiển thị tên workflow trên GitHub Actions (NÊN CÓ)
    
    on:                           # ✅ BẮT BUỘC – khai báo khi nào workflow được chạy
      push:                       # ❌ Không bắt buộc – chạy khi push code
        branches: [ "main" ]      # ❌ Không bắt buộc – chỉ chạy khi push lên branch main
      pull_request:               # ❌ Không bắt buộc – chạy khi tạo / update Pull Request
        branches: [ "main" ]      # ❌ Không bắt buộc – chỉ áp dụng cho PR vào main
    
    
    jobs:                         # ✅ BẮT BUỘC – workflow phải có ít nhất 1 job
      test:                       # ❌ Không bắt buộc – tên job (đặt gì cũng được)
        runs-on: ubuntu-latest    # ✅ BẮT BUỘC – máy runner dùng để chạy CI (Linux)
    
    
        steps:                    # ✅ BẮT BUỘC – danh sách các bước thực thi trong job
          - uses: actions/checkout@v4
            # ✅ GẦN NHƯ BẮT BUỘC – checkout source code từ repo về runner
            # Không có step này thì runner không có code để chạy test
    
          - name: Set up Python   # ❌ Không bắt buộc – chỉ để mô tả step
            uses: actions/setup-python@v5
            # ❌ Không bắt buộc về mặt cú pháp, nhưng BẮT BUỘC về mặt thực tế cho Python project
            with:
              python-version: "3.11"
              # ❌ Không bắt buộc – nhưng NÊN CÓ để fix version Python, tránh lỗi môi trường
    
          - name: Install dependencies   # ❌ Không bắt buộc – chỉ là tên step
            run: |                        # ✅ BẮT BUỘC (vì cần cài thư viện để chạy test)
              pip install -r requirements.txt
              # Cài pytest, playwright, pytest-html...
              playwright install --with-deps
              # Cài browser (Chromium/Firefox/WebKit) + dependency cho Linux runner
    
          - name: Run tests       # ❌ Không bắt buộc – tên step
            run: pytest -q --html=report.html --self-contained-html
            # ✅ BẮT BUỘC – step chính để chạy automation test
            # -q: giảm log
            # --html: tạo report HTML
            # --self-contained-html: report 1 file duy nhất
    
          - name: Upload report   # ❌ Không bắt buộc – tên step
            if: always()          # ❌ Không bắt buộc – nhưng RẤT NÊN CÓ
            # always(): dù test PASS hay FAIL vẫn upload report
            uses: actions/upload-artifact@v4
            # ❌ Không bắt buộc – dùng để upload file (report, screenshot, video...)
            with:
              name: playwright-report   # ❌ Không bắt buộc – tên artifact hiển thị trên GitHub
              path: report.html         # ❌ Không bắt buộc – file/folder cần upload
    
  2. Commit + push:


5.2) Push lên như thế nào? (từng lệnh)

git status
git add .github/workflows/playwright.yml
git commit -m "Add GitHub Actions CI for Playwright tests"
git push origin main

Nếu bạn đang làm trên branch:

git checkout -b ci/playwright
git add .github/workflows/playwright.yml
git commit -m "Add CI workflow"
git push origin ci/playwright

→ rồi lên GitHub tạo Pull Request.


5.3) Kiểm tra workflow đã chạy chưa

  1. Vào repo GitHub → tab Actions

  2. Chọn workflow “Playwright Tests”

  3. Click vào run gần nhất → xem log từng step

  4. Nếu có upload artifact → tải report.html về xem


6. Best Practices cho CI/CD

  • Headless Mode: Luôn chạy ở chế độ không giao diện (headless=True) trên CI để tiết kiệm tài nguyên.

  • Environment Variables: Không để Password/Token trong code. Dùng GitHub Secrets và đọc qua os.environ.

  • Artifacts: Luôn upload screenshot/video/html report khi test fail để dễ debug trên GitHub.

Teacher

Teacher

Hà Lan

QA Automation

With over 5 years of experience in web, API, and mobile test automation, built strong expertise in designing and maintaining automation frameworks across various domains and international projects. Committed to mentoring and knowledge sharing, I provide practical guidance and proven techniques to help aspiring testers develop their skills and succeed in the automation field.

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