Q14 of 42 · Playwright
How does Playwright handle cross-origin navigation differently from Cypress?
Short answer
Short answer: Playwright treats cross-origin navigation as a normal navigation — same `page`, same APIs, no special config. Cypress historically blocked cross-origin navigation; `cy.origin` (12+) supports it but with a constrained API. Playwright's design is uniformly cross-origin-friendly.
Detail
The difference is architectural. Cypress runs inside the browser tab, and the browser's same-origin policy used to lock Cypress out of cross-origin iframes and post-redirect pages. cy.origin('https://other.com', () => { ... }) was added in Cypress 12 to switch context, but it has caveats — limited subset of commands, no shared variables.
Playwright drives the browser from outside via the DevTools Protocol. The same-origin policy doesn't apply to the test driver; navigating from /login to https://idp.example.com/auth to /dashboard works seamlessly:
await page.goto('/login');
await page.click('[data-test=sso]'); // navigates to identity provider
await page.fill('input[name=username]', 'alice@x.com');
await page.click('button[type=submit]'); // returns to /dashboard
await expect(page).toHaveURL(/\/dashboard/);
Same page object, no context switch, no cross-origin block.
Implications:
- OAuth / SSO flows are first-class testable. No backdoor login required (though it's still preferable for speed).
- Embedded third-party iframes (Stripe, Auth0, reCAPTCHA) — Playwright can interact via
page.frameLocator(...)regardless of origin. - Multi-page authentication redirects (legal terms, MFA flows) work end-to-end.
The trade-off: Playwright's cross-origin freedom makes it tempting to test third-party flows that should be stubbed. Hitting the real Stripe or Auth0 in tests is slow and fragile; their sandboxes are better. Use Playwright's freedom for your own multi-origin flows; stub third parties at the network layer (page.route).
A bonus capability — multiple browser contexts in one test, each with its own cookies and origin, simulate multi-user scenarios:
const context1 = await browser.newContext();
const context2 = await browser.newContext();
const userA = await context1.newPage();
const userB = await context2.newPage();
// Both navigate independently to the same or different origins
This is genuinely impossible in Cypress.