Wiki

← Tất cả bài wiki

Navigation & Load States (Điều hướng và trạng thái tải)

automationplaywright

Navigation & load states là cách Playwright điều hướng và đo 'độ sẵn sàng' của trang qua các mốc domcontentloaded, load, networkidle. goto mặc định chờ đến load, nhưng SPA hiện đại cần waitForURL hoặc assert element thay vì tin vào load state — và networkidle nên tránh vì dễ treo với app có polling.

Khái niệm & Bản chất

  • Navigation = chuyển document: page.goto(), click link, history.back(), redirect server... Mỗi navigation có vòng đời gắn với các mốc sự kiện của browser.
  • Ba load state: domcontentloaded (HTML parse xong, DOM có thể query — JS/CSS/hình chưa chắc xong), load (mọi resource tĩnh xong: ảnh, CSS, script), networkidle (im lặng network ≥500ms — không còn request nào đang chạy).
  • page.goto(url) mặc định chờ đến load rồi mới return — đủ cho hầu hết trang render server-side.
  • Với SPA (React/Vue/Angular), điều hướng client-side không tạo document mới → các mốc load không bắn lại → phải chờ bằng waitForURL hoặc element state.
  • Click link thường không cần chờ thủ công — action tiếp theo của Playwright tự chờ; chỉ khi cần kiểm soát mốc mới dùng Promise.all([waitForNavigation/waitForURL, click]).

Cách hoạt động & API chính

  • page.goto(url, { waitUntil: 'domcontentloaded' | 'load' | 'networkidle' | 'commit', timeout }) — commit là bản sớm nhất (server bắt đầu trả response).
  • page.waitForURL('**/dashboard') / regex / predicate — chờ đến khi URL khớp; chờ cả sau khi điều hướng xong.
  • page.waitForLoadState(state) — chờ trang hiện tại đạt mốc; thường dùng sau khi bắt popup hoặc khi chắc chắn có document load.
  • page.goBack()/goForward()/reload() — điều hướng lịch sử, nhận option waitUntil tương tự goto.
  • page.waitForNavigation() (bản cũ) ≈ waitForURL + chờ navigation thật — code mới ưu tiên waitForURL vì rõ nghĩa hơn và cover cả hash-change.
  • Response của goto trả về Response object — const resp = await page.goto('/'); expect(resp?.status()).toBe(200) verify HTTP status.

Ví dụ & Cú pháp thực tế

  • Goto cơ bản + kiểm tra status: ``ts test('trang chủ load OK', async ({ page }) => { const resp = await page.goto('/'); expect(resp?.status()).toBe(200); await expect(page).toHaveTitle(/HocTest/); }); ``
  • Chờ URL sau hành động điều hướng SPA: ``ts await page.getByRole('button', { name: 'Đăng nhập' }).click(); await page.waitForURL('**/dashboard'); // an toàn hơn waitForLoadState vì SPA không reload document await expect(page.getByRole('heading', { name: 'Tổng quan' })).toBeVisible(); ``
  • Bắt navigation khi click link reload trang thật: ``ts await Promise.all([ page.waitForURL(/\/success/), page.getByRole('button', { name: 'Thanh toán' }).click(), ]); ``
  • Load state sau khi mở popup mới: ``ts const popup = await context.waitForEvent('page'); // sau click await popup.waitForLoadState('domcontentloaded'); await expect(popup).toHaveURL(/invoice/); ``
  • Back/forward + reload: ``ts await page.goBack({ waitUntil: 'domcontentloaded' }); await page.reload(); ``

Best practices & Khi nào dùng

  • Mặc định goto (waitUntil load) đủ dùng — đừng hạ xuống domcontentloaded trừ khi page có resource rất nặng và bạn chỉ cần DOM sớm để action.
  • Sau điều hướng SPA, tín hiệu tốt nhất là URL + element đặc trưng của màn hình đích: waitForURL + expect(heading).toBeVisible() — vừa là wait vừa là assert.
  • domcontentloaded hợp với trang SSR cần tương tác sớm (đọc DOM trước khi ảnh tải xong); load cho case cần chắc resource sẵn sàng (verify ảnh render).
  • Khi click có thể không điều hướng (validate fail) lẫn điều hướng (thành công): dùng Promise.race hoặc assert hai nhánh — đừng waitForURL cứng vì nhánh fail sẽ treo timeout.
  • Verify điều hướng bằng expect(page).toHaveURL(/regex/) (web-first, tự retry) thay vì waitForURL khi chỉ cần assert — gọn hơn và tận dụng auto-retry.

Gotchas, So sánh & Lưu ý

  • Né networkidle: app có analytics (GA, Sentry), polling, websocket, heartbeat → network không bao giờ 'idle' → goto(..., {waitUntil:'networkidle'}) hay waitForLoadState('networkidle') treo hết timeout. Chính docs Playwright khuyên không dùng; thay bằng chờ element/response cụ thể.
  • SPA client-side routing không bắn load state — waitForLoadState('load') sau click router-link return ngay (document cũ đã load lâu rồi) → tưởng là chờ nhưng thực ra không chờ gì; đây là flake kinh điển.
  • waitForURL match theo glob/regex của toàn URL — nhớ pattern **/path chứ 'path' thuần sẽ không khớp đầy đủ; với query string động dùng regex hoặc function.
  • goto vào trang redirect liên tục (SSO, http→https) vẫn theo dõi được — resp.url() là URL cuối cùng sau redirect; check resp.request().redirectedFrom() nếu cần dây chuyền.
  • Fragment #section đổi không tính là navigation mới nhưng waitForURL vẫn match được — tiện test anchor scroll trong SPA.
  • Timeout mặc định của navigation = navigationTimeout (mặc định bằng timeout 30s) — set riêng trong playwright.config.ts nếu môi trường test chậm, đừng rải { timeout: 60000 } khắp test.