Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

End-to-End Project — Build a Full Auto Suite

Mở đầu — vì sao bài này quan trọng

Suốt 58 bài trước, bạn đã học từng mảnh ghép riêng lẻ: locators, wait strategies, Page Object Model, data-driven, CI/CD, reporting, xử lý flaky test... Mỗi thứ đều hữu ích, nhưng trong thực tế phỏng vấn SDET hay ngày đầu đi làm, không ai hỏi bạn "viết cho tôi một explicit wait". Người ta đưa bạn một ứng dụng và nói: "Tự động hóa việc kiểm thử cho nó đi."

Đó chính là điểm khác biệt giữa người biết công cụ và người biết xây suite. Bài này là bài tổng hợp năng lực. Chúng ta sẽ không học thêm khái niệm mới — chúng ta sẽ ghép tất cả những gì đã học thành một automation suite hoàn chỉnh, chạy được, có báo cáo, tích hợp CI cho một demo app thật là saucedemo.com.

Tại sao điều này quan trọng với sự nghiệp của bạn? Vì portfolio. Một repo GitHub chứa suite tự động hóa sạch sẽ, có README, có CI badge màu xanh, đáng giá hơn mười dòng "thành thạo Selenium" trong CV. Nhà tuyển dụng tại các công ty như FPT Software, KMS Technology hay các product team ở TP.HCM đều đọc code của bạn trước khi đọc lời tự giới thiệu. Bài này giúp bạn có thứ để họ đọc.

Khái niệm cốt lõi

Một "full auto suite" không phải là một đống file test rời rạc. Nó là một hệ thống có kiến trúc rõ ràng, nơi mỗi tầng có trách nhiệm riêng và thay đổi ở tầng này không làm vỡ tầng kia.

Suite hoàn chỉnh gồm những tầng nào

Hãy hình dung suite như một tòa nhà bốn tầng:

  • Tầng test (test layer) — nơi chứa các kịch bản kiểm thử, viết theo ngôn ngữ nghiệp vụ. Ví dụ: "đăng nhập với tài khoản hợp lệ", "thêm sản phẩm vào giỏ và checkout". Tầng này không chứa selector hay logic click.
  • Tầng page object (page layer) — mỗi trang của app là một class, đóng gói selector và hành động. Test gọi loginPage.login(user, pass) chứ không tự tìm phần tử.
  • Tầng core/utilities — driver factory, config reader, quản lý test data, helper cho screenshot khi fail.
  • Tầng hạ tầng (infra) — CI pipeline, reporting, quản lý môi trường.
Nguyên tắc vàng: test đọc như tiếng người, page object giấu chi tiết kỹ thuật, core lo phần lặp lại. Nếu một junior đọc test của bạn mà hiểu ngay đang kiểm cái gì, bạn đã làm đúng.

Phạm vi (scope) — làm ít nhưng làm sâu

Sai lầm lớn nhất của người mới là cố automate mọi thứ. Với saucedemo, đừng viết 80 test. Hãy chọn các luồng nghiệp vụ cốt lõi (critical user journeys):

  • Đăng nhập: hợp lệ, sai mật khẩu, tài khoản bị khóa (locked_out_user).
  • Xem danh sách sản phẩm và sắp xếp (sort).
  • Thêm/xóa sản phẩm khỏi giỏ, kiểm tra badge số lượng.
  • Checkout đầy đủ: nhập thông tin → xác nhận → hoàn tất.
Khoảng 12–18 test được chọn kỹ, chạy ổn định, giá trị hơn 60 test flaky. Đây là tư duy risk-based: automate cái nào mà nếu hỏng thì khách hàng không mua được hàng.

Tiêu chí "hoàn chỉnh" (Definition of Done)

Một suite được coi là hoàn chỉnh khi thỏa mãn:

  • Chạy được bằng một lệnh (ví dụ mvn test hoặc npx playwright test).
  • Không hardcode URL, tài khoản, timeout — tất cả nằm trong file config.
  • Sinh báo cáo (Allure hoặc HTML report) sau mỗi lần chạy.
  • Screenshot tự động khi test fail.
  • Chạy được trên CI (GitHub Actions) mỗi khi push code.
  • README hướng dẫn người lạ cách clone và chạy trong 5 phút.
Nếu thiếu bất kỳ điểm nào ở trên, suite của bạn mới xong "phần code", chưa xong "phần sản phẩm".

Tình huống thực tế

Ví dụ 1 — Bài test sàng lọc ứng viên tại một agency ở Đà Nẵng

Một công ty outsourcing khoảng 300 người ở Đà Nẵng có bài test tuyển SDER: cho ứng viên 4 tiếng, tự động hóa luồng mua hàng của saucedemo, nộp repo GitHub. Hai ứng viên cùng làm được các test pass.

Bạn A nộp một file test.py dài 400 dòng, mọi selector viết thẳng trong test, không có README, chạy phải sửa tay đường dẫn chromedriver.

Bạn B nộp repo có cấu trúc pages/, tests/, config/, một README.md với đúng 3 lệnh để chạy, và một screenshot báo cáo Allure. Chỉ 9 test, nhưng có test cho locked_out_user và một test data-driven chạy qua 3 loại tài khoản.

Kết quả: Bạn B được nhận, dù bạn A viết nhiều code hơn. Người review (một team lead) nói thẳng: "Tôi không tuyển người viết được test. Tôi tuyển người viết test mà 6 tháng sau đồng đội vẫn đọc hiểu."

Bài học: kiến trúc và khả năng bàn giao quan trọng hơn số lượng test. Trong bài project cuối khóa này, hãy đầu tư 30% thời gian cho cấu trúc và README.

Ví dụ 2 — Suite "chạy máy mình thì pass, lên CI thì fail" tại một startup fintech TP.HCM

Một startup ví điện tử ở Quận 1 giao cho intern QA xây suite regression. Intern làm rất tốt trên laptop — 20 test xanh mướt. Nhưng khi đưa lên GitHub Actions, 7 test đỏ.

Nguyên nhân điều tra ra: (1) suite dùng Thread.sleep(2000) khắp nơi, mà runner CI chậm hơn máy cá nhân nên đôi khi 2 giây chưa đủ; (2) test phụ thuộc thứ tự chạy — test "xóa khỏi giỏ" giả định giỏ đã có hàng từ test trước; (3) chạy có giao diện (headed) nên trên CI không có màn hình thì lỗi.

Team mất 2 ngày sửa: thay hết sleep bằng explicit wait, làm mỗi test độc lập (tự setup dữ liệu qua @BeforeMethod), và bật chế độ headless. Sau đó CI ổn định.

Bài học: "chạy trên máy tôi" không phải tiêu chí. Suite phải chạy được trên môi trường sạch, headless, tốc độ khác nhau. Đây chính là lý do project cuối khóa bắt buộc phải chạy qua CI — CI là bài kiểm tra thật cho tính độc lập và ổn định của test.

Ví dụ 3 — Chọn công cụ phù hợp bối cảnh tại một team thương mại điện tử

Một team QA e-commerce (giả định tương tự Tiki hay Sendo về quy mô) tranh luận: junior mới học Selenium muốn dùng Selenium + TestNG cho project đội; nhưng team FE dùng React và đã có sẵn Node.

Họ quyết định thử cả hai trong một sprint spike: viết cùng 5 test cho saucedemo bằng Selenium/Java và bằng Playwright/TypeScript. Kết quả: Playwright ít flaky hơn nhờ auto-wait, setup nhanh hơn, chạy cùng ngôn ngữ với dev FE nên dev sẵn lòng đọc test. Selenium mạnh khi cần chạy trên grid nhiều trình duyệt cũ mà một số ngân hàng đối tác vẫn dùng.

Bài học: với project cuối khóa của bạn, hãy chọn một stack và làm cho tới, đừng ôm đồm. Nếu bạn theo hướng Java/QA truyền thống: Selenium + TestNG + Allure. Nếu bạn theo hướng modern/product: Playwright + built-in reporter. Cả hai đều được chấp nhận — điều quan trọng là làm trọn vẹn.

Hướng dẫn từng bước

Dưới đây là quy trình xây suite cho saucedemo. Tôi minh họa bằng Selenium + Java + Maven + TestNG + Allure (bạn có thể ánh xạ sang Playwright tương đương).

Bước 1 — Dựng khung dự án và cấu trúc thư mục

saucedemo-suite/
├── pom.xml
├── testng.xml
├── README.md
├── src/
│   ├── main/java/
│   │   ├── core/        (DriverFactory, ConfigReader, BaseTest)
│   │   └── pages/       (LoginPage, InventoryPage, CartPage, CheckoutPage)
│   └── test/
│       ├── java/tests/  (LoginTest, CartTest, CheckoutTest)
│       └── resources/   (config.properties, testdata.csv)

Cấu trúc này tách bạch page object (pages/) khỏi test (tests/) và khỏi hạ tầng (core/). Đây là xương sống của mọi suite chuyên nghiệp.

Bước 2 — Xây tầng core

ConfigReader đọc config.properties (chứa base.url, browser, timeout). DriverFactory khởi tạo WebDriver theo config, có hỗ trợ headless. BaseTest chứa @BeforeMethod/@AfterMethod để mở/đóng driver và chụp screenshot khi fail.

public class DriverFactory {
    public static WebDriver create() {
        ChromeOptions opts = new ChromeOptions();
        if (ConfigReader.getBool("headless")) opts.addArguments("--headless=new");
        WebDriver driver = new ChromeDriver(opts);
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(0)); // dùng explicit wait
        return driver;
    }
}

Chú ý: đặt implicit wait = 0 và dùng explicit wait ở page object — tránh trộn hai loại wait gây hành vi khó đoán (đây là bài học từ bài Wait Strategies).

Bước 3 — Viết page object cho từng trang

Mỗi trang một class. Selector là private, hành động là public. Ví dụ LoginPage:

public class LoginPage {
    private final WebDriver driver;
    private final By username = By.id("user-name");
    private final By password = By.id("password");
    private final By loginBtn = By.id("login-button");

public LoginPage(WebDriver driver) { this.driver = driver; }

public InventoryPage loginAs(String user, String pass) { driver.findElement(username).sendKeys(user); driver.findElement(password).sendKeys(pass); driver.findElement(loginBtn).click(); return new InventoryPage(driver); } public String errorMessage() { return new WebDriverWait(driver, Duration.ofSeconds(5)) .until(ExpectedConditions.visibilityOfElementLocated( By.cssSelector("[data-test='error']"))).getText(); } }

Phương thức trả về page tiếp theo (InventoryPage) tạo ra "fluent flow" — test đọc rất mượt.

Bước 4 — Viết test cho các luồng cốt lõi

Test chỉ gọi page object, không đụng selector:

public class LoginTest extends BaseTest {
    @Test
    public void validLoginShowsInventory() {
        InventoryPage inv = new LoginPage(driver)
            .loginAs("standard_user", "secret_sauce");
        Assert.assertTrue(inv.isLoaded(), "Phai vao duoc trang san pham");
    }

@Test public void lockedUserSeesError() { String err = new LoginPage(driver) .attemptLogin("locked_out_user", "secret_sauce"); Assert.assertTrue(err.contains("locked out")); } }

Bước 5 — Thêm data-driven cho phần lặp

Với nhiều loại tài khoản, dùng TestNG @DataProvider đọc từ CSV để tránh copy-paste (áp dụng bài Data-Driven Testing).

Bước 6 — Tích hợp báo cáo Allure

Thêm dependency Allure-TestNG, gắn @Step, @Description, và đính screenshot vào report khi fail. Sau khi chạy: allure serve target/allure-results.

Bước 7 — Viết pipeline GitHub Actions

Tạo .github/workflows/ci.yml: checkout code → setup JDK → mvn test (headless) → upload allure-results làm artifact. Mỗi lần push, CI tự chạy suite. Thêm badge vào README.

Bước 8 — Viết README và hoàn thiện

README gồm: mô tả app, stack, cách chạy (mvn test), cách xem report, và cấu trúc thư mục. Đây là "mặt tiền" của project — đầu tư nghiêm túc.

Lỗi thường gặp & mẹo

  • Test phụ thuộc lẫn nhau. Đừng để test B cần test A chạy trước. Mỗi test tự setup trạng thái của mình trong @BeforeMethod. Cách kiểm tra: chạy ngẫu nhiên thứ tự — nếu vẫn xanh, bạn ổn.
  • Hardcode mọi thứ. URL, tài khoản, timeout phải nằm trong config. Khi app đổi từ môi trường staging sang production, bạn chỉ đổi một file.
  • Quên headless trên CI. CI không có màn hình. Luôn test suite ở chế độ headless trước khi đẩy lên, đừng đợi CI báo đỏ.
  • Dùng Thread.sleep. Đây là "nợ kỹ thuật" số một gây flaky. Thay bằng explicit wait. Nếu bạn thấy mình gõ sleep, hãy dừng lại và tự hỏi "tôi đang đợi điều gì?" — rồi wait chính điều đó.
  • XPath dài kiểu /html/body/div[2]/.... Ưu tiên iddata-test (saucedemo có sẵn data-test). Selector ổn định giảm 80% công bảo trì.
  • Không chụp screenshot khi fail. Khi CI đỏ mà không có ảnh, bạn debug trong bóng tối. Gắn screenshot vào @AfterMethod khi result.getStatus() == FAILURE.
  • Mẹo bàn giao: đặt tên test theo nghiệp vụ (lockedUserSeesError) chứ không phải test1, test2. Report tự động sẽ đọc như một tài liệu đặc tả sống.

Bài tập thực hành

Đây là bài tập tổng kết cả khóa. Hãy làm nghiêm túc — nó sẽ là món đầu tiên trong portfolio của bạn.

Mức cơ bản (bắt buộc):

  • Tạo repo saucedemo-suite với cấu trúc 4 tầng như trên.
  • Viết tối thiểu 3 page object: LoginPage, InventoryPage, CartPage.
  • Viết tối thiểu 8 test phủ: đăng nhập hợp lệ/sai/bị khóa, thêm-xóa sản phẩm khỏi giỏ, kiểm tra badge giỏ hàng.
  • Đưa toàn bộ config (URL, browser, headless) ra file config.properties.
Mức nâng cao:
  • Thêm luồng checkout hoàn chỉnh: từ giỏ hàng → nhập thông tin → xác nhận → màn hình "Thank you".
  • Data-driven hóa test đăng nhập qua ít nhất 3 loại tài khoản bằng DataProvider/parametrize.
  • Tích hợp Allure (hoặc HTML report của Playwright), có screenshot khi fail.
Mức xuất sắc (làm nếu muốn nổi bật khi phỏng vấn):
  • Viết .github/workflows/ci.yml chạy suite headless mỗi lần push, upload report làm artifact.
  • Thêm badge CI vào README và viết README chuẩn để người lạ chạy được trong 5 phút.
  • Chạy suite qua thứ tự ngẫu nhiên 3 lần liên tiếp, tất cả phải xanh — chứng minh test độc lập, không flaky.
Tiêu chí tự chấm: nếu bạn đưa repo cho một người bạn chưa từng thấy nó, họ clone và chạy được mvn test (hoặc npx playwright test) mà không hỏi bạn câu nào, bạn đã đạt.

Tóm tắt

Bài này khép lại hành trình từ những mảnh ghép rời rạc thành một sản phẩm hoàn chỉnh. Điểm cốt lõi cần nhớ:

  • Suite là một hệ thống bốn tầng: test (ngôn ngữ nghiệp vụ) → page object (giấu selector) → core (driver, config, utilities) → infra (CI, report). Mỗi tầng một trách nhiệm.
  • Chọn scope thông minh: 12–18 test cho các luồng cốt lõi ổn định, giá trị hơn hàng chục test flaky.
  • Definition of Done thật sự gồm: chạy một lệnh, không hardcode, có report, screenshot khi fail, chạy được trên CI, và có README rõ ràng.
  • CI là bài kiểm tra thật cho tính độc lập và ổn định — "chạy trên máy tôi" không phải tiêu chí.
  • Khả năng bàn giao thắng số lượng code: kiến trúc sạch và README tốt là thứ nhà tuyển dụng và đồng đội thực sự đánh giá.
Hoàn thành bài tập này, bạn không chỉ "học xong khóa automation" — bạn có một tài sản thật để đặt lên bàn phỏng vấn. Ở bài cuối cùng, chúng ta sẽ nói về mindset và con đường phát triển tiếp theo sau khóa học.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi