skip to content

Why can one Playwright test drive two origins, a popup and its network traffic at once?

level: middleimportance: should knowfreq 55%

answer

  1. Connected to the browser, not a page
  2. Another origin is just another command
  3. Popups arrive as new pages
  4. Interception sits in the browser's network path
  5. Page security is never disabled

basics

~20 s

Because Playwright drives the browser from outside the page. The connection is to the browser, not to one document, so another origin, an extra tab and the browser's network layer are all in reach of the same test.

solid answer

~50 s

The driver is attached to the browser process, so a page is only one of the things it can address. Navigating to a second origin is just another command; a popup the application opens arrives as a new page on the same context and can be driven immediately; and `page.route()` intercepts requests inside the browser before they leave it, whatever origin they target. None of this requires disabling browser security. The same-origin policy and CORS still constrain the **application**, exactly as they do for a real user - they simply never constrained the test, because the test was never a script in the document. The practical payoff is that a journey crossing a quote site and a payment provider is one test with one set of assertions, rather than two tests joined by a hopeful comment.

code

typescript · 16 lines
typescript
import { test, expect } from '@playwright/test';

test('motor quote hands off to the payment provider', async ({ page, context }) => {
  await page.route('**/api/rates**', route =>
    route.fulfill({ json: { premium: 41200, currency: 'GBP' } }));

  await page.goto('https://quotes.example.com/motor/1042');

  const [payment] = await Promise.all([
    context.waitForEvent('page'),
    page.getByRole('link', { name: 'Pay now' }).click(),
  ]);

  await payment.waitForURL('**/payments.example.net/**');
  await expect(payment.getByRole('heading')).toHaveText('Card details');
});

go deeper

for a junior

Remember that a Playwright test is not stuck on one page. You can navigate to another site, work with a tab the application opened, and still be in the same test with the same handles.

for a middle

Explain why: the driver is attached to the browser rather than to a document, so pages, contexts and the network path are all addressable, and browser security is never switched off to make that work.

for a senior

Show where the reach helps and where it flatters. Decide which hand-offs to exercise for real and which to intercept, and justify each choice by the risk the test is meant to cover.

for a principal

Own the policy: which journeys are allowed to cross a third-party boundary in CI, what gets stubbed by default, and how the suite stays honest about integrations it can now trivially fake.

Most of what feels generous about Playwright's API is really the same fact restated: **the driver is connected to the browser, not to a page.** A document is one addressable thing among several. ## What the connection actually reaches - **Every page in the browser**, including ones the application opened itself. - **Every context**, each an isolated profile with its own cookies and storage. - **The browser's network layer**, which is why interception can happen before a request leaves. - **Browser-level state and signals** - dialogs, downloads, console output, crashes - delivered as events on the same connection. Your test is not a guest in the document, so the document's boundaries are not its boundaries. ## Crossing an origin `await page.goto('https://payments.example.net/checkout')` is not a special case; it is the same command with a different URL. The page that results is the same handle, now pointed somewhere else. This matters for real journeys: a motor-quote flow that hands off to an external payment provider and returns with a reference can be asserted end to end, instead of being cut in half at the origin boundary and stitched together with a mock. What has **not** happened is any weakening of browser security. The page still cannot read across origins, CORS still applies to the application's own requests, and a cross-origin frame is still opaque to `page.evaluate()`. The test can address these things because it sits outside them, not because they were turned off. ## Extra tabs, without asking permission When the application opens a new tab, the browser tells the driver, and the new page becomes a handle on the same context. A test can therefore follow a hand-off that it did not initiate. Under a model where the test is a script inside one document, a second tab is a different world; here it is another item the same connection can address. ## Network, from the browser side `page.route()` and its context-level twin install a handler that runs when the browser is about to issue a matching request. Because that hook is inside the browser rather than in the application, it sees requests the page makes on its own - a rating call fired by a bundled script, a beacon, a lazy chunk - not only the ones your test triggered directly. It also needs no proxy, no certificate work and no change to the application build. | Capability | Why the driving model allows it | What still limits you | |---|---|---| | Second origin in one test | The command targets the browser, not a document | The page's own cross-origin restrictions are intact | | Popup the app opened | The browser reports new pages over the connection | The popup lives in its context and closes with it | | Intercepting the app's own calls | The hook sits in the browser's network path | Only traffic the browser makes is visible | | Reading browser-level events | One event stream for the whole session | Events stop when the connection does | ## Using the reach without abusing it 1. **Cross an origin when the journey does.** If a user really goes to the payment provider and comes back, the test should too; that is the risk you are trying to cover. 2. **Do not stub away the boundary you are testing.** Intercepting the hand-off makes the test faster and also blind to the integration it exists to protect. Stub the noise, keep the subject. 3. **Assert on what the user sees in the tab that has focus.** Reach is not a licence to make assertions in three places at once; a test that touches many surfaces should still tell one story. 4. **Remember the isolation edge.** Two contexts are two profiles; a test that signs in twice in one context is sharing cookies whether it meant to or not. ## The misreading to avoid The reach is often described as "Playwright ignores the same-origin policy". It does not. The policy governs what the **page** may do, and it is untouched. Playwright is simply not standing inside the page, so the rules that fence the page never applied to it in the first place.

  • If the test can cross origins freely, what stops it from being an unrealistic test?
    Nothing structural - that is the author's job. The browser still enforces every rule on the application, so the page behaves as a user's would; the risk is that reach tempts you to stub the very hand-off you meant to cover, or to assert across surfaces in a way no user experiences. Keep the subject of the test real and stub only the noise around it.
  • Which traffic does a browser-side route handler not see?
    Anything the browser does not issue. Server-to-server calls made behind the application, traffic from another browser or process, and requests made before the handler was installed are all outside its path. It is an interception point in the browser under test, not a network proxy for the whole environment.

saying these in an interview costs you the question

  • Says Playwright disables the same-origin policy for tests
  • Thinks a second origin needs a second test or browser
  • Believes interception is an external proxy the test configures
  • Assumes a popup is unreachable unless the test opened it
  • Thinks route handlers see server-to-server traffic too