NỘI DUNG BÀI HỌC

✅ Hiểu rõ cấu trúc Browser → Context → Page → Frame → Storage trong Playwright.
✅ Nắm được lý do – mục đích – trường hợp thực tế cần xử lý:
    • Tab mới mở từ thao tác click.
    • Iframe chứa nội dung nhúng.
    • Local storage lưu token/session/setting phía client.
  •  

✅  Xây dựng BasePage hỗ trợ tính năng nâng cao (multi-tab).
✅  Xây dựng Page Object có khả năng:

    • Mở tab mới

    • Trả về PO của tab mới

    • Xử lý iFrame (nếu có)

    • Tương tác với LocalStorage

✅ Hoàn thiện kịch bản E2E thực tế dựa trên hệ thống HRM.



1️⃣ KIẾN TRÚC TRÌNH DUYỆT TRONG PLAYWRIGHT

Trước khi học multi-tab, iFrame hay storage, cần hiểu mô hình trình duyệt trong Playwright:

 
Browser
   └── Context (như 1 cửa sổ hay 1 profile độc lập)
         └── Page (mỗi tab là một page)
               └── Frame (một trang con nằm trong page)
               └── Storage (localStorage, sessionStorage)

Tại sao phải hiểu cấu trúc này?

  • Để biết tại sao click vào link lại mở Page mới, gọi là tab.

  • Để biết tại sao không thể locate được element trong iframe.

  • Để biết tại sao dữ liệu vẫn giữ lại sau khi reload (vì nó nằm trong storage).


2️⃣ MULTI-TABS (XỬ LÝ TAB MỚI)

⭐ 2.1. Lý do cần học Multi-tab?

Một số website mở tab mới khi:

  • Click vào “Help”, “Detail”, “Policy”, “Settings…”

  • Click vào liên kết có target="_blank"

  • Click vào script mở popup bằng window.open()

Trên HRM, trang Settings hoặc Help thường được thiết kế theo mô hình này (nếu thực tế không mở tab → giáo án mô phỏng tình huống thực tế cho automation cấp cao).

Nếu không xử lý đúng, test sẽ:
❌ Bị kẹt ở tab cũ
❌ Không biết tab mới đã xuất hiện
❌ Không điều khiển được tab mới → locator fail


⭐ 2.2. Nguyên tắc xử lý Multi-Tab

Playwright cung cấp sự kiện:

 
context.expect_page()

Quy tắc vàng: Lắng nghe trước → Click để mở tab → Nhận đối tượng Page mới.

Nếu click xong mới expect → sẽ miss event vì tab đã mở trước khi Playwright lắng nghe.


⭐ 2.3. BasePage hỗ trợ mở tab mới

📌 core/base_page.py

    def _click_and_wait_for_new_page(self, locator: str, name: str = "", timeout: int = 15000):
        """
        Click vào locator → mở tab mới → return Page mới.
        """
        print(f"[MultiTab]: Click '{name}' và chờ tab mới mở...")

        with self.page.context.expect_page(timeout=timeout) as new_page_info:
            self.page.locator(locator).click()

        new_page = new_page_info.value
        new_page.wait_for_load_state("load")

        print(f"[MultiTab]: Tab mới URL = {new_page.url}")
        return new_page


⭐ 2.4. Dashboard PO trả về Settings PO (tab mới)

📌 pages/dashboard_page.py

class DashboardPage(BasePage):

    SETTINGS_LINK = "a[href='/erp/settings/general']"
    URL_FRAGMENT = "/erp/dashboard"

    def assert_is_current_page(self):
        self.page.wait_for_url(f"**{self.URL_FRAGMENT}")

    def open_settings_in_new_tab(self):
        new_page = self._click_and_wait_for_new_page(
            self.SETTINGS_LINK,
            name="Mở trang Cài đặt"
        )
        return SettingsPage(new_page)
 

⭐ 2.5. SettingsPage (tab mới)

📌 pages/settings_page.py

class SettingsPage(BasePage):

    URL_FRAGMENT = "/erp/settings/general"
    HEADER = "h4:has-text('General Settings')"

    def assert_is_current_page(self):
        self.page.wait_for_url(f"**{self.URL_FRAGMENT}")
        expect(self.page.locator(self.HEADER)).to_be_visible()

    def update_company_name(self, new_name):
        self.page.fill("input[name='company_name']", new_name)
        self.page.click("button:has-text('Save')")

    def close_tab(self):
        self.page.close()

⭐ 2.6. Test case Multi-tab hoàn chỉnh

📌 tests/test_multi_tab_hrm.py

def test_settings_tab_flow(dashboard_page):
    dashboard_page.assert_is_current_page()

    settings_page = dashboard_page.open_settings_in_new_tab()
    settings_page.assert_is_current_page()

    settings_page.update_company_name("AnhTester Academy")
    settings_page.close_tab()

    dashboard_page.assert_is_current_page()
 

3️⃣ iFRAME

⭐ 3.1. Lý do phải học iFrame?

iFrame xuất hiện trong:

  • Form embed

  • Captcha

  • Chat widget

  • Video nhúng

  • Dashboard của HRM hoặc phần báo cáo (tùy phiên bản)

Đặc biệt, khi inspect thấy <iframe> → mọi element bên trong KHÔNG thuộc page hiện tại.

Vì vậy:

  • page.locator() KHÔNG tìm được element

  • Cần truy cập qua Frame object


⭐ 3.2. Cách truy cập iFrame

Lấy tất cả frame:

for frame in page.frames:
    print(frame.url)

Lấy bằng name / url / index:

frame = page.frame(name="myFrame")
frame = page.frame(url=re.compile("some-url"))
frame = page.frames[1]

Click element trong iFrame:

frame.locator("#submit").click()

 

⭐ 3.3. Ứng dụng trực tiếp trên HRM (có hoặc mô phỏng)

Một số trang HRM nội bộ có phần biểu đồ/báo cáo hiển thị qua iframe.
Nếu trang không có sẵn iframe → phần này vẫn là nền tảng bắt buộc
vì học Playwright cấp cao luôn phải xử lý iframe khi gặp.

Ví dụ minh họa:

dashboard_page.page.goto("https://www.w3schools.com/html/html_iframe.asp")

frame = dashboard_page.page.frame(name="iframeResult")
frame.locator("a:has-text('HTML Tutorial')").click()


4️⃣ LOCAL STORAGE

⭐ 4.1. Lý do cần học Local Storage?

Website hiện đại lưu rất nhiều dữ liệu trên client:

  • Token đăng nhập

  • Trạng thái toggle

  • Màu theme

  • Dữ liệu session

  • Ngôn ngữ đã chọn

Trên HRM, sau khi login, localStorage chứa dữ liệu version, theme, user info (tùy bản).

Tác dụng trong automation:

  • Có thể đọc token

  • Có thể lưu/restore state

  • Có thể kiểm tra UI theo trạng thái người dùng

  • Có thể reset hoặc chỉnh data phục vụ test


⭐ 4.2. Đọc Local Storage trong Playwright

local_data = page.evaluate("() => Object.entries(localStorage)")
print(local_data)


⭐ 4.3. Ghi Local Storage

page.evaluate("() => localStorage.setItem('theme', 'dark')")


⭐ 4.4. Xóa Local Storage

page.evaluate("() => localStorage.clear()")
 


⭐ 4.5. Ứng dụng trực tiếp với HRM

Sau khi login HRM:

📌 tests/test_local_storage.py

def test_local_storage_after_login(dashboard_page):

    entries = dashboard_page.page.evaluate("() => Object.entries(localStorage)")
    print("LocalStorage:", entries)

    assert len(entries) > 0
 

5️⃣ FIXTURES DÙNG CHUNG (LOGIN SẴN)

📌 conftest.py

@pytest.fixture
def login_page(page):
    return LoginPage(page)

@pytest.fixture
def dashboard_page(page, login_page):
    login_page.open()
    login_page.login("admin_example", "123456")

    dashboard = DashboardPage(page)
    dashboard.assert_is_current_page()
    return dashboard


6️⃣ KỊCH BẢN TỔNG HỢP CUỐI BUỔI

  1. Đăng nhập vào HRM qua fixture.

  2. Truy cập Dashboard.

  3. Mở tab Settings → thao tác → đóng tab.

  4. Đọc localStorage để xem dữ liệu lưu lại.

  5. Truy cập iFrame demo (nếu HRM không có iframe).

  6. Quay lại Dashboard và xác nhận vẫn còn trong session.


7️⃣ TÓM TẮT KIẾN THỨC CỐT LÕI

Mảng Cần hiểu Công cụ Ý nghĩa
Multi-Tab Vì sao tab mới mở ra? context.expect_page() Điều khiển được tab mới
iFrame Element nằm trong frame con page.frame() Tránh lỗi “locator not found”
LocalStorage Dữ liệu client-side page.evaluate() Kiểm soát token/theme/session
POM Mỗi trang = 1 class BasePage / Page Objects Code rõ ràng, dễ bảo trì
Fixtures Chuẩn bị state pytest fixtures Login 1 lần dùng nhiều nơi

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