Page Assertions (expect(page) — toHaveTitle / toHaveURL)
automationplaywright
expect(page) là nhóm assertion web-first áp trên cả Page object: chủ yếu là toHaveTitle và toHaveURL để kiểm tra tiêu đề tab và địa chỉ hiện tại của trang. Chúng cũng auto-retry như locator assertion nên rất hợp verify điều hướng (navigation), redirect sau login, hoặc mở link ngoài — những chỗ URL/title thay đổi bất đồng bộ.
Khái niệm & Bản chất
- Assertion ở tầng trang, không phải element:
expect(page)nhận Page object làm 'actual value' và poll các thuộc tính cấp trang —document.titlevàpage.url()— cho tới khi khớp hoặc timeout. - Cùng cơ chế auto-retry với locator assertion: redirect mất 800ms?
toHaveURLchờ tới khi URL ổn định khớp pattern thay vì check ngay lúc click xong (khi URL còn đang là trang cũ). - Hai matcher chính:
toHaveTitle(expected)kiểm tra<title>của document;toHaveURL(expected)kiểm tralocation.href. Cả hai nhậnstring,RegExp, vàtoHaveURLcòn nhậnfunction (url) => booleanhoặcURLobject. - Use-case điển hình: verify điều hướng sau submit form, sau OAuth callback, link external, SPA routing (URL đổi nhưng không reload trang), hoặc chắc chắn đúng trang trước khi đi sâu assert nội dung.
Cách hoạt động & API chính
await expect(page).toHaveTitle('Trang chủ')— match toàn bộ title khi truyền string; dùngRegExpđể partial match:toHaveTitle(/Trang chủ/).await expect(page).toHaveURL('https://app.hoctest.com/dashboard')— string so sánh exact cả URL (kể cả trailing slash, query string).toHaveURLvới RegExp là lựa chọn an toàn nhất:toHaveURL(/\/dashboard/)bỏ qua query param động, hash, locale prefix... Dùng**/pathglob pattern cũng được (Playwright convert glob → regex).toHaveURLhỗ trợ relative URL: nếubaseURLđã set trong config,toHaveURL('/dashboard')tự resolve thành full URL — giữ test độc lập môi trường dev/staging/prod.- Function predicate cho logic phức tạp:
toHaveURL(url => url.searchParams.get('status') === 'paid')— nhậnURLobject, trả boolean, vẫn được auto-retry. .not+{ timeout }giống mọi web-first assertion:await expect(page).not.toHaveURL(/login/)verify đã rời trang login.
Ví dụ & Cú pháp thực tế
- ```ts import { test, expect } from '@playwright/test'; test('verify navigation sau login', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('qa@hoctest.com'); await page.getByLabel('Mật khẩu').fill('Passw0rd!'); await page.getByRole('button', { name: 'Đăng nhập' }).click(); // Chờ redirect xong — không cần waitForURL thủ công await expect(page).toHaveURL(/\/dashboard/); await expect(page).toHaveTitle(/Dashboard/); });
- ```ts test('SPA route & query param', async ({ page }) => { await page.goto('/products'); // SPA đổi route không reload — toHaveURL vẫn retry được await page.getByRole('link', { name: 'Điện thoại' }).click(); await expect(page).toHaveURL(/\/products\?cat=phone/); // Predicate cho logic phức tạp await expect(page).toHaveURL( url => url.searchParams.get('cat') === 'phone' && url.searchParams.has('page'), { timeout: 10_000 }, ); // Relative URL khi đã set baseURL trong config await expect(page).toHaveURL('/products?cat=phone&page=1'); });
- ```ts test('link external mở tab mới', async ({ page, context }) => { // Bắt popup/tab mới trước khi click const [newPage] = await Promise.all([ context.waitForEvent('page'), page.getByRole('link', { name: 'Tài liệu' }).click(), ]); await newPage.waitForLoadState(); // Assert trên page mới, không phải page cũ await expect(newPage).toHaveURL(/docs\.hoctest\.com/); await expect(newPage).toHaveTitle(/Hướng dẫn/); });
Best practices & Khi nào dùng
- Verify navigation xong bằng
toHaveURLthay vìwaitForURL+ assert riêng — một dòng vừa chờ vừa kiểm tra, lỗi report cũng rõ ('expected URL ..., received ...'). - Dùng regex/glob cho URL có phần động — query param, hash, session id thay đổi mỗi run; assert exact string sẽ flaky ngay lần chạy thứ hai.
- Set
baseURLtrong config và dùng relative URL — test chạy được trên dev.hoctest.com lẫn prod chỉ bằng cách đổi env, không sửa assertion. toHaveTitlehợp smoke check đúng trang — nhanh, rẻ; nhưng đừng chỉ check title rồi coi như trang render đúng, kết hợp thêm mộttoBeVisibletrên element chủ chốt.- Với SPA, dùng
toHaveURLđể detect route change — trang không reload nênwaitForLoadStatekhông bắt được; URL assertion là cách đáng tin nhất để biết router đã chuyển xong.
Gotchas, So sánh & Lưu ý
- String = exact match:
toHaveURL('https://app.com/dashboard')KHÔNG khớphttps://app.com/dashboard?tab=1hay có trailing slash khác. Đây là lỗi phổ biến nhất — nghi ngờ thì chuyển sang regex. toHaveTitlevới string cũng exact: title hay đổi theo ngôn ngữ/A-B test nên regex (toHaveTitle(/Đăng nhập|Login/)) bền hơn cho suite chạy đa locale.- Chỉ có 2 matcher thật sự của Page:
toHaveTitlevàtoHaveURL(ngoài ratoHaveScreenshot— xem bài visual). Mọi thứ khác của trang (element, text) phải qua locator. - Với multiple pages/tabs:
expect(page)assert trên đúng Page object đó — nhớ truyềnnewPage/popup, nhiều người assert nhầm tab cũ rồi tưởng test sai. page.url()trả về ngay, không chờ — nếu bạn tự kéoexpect(page.url()).toBe(...)sẽ mất auto-retry (đây là non-retrying assertion, xem bài riêng). Luôn dùngexpect(page).toHaveURL().- Hash-based routing:
toHaveURL(/#\/settings/)— phần sau#không gửi lên server nhưng vẫn nằm tronglocation.hrefnên match bình thường.
