# 🤖 Lộ Trình Học Selenium Automation
## Dành cho Tester có 1 Năm Kinh Nghiệm Manual Testing

> **Cam kết:** 16 tuần · 1-2 giờ/ngày · Từ zero automation → job-ready automation tester

---

## 📊 Tổng Quan Tiến Độ

| Giai đoạn | Tuần | Chủ đề | Mục tiêu |
|-----------|------|--------|-----------|
| 🟡 Phase 1 | 1-2 | Setup & First Script | Chạy được Selenium |
| 🟠 Phase 2 | 3-4 | Manual → Automation | Convert test case thật |
| 🔵 Phase 3 | 5-8 | Page Object Model | Code có cấu trúc |
| 🟣 Phase 4 | 9-12 | E2E Testing | Test luồng thực tế |
| 🔴 Phase 5 | 13-16 | CI/CD & Reporting | Production-ready |

---

## 🟡 GIAI ĐOẠN 1 — Tuần 1-2: Nền Tảng & Script Đầu Tiên

> **Mục tiêu:** Cài đặt môi trường hoàn chỉnh và chạy được script Selenium đầu tiên

### 📦 Cài Đặt Môi Trường

- [ ] **Cài Python 3.10+** từ python.org, chọn "Add to PATH" khi cài
  > ⚠️ *Tại sao quan trọng:* Python là ngôn ngữ bạn sẽ viết test — không có nó, không có gì cả

- [ ] **Verify Python** bằng lệnh `python --version` và `pip --version` trên terminal
  > ⚠️ *Tại sao quan trọng:* Nhiều bug kỳ lạ xuất phát từ việc cài Python sai hoặc thiếu pip

- [ ] **Tạo virtual environment** với `python -m venv venv` và kích hoạt nó
  > ⚠️ *Tại sao quan trọng:* Tránh xung đột thư viện giữa các project — đây là best practice bắt buộc

- [ ] **Cài Selenium** bằng `pip install selenium`
  > ⚠️ *Tại sao quan trọng:* Selenium là bridge giữa Python code và browser

- [ ] **Cài pytest** bằng `pip install pytest`
  > ⚠️ *Tại sao quan trọng:* Pytest là test framework — giúp tổ chức, chạy và báo cáo test

- [ ] **Tạo file `requirements.txt`** bằng `pip freeze > requirements.txt`
  > ⚠️ *Tại sao quan trọng:* Giúp teammate cài cùng version — tránh lỗi "chạy được máy tôi nhưng không chạy được máy bạn"

### 🌐 Cài ChromeDriver

- [ ] **Kiểm tra version Chrome** hiện tại (Settings → About Chrome)
  > ⚠️ *Tại sao quan trọng:* ChromeDriver phải match version với Chrome — sai version là lỗi ngay

- [ ] **Cài `webdriver-manager`** bằng `pip install webdriver-manager`
  > ⚠️ *Tại sao quan trọng:* Tự động tải đúng ChromeDriver — không cần update thủ công mỗi lần Chrome update

- [ ] **Test kết nối ChromeDriver** với đoạn code khởi tạo driver đơn giản

```python
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("https://google.com")
print(driver.title)  # Nếu in ra "Google" là thành công!
driver.quit()
```

> ⚠️ *Tại sao quan trọng:* Xác nhận môi trường hoạt động trước khi viết test thật

### 🚀 Script Đầu Tiên

- [ ] **Viết script mở browser và navigate** đến một website bất kỳ
  > ⚠️ *Tại sao quan trọng:* Hands-on ngay từ đầu — đọc mà không code thì không nhớ được

- [ ] **Thêm `driver.quit()`** cuối mỗi script
  > ⚠️ *Tại sao quan trọng:* Nếu quên, browser zombie sẽ ngốn RAM — script chạy nhiều lần sẽ crash máy

- [ ] **Viết script điền form đơn giản** (Google Search, login demo site)
  > ⚠️ *Tại sao quan trọng:* Thao tác form là 70% công việc automation testing hàng ngày

- [ ] **Chạy thành công script đầu tiên** và thấy browser tự động thao tác
  > 🎉 *Milestone:* Đây là khoảnh khắc "aha!" — bạn đã viết code điều khiển browser!

### 🔍 3 Locator Cơ Bản

- [ ] **Học và thực hành `By.ID`**
  ```python
  driver.find_element(By.ID, "username")
  ```
  > ⚠️ *Tại sao quan trọng:* ID là locator nhanh nhất và ổn định nhất — ưu tiên dùng khi có

- [ ] **Học và thực hành `By.CSS_SELECTOR`**
  ```python
  driver.find_element(By.CSS_SELECTOR, ".btn-login")
  driver.find_element(By.CSS_SELECTOR, "input[type='email']")
  ```
  > ⚠️ *Tại sao quan trọng:* CSS Selector linh hoạt, nhanh, dễ đọc — đây là locator bạn dùng nhiều nhất

- [ ] **Học và thực hành `By.XPATH`**
  ```python
  driver.find_element(By.XPATH, "//button[text()='Login']")
  driver.find_element(By.XPATH, "//input[@placeholder='Email']")
  ```
  > ⚠️ *Tại sao quan trọng:* XPath mạnh nhất khi element không có ID/class — dùng khi CSS không đủ

- [ ] **Dùng Chrome DevTools (F12)** để inspect element và thử tìm locator
  > ⚠️ *Tại sao quan trọng:* Skill inspect DevTools là kỹ năng sống còn — không biết thì không tìm được locator

- [ ] **Thực hành trên [demoqa.com](https://demoqa.com)** hoặc [the-internet.herokuapp.com](https://the-internet.herokuapp.com)
  > ⚠️ *Tại sao quan trọng:* Các site demo được thiết kế để học — có đủ loại element thú vị để luyện

---

**✅ Checkpoint Tuần 1-2:** Bạn có thể mở browser, navigate, tìm element và tương tác với nó bằng code.

---

## 🟠 GIAI ĐOẠN 2 — Tuần 3-4: Chuyển Manual Test Sang Automation

> **Mục tiêu:** Convert test case bạn đã biết thành code — đây là lúc automation "có nghĩa" với bạn

### 🔄 Convert Test Case Manual

- [ ] **Chọn 3 test case manual** từ project hiện tại (ưu tiên: login, search, submit form)
  > ⚠️ *Tại sao quan trọng:* Dùng test case quen thuộc giúp bạn tập trung học code, không bị phân tâm hiểu nghiệp vụ

- [ ] **Phân tích test case 1 (Login):** Xác định từng step cần locator và action gì
  > ⚠️ *Tại sao quan trọng:* Kỹ năng "dịch" manual step sang code là core skill của automation tester

- [ ] **Viết automation script cho test case Login:**
  ```python
  def test_login_success():
      driver.find_element(By.ID, "email").send_keys("test@example.com")
      driver.find_element(By.ID, "password").send_keys("password123")
      driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
      assert "Dashboard" in driver.title
  ```
  > ⚠️ *Tại sao quan trọng:* Assert là linh hồn của test — không có assert thì script chỉ là "click bot"

- [ ] **Convert test case 2** (ví dụ: Search product)
  > ⚠️ *Tại sao quan trọng:* Lần 2 sẽ nhanh hơn lần 1 — bạn đang xây dụng muscle memory

- [ ] **Convert test case 3** (ví dụ: Submit form, add to cart)
  > ⚠️ *Tại sao quan trọng:* Sau 3 test case, bạn sẽ thấy pattern lặp lại và bắt đầu nghĩ đến tối ưu

### ⏱️ Học WebDriverWait

- [ ] **Hiểu tại sao cần Wait** — chạy script nhanh và thấy `NoSuchElementException`
  > ⚠️ *Tại sao quan trọng:* Web app ngày nay load async — script chạy nhanh hơn browser load element

- [ ] **Xóa `time.sleep()` khỏi toàn bộ code** của bạn
  ```python
  # ❌ Sai — cứng nhắc, lãng phí thời gian
  time.sleep(3)
  
  # ✅ Đúng — chờ đúng lúc cần, không hơn không kém
  WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "submit")))
  ```
  > ⚠️ *Tại sao quan trọng:* `time.sleep(5)` vừa chậm vừa không đáng tin — bộ test sẽ flaky liên tục

- [ ] **Thực hành `EC.presence_of_element_located`** — element xuất hiện trong DOM
  > ⚠️ *Tại sao quan trọng:* Dùng khi cần verify element tồn tại nhưng chưa cần visible hay clickable

- [ ] **Thực hành `EC.element_to_be_clickable`** — element sẵn sàng để click
  > ⚠️ *Tại sao quan trọng:* Button có thể hiện trong DOM nhưng bị disabled — locator này an toàn hơn

- [ ] **Thực hành `EC.visibility_of_element_located`** — element visible với user
  > ⚠️ *Tại sao quan trọng:* Dùng cho assertion — nếu user không thấy element thì test nên fail

- [ ] **Thực hành `EC.text_to_be_present_in_element`** — verify text xuất hiện
  > ⚠️ *Tại sao quan trọng:* Verify message success/error mà không cần sleep — test nhanh và đáng tin hơn

### 🧪 Setup/Teardown với Pytest

- [ ] **Đổi tất cả function thành pytest format** (prefix `test_`)
  > ⚠️ *Tại sao quan trọng:* Pytest chỉ nhận diện test function có prefix `test_` — sai là không chạy

- [ ] **Học `setup_method` và `teardown_method`:**
  ```python
  class TestLogin:
      def setup_method(self):
          self.driver = webdriver.Chrome(...)
          self.driver.get("https://yoursite.com")
      
      def teardown_method(self):
          self.driver.quit()  # Luôn chạy dù test pass hay fail
      
      def test_login_success(self):
          # test code here
  ```
  > ⚠️ *Tại sao quan trọng:* Đảm bảo mỗi test bắt đầu từ clean state — test không bị phụ thuộc nhau

- [ ] **Thêm `self.driver.quit()` trong teardown và verify** nó chạy ngay cả khi test fail
  > ⚠️ *Tại sao quan trọng:* Nếu teardown không chạy khi fail, browser tích tụ → máy crash sau vài chục test

- [ ] **Chạy `pytest -v`** và đọc output — hiểu PASSED/FAILED/ERROR khác nhau thế nào
  > ⚠️ *Tại sao quan trọng:* Đọc được output pytest là kỹ năng debug cơ bản nhất

---

**✅ Checkpoint Tuần 3-4:** Bạn có 3 test case tự động chạy được bằng `pytest`, có Wait đúng cách, không có `time.sleep()`.

---

## 🔵 GIAI ĐOẠN 3 — Tuần 5-8: Page Object Model

> **Mục tiêu:** Viết code có cấu trúc — đây là điểm phân biệt "automation tester" và "người viết script"

### 🏗️ Hiểu Vấn Đề Trước Khi Học Giải Pháp

- [ ] **Thử thay đổi 1 locator** trong code cũ và đếm xem phải sửa bao nhiêu chỗ
  > ⚠️ *Tại sao quan trọng:* Trải nghiệm "đau" này sẽ làm bạn hiểu TẠI SAO cần POM — không phải học vì phải học

- [ ] **Đọc và hiểu nguyên tắc POM:** Tách locator + action ra khỏi test logic
  > ⚠️ *Tại sao quan trọng:* POM là pattern phổ biến nhất trong automation — CV không có POM khó xin việc

### 📄 Tạo BasePage

- [ ] **Tạo file `base_page.py`** với class BasePage:

```python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
    
    def find_element(self, locator):
        return self.wait.until(EC.presence_of_element_located(locator))
    
    def click(self, locator):
        self.wait.until(EC.element_to_be_clickable(locator)).click()
    
    def type_text(self, locator, text):
        element = self.find_element(locator)
        element.clear()
        element.send_keys(text)
    
    def get_text(self, locator):
        return self.find_element(locator).text
    
    def is_displayed(self, locator):
        try:
            return self.find_element(locator).is_displayed()
        except:
            return False
```

> ⚠️ *Tại sao quan trọng:* BasePage tập trung xử lý WebDriverWait — các page class không cần lo về wait nữa

- [ ] **Thêm method `navigate_to(url)`** vào BasePage
  > ⚠️ *Tại sao quan trọng:* Navigation từ BasePage giúp test code clean hơn, dễ đọc hơn

- [ ] **Thêm method `take_screenshot(name)`** vào BasePage
  > ⚠️ *Tại sao quan trọng:* Screenshot khi fail là bằng chứng bug — không có screenshot thì dev khó tin report

### 📋 Tạo 3 Page Class

- [ ] **Tạo `login_page.py`:**

```python
from selenium.webdriver.common.by import By
from pages.base_page import BasePage

class LoginPage(BasePage):
    # Locators — đổi ở đây là đổi toàn bộ
    EMAIL_INPUT = (By.ID, "email")
    PASSWORD_INPUT = (By.ID, "password")
    LOGIN_BUTTON = (By.CSS_SELECTOR, "button[type='submit']")
    ERROR_MESSAGE = (By.CSS_SELECTOR, ".error-message")
    
    # Actions
    def enter_email(self, email):
        self.type_text(self.EMAIL_INPUT, email)
    
    def enter_password(self, password):
        self.type_text(self.PASSWORD_INPUT, password)
    
    def click_login(self):
        self.click(self.LOGIN_BUTTON)
    
    def login(self, email, password):
        """Full login flow trong 1 method"""
        self.enter_email(email)
        self.enter_password(password)
        self.click_login()
    
    def get_error_message(self):
        return self.get_text(self.ERROR_MESSAGE)
```

> ⚠️ *Tại sao quan trọng:* Locator định nghĩa 1 lần duy nhất — khi UI thay đổi chỉ sửa ở đây

- [ ] **Tạo `dashboard_page.py`** với các element sau khi login thành công
  > ⚠️ *Tại sao quan trọng:* Mỗi page có class riêng — test sau login sẽ dùng DashboardPage, không mix lẫn LoginPage

- [ ] **Tạo `product_page.py`** hoặc page liên quan đến feature bạn đang test
  > ⚠️ *Tại sao quan trọng:* Thực hành tạo page thứ 3 giúp bạn hình thành thói quen cấu trúc đúng

- [ ] **Đảm bảo mỗi Page class chỉ chứa element và action của page đó** — không có assert trong page class
  > ⚠️ *Tại sao quan trọng:* Assert thuộc về test, không thuộc về page — trộn lẫn sẽ làm page class khó reuse

### 📝 Viết 10 Test Case có POM

- [ ] **Tạo cấu trúc thư mục chuẩn:**
  ```
  project/
  ├── pages/
  │   ├── __init__.py
  │   ├── base_page.py
  │   ├── login_page.py
  │   └── dashboard_page.py
  ├── tests/
  │   ├── __init__.py
  │   └── test_login.py
  └── requirements.txt
  ```
  > ⚠️ *Tại sao quan trọng:* Cấu trúc rõ ràng giúp onboard teammate mới trong 5 phút thay vì 5 tiếng

- [ ] **Viết test 1-3:** Login scenarios (success, wrong password, empty field)
  > ⚠️ *Tại sao quan trọng:* Login là critical path — thường là test đầu tiên run trong mọi pipeline

- [ ] **Viết test 4-6:** Navigation và verify page content
  > ⚠️ *Tại sao quan trọng:* Smoke test đơn giản nhưng catch được nhiều production bug

- [ ] **Viết test 7-9:** Form interaction (submit, validation, clear)
  > ⚠️ *Tại sao quan trọng:* Form testing chiếm phần lớn automation — phải thành thạo

- [ ] **Viết test 10:** Negative test case (verify error message hiển thị đúng)
  > ⚠️ *Tại sao quan trọng:* Tester giỏi test cả negative path — automation phải cover cả 2

- [ ] **Chạy toàn bộ 10 test** bằng `pytest tests/ -v` — đảm bảo tất cả pass
  > ⚠️ *Tại sao quan trọng:* Test fail liên tục mà không fix thì mất niềm tin vào test suite

---

**✅ Checkpoint Tuần 5-8:** Bạn có 10 test case dùng POM, chạy được bằng 1 lệnh, code có cấu trúc rõ ràng.

---

## 🟣 GIAI ĐOẠN 4 — Tuần 9-12: E2E Testing & Advanced Scenarios

> **Mục tiêu:** Test luồng thực tế từ đầu đến cuối — đây mới là giá trị thật của automation

### 🛒 Test Luồng E-Commerce End-to-End

- [ ] **Map out toàn bộ luồng E2E:** Guest → Search → Product Detail → Add to Cart → Checkout → Confirm Order
  > ⚠️ *Tại sao quan trọng:* E2E test bắt được bug integration mà unit test và manual test từng bước không thấy

- [ ] **Tạo page class cho từng bước:** `SearchPage`, `ProductDetailPage`, `CartPage`, `CheckoutPage`
  > ⚠️ *Tại sao quan trọng:* E2E test nhiều page — POM càng quan trọng hơn để code không thành mớ hỗn độn

- [ ] **Viết test luồng happy path** — từ search đến order confirmation:
  ```python
  def test_purchase_product_e2e(driver):
      search_page = SearchPage(driver)
      search_page.search_product("laptop")
      
      product_page = ProductDetailPage(driver)
      product_page.select_first_result()
      product_page.add_to_cart()
      
      cart_page = CartPage(driver)
      cart_page.proceed_to_checkout()
      
      checkout_page = CheckoutPage(driver)
      checkout_page.fill_shipping_info(test_data)
      checkout_page.place_order()
      
      assert checkout_page.get_order_confirmation() is not None
  ```
  > ⚠️ *Tại sao quan trọng:* Happy path E2E là test có value cao nhất — release mà không có test này là gamble

- [ ] **Verify từng bước trong luồng** có assertion, không chỉ verify ở bước cuối
  > ⚠️ *Tại sao quan trọng:* Nếu chỉ assert cuối, bạn không biết bug xảy ra ở bước nào

- [ ] **Thực hành trên [automationexercise.com](https://automationexercise.com)** nếu không có app thật
  > ⚠️ *Tại sao quan trọng:* Site này có đầy đủ luồng e-commerce thật để luyện, miễn phí

### ⚡ Xử Lý Dynamic Element

- [ ] **Xử lý Dynamic ID** bằng partial CSS/XPath:
  ```python
  # ❌ Fragile — ID thay đổi mỗi lần load
  By.ID, "product_98765"
  
  # ✅ Dùng attribute ổn định hơn
  By.CSS_SELECTOR, "[data-product-id]"
  By.XPATH, "//div[contains(@class, 'product-card')]"
  ```
  > ⚠️ *Tại sao quan trọng:* Dynamic ID là thủ phạm số 1 gây flaky test — phải biết cách bypass

- [ ] **Xử lý Dropdown** với `Select` class:
  ```python
  from selenium.webdriver.support.ui import Select
  select = Select(driver.find_element(By.ID, "country"))
  select.select_by_visible_text("Vietnam")
  ```
  > ⚠️ *Tại sao quan trọng:* Dropdown native HTML rất phổ biến — không biết Select class là miss nhiều test

- [ ] **Xử lý Alert/Popup:**
  ```python
  alert = driver.switch_to.alert
  alert_text = alert.text
  alert.accept()  # hoặc alert.dismiss()
  ```
  > ⚠️ *Tại sao quan trọng:* Confirm dialog trong checkout, delete action — không handle được thì test stuck

- [ ] **Xử lý multiple tabs/windows:**
  ```python
  driver.switch_to.window(driver.window_handles[-1])  # Tab mới nhất
  ```
  > ⚠️ *Tại sao quan trọng:* Link mở tab mới rất phổ biến — không switch window thì test tiếp theo sẽ fail

- [ ] **Xử lý iframe:**
  ```python
  driver.switch_to.frame("payment-iframe")
  # ... interact with iframe elements
  driver.switch_to.default_content()  # Quay lại main page
  ```
  > ⚠️ *Tại sao quan trọng:* Payment gateway, map, video embed thường nằm trong iframe — không switch thì không tìm được element

- [ ] **Xử lý Scroll và element ngoài viewport:**
  ```python
  driver.execute_script("arguments[0].scrollIntoView(true);", element)
  ```
  > ⚠️ *Tại sao quan trọng:* Element ngoài màn hình không click được — scroll vào trước là best practice

- [ ] **Xử lý Hover/Tooltip:**
  ```python
  from selenium.webdriver.common.action_chains import ActionChains
  ActionChains(driver).move_to_element(element).perform()
  ```
  > ⚠️ *Tại sao quan trọng:* Navigation menu dropdown thường cần hover — thiếu kỹ năng này bỏ sót nhiều test

### 📊 Quản Lý Test Data

- [ ] **Tạo file `test_data.py`** hoặc `conftest.py` với dữ liệu test tập trung:
  ```python
  # test_data.py
  VALID_USER = {
      "email": "test@example.com",
      "password": "Test@123"
  }
  
  INVALID_USERS = [
      ("wrong@email.com", "Test@123", "Email not found"),
      ("test@example.com", "wrongpass", "Invalid password"),
      ("", "", "Email is required"),
  ]
  ```
  > ⚠️ *Tại sao quan trọng:* Hard-code data trong test là anti-pattern — thay đổi data phải sửa từng file

- [ ] **Dùng `@pytest.mark.parametrize`** cho data-driven test:
  ```python
  @pytest.mark.parametrize("email,password,expected_error", INVALID_USERS)
  def test_login_invalid(driver, email, password, expected_error):
      login_page = LoginPage(driver)
      login_page.login(email, password)
      assert login_page.get_error_message() == expected_error
  ```
  > ⚠️ *Tại sao quan trọng:* 1 test function cover nhiều data case — giảm code lặp, tăng coverage

- [ ] **Tạo file `config.py`** với URL và environment config:
  ```python
  BASE_URL = "https://staging.yoursite.com"
  IMPLICIT_WAIT = 10
  SCREENSHOT_PATH = "screenshots/"
  ```
  > ⚠️ *Tại sao quan trọng:* Đổi environment (staging/production) chỉ cần đổi 1 file, không phải 50 test file

---

**✅ Checkpoint Tuần 9-12:** Bạn có E2E test hoàn chỉnh, handle được dynamic element, data không hard-code trong test.

---

## 🔴 GIAI ĐOẠN 5 — Tuần 13-16: CI/CD & Professional Reporting

> **Mục tiêu:** Test suite của bạn chạy được tự động, có report đẹp — đây là level production-ready

### ⚙️ Cấu Hình Pytest Fixtures

- [ ] **Hiểu fixture là gì** và tại sao dùng fixture thay vì `setup_method`
  > ⚠️ *Tại sao quan trọng:* Fixture linh hoạt hơn, có scope control, có thể share giữa nhiều test file

- [ ] **Tạo `conftest.py`** với fixture driver:
  ```python
  import pytest
  from selenium import webdriver
  from selenium.webdriver.chrome.service import Service
  from webdriver_manager.chrome import ChromeDriverManager
  
  @pytest.fixture(scope="function")
  def driver():
      """Mỗi test function nhận 1 browser instance mới"""
      options = webdriver.ChromeOptions()
      options.add_argument("--headless")       # Không hiện browser
      options.add_argument("--no-sandbox")     # Cần thiết cho CI
      options.add_argument("--disable-dev-shm-usage")  # Tránh crash trên Linux
      
      driver = webdriver.Chrome(
          service=Service(ChromeDriverManager().install()),
          options=options
      )
      driver.maximize_window()
      driver.implicitly_wait(10)
      
      yield driver  # Đây là lúc test chạy
      
      driver.quit()  # Teardown tự động sau mỗi test
  ```
  > ⚠️ *Tại sao quan trọng:* `conftest.py` tự động được pytest nhận diện — fixture available cho mọi test file trong folder

- [ ] **Thêm fixture `base_url`** và dùng trong test
  > ⚠️ *Tại sao quan trọng:* URL từ fixture có thể override bằng command line — `pytest --base-url=staging.com`

- [ ] **Thêm `scope="session"`** cho fixture chỉ cần chạy 1 lần (ví dụ: login token):
  ```python
  @pytest.fixture(scope="session")
  def auth_token():
      # Gọi API login 1 lần, dùng cho cả session
      return get_token_via_api()
  ```
  > ⚠️ *Tại sao quan trọng:* Session scope tiết kiệm thời gian — không login lại mỗi test nếu test đó không test login

- [ ] **Tạo fixture chụp screenshot khi test fail:**
  ```python
  @pytest.fixture(scope="function")
  def driver(request):
      driver = webdriver.Chrome(...)
      yield driver
      
      # Chụp screenshot nếu test fail
      if request.node.rep_call.failed:
          driver.save_screenshot(f"screenshots/{request.node.name}.png")
      
      driver.quit()
  ```
  > ⚠️ *Tại sao quan trọng:* Screenshot tự động khi fail là "bằng chứng phạm tội" — giúp debug 10x nhanh hơn

### 📈 Tạo HTML Report

- [ ] **Cài `pytest-html`:** `pip install pytest-html`
  > ⚠️ *Tại sao quan trọng:* Report dạng HTML dễ share với manager, dev, PO hơn terminal output

- [ ] **Chạy test với HTML report:** `pytest tests/ -v --html=reports/report.html --self-contained-html`
  > ⚠️ *Tại sao quan trọng:* `--self-contained-html` tạo file duy nhất không cần folder asset kèm theo

- [ ] **Mở report và verify** screenshot lỗi đính kèm đúng chỗ
  > ⚠️ *Tại sao quan trọng:* Report phải readable — nếu khó đọc thì không ai xem

- [ ] **Tạo `pytest.ini`** hoặc `pyproject.toml` với config mặc định:
  ```ini
  [pytest]
  addopts = -v --html=reports/report.html --self-contained-html
  testpaths = tests
  ```
  > ⚠️ *Tại sao quan trọng:* Không cần nhớ và gõ options dài — chỉ cần `pytest` là đủ

- [ ] **(Bonus)** Cài `allure-pytest` và tạo Allure report:
  ```bash
  pip install allure-pytest
  pytest --alluredir=allure-results
  allure serve allure-results
  ```
  > ⚠️ *Tại sao quan trọng:* Allure report đẹp hơn pytest-html rất nhiều — dùng trong team lớn và corporate

### 🔄 Tích Hợp GitHub Actions CI/CD

- [ ] **Tạo repository trên GitHub** và push toàn bộ code lên
  > ⚠️ *Tại sao quan trọng:* Code trên GitHub = portfolio — link GitHub là bằng chứng kỹ năng khi phỏng vấn

- [ ] **Tạo file `.github/workflows/selenium-tests.yml`:**
  ```yaml
  name: Selenium Automation Tests
  
  on:
    push:
      branches: [ main ]
    pull_request:
      branches: [ main ]
    schedule:
      - cron: '0 9 * * 1-5'  # Chạy 9am mỗi ngày làm việc
  
  jobs:
    test:
      runs-on: ubuntu-latest
      
      steps:
        - name: Checkout code
          uses: actions/checkout@v3
        
        - name: Setup Python
          uses: actions/setup-python@v4
          with:
            python-version: '3.10'
        
        - name: Install dependencies
          run: pip install -r requirements.txt
        
        - name: Run Selenium Tests
          run: pytest tests/ -v --html=reports/report.html --self-contained-html
        
        - name: Upload Test Report
          uses: actions/upload-artifact@v3
          if: always()  # Upload report dù pass hay fail
          with:
            name: test-report
            path: reports/report.html
  ```
  > ⚠️ *Tại sao quan trọng:* CI/CD là yêu cầu tối thiểu ở hầu hết công ty — không biết thì khó lên senior

- [ ] **Push code và xem GitHub Actions chạy** ở tab Actions trên GitHub
  > ⚠️ *Tại sao quan trọng:* Nhìn test chạy tự động trên cloud lần đầu là cảm giác rất "wow" — và bạn sẽ hiểu giá trị CI

- [ ] **Verify test report được upload** thành công trong Artifacts
  > ⚠️ *Tại sao quan trọng:* Report accessible là điều kiện để team trust automation result

- [ ] **Thêm badge CI vào README.md:**
  ```markdown
  ![Tests](https://github.com/yourusername/repo/actions/workflows/selenium-tests.yml/badge.svg)
  ```
  > ⚠️ *Tại sao quan trọng:* Badge xanh trên repo là tín hiệu professionalism — interviewer thấy là điểm cộng

- [ ] **Cấu hình test chạy Headless** đảm bảo không lỗi trên Linux CI:
  ```python
  options.add_argument("--headless=new")
  options.add_argument("--no-sandbox")
  options.add_argument("--disable-dev-shm-usage")
  options.add_argument("--window-size=1920,1080")
  ```
  > ⚠️ *Tại sao quan trọng:* CI server không có màn hình — thiếu headless config là fail ngay bước đầu tiên

---

**✅ Checkpoint Tuần 13-16:** Test suite chạy tự động trên GitHub Actions, có HTML report, có badge CI trên README.

---

## 📋 Tổng Hợp Final Checklist

### Kỹ Năng Kỹ Thuật
- [ ] Viết và chạy Selenium script cơ bản
- [ ] Dùng được 3 locator: ID, CSS, XPath
- [ ] Implement WebDriverWait đúng cách — không `time.sleep()`
- [ ] Tổ chức code theo Page Object Model
- [ ] Viết test với pytest — fixtures, parametrize, assertions
- [ ] Handle dynamic elements, dropdown, alert, iframe
- [ ] Chạy test trong Headless mode
- [ ] Tạo HTML/Allure report
- [ ] Cấu hình GitHub Actions CI/CD

### Soft Skills & Habits
- [ ] Đặt tên test rõ ràng: `test_login_with_wrong_password_shows_error_message`
- [ ] Mỗi test chỉ test 1 thing (Single Responsibility)
- [ ] Code comment giải thích "tại sao", không phải "cái gì"
- [ ] Commit code thường xuyên với message rõ ràng
- [ ] Có `README.md` hướng dẫn cách chạy test suite

---

## 🚀 Dấu Hiệu Bạn Đã Sẵn Sàng Lên Senior

> Sau 16 tuần, nếu bạn đã tick được phần lớn checklist trên, đây là những tín hiệu để biết mình sẵn sàng bước tiếp:

### ✅ Về Kỹ Thuật

**Bạn sẵn sàng lên senior khi:**
- [ ] **Bạn giải thích được** tại sao dùng POM, không chỉ biết dùng — ai hỏi bạn trả lời được ngay
- [ ] **Test suite của bạn ổn định** — chạy 10 lần cho kết quả giống nhau, không flaky random
- [ ] **Khi test fail, bạn debug được** trong 15 phút mà không cần Google từng bước
- [ ] **Bạn tự viết được `conftest.py` từ đầu** mà không cần copy-paste
- [ ] **Bạn đọc được GitHub Actions log** và biết fix khi CI fail
- [ ] **Bạn tự chọn được locator tốt nhất** cho element mà không cần hỏi — ID > CSS > XPath, biết khi nào dùng cái nào
- [ ] **Bạn có thể review code của người khác** và chỉ ra chỗ nào nên dùng Wait, chỗ nào locator fragile

### 📈 Về Tư Duy

**Bạn sẵn sàng lên senior khi:**
- [ ] **Bạn nghĩ "test này có thể flaky không?"** trước khi merge — không phải sau khi CI fail
- [ ] **Bạn biết trade-off:** Khi nào nên dùng E2E test, khi nào unit test đủ rồi
- [ ] **Bạn chủ động đề xuất** "feature này nên có automation test cho flow X, Y, Z" — không đợi được giao
- [ ] **Bạn nghĩ đến maintainability:** Code này 6 tháng sau mình đọc còn hiểu không?

### 🎯 Dấu Hiệu Thực Tế Nhất

> Bạn thực sự sẵn sàng lên senior khi **một junior hỏi bạn về Selenium và bạn giải thích được mà không cần tra Google.** Khi bạn dạy được người khác, bạn đã thật sự hiểu.

---

### 🔭 Next Steps Sau Level Này

Sau khi hoàn thành 16 tuần, con đường tiếp theo:

| Hướng | Công Nghệ | Mục Tiêu |
|-------|-----------|-----------|
| 🔗 API Testing | `requests` + `pytest` | Test backend, không qua UI |
| 🐳 DevOps cho QA | Docker + Jenkins | Chạy test trên mọi môi trường |
| ⚡ Performance | Locust / k6 | Test tải, stress test |
| 📱 Mobile | Appium | Automation cho iOS/Android |
| 🤖 AI Testing | Playwright + AI tools | Next-gen automation |

---

*📅 Tạo ngày bắt đầu: ___/___/_______ | 🎯 Mục tiêu hoàn thành: ___/___/_______*

*💡 Tip: In checklist này ra, dán lên bàn làm việc — tick từng mục tay là cảm giác rất satisfying!*