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ờ đếnloadrồ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
loadkhông bắn lại → phải chờ bằngwaitForURLhoặ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 })—commitlà 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 optionwaitUntiltương tựgoto.page.waitForNavigation()(bản cũ) ≈waitForURL+ chờ navigation thật — code mới ưu tiênwaitForURLvì rõ nghĩa hơn và cover cả hash-change.- Response của
gototrả vềResponseobject —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(waitUntilload) đủ dùng — đừng hạ xuốngdomcontentloadedtrừ 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. domcontentloadedhợp với trang SSR cần tương tác sớm (đọc DOM trước khi ảnh tải xong);loadcho 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.racehoặc assert hai nhánh — đừngwaitForURLcứ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ìwaitForURLkhi 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'})haywaitForLoadState('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. waitForURLmatch theo glob/regex của toàn URL — nhớ pattern**/pathchứ'path'thuần sẽ không khớp đầy đủ; với query string động dùng regex hoặc function.gotovà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; checkresp.request().redirectedFrom()nếu cần dây chuyền.- Fragment
#sectionđổi không tính là navigation mới nhưngwaitForURLvẫn match được — tiện test anchor scroll trong SPA. - Timeout mặc định của navigation =
navigationTimeout(mặc định bằngtimeout30s) — set riêng trongplaywright.config.tsnếu môi trường test chậm, đừng rải{ timeout: 60000 }khắp test.
