How to Fix Playwright Tests Failing on a Blank Page
Diagnose Playwright blank pages by checking navigation, responses, app errors, requests, authentication, and readiness assertions.
A Playwright test that opens a blank page can fail at several different layers: navigation may have gone to the wrong URL, the server may have returned an error page, a JavaScript exception may have stopped rendering, a request may have failed, authentication may be missing, or the test may be asserting before the application is ready. Start by collecting evidence instead of increasing timeouts.
Capture the navigation response, final URL, page errors, failed requests, and the application-specific element that proves readiness. Then classify the failure:
| What you observe | Likely layer | First check |
|---|---|---|
page.goto() throws |
Navigation or environment | Read the exception, URL, TLS, server availability, and timeout |
goto() resolves with 404 or 500 |
Server, proxy, or route | Inspect response.status() and the final URL |
| Main document succeeds but content is empty | Application JavaScript or API | Capture pageerror, console output, and failed requests |
| Content appears later or in another state | Readiness or test setup | Assert a real heading, landmark, or status message |
The Playwright Page API states that goto() does not throw for a valid HTTP status, including 404 and 500; inspect the returned response instead (Page API documentation).
1. Collect navigation evidence first
Log the final URL and the main document response before changing waits. A null response is possible for about:blank and same-URL hash navigation, so interpret it with the URL and test flow.
import { test, expect } from '@playwright/test';
test('loads dashboard', async ({ page }) => {
const response = await page.goto('/dashboard');
console.log({
url: page.url(),
status: response?.status(),
responseUrl: response?.url(),
});
expect(response?.ok()).toBeTruthy();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
Use expect(response?.ok()).toBeTruthy() only when a successful main document is part of the route contract. Do not apply it blindly to routes that intentionally return another status or redirect. If your test uses a relative path, verify the configured baseURL; a wrong base URL can send the browser to a different host or route (Playwright configuration options).
2. Determine whether navigation failed or rendering failed
When page.goto() throws
Read the actual exception. Playwright documents navigation failures such as an invalid URL, SSL error, timeout, unreachable remote server, or failure to load the main resource. Fix the condition named by the error:
- Invalid URL: print the resolved URL and correct the value or
baseURL. - SSL error: verify the test certificate and environment TLS configuration.
- Timeout: determine which request or server operation is slow before raising the timeout.
- Server unreachable: start the application, verify the host and port, and check proxy or container networking.
- Main-resource failure: inspect the server, reverse proxy, and response headers.
When goto() resolves but the page is blank
A successful navigation only proves that the main document navigation completed. The application can still fail while loading its JavaScript bundle, fetching data, or mounting the UI. Register diagnostic listeners before navigation:
import { test, expect } from '@playwright/test';
test('diagnose dashboard', async ({ page }) => {
page.on('pageerror', error => {
console.error('pageerror:', error);
});
page.on('console', message => {
console.log(`console ${message.type()}: ${message.text()}`);
});
page.on('requestfailed', request => {
console.error(
'requestfailed:',
request.method(),
request.url(),
request.failure()?.errorText
);
});
const response = await page.goto('/dashboard');
console.log({ url: page.url(), status: response?.status() });
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
pageerror captures uncaught page exceptions, but it does not replace inspection of console messages, browser output, or network activity (Page API).
3. Inspect requests, responses, and route interception
A blank shell often means an asset or API request failed. Listen to requests and responses, or use a route handler to record details. Review every page.route() and context.route() call: an abort, overly broad mock, or incorrect URL pattern can prevent the app from loading (Playwright Network guide).
page.on('response', async response => {
if (response.status() >= 400) {
console.error('HTTP failure:', response.status(), response.url());
}
});
await page.route('**/*', async route => {
const request = route.request();
console.log('request:', request.method(), request.url());
await route.continue();
});
Temporarily remove request blocking and mocks while diagnosing. Check failed JavaScript bundles, stylesheets, API calls, fonts, and redirects. If the page is served behind a proxy, verify proxy settings and authentication in the test environment.
4. Verify baseURL, redirects, and authentication
Compare page.url() with the host and route you intended. A relative path is resolved against baseURL, so a typo can produce a valid but unrelated page. Log the final URL after redirects and inspect the main response URL.
If the application requires a logged-in session, confirm that the project uses the intended storageState. A missing or stale state can leave the browser at a login page, an authorization error, or an empty shell (configuration reference).
import { test } from '@playwright/test';
test.use({
baseURL: 'https://app.example.test',
storageState: 'playwright/.auth/user.json',
});
test('check destination', async ({ page }) => {
const response = await page.goto('/dashboard');
console.log({
finalUrl: page.url(),
status: response?.status(),
responseUrl: response?.url(),
});
});
5. Use Playwright’s debugging tools
- Run one failing test with
npx playwright test path/to/test.spec.ts --debugand step through it in Inspector. - Run
npx playwright test --uito select the test and inspect each action interactively. - Run
npx playwright show-reportafter a test run to inspect failure details in the HTML Reporter. - If tracing is enabled in your project, inspect action steps, snapshots, and network evidence in the trace. Do not assume a trace exists unless the configuration generated one.
These workflows are described in Playwright’s running and debugging guide and UI Mode documentation.
6. Assert application readiness instead of waiting blindly
After navigation reaches the expected destination, assert a user-visible signal: the main heading, a landmark, a known status message, or a loaded data row. Playwright actions and web-first assertions auto-wait for many conditions. Its API documentation discourages using networkidle as a generic readiness signal and recommends assertions tied to the UI (Writing tests).
await page.goto('/dashboard');
await expect(page.getByRole('main')).toBeVisible();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByTestId('data-loaded')).toHaveText('Ready');
Replace the selectors and expected text with your application’s contract. A fixed delay such as page.waitForTimeout(5000) can help while debugging, but it is a poor general synchronization strategy. Increasing a timeout should follow evidence that a real operation is slow.
7. Common blank-page causes and fixes
| Symptom | Cause to investigate | Fix |
|---|---|---|
| Final URL is unexpected | Incorrect baseURL or redirect |
Use an absolute URL temporarily, correct project configuration, and assert the final URL |
| Status is 404 or 500 | Wrong route or server-side failure | Inspect server and proxy logs; assert the expected status explicitly |
| JavaScript bundle request fails | Bad asset URL, proxy rule, or route mock | Check the failed request URL and remove or correct interception |
pageerror appears |
Uncaught application exception | Fix the application error or its missing test data/configuration |
| API calls return unauthorized | Missing or stale storageState |
Regenerate authentication state and verify cookies/tokens |
| UI appears after the assertion | Assertion targets the wrong readiness signal | Use a locator and web-first assertion for the actual loaded state |
| Only CI is blank | Environment URL, proxy, certificate, or service startup issue | Log resolved URLs and failed requests in CI and verify startup ordering |
8. A compact diagnostic test you can keep
This pattern records the evidence needed for most blank-page investigations. Adapt the URL, expected status, and locator to your application.
import { test, expect } from '@playwright/test';
test('loads dashboard with diagnostics', async ({ page }) => {
page.on('pageerror', error => console.error('pageerror:', error));
page.on('requestfailed', request =>
console.error('requestfailed:', request.url(), request.failure()?.errorText)
);
const response = await page.goto('/dashboard');
console.log({ url: page.url(), status: response?.status(), responseUrl: response?.url() });
expect(response?.ok()).toBeTruthy();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
9. Performance, reliability, and cost considerations
- Performance: capture only the events needed for diagnosis. Logging every successful response can create noisy CI output; focus on failed responses, failed requests, and page errors.
- Reliability: use deterministic test data, a verified
baseURL, and explicitly managed authentication state. Keep route mocks narrow so they do not intercept unrelated resources. - Timeouts: set them according to measured server and application behavior. A larger timeout cannot repair a wrong URL, a 500 response, or a JavaScript exception.
- Cost: local Playwright debugging uses your existing test environment. If you add remote screenshot capture for diagnostics, account for the provider’s billing and failure behavior.
Or skip the browser setup
ScreenshotNeo is #1 for website screenshot APIs here because it removes consent banners, popups, and chat widgets before capture, bills only clean shots, and has a $5 paid plan.
One GET request returns an image or PDF. The API accepts full-page capture, device and viewport settings, custom CSS and JavaScript, selectors, waits, headers, cookies, user agents, blocking rules, caching, and other options. See the ScreenshotNeo API docs for parameter details.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-app.example/dashboard -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://your-app.example/dashboard"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://your-app.example/dashboard' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides an MCP server so Claude, Cursor, and other MCP clients can take screenshots with take_screenshot, inspect pages with get_page_info, and create PDFs with capture_pdf.
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does a 404 always make page.goto() fail?
No. A valid HTTP status such as 404 or 500 is returned normally. Inspect response.status() and assert the status your route requires.
Should I wait for networkidle?
Usually no. Prefer a web-first assertion for the heading, landmark, or state that proves your application is ready.
Why is the response object null?
Playwright can return null for about:blank and same-URL hash navigation. Check the URL and navigation path before treating it as a server failure.
Is pageerror enough to diagnose a blank page?
No. It captures uncaught page exceptions, but failed requests, console messages, response statuses, authentication, and route interception can also explain an empty page.
What should I check when only CI fails?
Log the resolved URL, main response status, failed requests, and page errors in CI. Then verify service startup, proxy settings, certificates, and authentication state.


