QA How-To
Playwright expect toHaveValue: Examples and Best Practices
Use playwright expect toHaveValue examples for text, dates, selects, async updates, normalization, persistence, regex matching, debugging, and best practices.
22 min read | 2,476 words
TL;DR
The strongest Playwright value examples locate a control through a user-facing label, trigger a real workflow, and await an exact final value or tightly constrained regex. Use control-specific matchers for multi-selects and checked state.
Key Takeaways
- Assert exact expected strings when form data is controlled and deterministic.
- Let toHaveValue retry debounced and calculated fields instead of sleeping for implementation delays.
- Use browser-normalized strings for number, date, time, color, and select values.
- Keep expected normalization outcomes explicit rather than reusing the production algorithm.
- Prove persistence by asserting after a reload or fresh session when durable state is the claim.
- Pair negative checks with a positive required destination or recovery state.
The best playwright expect toHaveValue examples verify user-visible form state after a meaningful action, and they rely on Playwright's retrying locator assertion instead of fixed delays. The core syntax is await expect(locator).toHaveValue(expected), where expected is a string or regular expression.
This example-driven guide moves beyond a single text field. You will test trimming, delayed calculations, masked values, dates, selects, data-driven forms, server normalization, and failure diagnostics using current Playwright Test APIs. Every complete test uses page.setContent(), so it can run without a demo site.
TL;DR
const email = page.getByLabel('Work email');
await email.fill('sdet@example.com');
await expect(email).toHaveValue('sdet@example.com');
| Scenario | Expected value | Matcher pattern |
|---|---|---|
| Deterministic input | Exact string | toHaveValue('QA Lead') |
| Generated identifier | Anchored regex | toHaveValue(/^JOB-[0-9]{6}$/) |
| Delayed controlled field | Exact final string | Let the assertion retry |
| Single select | Option value | toHaveValue('senior') |
| Multiple select | Array of values | toHaveValues(['api', 'ui']) |
| Checkbox or radio | Checked state | toBeChecked() |
Good examples assert the product contract, not the implementation timing. Keep locators user-facing and regular expressions narrow.
1. playwright expect toHaveValue examples for basic fields
Start with a form that covers text, email, password, and textarea controls. The value matcher treats each current DOM value as a string.
import { test, expect } from '@playwright/test';
test('verifies a completed candidate profile', async ({ page }) => {
await page.setContent(`
<label for="name">Full name</label>
<input id="name">
<label for="email">Work email</label>
<input id="email" type="email">
<label for="password">Temporary password</label>
<input id="password" type="password">
<label for="summary">Professional summary</label>
<textarea id="summary"></textarea>
`);
await page.getByLabel('Full name').fill('Morgan Lee');
await page.getByLabel('Work email').fill('morgan@example.com');
await page.getByLabel('Temporary password').fill('Safe-Example-42');
await page.getByLabel('Professional summary')
.fill('Automation engineer focused on reliable feedback.');
await expect(page.getByLabel('Full name')).toHaveValue('Morgan Lee');
await expect(page.getByLabel('Work email'))
.toHaveValue('morgan@example.com');
await expect(page.getByLabel('Temporary password'))
.toHaveValue('Safe-Example-42');
await expect(page.getByLabel('Professional summary'))
.toHaveValue('Automation engineer focused on reliable feedback.');
});
A password field's text is visually obscured, but its browser value still exists and can be asserted. Avoid exposing real credentials in test source, traces, screenshots, or error output. The example uses fake data.
Notice that each field has a label. getByLabel() expresses the way a user identifies a form control and usually survives CSS refactors. The assertion is kept next to the scenario data so the failure says exactly which value was expected.
For empty state, use toHaveValue('') when you specifically need the control's value to be an empty string. toBeEmpty() can also express emptiness for editable elements, but toHaveValue('') keeps the assertion vocabulary consistent when a test later expects a populated value.
Read-only does not mean value-less. A calculated or server-populated input may be noneditable but still have a meaningful current value. Assert it with toHaveValue() and separately check toBeEditable({ editable: false }) only when the inability to edit is also a requirement. One assertion should not be assumed to prove both facts.
2. Assert trimming, casing, and normalization
Many forms normalize input on blur or submit. Test the transformation as a product requirement. Do not calculate the expected result by calling the same production helper from the test, because a shared bug can make both sides agree.
import { test, expect } from '@playwright/test';
test('normalizes a username on blur', async ({ page }) => {
await page.setContent(`
<label for="username">Username</label>
<input id="username">
<button>Continue</button>
<script>
const input = document.querySelector('#username');
input.addEventListener('blur', () => {
input.value = input.value.trim().toLowerCase();
});
</script>
`);
const username = page.getByLabel('Username');
await username.fill(' Quality.Engineer ');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(username).toHaveValue('quality.engineer');
});
The click moves focus, which triggers the behavior the user would trigger. Calling evaluate() to invoke an internal normalization function would skip the interaction contract.
Whitespace should be intentional. Unlike text locators, a form value is not generally whitespace-normalized for assertion convenience. If the product must trim spaces, assert the trimmed output. If leading spaces are valid data, keep them in the expected string.
Email domains, postal codes, credit-card presentation, and telephone values may have different normalization rules. Write one focused example for each rule and include boundary cases. Exact strings give the strongest signal. Use a regular expression only when part of the value is legitimately variable.
Negative assertions can support a security or sanitization scenario, such as not.toHaveValue('<script>'), but they do not establish the desired safe output. Prefer asserting the expected encoded, removed, or rejected state and the associated validation message.
Normalization may occur on input, change, blur, or submit. Trigger the same event path a supported user triggers. A test that directly assigns element.value through evaluate() can bypass framework handlers and prove only a DOM mutation. Use fill(), keyboard actions, selection, and ordinary focus transitions unless the low-level assignment itself is under test.
3. Wait for debounced and calculated values
Debounced search fields and calculated totals are ideal uses of web-first assertions. The test should wait for the observable value, not copy the component's delay.
import { test, expect } from '@playwright/test';
test('waits for a delayed total calculation', async ({ page }) => {
await page.setContent(`
<label for="quantity">Quantity</label>
<input id="quantity" type="number" value="1">
<label for="unit-price">Unit price</label>
<input id="unit-price" value="19.50">
<label for="total">Total</label>
<input id="total" readonly value="19.50">
<script>
const quantity = document.querySelector('#quantity');
const price = document.querySelector('#unit-price');
const total = document.querySelector('#total');
let timer;
const calculate = () => {
clearTimeout(timer);
timer = setTimeout(() => {
total.value = (Number(quantity.value) * Number(price.value)).toFixed(2);
}, 200);
};
quantity.addEventListener('input', calculate);
price.addEventListener('input', calculate);
</script>
`);
await page.getByLabel('Quantity').fill('3');
await expect(page.getByLabel('Total')).toHaveValue('58.50');
});
The assertion polls until the value becomes 58.50. A waitForTimeout(200) would duplicate implementation knowledge and could still race under load. It would also force every successful run to wait the full duration.
For a network-backed calculation, wait on the UI result unless the network response is itself a distinct test requirement. If you must coordinate a click with a request, start page.waitForResponse() before the action, then assert the resulting value. Avoid using a response wait as a substitute for checking that the UI processed the response.
The Playwright clock API examples are useful when timer behavior itself is under test. For normal end-to-end flows, allowing toHaveValue to retry is simpler and closer to the user outcome.
4. Use exact and regex value examples correctly
A deterministic result deserves an exact string. A generated result can use a regular expression that encodes all stable requirements.
import { test, expect } from '@playwright/test';
test('validates a generated application reference', async ({ page }) => {
await page.setContent(`
<label for="reference">Application reference</label>
<input id="reference" readonly value="JOB-260713-4821">
`);
const reference = page.getByLabel('Application reference');
await expect(reference).toHaveValue(/^JOB-[0-9]{6}-[0-9]{4}$/);
});
The anchors prevent extra characters. The numeric widths protect the format without assuming the actual generated suffix. If the date segment is required to equal the current business date, inject or control the clock and assert the exact expected reference instead of accepting any six digits.
| Requirement | Weak expression | Better expression | Reason |
|---|---|---|---|
| Four-digit QA ticket | /QA-/ |
/^QA-[0-9]{4}$/ |
Rejects extra text and wrong length |
| Lowercase slug | /release/i |
/^[a-z0-9]+(?:-[a-z0-9]+)*$/ |
Enforces allowed structure |
| Two decimal currency | /[0-9.]+/ |
/^[0-9]+[.][0-9]{2}$/ |
Rejects malformed decimals |
| Optional country prefix | /555/ |
/^(?:[+]1 )?555-[0-9]{4}$/ |
Encodes the allowed alternatives |
Regex flags are ordinary JavaScript flags. Use i only if case is irrelevant by requirement. Avoid .* around a known format because it often turns a meaningful check into a substring check.
5. Cover number, date, time, and color inputs
Specialized HTML inputs still expose string values. Browser-normalized formats matter: date values use YYYY-MM-DD, time values use a time string, and color values use a color value such as #1a2b3c.
import { test, expect } from '@playwright/test';
test('checks normalized specialized input values', async ({ page }) => {
await page.setContent(`
<label for="score">Score</label>
<input id="score" type="number" value="85">
<label for="start">Start date</label>
<input id="start" type="date" value="2026-07-13">
<label for="meeting">Meeting time</label>
<input id="meeting" type="time" value="14:30">
<label for="theme">Theme color</label>
<input id="theme" type="color" value="#1a2b3c">
`);
await expect(page.getByLabel('Score')).toHaveValue('85');
await expect(page.getByLabel('Start date')).toHaveValue('2026-07-13');
await expect(page.getByLabel('Meeting time')).toHaveValue('14:30');
await expect(page.getByLabel('Theme color')).toHaveValue('#1a2b3c');
});
Do not pass 85 as a number. The matcher signature expects a string or regex because the DOM value is textual. If you need to verify numeric semantics, read the string after establishing stable UI state, convert it explicitly, and use a generic numeric assertion.
Locale can affect how an application presents a date in visible text, but the native date input value remains normalized. Test both layers when both are requirements: toHaveValue('2026-07-13') for the form state and toHaveText('Jul 13, 2026') for a US-formatted summary.
Invalid programmatic assignments to specialized controls can produce an empty value due to browser rules. If a test unexpectedly receives '', inspect whether the string conforms to the input type before blaming Playwright.
6. Test select values and avoid checkbox confusion
A single select has one current value and works naturally with toHaveValue. A multiple select needs toHaveValues. Checkboxes and radio buttons need checked-state assertions.
import { test, expect } from '@playwright/test';
test('uses the assertion that matches each control', async ({ page }) => {
await page.setContent(`
<label for="level">Experience level</label>
<select id="level">
<option value="junior">Junior</option>
<option value="senior">Senior</option>
</select>
<label for="skills">Skills</label>
<select id="skills" multiple>
<option value="api">API testing</option>
<option value="ui">UI automation</option>
<option value="perf">Performance</option>
</select>
<label><input type="checkbox" value="alerts"> Email alerts</label>
`);
const level = page.getByLabel('Experience level');
await level.selectOption('senior');
await expect(level).toHaveValue('senior');
const skills = page.getByLabel('Skills');
await skills.selectOption(['api', 'ui']);
await expect(skills).toHaveValues(['api', 'ui']);
const alerts = page.getByRole('checkbox', { name: 'Email alerts' });
await alerts.check();
await expect(alerts).toBeChecked();
});
Asserting a checkbox's value would tell you its submission token, not whether the user selected it. That distinction is a frequent interview question and a practical source of false tests.
For select controls, distinguish the option's visible label from its submitted value. selectOption({ label: 'Senior' }) can choose by label, while toHaveValue('senior') checks the underlying current value. If visible wording is also critical, assert the selected option's accessible or text representation through a suitable locator.
7. Create data-driven playwright expect toHaveValue examples
Data-driven tests are useful when the interaction and assertion are the same but input classes differ. Keep each case independently named so the report identifies the failed boundary.
import { test, expect } from '@playwright/test';
const cases = [
{ name: 'plain text', entered: 'QA Engineer', expected: 'QA Engineer' },
{ name: 'outer spaces', entered: ' SDET ', expected: 'SDET' },
{ name: 'mixed case', entered: 'PlayWright', expected: 'playwright' },
];
for (const item of cases) {
test(`normalizes ${item.name}`, async ({ page }) => {
await page.setContent(`
<label for="tag">Profile tag</label>
<input id="tag">
<button>Apply</button>
<script>
const tag = document.querySelector('#tag');
document.querySelector('button').addEventListener('click', () => {
tag.value = tag.value.trim().toLowerCase();
});
</script>
`);
const tag = page.getByLabel('Profile tag');
await tag.fill(item.entered);
await page.getByRole('button', { name: 'Apply' }).click();
await expect(tag).toHaveValue(item.expected);
});
}
Three separate tests are better than a loop inside one test because one failure does not prevent later cases from running. The title includes the case name, which improves triage.
Do not generate hundreds of UI cases for a pure normalization function. Put exhaustive combinations in fast unit or component tests, then keep a small set of end-to-end cases that prove browser wiring and user workflow. Data volume should reflect risk, not the ease of adding rows.
Include boundary values that exercise distinct browser behavior, such as empty input, maximum accepted length, Unicode, or a rejected character, but only if the product defines them. Give every row a short domain name. A report titled normalizes case 7 forces triage to reopen the data table, while trims outer spaces communicates the failed rule immediately.
Keep expected values explicit in the table. Deriving expected with entered.trim().toLowerCase() duplicates production logic and makes incorrect behavior self-confirming.
8. Verify server-restored and reloaded form state
A strong form test often needs to prove persistence, not just local typing. Assert the value after the save response and again after a reload or fresh context, depending on the product contract.
import { test, expect } from '@playwright/test';
test('restores a saved display name after reload', async ({ page }) => {
let savedName = 'Guest';
await page.route('https://profile.example.test/api/profile', async route => {
if (route.request().method() === 'PUT') {
const body = route.request().postDataJSON() as { displayName: string };
savedName = body.displayName.trim();
await route.fulfill({ json: { displayName: savedName } });
return;
}
await route.fulfill({ json: { displayName: savedName } });
});
await page.route('https://profile.example.test/', route => route.fulfill({
contentType: 'text/html',
body: `
<label for="name">Display name</label><input id="name">
<button>Save</button>
<script>
const name = document.querySelector('#name');
fetch('/api/profile').then(r => r.json()).then(p => name.value = p.displayName);
document.querySelector('button').addEventListener('click', async () => {
const response = await fetch('/api/profile', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ displayName: name.value })
});
name.value = (await response.json()).displayName;
});
</script>
`,
}));
await page.goto('https://profile.example.test/');
const name = page.getByLabel('Display name');
await expect(name).toHaveValue('Guest');
await name.fill(' Casey QA ');
await page.getByRole('button', { name: 'Save' }).click();
await expect(name).toHaveValue('Casey QA');
await page.reload();
await expect(name).toHaveValue('Casey QA');
});
This test is still self-contained because Playwright routes both page and API requests. The final assertion proves restoration, not just client memory. In a real application, use controlled test data and verify the API or database only when that deeper boundary is part of the scenario.
9. Design useful negative and transition checks
Negative checks are most useful when paired with a positive destination. Suppose an invalid coupon must leave the code in the field and display an error. Assert both facts instead of merely proving the value is not cleared.
const coupon = page.getByLabel('Coupon code');
await coupon.fill('EXPIRED26');
await page.getByRole('button', { name: 'Apply coupon' }).click();
await expect(coupon).toHaveValue('EXPIRED26');
await expect(page.getByRole('alert'))
.toHaveText('This coupon has expired.');
Use not.toHaveValue() for a stable forbidden state, such as confirming that a server-secret placeholder never appears. Be careful with transitions. Immediately after a page loads, a field might briefly differ from READY, so a negative assertion can pass before the app finishes and tell you nothing about its final condition.
When the workflow changes from draft to saved, assert saved. If the intermediate draft state is separately important, observe it before the triggering action and then assert the transition's destination. A sequence of explicit business states is more diagnostic than several broad negatives.
For dialog-driven form changes, coordinate the dialog and action correctly. The Playwright dialog handling examples cover listener timing and user choices without fixed waits.
10. Debug and harden value examples for CI
If an example passes locally but fails in CI, resist adding a sleep. First classify the failure: locator resolution, actionability, wrong actual value, never-completed update, or insufficient assertion timeout.
Use trace viewer to see DOM snapshots and actions. Run a focused test with npx playwright test path/to/spec.ts --trace on. Check whether multiple controls share the label, whether a hidden duplicate remains in the DOM, and whether a re-render reset the field. A strictness error must be fixed at the locator level.
During focused diagnosis, read evidence once:
const field = page.getByLabel('Generated slug');
console.log('matches:', await field.count());
console.log('current value:', await field.inputValue());
await expect(field).toHaveValue('expected-slug');
Do not keep sensitive values in CI logs. Remove diagnostic output after repair.
Timeout overrides belong to known slow contracts:
await expect(page.getByLabel('Import status'))
.toHaveValue('complete', { timeout: 15_000 });
If the condition can never happen, 15 seconds only delays the same failure. Use the Playwright timeout troubleshooting guide to separate test, action, navigation, and assertion timeouts. For locator assertion type errors, see why Playwright expect needs a Locator.
11. Review playwright expect toHaveValue examples before merging
Start with intent: the example should prove a current form value after a real user or server-driven transition. Check that a stable user-facing locator identifies one control, that every promise is awaited, and that the expected string or regex encodes the requirement without duplicating production logic.
Next, verify control semantics. Use toHaveValues() for multi-selects, toBeChecked() for checkboxes and radios, and text assertions for displayed summaries. Make sure number and date expectations use browser string formats. For persistence scenarios, require a reload or fresh session when durable state is the claim.
Finally, inspect synchronization and evidence. Remove fixed sleeps, broad negative checks, sensitive logging, and unexplained timeout increases. Run the example in every browser project that matters, capture a trace on failure, and keep each data-driven row independently named. This review catches most flaky value examples before they become copied patterns across the suite.
Interview Questions and Answers
Q: What values can Playwright toHaveValue() accept as the expectation?
It accepts a string or JavaScript regular expression. A string asserts the complete current value exactly. A regex is appropriate for controlled dynamic formats and should usually be anchored when the whole value matters.
Q: Why should a value test avoid waitForTimeout() before the assertion?
A fixed delay duplicates implementation timing and can still race on a slower environment. toHaveValue() retries the actual UI condition and can finish as soon as it succeeds. This is both faster and more representative of the contract.
Q: How do you assert a number input containing 25?
Use await expect(locator).toHaveValue('25'). DOM input values are strings even when the input type is number. Convert to a number only when testing a separate arithmetic rule.
Q: What is the correct matcher for a multi-select?
Use toHaveValues() and supply an array of expected selected values. Use toHaveValue() only for a control with one current value, such as a text input or single select.
Q: How would you test a normalized value saved by a server?
Enter the raw value, trigger the real save workflow, assert the normalized UI value, reload or open a fresh session as required, and assert the restored value. This distinguishes local transformation from durable persistence.
Q: When is a regular expression too weak?
It is too weak when it permits states the requirement forbids. Unanchored fragments and broad wildcards commonly accept invalid prefixes, suffixes, lengths, or casing. Prefer an exact string for deterministic values and a narrow pattern for intentional variation.
Q: What evidence do you inspect when toHaveValue() times out?
Inspect locator uniqueness, the action that should update the field, the actual value, trace snapshots, console errors, and relevant network activity. Determine whether the app reverted, normalized, or never applied the state before changing timeout configuration.
Common Mistakes
- Comparing one
inputValue()snapshot with generictoBe()when the state is asynchronous. - Forgetting to await the locator assertion.
- Passing numeric expected data instead of its string value.
- Checking a checkbox's
valuetoken instead of its checked state. - Using
toHaveValue()for a multiple select rather thantoHaveValues(). - Copying a debounce duration into
waitForTimeout(). - Deriving expected normalization with the same algorithm as production.
- Accepting malformed results with an unanchored or overly broad regex.
- Proving only
not.toHaveValue(oldValue)instead of the required final value. - Asserting local form state without proving persistence after reload.
- Logging passwords or personal data while diagnosing CI failures.
- Raising assertion timeouts before correcting ambiguous locators or impossible expectations.
Conclusion
Reliable playwright expect toHaveValue examples share three qualities: they identify the control through a resilient locator, trigger a real user workflow, and assert the exact eventual value or a tightly defined format. The matcher handles normal asynchronous updates without manual polling or sleeps.
Choose examples that represent your product's real risks, such as normalization, calculated output, persistence, and specialized controls. Keep exhaustive pure-data combinations below the browser layer, and let each Playwright test prove a clear piece of user-visible wiring.
Interview Questions and Answers
What expected argument types does toHaveValue accept?
It accepts a string or JavaScript regular expression. Strings assert a complete exact value. Regular expressions suit controlled dynamic formats and should generally be anchored when the whole value matters.
Why should a value test avoid waitForTimeout?
A fixed delay duplicates implementation timing, always consumes its full duration, and can still race under load. `toHaveValue()` polls the actual condition and finishes as soon as it succeeds.
How do you assert a number input containing 25?
Use `await expect(locator).toHaveValue("25")`. Even a number input exposes a string value. Convert separately only when testing numeric business behavior.
What matcher should verify a multiple select?
Use `toHaveValues()` with an array of expected selected values. The singular matcher is for a control with one current value.
How would you test server-side normalization and persistence?
Enter the raw value, trigger the real save, assert the server-normalized UI state, reload or create the required fresh session, and assert the restored value. This separates local rendering from durable persistence.
When is a value regular expression too weak?
It is too weak when invalid prefixes, suffixes, casing, lengths, or characters can still match. Broad fragments and wildcards often hide defects. Prefer exact strings for deterministic outcomes and narrow patterns for required variation.
How do you diagnose a CI-only toHaveValue failure?
Classify locator, actionability, actual value, update completion, and timeout failures. Inspect the first-failure trace, network, and console, then compare the observed transformation with the requirement. Do not begin with a sleep.
Frequently Asked Questions
What is a basic Playwright toHaveValue example?
Use `const email = page.getByLabel("Email"); await email.fill("qa@example.com"); await expect(email).toHaveValue("qa@example.com");`. The locator assertion retries and checks the current form value.
How do I test a debounced input value?
Trigger the normal user action and immediately await the final value with `toHaveValue()`. Do not copy the debounce duration into `waitForTimeout()`, because the matcher already retries the observable condition.
How do I assert an HTML date input?
Use its normalized string format, such as `toHaveValue("2026-07-13")`. Assert localized visible date text separately if the product presents both layers.
Can I assert a single select with toHaveValue?
Yes. Select the option and expect its submitted value, for example `toHaveValue("senior")`. Use `toHaveValues()` for a select with the `multiple` attribute.
How should I test form normalization?
Enter raw data, trigger the real blur or submit path, and assert an independently specified result. Avoid calculating expected output with the same helper used by production code.
How do I prove that a form value was saved?
Assert the normalized value after saving, reload or open a fresh session as the requirement demands, and assert it again. A value that remains only in the current page does not prove persistence.
Should data-driven value cases run in one Playwright test?
Usually create one named test per row so one failure does not block other cases and the report identifies the rule. Keep exhaustive pure transformations in unit or component tests.
Related Guides
- Playwright drag and drop: Examples and Best Practices
- Playwright expect soft: Examples and Best Practices
- Playwright expect toBeVisible: Examples and Best Practices
- Playwright expect toHaveCount: Examples and Best Practices
- Playwright expect toHaveText: Examples and Best Practices
- Playwright expect toHaveURL: Examples and Best Practices