Wiki

← Tất cả bài wiki

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.title và 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? toHaveURL chờ 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 tra location.href. Cả hai nhận string, RegExp, và toHaveURL còn nhận function (url) => boolean hoặc URL object.
  • 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ùng RegExp để 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).
  • toHaveURL với RegExp là lựa chọn an toàn nhất: toHaveURL(/\/dashboard/) bỏ qua query param động, hash, locale prefix... Dùng **/path glob pattern cũng được (Playwright convert glob → regex).
  • toHaveURL hỗ trợ relative URL: nếu baseURL đã 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ận URL object, 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 toHaveURL thay 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 baseURL trong 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.
  • toHaveTitle hợ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ột toBeVisible trên element chủ chốt.
  • Với SPA, dùng toHaveURL để detect route change — trang không reload nên waitForLoadState khô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ớp https://app.com/dashboard?tab=1 hay có trailing slash khác. Đây là lỗi phổ biến nhất — nghi ngờ thì chuyển sang regex.
  • toHaveTitle vớ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: toHaveTitle và toHaveURL (ngoài ra toHaveScreenshot — 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ền newPage/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éo expect(page.url()).toBe(...) sẽ mất auto-retry (đây là non-retrying assertion, xem bài riêng). Luôn dùng expect(page).toHaveURL().
  • Hash-based routing: toHaveURL(/#\/settings/) — phần sau # không gửi lên server nhưng vẫn nằm trong location.href nên match bình thường.