NỘI DUNG BÀI HỌC

Chống lặp code (DRY): Hiểu rõ Fixture là gì và tại sao nó là "cỗ máy" chuẩn bị (setup) và dọn dẹp (teardown) mạnh mẽ nhất của Pytest.
Làm chủ Setup & Teardown: Sử dụng yield để tách biệt phần "chuẩn bị" (ví dụ: đăng nhập, tạo data) khỏi phần "dọn dẹp" (ví dụ: đăng xuất, xóa data) một cách an toàn.
TỐI ƯU HÓA TỐC ĐỘ: Hiểu sâu sắc 4 cấp độ scope của Fixture (function, class, module, session) để quyết định khi nào cần cô lập (isolation) và khi nào cần hiệu suất (performance).
Tổ chức và Lọc Test Case: Thành thạo việc "dán nhãn" (Markers) cho các test (ví dụ: smoke, regression) và học cách chạy có chọn lọc (pytest -m "smoke").

🧠 I. Quản lý Setup/Teardown với Fixtures

1. Khái niệm: Fixture là gì?

Hãy tưởng tượng bạn là một đầu bếp. Mỗi món ăn (test case) cần một số nguyên liệu đã sơ chế (ví dụ: page đã đăng nhập, data test đã được tạo). Một Fixture chính là người "phụ bếp" (helper) chuyên chuẩn bị sẵn các nguyên liệu đó cho bạn.

  • Ví dụ thực tế: Chuẩn bị đối tượng page đã đăng nhập, tạo một user mới trong CSDL trước khi test, kết nối API, hoặc dọn dẹp (đăng xuất, xóa data đã tạo) sau khi test.

  • Vấn đề: Bạn phải lặp lại code đăng nhập ở mọi test case cần đăng nhập. Nếu quy trình đăng nhập thay đổi (thêm 1 bước, đổi locator), bạn phải đi sửa ở 100 nơi.

  • Hậu quả: Code lặp lại (vi phạm nguyên tắc DRY - Don't Repeat Yourself), khó bảo trì, tốn thời gian, và dễ sai sót.

2. Giải pháp: @pytest.fixtureyield

Pytest cho phép bạn định nghĩa các hàm "chuẩn bị" bằng decorator @pytest.fixture. Từ khóa yield là mấu chốt:

  1. Code trước yieldSetup (chạy trước khi test bắt đầu).

  2. yield trả về đối tượng (ví dụ: page đã đăng nhập) cho test case sử dụng.

  3. Test case sẽ chạy ngay sau yield.

  4. Code sau yieldTeardown (chạy sau khi test xong, bất kể test đó pass hay fail).


Ví dụ 1: Fixture page cơ bản (Bạn đang dùng mà không biết!)

Khi bạn viết def test_example(page):, page ở đây chính là một fixture do pytest-playwright cung cấp sẵn. Nó tự động làm 4 việc: (Setup) Mở trình duyệt, tạo context, tạo page MỚI ➝ (Yield) Trả về page cho bạn ➝ (Teardown) Đóng page, context, trình duyệt.


Ví dụ 2: Fixture "Đăng nhập" tùy chỉnh

Hãy tạo một fixture logged_in_page chuyên lo việc đăng nhập.

Ví dụ Automation:

import pytest
from playwright.sync_api import Page, expect

# 1. ĐỊNH NGHĨA FIXTURE
# Fixture này "phụ thuộc" vào fixture 'page' (bằng cách nhận 'page' làm tham số)
@pytest.fixture
def logged_in_page(page: Page):
    # --- PHẦN 1: SETUP (Chạy trước test) ---
    print("\n[Fixture Setup]: Đang đăng nhập...")
    page.goto("https://the-internet.herokuapp.com/login")
    page.locator("#username").fill("tomsmith")
    page.locator("#password").fill("SuperSecretPassword!")
    page.get_by_role("button", name="Login").click()
    # Đảm bảo đăng nhập thành công
    expect(page.locator("#flash")).to_contain_text("You logged in!")
    
    # --- PHẦN 2: YIELD ---
    # Giao 'page' (đã đăng nhập) cho test case
    yield page
    
    # --- PHẦN 3: TEARDOWN (Chạy sau test) ---
    print("\n[Fixture Teardown]: Đang đăng xuất...")
    page.get_by_role("link", name="Logout").click()
    expect(page.locator("#flash")).to_contain_text("You logged out!")

# 2. SỬ DỤNG FIXTURE

# ❌ SAI: (Cách cũ - lặp code)
def test_logout_button_visible_OLD_WAY(page: Page):
    # Lặp lại toàn bộ code login...
    page.goto("https://the-internet.herokuapp.com/login")
    page.locator("#username").fill("tomsmith")
    page.locator("#password").fill("SuperSecretPassword!")
    page.get_by_role("button", name="Login").click()
    
    # Assert
    expect(page.get_by_role("link", name="Logout")).to_be_visible()

# ✅ ĐÚNG: (Dùng fixture)
# Chỉ cần "yêu cầu" fixture 'logged_in_page' làm tham số
def test_logout_button_visible(logged_in_page: Page):
    # ARRANGE: Fixture 'logged_in_page' đã lo
    
    # ACT & ASSERT
    # 'logged_in_page' đã ở trạng thái đăng nhập thành công
    print("[Test Body]: Đang kiểm tra nút Logout...")
    expect(logged_in_page.get_by_role("link", name="Logout")).to_be_visible()

def test_welcome_message(logged_in_page: Page):
    # ARRANGE: Fixture 'logged_in_page' cũng lo
    
    # ACT & ASSERT
    print("[Test Body]: Đang kiểm tra lời chào...")
    expect(logged_in_page.locator("#flash")).to_contain_text("You logged in!")



🧠 II. (NÂNG CAO) Giải mã Scope của Fixture: Khi nào dùng gì?

1. Khái niệm: Scope là gì?

Scope (Phạm vi) quyết định tần suất một fixture được tạo ra (setup) và phá hủy (teardown). Nó là sự cân bằng giữa Tính Cô Lập (an toàn nhất) và Tốc Độ (nhanh nhất).

Hãy tưởng tượng bạn chuẩn bị dụng cụ cho đầu bếp (test case):

  • scope="function": Đưa cho mỗi đầu bếp một con dao mới tinh, dùng xong vứt đi. Rất an toàn, không bị lẫn mùi, nhưng tốn kém.

  • scope="session": Đưa cho tất cả đầu bếp dùng chung một cái lò nướng đã bật sẵn từ sáng. Rất nhanh, không phải chờ lò nóng, nhưng nếu ai đó làm bẩn lò thì người sau bị ảnh hưởng.


2. Phân tích chi tiết 4 cấp độ Scope

A. scope="function" (Mặc định)

  • Tần suất: Chạy lại Setup/Teardown cho MỖI HÀM TEST.

  • Analogy (Đời sống): Một chiếc bát ăn cơm dùng một lần. Mỗi "lần ăn" (test case) bạn lại lấy một cái bát mới tinh. Ăn xong là vứt (teardown).

  • Cú pháp:
    import pytest
    from playwright.sync_api import Page, expect
    
    # Bạn có thể viết @pytest.fixture() hoặc @pytest.fixture(scope="function")
    @pytest.fixture(scope="function") 
    def logged_in_page(page: Page):
        # --- 1. SETUP (Chạy trước mỗi test) ---
        print("\n[Fixture function]: Đang đăng nhập...")
        page.goto("https://www.saucedemo.com/")
        page.locator("#user-name").fill("standard_user")
        page.locator("#password").fill("secret_sauce")
        page.locator("#login-button").click()
        expect(page).to_have_url("https://www.saucedemo.com/inventory.html")
    
        # --- 2. YIELD ---
        yield page  # Giao page đã đăng nhập cho test
    
        # --- 3. TEARDOWN (Chạy sau mỗi test) ---
        print("\n[Fixture function]: Đang đăng xuất...")
        page.get_by_role("button", name="Open Menu").click()
        page.get_by_role("link", name="Logout").click()
        expect(page).to_have_url("https://www.saucedemo.com/")
    
    # CÁCH DÙNG
    def test_add_item_to_cart(logged_in_page: Page):
        # 'logged_in_page' là page đã login do fixture cung cấp
        logged_in_page.locator("[data-test='add-to-cart-sauce-labs-backpack']").click()
        expect(logged_in_page.locator(".shopping_cart_badge")).to_have_text("1")
    
    def test_view_item_detail(logged_in_page: Page):
        # Fixture sẽ chạy lại TỪ ĐẦU cho test này (login lại)
        logged_in_page.locator("#item_4_title_link").click()
        expect(logged_in_page.locator(".inventory_details_name")).to_have_text("Sauce Labs Backpack")​

     

  • Khi nào dùng trong Auto Test:

    • Đây là lựa chọn AN TOÀN và PHỔ BIẾN NHẤT.

    • Khi bạn cần sự cô lập tuyệt đối. Mỗi test case phải bắt đầu từ một trạng thái sạch sẽ, không bị ảnh hưởng bởi test case trước đó.

    • Ví dụ 1 (Playwright): Fixture page mặc định của pytest-playwright dùng scope này. Mỗi test case (def test_abc(page):) nhận được một tab (page) hoàn toàn mới, không có cookie, không cache, không lịch sử từ test trước.

    • Ví dụ 2 (Tạo Data): Một fixture create_new_user tạo ra một user với email ngẫu nhiên trong CSDL. Bạn muốn mỗi test đăng ký (test_register_success, test_register_duplicate_email) đều chạy với một user mới hoàn toàn, nên bạn dùng scope="function".


B. scope="class"

  • Tần suất: Chạy Setup 1 LẦN trước test method đầu tiên trong Class, và Teardown 1 LẦN sau test method cuối cùng trong Class.

  • Analogy (Đời sống): Một bàn làm việc nhóm. Cả nhóm (class) vào phòng họp (setup) lúc 9h sáng. Mọi người (test methods) dùng chung cái bàn đó để làm việc. 5h chiều họp xong, cả nhóm dọn dẹp bàn và rời đi (teardown).

  • Khi nào dùng trong Auto Test:

    • Khi bạn có một nhóm test (trong 1 class) đều yêu cầu chung một bước setup tốn kém và các test đó không phá hỏng trạng thái của nhau.

    • Ví dụ 1 (Login 1 lần): Bạn có class TestProfileSettings: chứa 10 test nhỏ (test_update_avatar, test_update_bio, test_change_address...). Tất cả 10 test này đều yêu cầu user phải đăng nhập.

      • Dùng scope="function": Sẽ phải Login 10 lần và Logout 10 lần ➝ Rất chậm!

      • Dùng scope="class": Tạo 1 fixture logged_in_page_class_scope (scope="class"). Nó sẽ Login 1 LẦN, sau đó cả 10 test case cùng chạy trên page đã đăng nhập đó. Cuối cùng, nó Logout 1 LẦN. ➝ Nhanh hơn rất nhiều.

      • Lưu ý: Để dùng, bạn phải đánh dấu class với @pytest.mark.usefixtures("<tên_fixture>"). Các method trong class sẽ không nhận fixture làm tham số trực tiếp, mà sẽ dùng page (hoặc self.page) đã được fixture đó "biến đổi".

      • Cú pháp:

        import pytest
        from playwright.sync_api import Page, expect
        
        @pytest.fixture(scope="class")
        def class_scoped_login(page: Page): # Nhận 'page' (function scope) để thiết lập
            # --- 1. SETUP (Chạy 1 lần trước test đầu tiên của class) ---
            print("\n[Fixture class]: Đang đăng nhập 1 LẦN...")
            page.goto("https://www.saucedemo.com/")
            page.locator("#user-name").fill("standard_user")
            page.locator("#password").fill("secret_sauce")
            page.locator("#login-button").click()
            expect(page).to_have_url("https://www.saucedemo.com/inventory.html")
        
            # --- 2. YIELD ---
            yield page  # Giao page đã đăng nhập cho cả class
        
            # --- 3. TEARDOWN (Chạy 1 lần sau test cuối cùng của class) ---
            print("\n[Fixture class]: Đang đăng xuất 1 LẦN...")
            page.get_by_role("button", name="Open Menu").click()
            page.get_by_role("link", name="Logout").click()
        
        # CÁCH DÙNG
        @pytest.mark.usefixtures("class_scoped_login")
        class TestInventory:
        
            def test_add_item(self, page: Page):
                # 'page' ở đây chính là 'page' đã được fixture 'class_scoped_login' đăng nhập
                print("\n[Test 1]: Thêm item...")
                page.locator("[data-test='add-to-cart-sauce-labs-backpack']").click()
                expect(page.locator(".shopping_cart_badge")).to_have_text("1")
        
            def test_remove_item(self, page: Page):
                # 'page' này VẪN LÀ PAGE ĐÓ, nó KHÔNG login lại
                # Nó vẫn giữ nguyên trạng thái từ test_add_item (giỏ hàng có "1")
                print("\n[Test 2]: Xóa item...")
                page.locator("[data-test='remove-sauce-labs-backpack']").click()
                expect(page.locator(".shopping_cart_badge")).not_to_be_visible()



C. scope="module"

  • Tần suất: Chạy Setup 1 LẦN trước test đầu tiên trong file .py, và Teardown 1 LẦN sau test cuối cùng trong file .py.

  • Analogy (Đời sống): Kết nối Wi-Fi cho một tầng lầu. Khi người đầu tiên của tầng lầu (module) đến, bộ phát Wi-Fi (fixture) được bật lên (setup). Mọi người trong tầng (all functions/classes in the file) dùng chung Wi-Fi đó. Khi người cuối cùng rời đi, bộ phát được tắt (teardown).

  • Cú pháp:
    import pytest
    from playwright.sync_api import Browser, BrowserContext, Page, expect
    
    @pytest.fixture(scope="module")
    def shared_context(browser: Browser):
        # --- 1. SETUP (Chạy 1 lần trước test đầu tiên trong file) ---
        print(f"\n[Fixture module]: Tạo context chung...")
        # Ví dụ: tạo context với storage state đã login sẵn
        context = browser.new_context(storage_state="auth.json")
    
        # --- 2. YIELD ---
        yield context
    
        # --- 3. TEARDOWN (Chạy 1 lần sau test cuối cùng trong file) ---
        print(f"\n[Fixture module]: Đóng context chung...")
        context.close()
    
    # CÁCH DÙNG
    # Các test này sẽ dùng chung 1 context, nhưng vẫn có thể tạo page mới từ context đó
    # (Cần cấu hình lại fixture 'page' để nó dùng 'shared_context')
    # Hoặc đơn giản là dùng context đó trực tiếp:
    
    def test_profile_page(shared_context: BrowserContext):
        page = shared_context.new_page() # Tạo page mới từ context đã login
        print("\n[Test 1]: Kiểm tra trang profile...")
        page.goto("https://github.com/profile") # Giả sử auth.json là của Github
        expect(page.locator(".user-profile-name")).to_be_visible()
        page.close()
    
    def test_settings_page(shared_context: BrowserContext):
        page = shared_context.new_page() # Tạo page mới TỪ CÙNG CONTEXT
        print("\n[Test 2]: Kiểm tra trang settings...")
        page.goto("https://github.com/settings/profile")
        expect(page.locator("#user_profile_name")).to_be_visible()
        page.close()​

     

  • Khi nào dùng trong Auto Test:

    • Khi bạn cần một tài nguyên cho toàn bộ file test, nhưng không muốn ảnh hưởng đến các file test khác.

    • Ví dụ 1 (Kết nối DB): File test_user_database.py chứa 50 test case cần truy vấn CSDL. Bạn tạo 1 fixture db_connection (scope="module") để Mở kết nối CSDL 1 LẦN lúc bắt đầu file, và Đóng kết nối 1 LẦN lúc kết thúc file, thay vì mở/đóng 50 lần.

 

D. scope="session"

  • Tần suất: Chạy Setup 1 LẦN DUY NHẤT khi bắt đầu toàn bộ phiên chạy pytest, và Teardown 1 LẦN DUY NHẤT khi tất cả test trong mọi file đã chạy xong.

  • Analogy (Đời sống): Xây dựng tòa nhà. Bạn xây tòa nhà (fixture) 1 LẦN (setup). Sau đó, hàng ngàn người (test cases) ra vào, sử dụng tòa nhà đó trong nhiều năm. Hàng chục năm sau, khi tòa nhà không còn được sử dụng nữa, nó mới bị phá dỡ (teardown).

  • Cú pháp:
    import pytest
    import json
    
    # Fixture này thường được đặt trong file conftest.py
    # để tất cả các file test đều có thể dùng
    
    @pytest.fixture(scope="session")
    def app_config():
        # --- 1. SETUP (Chạy 1 lần duy nhất khi bắt đầu phiên test) ---
        print("\n[Fixture session]: Đọc file config.json 1 LẦN...")
        with open("config.json", "r") as f:
            config = json.load(f)
    
        # --- 2. YIELD ---
        yield config
    
        # --- 3. TEARDOWN (Chạy 1 lần duy nhất khi kết thúc) ---
        print(f"\n[Fixture session]: Kết thúc phiên test. Tạm biệt!")
        # Thường không cần teardown gì khi đọc file config
    
    # CÁCH DÙNG (Giả sử file config.json có {"base_url": "https://www.saucedemo.com/"})
    
    # File: test_login.py
    def test_login_page_title(page, app_config): # 'app_config' được inject vào
        base_url = app_config["base_url"]
        page.goto(base_url)
        expect(page).to_have_title("Swag Labs")
    
    # File: test_inventory.py
    def test_inventory_url(page, app_config): # 'app_config' cũng được inject vào
        base_url = app_config["base_url"]
        # (Giả sử cần login...)
        page.goto(f"{base_url}/inventory.html") # Dùng config
        expect(page.locator(".title")).to_have_text("Products")​

     

  • Khi nào dùng trong Auto Test:

    • Cho những tài nguyên siêu tốn kém để tạo ra, và thường là chỉ đọc (read-only), hoặc được dùng chung bởi tất cả các test.

    • Ví dụ 1 (Đọc file Config): Tạo một fixture config (scope="session") để đọc file config.json (chứa URL, username, password) 1 LẦN duy nhất khi bắt đầu. Mọi test case, mọi file test sau đó đều có thể lấy config này ra dùng mà không cần đọc lại file.

    • Ví dụ 2 (Khởi tạo Browser): (Nếu bạn tự build) Fixture browser của pytest-playwright thực chất dùng scope="session". Nó khởi động trình duyệt (vd: Chromium) 1 LẦN duy nhất, và tất cả các page (scope="function") đều được "sinh ra" từ cái browser duy nhất đó. Khi tất cả test chạy xong, nó mới đóng trình duyệt. Đây là lý do Playwright chạy rất nhanh.

    • Ví dụ 3 (Tạo Master Data): Tạo một "Super Admin" user (scope="session") 1 LẦN để dùng cho TẤT CẢ các test (chỉ dùng để login, không thay đổi).


3. Bảng tổng kết nhanh

Scope Tần suất chạy Setup/Teardown Analogy (Phép ví von) Ví dụ Auto Test
function Cho mỗi hàm test Bát ăn 1 lần (sạch sẽ, cô lập) page (để mỗi test có tab mới)
class 1 lần cho mỗi class test Bàn làm việc nhóm (dùng chung cho nhóm) Login 1 lần cho class TestProfile
module 1 lần cho mỗi file .py Wi-Fi tầng lầu (dùng chung cho file) Kết nối CSDL cho file test_db.py
session 1 lần DUY NHẤT cho cả phiên Tòa nhà (dùng chung cho TẤT CẢ) Đọc file config.json, khởi động browser

 

🧠 III. Gom nhóm & Lọc Test với Markers

1. Khái niệm: Marker là gì?

Hãy tưởng tượng bạn có một thùng 1000 hồ sơ (test case). Markers giống như những chiếc "nhãn dán" (labels) nhiều màu. Bạn dán nhãn MÀU ĐỎ (smoke) cho 10 hồ sơ "Khẩn cấp" và nhãn MÀU XANH (regression) cho tất cả 1000 hồ sơ.

  • Ví dụ thực tế: Phân loại test: smoke (test cơ bản, quan trọng), regression (test hồi quy), login, payment, ui, api.

  • Vấn đề: Bạn có 1000 test. Bạn chỉ muốn chạy 10 test smoke để kiểm tra nhanh hệ thống có "sống" không trước khi deploy, hoặc chỉ muốn chạy các test liên quan đến payment.

  • Hậu quả: Phải chạy toàn bộ 1000 test, tốn thời gian, CI/CD chậm chạp.


2. Giải pháp: @pytest.mark

Pytest cho phép "dán nhãn" bất kỳ test nào bằng decorator @pytest.mark.<tên_nhãn>.

Ví dụ 1: Các Marker Tích hợp sẵn

  • @pytest.mark.skip(reason=...): Luôn bỏ qua test này.

  • @pytest.mark.skipif(condition, reason=...): Bỏ qua nếu điều kiện đúng.

  • @pytest.mark.xfail(reason=...): Đánh dấu là "dự kiến sẽ thất bại" (ví dụ: test cho một bug đã được báo cáo nhưng chưa fix).

Ví dụ Automation:

import pytest
import sys

@pytest.mark.skip(reason="Chức năng này đang được bảo trì, sẽ chạy sau")
def test_new_feature_B(page):
    # Code sẽ không bao giờ chạy
    pass

@pytest.mark.skipif(sys.platform == "win32", reason="Test này chỉ chạy trên Linux/MacOS")
def test_linux_specific_feature(page):
    # Sẽ bị skip nếu bạn chạy trên Windows
    pass

@pytest.mark.xfail(reason="Bug #1234 chưa được fix, test này dự kiến sẽ fail")
def test_bug_1234_reproduction(page):
    page.goto("...")
    # Code test cho bug
    expect(page.locator(".buggy-element")).to_be_visible() # Giả sử element này không có


Ví dụ 2: Markers Tùy chỉnh (Phần mạnh nhất)

Bạn có thể tự tạo bất kỳ nhãn nào bạn muốn.

Ví dụ Automation:

import pytest

@pytest.mark.smoke  # Nhãn 'smoke'
@pytest.mark.login  # Có thể có nhiều nhãn
def test_login_success(page):
    # ... code test login thành công ...
    pass

@pytest.mark.regression
@pytest.mark.login
def test_login_wrong_password(page):
    # ... code test login sai pass ...
    pass

@pytest.mark.smoke
@pytest.mark.search
def test_search_product(page):
    # ... code test tìm kiếm ...
    pass



3. Cách chạy Test với Markers (Từ Terminal)

Sau khi dán nhãn, bạn dùng cờ -m (marker) trong terminal để lọc:

  • Chỉ chạy smoke: pytest -m smoke

    • (Sẽ chạy test_login_successtest_search_product)

  • Chỉ chạy login: pytest -m login

    • (Sẽ chạy test_login_successtest_login_wrong_password)

  • Chạy tất cả TRỪ smoke: pytest -m "not smoke"

    • (Sẽ chạy test_login_wrong_password)

  • Chạy smoke login: pytest -m "smoke and login"

    • (Chỉ chạy test_login_success)

  • Chạy smoke HOẶC search: pytest -m "smoke or search"

    • (Chạy cả 3 test)


4. Đăng ký Markers (Rất quan trọng)

Để tránh Pytest báo "Warning: Unknown marker...", bạn PHẢI đăng ký các nhãn tùy chỉnh trong file pytest.ini (tạo file này ở thư mục gốc của project).

Nội dung file pytest.ini:

Ini, TOML
[pytest]
markers =
    smoke: Các test case quan trọng (critical path)
    regression: Toàn bộ test case hồi quy
    login: Các test liên quan đến đăng nhập
    search: Các test liên quan đến tìm kiếm
    payment: Các test về thanh toán



🧩 IV. Bài tập Thực hành Tổng hợp

Trang web thực hành: https://the-internet.herokuapp.com/loginhttps://www.saucedemo.com/

Bài 1: Refactor với Fixture

  • Trang: https://www.saucedemo.com/ (Trang này không tự logout sau khi test)

  • Yêu cầu:

    1. Tạo một file test mới (ví dụ: test_saucedemo.py).

    2. Tạo một fixture tên logged_in_page (scope="function").

    3. Setup: Fixture này phải truy cập trang, điền user (standard_user) và pass (secret_sauce), sau đó click Login.

    4. Yield: Trả về page đã đăng nhập.

    5. Teardown: (Quan trọng) Fixture phải click vào menu (burger button), click "Logout", và expect để đảm bảo đã quay về trang login.

    6. Viết 2 test case sử dụng fixture này:

      • test_add_to_cart: Kiểm tra việc thêm 1 sản phẩm vào giỏ hàng (click "Add to cart" và expect số trên giỏ hàng là "1").

      • test_view_product_detail: Click vào tên 1 sản phẩm và expect trang chi tiết có nút "Back to products".

Bài 2: Đánh dấu (Markers) cho Test Case

  • Trang: Lấy các test case ở Bài 1 và các test case cũ.

  • Yêu cầu:

    1. Tạo file pytest.ini ở gốc project. Đăng ký 3 marker: smoke, regression, login.

    2. Gắn marker cho các test case:

      • Gắn @pytest.mark.smoke@pytest.mark.login cho test login thành công.

      • Gắn @pytest.mark.regression@pytest.mark.login cho test login thất bại.

      • Gắn @pytest.mark.smoke@pytest.mark.regression cho test_add_to_cart (Bài 1).

      • Gắn @pytest.mark.regression cho test_view_product_detail (Bài 1).

    3. Thực hành chạy trên terminal:

      • pytest -m smoke (Kiểm tra xem có 2 test chạy không?)

      • pytest -m login (Kiểm tra xem có 2 test chạy không?)

      • pytest -m "smoke and not login" (Kiểm tra xem có 1 test chạy không?)

 

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