How to fix net::ERR_ABORTED during page.goto in Playwright?

Playwright

page.goto: net::ERR_ABORTED means the browser started a navigation but the request was cancelled before a response was received. A frequent variant of the full error message is page.goto: net::ERR_ABORTED; maybe frame was detached?, which Playwright appends as a hint when it suspects the abort was caused by the frame being removed during navigation — for example, when an SPA replaces the root frame or a redirect chain removes the page before the new document loads. Other causes include a page.route() handler calling route.abort(), a page.close() called during navigation, or a redirect that sends the browser to a URL your test didn't expect.

Common mistake

test('loads dashboard after login', async ({ page }) => {
  // Triggers a redirect chain: /login -> /oauth -> /dashboard
  await page.goto('https://app.example.com/login');

  // Second goto fires while first redirect is still in flight
  await page.goto('https://app.example.com/dashboard');
});

Firing two navigations in quick succession will abort the first one, which can cascade into the error depending on timing.

The fix

For overlapping navigations — wait for the URL you want:

import { test, expect } from '@playwright/test';

test('loads dashboard via redirect', async ({ page }) => {
  await page.goto('https://app.example.com/login');

  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('secret');
  await page.getByRole('button', { name: 'Sign in' }).click();

  // Wait for the final destination — handles any redirect chain
  await page.waitForURL('**/dashboard', { timeout: 15000 });
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

For the "maybe frame was detached" variant — avoid acting on frames that may be replaced:

// Instead of going directly to a URL inside an SPA that rebuilds its frame
await page.goto('https://app.example.com');
await page.getByRole('link', { name: 'Settings' }).click();
await page.waitForURL('**/settings');

For route handlers that accidentally abort requests:

await page.route('**/api/**', async (route) => {
  // Always either fulfill, continue, or abort — never leave hanging
  if (route.request().url().includes('/telemetry')) {
    await route.abort();
  } else {
    await route.continue();
  }
});

Why it works

ERR_ABORTED is a Chromium-level signal that the network request was cancelled at the browser's request — not a server-side failure. Chaining page.waitForURL after a click that triggers navigation ensures you only proceed after the browser has settled on the target URL, eliminating races where a second navigation fires into an in-flight first one. For the frame-detached variant, navigating through the UI rather than jumping directly to deep URLs avoids the SPA race where the router removes and reconstructs the root frame.

Tips

  • Inspect the page.on('requestfailed') event during debugging to identify which exact request was aborted and what reason code Chromium reported.
  • If you have a page.route() handler, verify every code path calls route.fulfill(), route.continue(), or route.abort() — unresolved route handlers can cause aborted navigations when the test runner tears down.
  • The "maybe frame was detached" hint points to frame was detached — if your test uses iframes, check that page.
  • For offline-mode or network-failure scenarios that produce ERR_ABORTED as a secondary error, see ERR_INTERNET_DISCONNECTED.