skip to content

Injected Setup

What a test receives by naming it in its arguments: the ready-made page and context, the fixtures you write yourself, and the choice of per-test or per-worker lifetime.

on this pageshow

explore

questions

16

In Playwright Test, what do you get when a test destructures the page fixture?

level: juniorimportance: must knowfreq 88%

answer

  1. Named argument, not a hook
  2. Fresh for every single test
  3. It sits inside a new context
  4. Browser is the shared part
  5. page.context() equals the context fixture

basics

~20 s

Playwright Test builds a brand-new BrowserContext for that one test and hands back the single page opened inside it. The runner creates it before the test body and closes it afterwards, so no test inherits another test's page.

solid answer

~50 s

`page` is a **built-in, test-scoped fixture**. Naming it in the destructured argument object is the setup: before the body runs, the runner asks the worker's browser for a new `BrowserContext` and opens one page in it; after the body settles, pass or fail, it tears both down. So every test starts on a context nothing else has touched, and `page.context()` returns exactly the object the `context` fixture would hand you. The expensive part, the `browser` itself, is *not* rebuilt per test: it is shared by every test the worker runs. That split is the design, cheap fresh context per test on top of one costly browser. Practically it means you never write a `beforeEach` that opens a page, and you cannot sign in during one test and expect the next one's `page` to still be signed in.

code

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

test('the staff list renders', async ({ page, context }) => {
  // The page fixture is the single page of the test's own context.
  expect(page.context()).toBe(context);
  expect(context.pages()[0]).toBe(page);

  await page.goto('https://admin.example.com/staff');
  await expect(page.getByRole('heading', { name: 'Staff' })).toBeVisible();
});

go deeper

for a junior

Remember the shape: name page in the test arguments and the runner hands you a ready page in a fresh context. You never open or close it yourself.

for a middle

Be able to draw the chain: worker browser, then a new context per test, then one page in it. Explain why page.context() is the same object the context fixture gives you.

for a senior

Show you use the guarantee deliberately: no order-dependent suites, no shared page held in module scope, and sign-in handled per test or through a saved session rather than by leaking state.

for a principal

Own the trade-off you are buying: one expensive shared browser against cheap per-test contexts. Decide where a team may deviate from it and what the reviewers should reject.

## What "fixture" means in Playwright Test Playwright Test builds the objects a test needs **by name**. The second argument of `test()` is destructured, `async ({ page }) => { ... }`, and the runner reads those property names before the body runs, constructs each one, and passes them in. `page` is the most-used of the **built-in fixtures** that ship with `@playwright/test`; its siblings are `context`, `browser`, `browserName` and `request`. Setup and teardown are the runner's job, not yours. There is no hook you write to open a page and none to close it: naming `page` in the argument list *is* the setup, and the teardown runs after the body settles, whether it passed, failed or threw. ## What the page fixture actually is A `Page`, the handle you drive with `page.goto()`, `page.getByRole()` and the rest, that is: - **test-scoped**: constructed for one `test()` call and discarded when it ends; - **the only page in a brand-new `BrowserContext`** the runner created moments earlier for this test; - **never shared**: two tests, even neighbours in the same file, never receive the same `Page`; - **already open**: it exists before your first statement, sitting on a blank page until you navigate. ## The chain behind it 1. The worker process launches a browser, the `browser` fixture. A launch is expensive, so it is **shared by every test that worker runs**. 2. For each test, the runner asks that browser for a **new `BrowserContext`**, the `context` fixture. 3. It opens **one page** in that context and gives it to you as `page`. Because step 3 builds on step 2, `page.context()` is the very object the `context` fixture provides, so `expect(page.context()).toBe(context)` holds inside a test that destructured both. Likewise `context.pages()[0]` is your `page`. | Fixture | Lives for | Cost to build | |---|---|---| | `browser` | the whole worker | high, a browser process | | `context` | one test | low | | `page` | one test | low | | `request` | one test | low | That table is the entire trade-off: the expensive object is shared, the cheap objects are per test, and isolation is bought with the cheap ones. ## Where isolation begins Take an internal admin console with several staff roles. One test signs in as an auditor, another as an administrator. Both take `page`. They cannot interfere, because each runs against a context created for it alone, no matter which ran first or whether the same worker ran both. How a context keeps that state apart belongs to the browser-contexts topic; what matters here is that the fixture hands you a fresh one **every single time**. Isolation also means test order is not a design tool. You cannot sign in during the first test and reuse that session through the second test's `page`, because that page lives in a different context. Reusing a signed-in session is a separate, deliberate mechanism, not a side effect of the fixture. ## What the fixture does not do - It does **not** launch a new browser per test. That would cost seconds per test; only the context and the page are new. - It does **not** survive the test. Stashing the `Page` in a module-level variable and touching it in the next test is a bug: it belongs to a context the runner has already closed. - It does **not** forbid extra pages. It guarantees the first one; opening more inside the same context is your call. - It does **not** appear if you never ask. A test that names no fixtures gets no page at all. ## Saying it in an interview Lead with the one-liner: "`page` is a test-scoped built-in fixture, a fresh `BrowserContext` per test with one page in it, created and torn down by the runner." Then add the other half unprompted, that `browser` is shared per worker, because the pairing of one costly shared browser with a cheap per-test context is the point of the design and is usually the interviewer's next question.

  • If page is new for every test, why is the browser not?
    Launching a browser process costs far more than creating a context. Playwright pays that cost once per worker and then buys isolation cheaply, one `BrowserContext` per test. You still get full separation between tests without paying a launch each time.
  • What happens to the page fixture when the test body throws?
    The runner still tears it down. Fixture teardown runs after the body settles regardless of outcome, so a failed or thrown test does not leak its page or context into the next one. You never need a try/finally around the fixture itself.
  • Can a test open a second page, and is that page also managed for you?
    Yes, a test can open more pages in its own context, and they go away when the runner closes that context at the end of the test. Only the first page arrives ready-made as the `page` fixture.

Each test checks into a freshly made hotel room. The building stays open all day and every guest uses it, but the room is made up again before the next guest walks in.

saying these in an interview costs you the question

  • Says each test gets its own browser process
  • Thinks page is created by a beforeEach you write
  • Reuses the page object in a later test
  • Believes tests in one file share one page
  • Expects a sign-in in test one to carry into test two
open as a page

In Playwright, what does the await use(value) call do inside a test.extend fixture?

level: juniorimportance: must knowfreq 74%

basics

~20 s

It hands the fixture's value to the test and pauses the fixture there. Everything written above the call is setup; everything below it is teardown, which runs once the test finishes, whether it passed or failed.

open as a page

How do Playwright's built-in browser, context and page fixtures relate to each other in one test?

level: middleimportance: must knowfreq 70%

basics

~20 s

They nest. The browser fixture is one browser shared by every test in a worker; the context fixture is a BrowserContext built fresh for the single test; the page fixture is the one page opened inside that context.

open as a page

In Playwright, how do you override the built-in page fixture without losing the original?

level: middleimportance: must knowfreq 57%

basics

~20 s

Define a fixture with the same name in test.extend and destructure that same name in its first argument. Playwright passes the original value in, so you can configure it and then hand the very same object to use.

open as a page

In Playwright, how does a fixture declared { scope: 'worker' } differ from the default test scope?

level: middleimportance: must knowfreq 74%

basics

~20 s

A worker-scoped Playwright fixture is created once per worker process and reused by every test that worker runs, with its teardown deferred to worker shutdown. The default test scope rebuilds the value for each test.

open as a page

In Playwright, what does test.info().workerIndex tell you inside a running test?

level: juniorimportance: should knowfreq 45%

basics

~10 s

Playwright's test.info().workerIndex returns the number identifying the worker process running the test, starting at zero. Every worker process the run starts gets its own value, and a replacement worker receives a new, higher number.

open as a page

What does Playwright Test do with a fixture that a test never lists in its arguments?

level: middleimportance: should knowfreq 44%

basics

~20 s

Nothing. Playwright Test sets up fixtures on demand, so a fixture no test names is never built for that test. A test that asks only for request never causes a browser context or page to be created.

open as a page

In Playwright, in what order do dependent custom fixtures set up and tear down around a test?

level: middleimportance: should knowfreq 46%

basics

~20 s

A fixture asks for another by naming it in its first argument, and Playwright builds a dependency graph from those names. Dependencies set up first and tear down last: teardown runs in the reverse order of setup.

open as a page

In Playwright, what does declaring a fixture { auto: true } change about when it runs?

level: middleimportance: should knowfreq 50%

basics

~20 s

Normally a Playwright fixture is set up only if a test lists it in its arguments. With auto true it is set up for every test in its scope regardless, so it runs even though no test mentions it.

open as a page

A Playwright suite opens one page in test.beforeAll and reuses it across tests. What breaks?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Test independence. Every test now drives the same page in the same context, so state accumulates, results depend on run order, and cleanup becomes the author's problem instead of the runner's. The per-test guarantee of the page fixture is gone.

open as a page

In Playwright, a fixture seeds a staff account and deletes it after await use() — when does that delete never run?

level: seniorimportance: should knowfreq 36%

basics

~20 s

When the fixture throws before it reaches use, because execution never gets to the lines below. Once use has been reached, Playwright resumes the fixture after the test, so a failing or timed-out test still triggers the delete.

open as a page

Why do a Playwright suite's tests pass alone yet fail in sequence once their setup moved to a worker-scoped fixture?

level: seniorimportance: should knowfreq 47%

basics

~20 s

A Playwright worker-scoped fixture is built once and the same instance is given to every test that worker runs, so a mutation by one test survives into the next. Run alone, a test always gets a freshly built value.

open as a page

In Playwright, how do you decide what becomes a custom fixture and what stays in test.beforeEach?

level: principalimportance: should knowfreq 40%

basics

~20 s

Setup that produces something the test uses belongs in a fixture: it names what it provides, pairs its own teardown, and runs only for tests that ask for it. Local arrangement for one file can stay in a hook.

open as a page

In a growing Playwright suite, how do you decide which setup earns worker scope over test scope?

level: principalimportance: should knowfreq 34%

basics

~20 s

Promote a Playwright fixture to worker scope only when its value is read-only once built and its setup cost is measured and material. Test scope stays the default, because per-test isolation is what keeps a suite diagnosable as it grows.

open as a page

In Playwright Test, how does a test find out which browser engine it is running in?

level: juniorimportance: nice to knowfreq 36%

basics

~10 s

By destructuring the built-in browserName fixture, which is a plain string: chromium, firefox or webkit. Tests use it to branch, most often to skip a case that cannot run on one engine.

open as a page

In Playwright, how do you use two separately extended test objects in one spec file?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

Combine them with mergeTests from the Playwright test package: mergeTests(staffTest, auditTest) returns one test object carrying both fixture sets. Importing both objects side by side does not work, and mergeExpects does the same job for custom matchers.

open as a page