Why does await page.pause() in a Playwright test require a headed browser?
answer
- It is an in-code breakpoint
- Something has to press Resume
- The Inspector is a window
- The page stays live, not frozen
- Delete it before you push
basics
~20 sThe only way past page.pause() is a human pressing Resume in the Playwright Inspector, and the Inspector is a window. Headless there is nobody to resume it, so the call belongs in a local session and never in committed code.
solid answer
~50 s`page.pause()` stops the script at that exact line and hands control to the Playwright Inspector, where **Resume** continues to the end and **Step over** advances one action at a time. Playwright documents it as requiring headed mode with a falsy `headless` option - the resume control is part of the Inspector window, so a headless run has no surface on which anyone could press it. While paused the browser is live: you can click around the page yourself, use Pick locator to build or verify a selector, and read the call log for the pending action. The practical consequence is that `page.pause()` is a temporary local instrument, run with `--headed` or `--debug`, and it must never be committed. Unlike `--debug`, which pauses before the very first action, `page.pause()` lets you run at full speed up to precisely the step you care about.
code
typescript · 11 linesimport { test, expect } from '@playwright/test';
test('driver marker appears once the order is out for delivery', async ({ page }) => {
await page.goto('/orders/8412');
await expect(page.getByTestId('order-status')).toHaveText('Out for delivery');
await page.pause(); // TEMPORARY: run with --headed or --debug, remove before commit
await page.getByRole('button', { name: 'Track driver' }).click();
await expect(page.getByTestId('driver-map')).toBeVisible();
});go deeper
Remember it as a breakpoint you write into the test, that you must run headed for it to work, and that it has to come out of the code before you push.
Explain the mechanism: the pause ends when a person presses Resume in the Inspector, so a headless run has no surface to resume from. Contrast it with --debug, which pauses before the first action of every test instead.
Show judgment about placement and hygiene - pause immediately before the suspect step, keep it out of fixtures, and guard the repository with a grep or lint rule so it never reaches a pipeline.
Weigh in-code breakpoints against surfaces that need no source edit at all, and set the team default so debugging aids cannot be committed by accident in the first place.
`page.pause()` is Playwright's in-code breakpoint. Put it on a line, run the test, and execution stops there with the browser sitting in whatever state your test just produced. ## What the call does - It suspends the test at that line - no timeout is ticking against you while it is paused. - It hands control to the **Playwright Inspector**, the window with Resume, Step over, the source pane and the call log. - It leaves the page **live**. This is not a snapshot: you can click, type and scroll in the real browser, and anything you do there is really happening to the page under test. ## Why headed is not optional Playwright documents `page.pause()` as requiring the browser to be started in headed mode, with a falsy `headless` value. The reason is structural rather than arbitrary: **the thing that ends the pause is a person clicking Resume in the Inspector.** No window, no Inspector, no Resume, no way forward. A pause is a request for human attention, and headless runs have no human attached. That single fact drives all the practical rules around it: - Run the test with `--headed`, or with `--debug` (which forces headed and opens the Inspector anyway). - Treat the call as scaffolding you delete before you push, exactly like a stray `console.log` but with worse consequences. - Never put it in a fixture, a `beforeEach`, or an `afterEach` "so failures stop for inspection" - that pattern reaches CI, where nobody can inspect anything. ## What you actually do while paused 1. Look at the page. Half of all selector bugs are visible immediately: the modal never opened, the order list is empty, the driver map has not loaded. 2. Use **Pick locator** to hover an element and read the locator Playwright would generate, or type a candidate locator and see what highlights. 3. Read the call log for the pending action to see what it is resolving and waiting on. 4. Interact manually to test a hypothesis - click the button yourself and see whether the state changes at all. 5. **Step over** one action at a time, or **Resume** to run out the rest of the test. ## Where to put the call Place it *after* the setup and immediately *before* the step that misbehaves. On a food-delivery order tracker, pausing right after the order page loads but before the click on **Track driver** gives you the page in exactly the state the failing click sees. Pausing at the top of the test only shows you a blank page, and pausing after the failure shows you the wreckage rather than the cause. ## What it is not - **Not a wait.** It does not delay the test by some fixed amount and then carry on; it stops until a person acts. - **Not a freeze of the application.** The browser keeps running: timers fire, polling continues, and a live-updating driver map keeps refreshing while you look at it. Only your test script is suspended. - **Not evidence.** Once you resume, nothing about what you saw during the pause is recorded anywhere, which is why a surface that keeps snapshots is the better default. ## page.pause() versus --debug | | `page.pause()` | `--debug` | |---|---|---| | Where it stops | the exact line you wrote it on | before the first action of every test | | How it is enabled | an edit to the spec file | a command-line flag, no code change | | Getting to the interesting step | runs at full speed until the pause | you step or resume your way there | | Risk | can be committed by accident | none - it never touches the source | | Worker count | unchanged | forced to one | They combine well: run with `--debug` for the Inspector and zeroed timeouts, and let a `page.pause()` mark the spot so you are not stepping through forty actions to reach it. ## Hygiene that keeps it out of CI - Add a lint rule or a simple grep in the pre-commit hook for `page.pause(` in `tests/`. - Keep it on its own line with a trailing marker comment so it is obvious in review. - Reach for UI mode first: it gives you snapshots of every action without editing the spec at all, and there is nothing to forget to remove.
- Where in a test should page.pause() go, and why does the position matter?Immediately before the step that misbehaves, after setup has run. That gives you the page in precisely the state the failing action sees. Pausing at the top shows a blank page; pausing after the failure shows the aftermath instead of the conditions that produced it.
- What stops a stray page.pause() from reaching CI?Process, not the tool: a grep for `page.pause(` in a pre-commit hook or a lint rule, plus keeping the call on its own clearly marked line so review catches it. Preferring UI mode, which needs no code edit at all, removes the risk entirely.
saying these in an interview costs you the question
- Thinks page.pause works fine in a headless CI run
- Puts page.pause in beforeEach or a shared fixture
- Confuses it with waitForTimeout as a way to slow a test
- Believes the page is frozen and cannot be interacted with
- Expects the test timeout to keep counting during the pause