In Playwright, what does page.goto() wait for before it resolves?
answer
- Resolving is gated on a lifecycle event
- One option name chooses that gate
- Four values, one of them discouraged
- The default is the heaviest one
- Headers-only value is named commit
basics
~10 spage.goto() resolves when the load event fires on the new document. The waitUntil option moves that gate to domcontentloaded, commit, or the discouraged networkidle, and the call returns the main resource response.
solid answer
~40 sBy default `page.goto(url)` waits for the main frame's `load` event - the document plus dependent resources such as stylesheets, scripts, iframes and images - and resolves with the main resource `Response`. The `waitUntil` option moves that gate: `'domcontentloaded'` returns when the `DOMContentLoaded` event fires, `'commit'` returns as soon as the response headers are parsed and the document starts loading, and `'networkidle'` waits for 500 ms with no network connections but is explicitly discouraged. A client-side redirect before `load` is followed, so you get the redirected document's `load`. `goto` throws on SSL errors, an invalid URL, an unreachable server or a timeout, but never on a 4xx or 5xx - read `response.status()` if the test cares. In practice the default is right: every action auto-waits, so tuning `waitUntil` is rarely the fix for anything.
code
typescript · 8 linesimport { test, expect } from '@playwright/test';
test('issue board renders open issues', async ({ page }) => {
const response = await page.goto('/issues', { waitUntil: 'domcontentloaded' });
expect(response?.status()).toBe(200);
await expect(page.getByRole('heading', { name: 'Open issues' })).toBeVisible();
});go deeper
Remember the default: page.goto() resolves on the load event, and returns the main resource response. Know the four waitUntil values by name and that networkidle is the one you do not use.
Explain the commit-then-load split, so you can say why commit resolves earliest and why there is no commit load state. Explain why goto never throws on a 404 and where the status actually lives.
Show judgment about when tuning waitUntil is a real fix versus a plaster. Point out that load is a browser milestone, not an app one, and route readiness through assertions instead of a heavier navigation gate.
Own the convention: a default navigation gate for the suite, a documented navigation timeout budget, and a review rule that any non-default waitUntil carries a comment explaining the page behaviour that forced it.
`page.goto(url)` navigates the page's main frame and does not resolve until the new document reaches a lifecycle milestone. Which milestone is decided by the `waitUntil` option, whose default is `'load'`: the browser's `load` event, fired once the document **and** its dependent resources - stylesheets, scripts, iframes, images - have finished. The call resolves with the main resource `Response`, so `(await page.goto('/issues'))?.status()` is a legitimate way to assert that the issue board answered 200. Navigating to `about:blank`, or to the same URL with only a different hash, resolves with `null` instead. ## Navigation versus loading Playwright models showing a document as two phases, and the `waitUntil` values are points along them. **Navigation** starts when the URL changes or the user interacts with the page. It is *committed* once the response headers have been parsed and session history is updated; the intent can still be cancelled before that, for example by an unresolved DNS name or by the response turning into a download. **Loading** only begins after commit: the body arrives over the network, the document is parsed, `DOMContentLoaded` fires, sub-resources load, and finally `load` fires. ## The four waitUntil values | Value | Resolves when | Reach for it when | |---|---|---| | `'commit'` | Headers parsed, document starts loading | The response streams indefinitely, or you want to drive the page at once | | `'domcontentloaded'` | The `DOMContentLoaded` event fires | Slow images or third-party scripts are irrelevant to the test | | `'load'` *(default)* | The `load` event fires | Almost always - leave it alone | | `'networkidle'` | No network connections for at least 500 ms | Never in a test; Playwright documents it as discouraged | Consequences worth holding on to: - A **client-side redirect** before `load` is followed: `goto` waits for the redirected document's `load`, not the intermediate one. - `'commit'` is the only value about navigation rather than loading, which makes it the right gate for a page that never finishes - a streaming activity feed on the issue detail screen, say. - `'networkidle'` is marked **DISCOURAGED** in the API docs. An issue board that polls for new comments never goes quiet, so the wait simply burns the timeout. - `page.waitForLoadState()` accepts only `'load'`, `'domcontentloaded'` and `'networkidle'`. There is no `'commit'` load state, because commit has necessarily already happened by the time you can call it. ## What goto does not tell you The `load` event is a browser milestone, not an application one. A modern issue tracker fires `load` and only then fetches the board, hydrates it and renders rows. Nothing in `goto` knows about that, which is why raising `waitUntil` is almost never the fix for a flaky test: the condition you actually want is *the row I am about to click exists*, and that is a locator assertion, not a lifecycle event. Playwright's stated position is that you may interact with the page at any moment, because every action auto-waits for its target to become actionable. ## Errors, status codes and timeouts 1. `goto` **throws** on an SSL error, an invalid URL, an unreachable or silent server, a failed main resource, or an exceeded timeout. 2. `goto` **does not throw** on any valid HTTP status, 404 and 500 included. Read `response.status()` from the returned value when the test cares. 3. The navigation timeout is separate from the action timeout. In the library it defaults to 30 seconds; under the Playwright test runner the `navigationTimeout` option defaults to `0`, meaning no timeout of its own, so the test timeout bounds it. Set it per project with `use: { navigationTimeout: 15_000 }`, per page with `page.setDefaultNavigationTimeout()`, or per call with `page.goto(url, { timeout: 15_000 })`. ## Reading it in review - `waitUntil: 'networkidle'` in a diff is a smell - ask what condition the author was really waiting for. - `waitUntil: 'domcontentloaded'` is a defensible speed optimisation on a heavy page, and harmless because the assertion after it retries anyway. - `waitUntil: 'commit'` nearly always means the page never fires `load`. That is a real reason, and it deserves a one-line comment saying so. - No `waitUntil` at all is the healthy default and needs no justification. ```typescript // The board streams an activity log, so load never fires. await page.goto('/issues/42/activity', { waitUntil: 'commit' }); await expect(page.getByRole('heading', { name: 'Activity' })).toBeVisible(); ``` The rule of thumb: pick the gate for the shape of the *document*, and let a retrying assertion decide when the *application* is ready.
- What does page.goto() return, and when is that return value null?It returns the main resource `Response`, or the response of the first non-redirect hop after redirects, so you can read `status()`, `headers()` or `body()` from it. It returns `null` only for navigation to `about:blank` and for navigation to the same URL with a different hash.
- The issue board is behind a client-side redirect. Which document's load event does goto wait for?The redirected one. If the page performs a client-side redirect before `load` fires, `page.goto()` keeps waiting and resolves on the final document's `load` event, so the test does not resume on the intermediate page.
- How do you cap how long a single page.goto() may take?Pass `timeout` to the call, or set a default. Under the test runner `navigationTimeout` defaults to `0` - no separate limit - so the test timeout is the real bound; `use: { navigationTimeout: 15_000 }` in the config, or `page.setDefaultNavigationTimeout()`, sets a tighter one.
saying these in an interview costs you the question
- Says goto resolves as soon as the request is sent
- Thinks goto throws on a 404 or 500 response
- Uses networkidle as the default for every navigation
- Believes goto waits until the app has rendered its data
- Thinks commit waits for the DOMContentLoaded event
- Assumes waitUntil accepts a load state called ready