Đang tải...

Lửng Lọc Lõi

Đeo kính để soi Bug cho rõ, nhíu mày để nhắc Dev sửa cho kỹ. Với tôi, 'chạy được' thôi là chưa đủ! 🧐💻


0

Thực hành Selenium test e-commerce: case thực tế cho người mới

Lửng Lọc Lõi
Lửng Lọc Lõi

5 tháng trước · 11 phút đọc

Tại sao e-commerce là case lý tưởng để học Selenium?

Mình nhớ lần đầu ngồi viết script Selenium, mình chọn test trang đăng nhập đơn giản và nghĩ vậy là đủ. Khi đi phỏng vấn, interviewer hỏi: "Bạn đã test luồng thanh toán chưa? Xử lý popup và Ajax thế nào?" - mình không có câu trả lời.

E-commerce không chỉ là bài tập. Đó là môi trường gần nhất với production thực tế mà bạn sẽ gặp khi đi làm.

343597 Ba luồng này đủ để chinh phục hầu hết câu hỏi phỏng vấn automation

Một trang thương mại điện tử điển hình có đủ mọi thứ một tester cần luyện: form nhập liệu có validate, giỏ hàng thay đổi real-time bằng Ajax, popup/modal thanh toán, loading state, redirect sau submit. Một project test e-commerce "ngon" trong portfolio có giá trị hơn năm bảy script test form đơn giản cộng lại.

Con số hơn 60% job tester tại Việt Nam yêu cầu kinh nghiệm automation, dù là fresher - đó là lý do bạn cần một project thực chiến thuyết phục ngay từ đầu.


Chuẩn bị môi trường: đừng bỏ qua bước này

Nhiều bạn nhảy thẳng vào viết code rồi mất 2 tiếng debug chỉ vì thiếu setup cơ bản. Mình từng vậy. Đây là những gì bạn thực sự cần:

Công cụ cần cài:

  • Python 3.8+ (hoặc Java nếu bạn quen)
  • Selenium WebDriver: pip install selenium
  • ChromeDriver khớp version Chrome đang dùng
  • WebDriverManager để tự động manage driver: pip install webdriver-manager
  • Trang demo để test: Trang demo e-commerce để luyện test - dùng trang này thay vì test lên Shopee thật

343598 Dùng WebDriverManager để tránh lỗi version ChromeDriver không khớp

Đây là đoạn setup cơ bản, bạn copy vào file conftest.py nếu dùng pytest:

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

def get_driver():
    # Tự động tải ChromeDriver đúng version - không cần cài tay
    service = Service(ChromeDriverManager().install())
    options = webdriver.ChromeOptions()
    # Chạy không mở cửa sổ (headless) - tiện cho CI/CD sau này
    # options.add_argument('--headless')
    driver = webdriver.Chrome(service=service, options=options)
    driver.implicitly_wait(10)  # Chờ tối đa 10s mỗi lần tìm element
    return driver

implicitly_wait(10) là dòng nhiều người bỏ qua, nhưng lại cứu bạn khỏi hàng loạt lỗi ElementNotFound khi trang load chậm. Đặt một lần, áp dụng cho toàn bộ session.


Test luồng nhập liệu và validate form

Form nhập liệu tưởng đơn giản nhưng là nơi bug ẩn nhiều nhất. Mình từng skip test edge case input và bug nổ khi user nhập email có dấu + như [email protected] - email hoàn toàn hợp lệ nhưng hệ thống từ chối.

Viết test case cho form đăng ký/đăng nhập cần cover ít nhất 3 nhóm:

Happy path - dữ liệu hợp lệ hoàn toàn Invalid input - email sai format, password quá ngắn, field bỏ trống Edge case - ký tự đặc biệt, khoảng trắng đầu/cuối, copy-paste từ clipboard

343599 Test case boundary value giúp phát hiện bug mà happy path không thấy được

Đây là script test form đăng nhập thực tế:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def test_login_invalid_password(driver):
    driver.get("https://www.saucedemo.com/")
    
    # Nhập username đúng, password sai
    driver.find_element(By.ID, "user-name").send_keys("standard_user")
    driver.find_element(By.ID, "password").send_keys("wrong_password")
    driver.find_element(By.ID, "login-button").click()
    
    # Verify: phải hiện thông báo lỗi rõ ràng
    wait = WebDriverWait(driver, 5)
    error_msg = wait.until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-test='error']"))
    )
    
    # Kiểm tra nội dung thông báo lỗi
    assert "Username and password do not match" in error_msg.text, \
        f"Thông báo lỗi không đúng: {error_msg.text}"

def test_login_empty_fields(driver):
    driver.get("https://www.saucedemo.com/")
    
    # Bấm login không nhập gì
    driver.find_element(By.ID, "login-button").click()
    
    # Verify: phải có thông báo yêu cầu nhập username
    error_msg = driver.find_element(By.CSS_SELECTOR, "[data-test='error']")
    assert "Username is required" in error_msg.text

Dùng WebDriverWait thay vì time.sleep() - đây là điểm phân biệt script viết kỹ với script viết cho xong. sleep() cứng nhắc và chậm, WebDriverWait chờ đúng khi element xuất hiện, không thừa không thiếu.


Xử lý giỏ hàng và Ajax real-time

Đây là phần khiến nhiều fresher bí. Khi bấm "Thêm vào giỏ", số lượng trong icon giỏ hàng cập nhật ngay - không reload trang. Nếu bạn dùng find_element ngay sau khi click, rất có thể element chưa update xong và bạn sẽ đọc giá trị cũ.

Vấn đề không phải code sai. Vấn đề là timing.

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

def test_add_to_cart(driver):
    driver.get("https://www.saucedemo.com/")
    
    # Đăng nhập trước
    driver.find_element(By.ID, "user-name").send_keys("standard_user")
    driver.find_element(By.ID, "password").send_keys("secret_sauce")
    driver.find_element(By.ID, "login-button").click()
    
    # Chờ trang sản phẩm load xong
    WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "inventory_item"))
    )
    
    # Thêm sản phẩm đầu tiên vào giỏ
    add_btn = driver.find_element(By.CSS_SELECTOR, ".btn_inventory")
    add_btn.click()
    
    # QUAN TRỌNG: chờ badge giỏ hàng UPDATE, không chỉ chờ xuất hiện
    cart_badge = WebDriverWait(driver, 5).until(
        EC.text_to_be_present_in_element(
            (By.CLASS_NAME, "shopping_cart_badge"), "1"
        )
    )
    
    # Verify số lượng đúng
    badge = driver.find_element(By.CLASS_NAME, "shopping_cart_badge")
    assert badge.text == "1", f"Giỏ hàng hiển thị {badge.text}, expected 1"

343600 text_to_be_present_in_element là cách đúng để chờ Ajax cập nhật - không phải sleep

EC.text_to_be_present_in_element là condition mình dùng nhiều nhất khi test Ajax. Nó chờ đến khi text trong element khớp với giá trị mong đợi, không phải chờ cứng N giây. Bạn test thêm trường hợp: thêm 3 sản phẩm, xóa 1, verify badge còn 2 - đó là bộ test case giỏ hàng "đủ" theo tiêu chuẩn mình hay nhắc.


Test popup và modal thanh toán

Popup thanh toán là thử thách thực sự. Có hai loại bạn sẽ gặp: popup của trang web (HTML element ẩn hiện) và popup của trình duyệt (alert/confirm native). Xử lý sai loại là lý do phổ biến nhất khiến script crash.

Popup HTML (phổ biến hơn):

def test_checkout_flow(driver):
    # [Sau khi đã thêm sản phẩm vào giỏ]
    
    # Vào giỏ hàng
    driver.find_element(By.CLASS_NAME, "shopping_cart_link").click()
    
    # Bấm Checkout
    WebDriverWait(driver, 5).until(
        EC.element_to_be_clickable((By.ID, "checkout"))
    ).click()
    
    # Điền thông tin checkout
    driver.find_element(By.ID, "first-name").send_keys("Nguyen")
    driver.find_element(By.ID, "last-name").send_keys("Van A")
    driver.find_element(By.ID, "postal-code").send_keys("70000")
    driver.find_element(By.ID, "continue").click()
    
    # Verify trang overview: hiển thị tổng tiền
    total = WebDriverWait(driver, 5).until(
        EC.visibility_of_element_located((By.CLASS_NAME, "summary_total_label"))
    )
    assert "Total:" in total.text, "Không hiển thị tổng tiền trên trang overview"

343601 Popup HTML dùng WebDriverWait, popup trình duyệt dùng driver.switch_to.alert

Popup native của trình duyệt (alert/confirm):

def test_handle_browser_alert(driver):
    # Nếu gặp alert/confirm của trình duyệt sau khi bấm xóa/hủy
    driver.find_element(By.ID, "delete-item").click()
    
    # KHÔNG dùng find_element ở đây - đây là native alert
    alert = WebDriverWait(driver, 5).until(EC.alert_is_present())
    
    # Đọc nội dung alert trước khi accept/dismiss
    alert_text = alert.text
    print(f"Alert nói: {alert_text}")
    
    alert.accept()  # Bấm OK
    # hoặc alert.dismiss()  # Bấm Cancel

Mình thấy nhiều bạn dùng driver.switch_to.alert mà không wait trước - script chạy nhanh hơn trang render, alert chưa kịp xuất hiện đã throw exception. Luôn WebDriverWait trước khi switch.


Build project portfolio từ case này

Test viết xong không để đó. Portfolio của bạn cần cho interviewer thấy bạn nghĩ như tester thật, không chỉ "chạy được script".

Cấu trúc project trên GitHub mình hay gợi ý:

ecommerce-selenium-tests/
├── tests/
│   ├── test_login.py        # Luồng đăng nhập
│   ├── test_cart.py         # Giỏ hàng và Ajax
│   └── test_checkout.py     # Thanh toán
├── pages/                   # Page Object Model
│   ├── login_page.py
│   ├── product_page.py
│   └── checkout_page.py
├── conftest.py              # Setup/teardown driver
├── requirements.txt
└── README.md                # QUAN TRỌNG: viết rõ test gì, tại sao

Page Object Model (POM) là pattern bạn cần biết tên. Thay vì viết By.ID, "login-button" lặp lại ở mọi test file, bạn tập trung locator vào một class LoginPage. Khi UI thay đổi, sửa 1 chỗ thay vì tìm khắp project.

343602 README tốt = interviewer hiểu project của bạn trong 30 giây

File README là thứ mình hay nhắc nhưng mọi người hay bỏ qua. Viết ngắn thôi, nhưng phải có:

  • Test gì (luồng nào, trang nào)
  • Cách chạy (pip install -r requirements.txt rồi pytest)
  • Kết quả mong đợi (pass/fail bao nhiêu test)

Interviewer mở GitHub của bạn, đọc README 30 giây - nếu không hiểu project test cái gì thì coi như chưa có portfolio. Mình tải sẵn template project đầy đủ để bạn bắt đầu: Tải checklist portfolio Selenium e-commerce


Luyện phỏng vấn với những câu hỏi hay gặp

Bạn không cần thuộc lòng tất cả. Mình tổng hợp 5 câu interviewer hay hỏi nhất khi bạn apply vị trí automation tester fresher - dựa trên những câu hỏi mình gặp và nghe từ các bạn trong lớp:

"Bạn xử lý dynamic element thế nào?" Element có ID thay đổi mỗi lần load. Dùng CSS selector ổn định hơn, ví dụ [data-test='product-name'] thay vì #product-12345.

"Sự khác nhau giữa implicit wait và explicit wait?" implicitly_wait áp dụng cho toàn session, chờ element tồn tại trong DOM. WebDriverWait + expected_conditions linh hoạt hơn, chờ điều kiện cụ thể (clickable, visible, text thay đổi).

"Page Object Model là gì, tại sao dùng?" Pattern tách locator và action ra khỏi test file. UI thay đổi thì sửa 1 chỗ, không phải tìm khắp project. Maintainability tốt hơn.

"Script của bạn có thể chạy headless không?" Thêm options.add_argument('--headless') vào Chrome options. Cần thiết cho CI/CD pipeline.

"Làm thế nào để biết test thất bại do bug hay do script sai?" Kiểm tra screenshot chụp lúc fail, đọc error message kỹ, reproduce tay trước khi kết luận bug.

343603 Trả lời được 5 câu này, bạn đã vượt qua được 80% vòng phỏng vấn fresher automation

Nếu bạn chưa tự tin với JavaScript hay cấu trúc web khi đọc selector, khóa Lập Trình JavaScript Cơ Bản trên F8 giải thích rõ DOM và cách trình duyệt render - kiến thức nền giúp bạn viết selector chính xác hơn nhiều.

Không cần 35 tuổi mới bắt đầu muộn. Lớp mình có bạn 31 tuổi từ ngành kế toán, bây giờ đang làm automation tester tại một công ty product. Điều quan trọng là project thực chiến trên GitHub - đó là thứ interviewer nhìn vào đầu tiên.