What does Playwright's expect.configure() return, and which options can you preset on it?
answer
- Returns something, does not mutate
- A second expect with its own defaults
- Presets waiting, softness, description
- Capture it in a named variable
- Per-call option still beats the preset
basics
~20 sIt returns a new expect instance carrying preset options - timeout, soft and message - and leaves the imported expect untouched. You call the returned instance exactly like expect, and its matchers inherit those defaults.
solid answer
~40 s`expect.configure({ timeout, soft, message })` builds and returns a **new** expect object; the one you imported keeps its own behaviour, so nothing leaks between call sites. Three options are presettable: `timeout` (how long the retrying matchers wait), `soft` (whether failures are collected instead of thrown), and `message` (a fixed description attached to failures from that instance). A test file checking a bank statement page might keep `const slowExpect = expect.configure({ timeout: 15_000 })` for the balance widget that waits on a rates service, and `const softExpect = expect.configure({ soft: true })` for a block of cosmetic checks. What configure does not do is add matchers - that is `expect.extend` - or mutate the global expect, and an explicit option passed on an individual matcher call is more specific and still wins.
code
typescript · 14 linesimport { test, expect } from '@playwright/test';
const slowExpect = expect.configure({ timeout: 15_000 });
const softExpect = expect.configure({ soft: true, message: 'statement widget' });
test('balance widget', async ({ page }) => {
await page.goto('/accounts/1/statement');
await slowExpect(page.getByTestId('balance')).toHaveText('$1,204.55');
await softExpect(page.getByRole('button', { name: 'Export' })).toBeEnabled();
// Plain expect is untouched: still throwing, still the default timeout.
await expect(page.getByTestId('statement')).toBeVisible();
});go deeper
Remember that expect.configure hands back a new expect you have to store in a variable and call by name; the expect you imported carries on unchanged.
Be able to list the three presettable options - timeout, softness and a fixed message - and explain why an instance is preferable to repeating the same option on ten calls.
Show judgment about scope: a configured instance is for a group of checks that share an intention, while a genuinely one-off wait belongs on the individual matcher call.
Talk about legibility. Named instances make assertion policy visible in the file rather than hidden in defaults, and that is what stops timeouts drifting upward one call at a time.
## What the call returns `expect.configure(options)` does not change anything in place. It **returns a new expect instance** whose matchers start with the options you supplied; the `expect` you imported is unaffected, and two configured instances are independent of each other. That is why the result must be captured in a variable and used by name: ```ts const slowExpect = expect.configure({ timeout: 15_000 }); await slowExpect(page.getByTestId('balance')).toHaveText('$1,204.55'); ``` Calling `expect.configure({ timeout: 15_000 })` and then continuing to use plain `expect` is the classic mistake - the plain one still has its original defaults. ## The three options | Option | Type | Effect on that instance | |---|---|---| | `timeout` | number (ms) | How long each retrying matcher polls before it gives up | | `soft` | boolean | Failures are recorded on the test instead of thrown | | `message` | string | A fixed description carried by failures from this instance | They combine in one call - `expect.configure({ soft: true, message: 'statement widgets' })` yields an instance that both collects failures and labels them. ## Why a separate instance rather than a flag Because assertion defaults are a property of *a group of checks*, not of a single call or of the whole world: - A **named instance** documents intent at the call site: `slowExpect(...)` says the wait is deliberate. - It is **local**: nothing you configure in one file changes another file's behaviour. - It **repeats without duplication**: ten checks against a slow widget do not each carry `{ timeout: 15_000 }`. - It **composes with softness**: a soft-by-default instance keeps a block of cosmetic checks readable without `expect.soft` on every line. ## Precedence you should expect 1. An option passed directly on the matcher call is the most specific and wins. 2. Otherwise the configured instance's preset applies. 3. Otherwise the runner's own default applies - 5 seconds for the expect timeout unless configured otherwise. So `slowExpect(locator).toHaveText('$1,204.55', { timeout: 1_000 })` waits one second, not fifteen: the explicit option on the call beats the preset it was called through. ## What configure is not - **Not a matcher factory.** It cannot add `toHaveAmount` to your expect; that is `expect.extend`. - **Not a mutation.** The imported `expect` keeps throwing and keeps its own timeout. - **Not a substitute for a one-off option.** If exactly one assertion needs longer, pass `{ timeout }` on that call rather than manufacturing an instance. - **Not a way to make failures optional.** A soft-configured instance still fails the test; it only collects rather than throws. ## Using it on a statement page A file that exercises a bank statement typically wants two flavours. The balance widget arrives late because it waits on a currency service, so a `slowExpect` with a generous timeout keeps that one check honest without inflating the whole file's budget. The cosmetic details - the export button's label, the column headings, the row count - are independent observations, so a `softExpect` reports all of them in one run. The plain `expect` stays for the anchors: the page rendered, the table exists, the click succeeded. Three names, three intentions, and a reader can tell which is which without reading options.
- If a configured instance sets timeout 15000 and the matcher call passes timeout 1000, which wins?The option on the matcher call - one second. The preset is a default for checks made through that instance, and the more specific value supplied at the call site overrides it.
- When would you prefer expect.configure({ soft: true }) over writing expect.soft on each line?When a whole block is soft by intent - a run of cosmetic or independent observations. A named instance states that once and keeps the block readable, while scattered expect.soft calls invite someone to add a hard check in the middle by accident.
saying these in an interview costs you the question
- Thinking expect.configure mutates the imported expect
- Expecting configure to add new matchers to expect
- Calling configure and then still using plain expect
- Assuming a preset timeout beats one passed on the call
- Reaching for configure when one assertion needs longer