skip to content

Fixtures and Test Config

The runner's setup model: what it hands a test, what you write yourself, and the config file shaping projects, ordering, timeouts and signed-in state. Interviewers probe where setup belongs.

on this pageshow

explore

questions

page 1 of 2

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

What does Playwright's webServer config option do before the first test runs?

level: juniorimportance: must knowfreq 71%

basics

~10 s

Playwright's webServer option spawns a command before the suite and waits until the url or port it names answers, then runs the tests and stops the process it started when the run ends.

open as a page

In Playwright, how do you run one spec as an admin and another as a read-only staff user?

level: juniorimportance: must knowfreq 68%

basics

~20 s

Capture one storage-state file per role, then choose the file explicitly: a project per role carrying use.storageState in the config, or test.use with that role's file at the top of a spec or describe block.

open as a page

In a Playwright config, what does the `projects` array do?

level: juniorimportance: must knowfreq 78%

basics

~20 s

The projects array lists named run variants, each with its own use options and file filters. Playwright runs every matching spec once per project, so one test yields one result per project, and --project=<name> runs a single variant.

open as a page

In Playwright, what does context.storageState({ path }) write, and how do other tests reuse it?

level: juniorimportance: must knowfreq 76%

basics

~20 s

Playwright's context.storageState({ path }) writes a JSON file of that context's cookies and per-origin localStorage. Other tests reuse it by naming the path as storageState in the config, in test.use, or when creating a context.

open as a page

In Playwright, what is the difference between the test timeout and the expect timeout?

level: juniorimportance: must knowfreq 80%

basics

~10 s

Playwright runs two independent clocks: the test timeout caps a whole test at 30 seconds by default, including its fixtures and hooks, while the expect timeout caps a single auto-retrying assertion at 5 seconds.

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 a Playwright config, what does naming a project in another project's dependencies guarantee?

level: middleimportance: must knowfreq 62%

basics

~10 s

It guarantees ordering: every test in the named project finishes before the dependent project starts, and if any of them fails, the dependent project's tests are skipped rather than run.

open as a page

In Playwright, how does `test.use()` in a spec file interact with a project's `use` options?

level: middleimportance: must knowfreq 62%

basics

~20 s

test.use() merges over the project's use block key by key for the tests in its scope, so the file wins on the keys it names and inherits the rest. It applies in every project that runs the file.

open as a page

What is inside a Playwright storageState JSON file, and which session state does it leave out?

level: middleimportance: must knowfreq 61%

basics

~10 s

A Playwright state file has two arrays: cookies, with each cookie's attributes, and origins, pairing an origin with its localStorage name/value entries. sessionStorage, in-memory JavaScript state and IndexedDB (unless opted in) are absent.

open as a page

In Playwright, how do you give one slow test a larger budget than the configured timeout?

level: middleimportance: must knowfreq 66%

basics

~10 s

Call test.setTimeout(120_000) inside the test to replace its budget, or test.slow() to triple the configured one. Both affect only the current test, leaving the suite-wide timeout tight enough that a hang still fails fast.

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

Why does a Playwright run with --project=admin-console --no-deps fail when the same command without the flag passes?

level: middleimportance: should knowfreq 34%

basics

~20 s

Because --no-deps tells Playwright to run only the named project and skip the projects it lists in dependencies, so whatever the setup project would have prepared is missing or stale and the tests break on the precondition.

open as a page

In Playwright, how do you make an API request context send calls as the same signed-in role?

level: middleimportance: should knowfreq 36%

basics

~20 s

Seed the API context from that role's saved state file with request.newContext({ storageState }), or issue the calls through page.request, which shares cookies with the page's own context. A sign-in performed in the browser during a test never reaches a separate request context.

open as a page

In Playwright, why can't the storageState option be worker-scoped, and what do you do instead?

level: middleimportance: should knowfreq 45%

basics

~20 s

storageState is a built-in test option, and options are resolved per test so a describe block can change them, which keeps them test-scoped. Add a separate worker-scoped fixture that produces the file path, then override storageState to return it.

open as a page

In a Playwright project, how do `testDir`, `testMatch` and `testIgnore` decide which specs run?

level: middleimportance: should knowfreq 47%

basics

~20 s

Each project resolves its own file list: testDir is the root it scans, testMatch keeps files matching a glob or regex, and testIgnore drops files. Ignore beats match, and each key overrides the top-level value for that project only.

open as a page

How do you run one Playwright spec signed out when the project sets storageState for every test?

level: middleimportance: should knowfreq 44%

basics

~20 s

Override the option at file scope. Putting test.use({ storageState: { cookies: [], origins: [] } }) at the top of the spec replaces the project's saved session with an empty one for that file, leaving every other file untouched.

open as a page

In Playwright, what changes when you set use.actionTimeout instead of leaving it at its default?

level: middleimportance: should knowfreq 52%

basics

~20 s

Playwright leaves actionTimeout at 0, so a stuck click waits until the test clock expires. Setting it under use gives every action its own budget, so the failure names the call and the test keeps its remaining time.

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 teams prefer a Playwright setup project over globalSetup for suite preconditions?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Because a setup project is made of real tests, it gets fixtures, retries, a trace and a report entry, while globalSetup is a plain function the runner calls with none of those, so its failures leave only a log line.

open as a page

How do you give each parallel Playwright worker its own admin account without signing in per test?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Sign in once inside a worker-scoped fixture and key both the account and its saved-state file off test.info().parallelIndex. That index never exceeds the configured worker count, so a fixed pool of accounts covers a run of any size.

open as a page

In Playwright, why might admin.json and viewer.json end up holding the same signed-in session?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Because each capture ran in a context that was already signed in. With a state file supplied under use, the login page redirects straight to the console, the form steps do nothing, and the capture saves the inherited session under a new name.

open as a page

One project in a Playwright matrix is intermittently red while the others are green — which project-level options contain it without editing the specs?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Reproduce with --project on the failing variant, then contain it in the config: raise retries on that project only, adjust its use options, and drop the genuinely unsupported specs with that project's testIgnore. Leave the other projects untouched.

open as a page

showing 1–30 of 41