Resource library

QA How-To

How to Use Playwright expect toHaveValue (2026)

Learn playwright expect toHaveValue with runnable TypeScript, async form examples, regex matching, timeout guidance, debugging, and interview answers for 2026.

21 min read | 2,724 words

TL;DR

Use `await expect(locator).toHaveValue(expected)` to verify the current value of a single form control. The matcher retries until a string or regular expression matches, making it suitable for asynchronous UI updates.

Key Takeaways

  • Use await expect(locator).toHaveValue() for a retrying assertion against one control's live value.
  • Use exact strings for deterministic data and anchored regular expressions only for intentional variation.
  • Distinguish the current value property from the initial HTML value attribute.
  • Use toHaveValues for multiple selects and toBeChecked for checkboxes or radios.
  • Prefer a web-first value assertion over comparing one inputValue snapshot for asynchronous state.
  • Diagnose locator, action, actual value, and application mechanism before increasing a timeout.

Playwright expect(locator).toHaveValue() is the web-first assertion for verifying the current value of a single form control. A correct playwright expect toHaveValue check retries against live page state until an input, textarea, or select has the expected string or matches a regular expression.

That retry behavior is why the matcher is usually better than reading inputValue() and comparing one snapshot. This guide explains the contract, property-versus-attribute behavior, asynchronous updates, control types, timeout design, page objects, and failure diagnosis with runnable TypeScript.

TL;DR

import { test, expect } from '@playwright/test';

test('shows the saved email', async ({ page }) => {
  await page.goto('/profile');
  const email = page.getByLabel('Email');
  await email.fill('qa@example.com');
  await expect(email).toHaveValue('qa@example.com');
});
Need Preferred API Reason
Assert one current form value toHaveValue() Retries the locator and live value
Assert a multiple select toHaveValues() Compares the selected value list
Read a value for computation inputValue() Returns a string once
Check markup's value attribute toHaveAttribute() Tests the attribute, not necessarily live state
Check checkbox selection toBeChecked() Models boolean checked state

Always await the assertion. Use a string for deterministic output and an anchored regex for intentionally variable formats.

1. What playwright expect toHaveValue verifies

toHaveValue is a locator assertion exported through Playwright Test's expect. Its target is a Locator, its expected argument is a string or JavaScript RegExp, and its optional settings include an assertion timeout. Because it returns Promise<void>, omitting await is a correctness bug.

The matcher reads a control's current value. That value may change through typing, JavaScript assignment, selecting an option, validation, or a framework re-render. It is the state a form would submit now, not necessarily the literal value attribute present in initial HTML.

await expect(page.getByLabel('Search')).toHaveValue('playwright');
await expect(page.getByLabel('Order code')).toHaveValue(/^QA-[0-9]{4}$/);
await expect(page.getByLabel('Search')).not.toHaveValue('selenium');

The assertion is web-first. Playwright repeatedly resolves the locator and tests its current value until the expectation succeeds or times out. A locator is a query, so a React, Vue, or other component re-render that replaces the node can still be handled. An element handle captured earlier would not provide the same resilient model.

Negative assertions also retry, but they should not replace a positive final-state check. not.toHaveValue('draft') can succeed for loading, error, or an empty string. If the required outcome is published, assert published.

Use the matcher only when value state is the contract. A checkbox needs toBeChecked(), visible non-form text needs toHaveText(), and a multiple select needs the plural toHaveValues().

toHaveValue() is available through Playwright Test's bundled assertion system, so import both test and expect from @playwright/test or from a project module that re-exports them. A generic assertion library may use similar method names but will not provide Playwright's locator polling. Consistent imports prevent subtle differences between files.

2. Set up a runnable value assertion test

Install Playwright Test and the required browser binaries:

npm init playwright@latest
npx playwright install

Save this self-contained test as tests/value.spec.ts. It uses page.setContent(), so no application server is required.

import { test, expect } from '@playwright/test';

test('fills and verifies an account form', async ({ page }) => {
  await page.setContent(`
    <form>
      <label for="name">Display name</label>
      <input id="name" value="Guest">
      <label for="bio">Bio</label>
      <textarea id="bio"></textarea>
    </form>
  `);

  const name = page.getByLabel('Display name');
  const bio = page.getByLabel('Bio');

  await expect(name).toHaveValue('Guest');
  await name.fill('Avery QA');
  await bio.fill('Builds reliable browser tests.');

  await expect(name).toHaveValue('Avery QA');
  await expect(bio).toHaveValue('Builds reliable browser tests.');
});

Run it with npx playwright test tests/value.spec.ts. getByLabel() gives each control a user-facing identity. The action and assertion remain separate, so reports distinguish inability to fill from failure to preserve the value.

In an application suite, replace setContent() with page.goto() and retain the locator and assertion pattern. A configured baseURL makes relative navigation convenient, but it does not change matcher behavior.

Keep the expected string close to scenario data. If setup creates a candidate named Avery QA, pass that same explicit fixture value into the interaction and expectation. Avoid unrelated globals that make failures difficult to read.

The test is intentionally small, but it still follows Arrange, Act, Assert. HTML and locators establish the condition, fill() performs the behavior, and toHaveValue() verifies the postcondition. Larger tests should preserve that readable sequence around each important state transition instead of placing every assertion at the end of a long workflow.

3. Distinguish value property from value attribute

The live DOM property and markup attribute are different layers. Initial HTML can set value="Boston". When a user types Seattle, the current property becomes Seattle, while the original attribute can remain Boston.

import { test, expect } from '@playwright/test';

test('separates current state from initial markup', async ({ page }) => {
  await page.setContent(`
    <label for="city">City</label>
    <input id="city" value="Boston">
  `);

  const city = page.getByLabel('City');
  await city.fill('Seattle');

  await expect(city).toHaveValue('Seattle');
  await expect(city).toHaveAttribute('value', 'Boston');
});

Both expectations are correct. toHaveValue() answers what the control holds now. toHaveAttribute('value', ...) answers what the element's attribute contains. Use the attribute assertion for a server-rendering or serialization contract, not as a substitute after user input.

toHaveText() is different too. An input's displayed value does not live in a text node. A read-only summary in a <p> or <output> should use a text assertion, while an editable input uses a value assertion.

This distinction explains a common debugging trap: page source shows the old attribute, but the UI visibly shows new text. Inspect the current property with the locator assertion or inputValue(). Do not change a valid expected value to match stale markup.

Framework-controlled inputs can write the property repeatedly. If a reducer, validation rule, or stale response restores an older value, the timeout is valuable evidence of a product or test-state problem.

4. Match exact strings and controlled patterns

A string requires an exact current value and is the clearest option for known data. A regular expression is useful when part of a result is generated but its format is stable.

import { test, expect } from '@playwright/test';

test('checks exact and generated values', async ({ page }) => {
  await page.setContent(`
    <label for="slug">Slug</label>
    <input id="slug" value="release-checklist-2026">
    <label for="ticket">Ticket</label>
    <input id="ticket" value="QA-4821">
  `);

  await expect(page.getByLabel('Slug'))
    .toHaveValue('release-checklist-2026');
  await expect(page.getByLabel('Ticket'))
    .toHaveValue(/^QA-[0-9]{4}$/);
});

Anchor a pattern with ^ and $ when the whole value matters. /QA-/ would accept INVALID-QA-4821-SUFFIX, which weakens the test. Use explicit allowed characters and lengths rather than broad wildcards.

Case sensitivity follows JavaScript. String matching is case-sensitive. A regex can use the i flag when the product requirement ignores case. Do not add it merely to silence a mismatch.

Whitespace is part of a control value. toHaveValue('Alice') should fail for Alice . This is useful when testing trim rules. If spaces are valid, include them explicitly. Unlike some text locators, a value assertion should not make form data look cleaner than it is.

If a generated identifier must correspond to a known date, tenant, or request, control those inputs and prefer an exact expectation. Regex is not a replacement for deterministic setup.

Treat masked fields carefully. A visual mask may store a formatted value such as (415) 555-0100, an unformatted value such as 4155550100, or both in different controls. Assert the value owned by the field under test, then separately assert any submitted API payload when that integration matters. Do not guess which representation the widget uses.

5. Wait for asynchronous value changes without sleeps

Applications often derive one control from another after a debounce, network response, or controlled-state update. toHaveValue() waits for the observable result, so the test does not need the implementation delay.

import { test, expect } from '@playwright/test';

test('waits for an asynchronously generated slug', async ({ page }) => {
  await page.setContent(`
    <label for="title">Article title</label>
    <input id="title">
    <label for="slug">Generated slug</label>
    <input id="slug" readonly>
    <script>
      const title = document.querySelector('#title');
      const slug = document.querySelector('#slug');
      title.addEventListener('input', () => {
        window.setTimeout(() => {
          slug.value = title.value
            .toLowerCase()
            .replaceAll(/[^a-z0-9]+/g, '-')
            .replaceAll(/(^-|-$)/g, '');
        }, 150);
      });
    </script>
  `);

  await page.getByLabel('Article title').fill('Playwright Value Checks');
  await expect(page.getByLabel('Generated slug'))
    .toHaveValue('playwright-value-checks');
});

There is no waitForTimeout(150). A fixed sleep duplicates internal timing, still races on a slow worker, and waits the full duration on fast runs. The assertion can finish as soon as the required state appears.

For network-backed changes, coordinate a specific request only when that boundary matters, then still assert the UI state. A successful response does not prove the component applied its payload. The Playwright waiting and stability guide expands this state-based approach.

If the value never arrives, investigate the triggering event, validation, stale state, rejected requests, or wrong locator. Increasing a timeout cannot make an impossible state correct.

6. Use playwright expect toHaveValue across control types

The matcher works for controls with one current value, including textareas, number and date inputs, and single selects. Browser values are strings even when a control represents numeric or temporal data.

import { test, expect } from '@playwright/test';

test('verifies several form-control values', async ({ page }) => {
  await page.setContent(`
    <label for="quantity">Quantity</label>
    <input id="quantity" type="number" value="2">
    <label for="delivery">Delivery date</label>
    <input id="delivery" type="date" value="2026-07-13">
    <label for="notes">Notes</label>
    <textarea id="notes">Leave at reception</textarea>
    <label for="priority">Priority</label>
    <select id="priority">
      <option value="normal">Normal</option>
      <option value="urgent">Urgent</option>
    </select>
  `);

  await expect(page.getByLabel('Quantity')).toHaveValue('2');
  await expect(page.getByLabel('Delivery date')).toHaveValue('2026-07-13');
  await expect(page.getByLabel('Notes')).toHaveValue('Leave at reception');

  const priority = page.getByLabel('Priority');
  await priority.selectOption('urgent');
  await expect(priority).toHaveValue('urgent');
});

Pass '2', not 2. The matcher signature reflects the DOM's textual value. Convert with Number() only when a later assertion needs numeric semantics.

For <select multiple>, use toHaveValues(['api', 'ui']). A multi-select represents a collection. For a checkbox or radio, use toBeChecked() because the element's value attribute is a submission token that exists even while unchecked.

Contenteditable regions accept text input but are not ordinary value controls. Assert their text or a product-specific state. Hidden inputs can be checked with toHaveValue() when their state is genuinely part of a technical integration contract, but end-to-end scenarios should emphasize visible user outcomes.

Specialized inputs normalize values according to browser rules. Date values use YYYY-MM-DD. An invalid programmatic value can become an empty string, so verify format before assuming Playwright lost data.

File inputs are another special case. Their value is security-constrained and platform-formatted, so verify selected files through Playwright's file APIs and application UI rather than hard-coding a local path into toHaveValue(). The general rule is to choose the assertion that models the control's user-relevant state, not merely an available DOM property.

7. Choose toHaveValue versus inputValue and related APIs

Choose an API based on the question the test asks. Assertions, extraction, and markup inspection are separate jobs.

Question API Retries expectation? Typical use
Does one control reach this value? expect(locator).toHaveValue() Yes Final UI state
Which options are selected? expect(locator).toHaveValues() Yes Multiple select
What string should code calculate with? locator.inputValue() No generic comparison retry Extraction
Did markup include this attribute? expect(locator).toHaveAttribute() Yes HTML contract
Is a checkbox selected? expect(locator).toBeChecked() Yes Boolean state
Does a summary show this text? expect(locator).toHaveText() Yes Text content

This read-then-compare pattern observes one snapshot:

expect(await page.getByLabel('Status').inputValue()).toBe('READY');

Prefer a locator assertion for eventual UI state:

await expect(page.getByLabel('Status')).toHaveValue('READY');

inputValue() is still correct when you need data for a calculation, a request, or targeted diagnostics. If the field is asynchronous, first establish a stable expected state or use a suitable retrying construct.

Do not call toHaveValue() on the returned string. It belongs to locator assertions. The received value must be a Locator fix explains this common boundary.

8. Configure playwright expect toHaveValue timeouts responsibly

toHaveValue accepts { timeout: milliseconds } and otherwise uses the configured Playwright expect timeout. Set a realistic suite default, then override only a measured slow contract.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  expect: {
    timeout: 7_000,
  },
});
await expect(page.getByLabel('Import status'))
  .toHaveValue('complete', { timeout: 20_000 });

The local override says this import can legitimately take longer. It should not be copied to every field. If many assertions need it, investigate environment capacity, workflow design, or a better completion signal.

Test, action, navigation, and expect timeouts have different ownership. An expect timeout controls polling of this assertion. It does not retroactively extend a failed click or navigation, and the overall test timeout can still end the test.

Before increasing time, confirm that the locator resolves uniquely and the exact expected value can occur. Inspect the actual string for capitalization, separators, normalization, and whitespace. A 30-second wait for the wrong locator is still wrong.

The Playwright timeout diagnosis guide provides a structured method for determining which budget expired and why.

9. Encapsulate value checks in maintainable page objects

Page objects should give domain names to locators and workflows without hiding useful failure detail. Store Locator objects rather than element handles so re-renders remain safe.

import { expect, type Locator, type Page } from '@playwright/test';

export class ProfilePage {
  readonly displayName: Locator;
  readonly email: Locator;

  constructor(private readonly page: Page) {
    this.displayName = page.getByLabel('Display name');
    this.email = page.getByLabel('Email');
  }

  async goto(): Promise<void> {
    await this.page.goto('/profile');
  }

  async updateName(name: string): Promise<void> {
    await this.displayName.fill(name);
    await this.page.getByRole('button', { name: 'Save profile' }).click();
    await expect(this.displayName).toHaveValue(name);
  }
}

Including the postcondition inside updateName() is reasonable if every caller needs proof that the workflow completed. Alternatively, expose the locator and let the test make a scenario-specific assertion. Be consistent about ownership.

Avoid a verifyProfile() helper that combines a dozen unrelated fields. One optional field can then obscure the scenario's real failure. Prefer focused methods such as expectSavedName(name) and data objects whose expected values are explicit.

Do not swallow Playwright assertion errors or replace them with a generic Profile invalid exception. Matcher output includes expected and received information that speeds diagnosis.

For fixture injection and lifecycle, use typed Playwright fixture patterns. Keep page objects normal classes and let fixtures manage creation.

10. Diagnose value assertion failures systematically

Classify the failure before editing code. A strictness error means multiple controls matched. An actionability failure may occur before the value check. A timeout with the wrong actual value means the matcher ran but the product never reached the expected state.

Answer four questions:

  1. Does the locator resolve to exactly one intended control?
  2. Did the action that should change the value complete?
  3. What value was actually present and how did it change over time?
  4. Which application mechanism prevented the expected final state?

Use a trace from a focused reproduction. Inspect DOM snapshots, action history, network, and console output. Temporary evidence can help:

const field = page.getByLabel('Customer code');
console.log({
  matches: await field.count(),
  value: await field.inputValue(),
});
await expect(field).toHaveValue('CUST-1042');

Remove logging after repair and never expose secrets or customer data. Normal matcher output often already contains enough information.

Check whether the application trims, masks, localizes, or reverts the input. Decide from requirements whether the transformed string is correct. Do not weaken the expectation until the contract is clear.

Some custom widgets respond to specific keyboard events rather than ordinary input behavior. Prefer fixing inaccessible or nonstandard widget behavior. Use a specialized interaction only when that is genuinely how supported users operate the control.

11. Adopt value assertions with a review checklist

When reviewing an existing form test, identify every read-then-compare sequence and ask whether it represents eventual UI state. Replace it with toHaveValue() when retrying the locator is the intended behavior. Keep inputValue() where the returned data feeds a calculation or another action.

For each value assertion, confirm the locator represents the user's label, the expected type is a string or regex, the transformation event is triggered realistically, and the expected result comes from the requirement. Check special controls separately: multi-select, checkbox, radio, file, and contenteditable state each have better-specific APIs.

Finally, review failure cost. An assertion should identify one meaningful postcondition, use the suite's normal timeout, and retain Playwright's diagnostic output. If a helper hides expected data, catches the error, or waits with a fixed delay, simplify it. A consistent review checklist turns toHaveValue() from isolated syntax into a maintainable suite convention.

Interview Questions and Answers

Q: Why is toHaveValue() preferred over expect(await locator.inputValue()).toBe()?

toHaveValue() is a web-first locator assertion that retries current DOM state. inputValue() returns one observed string, and generic toBe() compares it once. The locator matcher expresses an eventual UI condition directly and usually gives stronger diagnostics.

Q: Does toHaveValue() inspect the HTML value attribute?

It checks the control's current value. User typing can update the property while the initial HTML attribute remains unchanged. Use toHaveAttribute() only when the markup attribute itself is the requirement.

Q: Can the expected value be a regular expression?

Yes. Supply a JavaScript RegExp for intentionally dynamic formats. Anchor it when the whole value must conform, and prefer an exact string when the value is deterministic.

Q: Which matcher should verify a multiple select?

Use toHaveValues() with an array of expected selected values. toHaveValue() models one current value and is appropriate for a single select.

Q: How do you assert a number input containing twelve?

Use await expect(locator).toHaveValue('12'). DOM input values are strings even when the input type is number. Test numeric calculations separately if needed.

Q: How do you debug a timed-out value assertion?

Confirm locator uniqueness, inspect the triggering action, read the actual value, and use the trace to find validation, re-render, or network behavior. Establish that the expected state is possible before changing timeout settings.

Q: When should you use inputValue()?

Use it when the test needs to extract a string for later calculation, logging, or a request rather than assert an eventual state. Avoid sensitive logging, and establish stable UI state first if timing matters.

Common Mistakes

  • Forgetting await on the asynchronous assertion.
  • Calling the locator matcher on a string returned by inputValue().
  • Using toHaveAttribute('value', ...) after typing and checking stale markup.
  • Using toHaveText() for an input's displayed value.
  • Passing a number rather than the string held by a number input.
  • Using toHaveValue() for checkbox state rather than toBeChecked().
  • Applying the singular matcher to a multiple select.
  • Writing an unanchored regex that accepts invalid prefixes or suffixes.
  • Adding fixed sleeps before a retrying assertion.
  • Increasing timeouts without checking actual state and locator strictness.
  • Hiding unrelated checks inside one vague page-object method.
  • Logging passwords or personal form values during CI diagnosis.

Conclusion

Playwright expect(locator).toHaveValue() is the right default for asserting the live value of one form control. Locate the control through a stable user-facing identity, trigger the real workflow, and await an exact string or intentionally constrained pattern. Playwright will retry normal asynchronous updates and report a meaningful failure when the result never arrives.

Replace one read-then-compare assertion in your suite with the direct locator matcher. Then review attributes, multi-selects, checkboxes, timeouts, and helpers so every check represents the same state a user would submit or observe.

Interview Questions and Answers

Why is toHaveValue preferred over expect(await locator.inputValue()).toBe()?

`toHaveValue()` retries the Locator against current DOM state until the expectation succeeds or times out. `inputValue()` returns one snapshot and generic `toBe()` compares it once. The locator assertion better represents eventual UI state and usually provides more useful failure output.

Does toHaveValue inspect the HTML value attribute?

No. It checks the current value property of the form control. User input can change that property while the original attribute remains unchanged, so `toHaveValue()` and `toHaveAttribute("value", ...)` can correctly expect different strings.

When should you use a regular expression with toHaveValue?

Use it when variation is intentional but the format is constrained, such as a generated identifier. Anchor and narrow the pattern to reject invalid values. Prefer an exact string whenever setup can make the result deterministic.

What assertion should verify a multi-select?

Use `toHaveValues()` and provide an array of expected strings or regular expressions. A multi-select represents a selected collection, so a singular value matcher does not model the state completely.

How do you assert a numeric input value?

Pass a string such as `"25"` because DOM input values are strings. If arithmetic behavior is the requirement, establish the stable UI value first, convert explicitly, and perform a separate numeric assertion.

How would you debug a toHaveValue timeout?

Confirm the locator resolves uniquely, verify the action that should change the field, inspect the actual value, and review trace, network, and console evidence. Determine whether formatting, validation, stale state, or a failed request blocks the outcome before increasing timeouts.

When is inputValue still the right API?

Use `inputValue()` when the test must extract a string for calculation, logging, or another request rather than assert eventual state. Avoid sensitive logging and establish stable state first when timing matters.

Frequently Asked Questions

What does Playwright toHaveValue check?

It checks the current value of the control resolved by a Locator. It does not simply compare the initial HTML value attribute, so it reflects typing, selection, framework updates, and JavaScript changes.

Does Playwright toHaveValue wait for the value to change?

Yes. It is a web-first locator assertion that retries until the expected string or regular expression matches or the assertion timeout expires. This usually removes the need for fixed sleeps.

Can toHaveValue use a regular expression?

Yes. Pass a JavaScript RegExp, such as `/^QA-[0-9]{4}$/`. Anchor it when the entire value must match so extra prefixes or suffixes do not pass.

What is the difference between inputValue and toHaveValue?

`inputValue()` returns one string for extraction or calculation. `toHaveValue()` is an assertion that repeatedly checks live locator state and reports expected versus received value when it fails.

How do I assert a number input in Playwright?

Assert its browser value as a string, for example `toHaveValue("12")`. Convert the value to a number only when a separate business assertion needs numeric semantics.

Which matcher should I use for a multiple select?

Use `toHaveValues()` with an array of expected selected values. The singular matcher models one current value and is appropriate for inputs, textareas, and single selects.

Why does toHaveValue time out even though the field is visible?

Visibility proves only that the element can be seen. The locator may target the wrong control, validation may revert the value, a request may fail, or formatting may differ. Inspect the trace and actual value before changing timeouts.

Related Guides