QA How-To
How to Use Playwright getByPlaceholder (2026)
Learn playwright getByPlaceholder with exact and regex matching, search and form examples, scoped locators, accessibility guidance, and TypeScript debugging.
21 min read | 3,123 words
TL;DR
Playwright getByPlaceholder locates an input or textarea by its placeholder attribute and returns a retryable Locator. Use a specific string, exact match, or regular expression, scope duplicates to a semantic container, and verify the behavior caused by the entered value.
Key Takeaways
- Use getByPlaceholder for input and textarea hints whose placeholder copy is stable and meaningful.
- Remember that a placeholder is a hint, not a durable substitute for a visible or accessible label.
- Choose exact strings or bounded regular expressions to prevent accidental matches across similar fields.
- Scope repeated placeholders to named forms, dialogs, search regions, or cards.
- Assert search results, validation, or persistence instead of stopping at a filled value.
- Inspect the final placeholder attribute and locator match count before adding waits or fallback selectors.
Playwright getByPlaceholder locates an input or textarea by the hint displayed when the control is empty. It returns a normal Playwright Locator, so actions use auto-waiting and assertions retry against the current DOM state.
The API is concise, but selector convenience and form design are separate concerns. A placeholder can help a user enter the right format, yet it disappears after typing and does not replace a properly associated label. This guide shows how to use the locator accurately, where it fits in a locator strategy, and how to debug it without hiding product defects.
TL;DR
const search = page.getByPlaceholder('Search documentation');
await search.fill('locator strictness');
await search.press('Enter');
await expect(page.getByRole('heading', { name: 'Search results' })).toBeVisible();
| Situation | Recommended approach |
|---|---|
| Unique stable input hint | getByPlaceholder('Search documentation') |
| Exact phrase required | getByPlaceholder('Search', { exact: true }) |
| Controlled case or copy variation | getByPlaceholder(/^search (docs |
| Visible label also exists | Usually prefer getByLabel('Search query') |
| Repeated hint in modal and page | dialog.getByPlaceholder('Enter a name') |
Use the placeholder when it represents a stable user-facing hint. Prefer the label when the field has one, and always assert what the application did with the input.
1. What Playwright getByPlaceholder Actually Matches
The API takes a string or regular expression and an optional exact setting. It targets controls through the placeholder attribute, most commonly input and textarea elements. It does not search arbitrary nearby text or infer a hint from styling.
import { test, expect } from '@playwright/test';
test('finds input and textarea placeholders', async ({ page }) => {
await page.setContent(`
<input type="search" placeholder="Search documentation">
<textarea placeholder="Describe the issue"></textarea>
`);
await expect(page.getByPlaceholder('Search documentation')).toHaveAttribute(
'type',
'search'
);
await expect(page.getByPlaceholder('Describe the issue')).toHaveCount(1);
});
getByPlaceholder returns a Locator, not a fixed element handle. When fill or expect runs, Playwright resolves the selector against the current page. If a framework replaces the input during rendering, the locator can resolve the current matching element on the next operation.
Strictness still applies. An action that requires one control fails if two inputs match. That is useful evidence that the hint is too broad or the test lacks context. Do not treat strictness as noise to suppress with first().
The attribute value is the selector contract. A floating-label component may visually move text when focused, but if the underlying placeholder changes or disappears, the locator follows the actual attribute. Inspect rendered markup rather than assuming from a design screenshot.
2. Run a First Placeholder-Based Test
Initialize Playwright Test with npm init playwright@latest, or install @playwright/test in the existing project and install configured browser binaries. A self-contained page fixture makes the basic workflow easy to verify.
import { test, expect } from '@playwright/test';
test('submits a support request', async ({ page }) => {
await page.setContent(`
<form aria-label="Contact support">
<label for="summary">Summary</label>
<input id="summary" placeholder="Example: Login fails after reset">
<label for="details">Details</label>
<textarea id="details" placeholder="Include steps and observed result"></textarea>
<button>Send request</button>
<p role="status"></p>
</form>
<script>
document.querySelector('form').addEventListener('submit', event => {
event.preventDefault();
document.querySelector('[role=status]').textContent = 'Request sent';
});
</script>
`);
await page.getByPlaceholder('Example: Login fails after reset').fill(
'Checkout returns an empty confirmation page'
);
await page.getByPlaceholder('Include steps and observed result').fill(
'Submit a valid card, then observe a blank main region.'
);
await page.getByRole('button', { name: 'Send request' }).click();
await expect(page.getByRole('status')).toHaveText('Request sent');
});
This example intentionally gives both controls visible labels. The placeholder supplies a concrete format hint, while the label keeps each field identifiable after a value is entered. A production test could reasonably choose getByLabel as the primary locator and assert the placeholder separately when hint copy is a requirement.
Run the test with npx playwright test. Add --headed for visual observation or --debug for Inspector. Manual sleeps are not required around locator actions.
3. Match Placeholder Text with Strings, exact, and Regex
Plain strings are readable and generally use case-insensitive substring matching. exact: true requires the full string and casing. Regular expressions define their own boundaries, alternatives, and case flags.
import { test, expect } from '@playwright/test';
test('controls placeholder matching precision', async ({ page }) => {
await page.setContent(`
<input placeholder="Search projects">
<input placeholder="Search archived projects">
<input placeholder="Filter by owner, status, or tag">
`);
await expect(
page.getByPlaceholder('Search projects', { exact: true })
).toHaveCount(1);
await expect(
page.getByPlaceholder(/^search archived projects$/i)
).toHaveCount(1);
await expect(
page.getByPlaceholder(/owner, status, or tag/i)
).toHaveCount(1);
});
Use the narrowest stable text that represents the product requirement. Search alone is likely to match several controls on an administration page. Search projects communicates more context. If project terminology is stable, exact matching makes ambiguity obvious.
A regex should permit known variation, not every variation. Patterns with .* around a short word can match unrelated controls and make maintenance harder. If copy is completely unstable, choose a different semantic contract rather than writing a permissive expression.
When localization is under test, pass the expected localized string for that case. A multilingual regex can allow the wrong locale to pass. Validate the locale through a heading, landmark, URL, or application state as well as the placeholder.
4. Fill, Clear, Focus, and Submit Inputs Reliably
fill replaces the current value and dispatches the relevant input behavior. It is the right default for text-like inputs. Use press for keyboard behavior such as Enter submission, and web-first assertions for the resulting value, focus, or application state.
import { test, expect } from '@playwright/test';
test('replaces a prefilled query and submits it', async ({ page }) => {
await page.setContent(`
<form>
<input type="search" value="old query" placeholder="Search test reports">
<p role="status"></p>
</form>
<script>
document.querySelector('form').addEventListener('submit', event => {
event.preventDefault();
const value = document.querySelector('input').value;
document.querySelector('[role=status]').textContent = 'Searching for ' + value;
});
</script>
`);
const query = page.getByPlaceholder('Search test reports');
await query.fill('failed checkout');
await expect(query).toHaveValue('failed checkout');
await query.press('Enter');
await expect(page.getByRole('status')).toHaveText('Searching for failed checkout');
});
To clear a field, fill it with an empty string or use the supported clear action when that best expresses the test. Do not simulate dozens of Backspace presses unless key-by-key behavior matters. If a typeahead relies on individual keyboard timing, pressSequentially can be appropriate, but use it only for behavior that fill does not cover.
Never log sensitive values such as passwords, payment data, or personal records. Placeholder locators are readable, but the filled test data still needs normal security controls and synthetic fixtures.
5. Test Search and Autocomplete Behavior
Search fields often trigger debounced requests, suggestion lists, empty states, and keyboard selection. Synchronize on those observable results instead of sleeping for the debounce interval. The application may change its delay without changing the behavior contract.
import { test, expect } from '@playwright/test';
test('chooses an autocomplete suggestion', async ({ page }) => {
await page.setContent(`
<label for="city">Destination</label>
<input id="city" role="combobox" aria-expanded="false" placeholder="Start typing a city">
<ul role="listbox" hidden>
<li role="option">Seattle, WA</li>
<li role="option">Seaside, OR</li>
</ul>
<script>
const input = document.querySelector('input');
const list = document.querySelector('[role=listbox]');
input.addEventListener('input', () => {
list.hidden = input.value.length < 3;
input.setAttribute('aria-expanded', String(!list.hidden));
});
document.querySelectorAll('[role=option]').forEach(option => {
option.addEventListener('click', () => {
input.value = option.textContent;
list.hidden = true;
input.setAttribute('aria-expanded', 'false');
});
});
</script>
`);
const city = page.getByPlaceholder('Start typing a city');
await city.fill('Sea');
await page.getByRole('option', { name: 'Seattle, WA' }).click();
await expect(city).toHaveValue('Seattle, WA');
await expect(city).toHaveAttribute('aria-expanded', 'false');
});
The placeholder is useful for entering the query, while roles describe the combobox options. A complete application test may also route the suggestion request and return deterministic results, but it should still exercise the real component state transitions.
Test no-result and error states separately. Broad selectors such as page.locator('li').first() can choose an unrelated list item. Role plus accessible name keeps the selected option explicit.
6. Do Not Confuse a Placeholder with a Label
A placeholder is temporary instructional text. Once the user enters a value, the visible hint normally disappears. A label provides a persistent name and a semantic association that supports navigation and error understanding. Styling a placeholder to look like a label does not create that relationship.
Consider a field with label Email address and placeholder name@company.com. The label explains what information belongs there. The placeholder supplies an example format. In most functional tests, page.getByLabel('Email address') is the best primary locator. A focused content test can assert that the placeholder equals the approved example.
The Playwright getByLabel guide explains native label, aria-label, and aria-labelledby patterns. If the only identifiable text is a placeholder, the automation may pass while the product remains difficult for users who need a persistent field name. Raise the design question rather than declaring the locator sufficient accessibility coverage.
There are legitimate minimalist interfaces, especially a single site search input, where a visually hidden accessible label and a clear placeholder coexist. In that case, role or label locators can test the control identity, and getByPlaceholder can test the hint. Locator choice should follow the specific assertion, not a universal ranking applied without context.
7. Scope Duplicate Placeholders to Semantic Containers
The same placeholder may appear in a page editor and a modal editor, or in multiple cards. First locate the meaningful container, then locate the hint within it.
import { test, expect } from '@playwright/test';
test('edits the comment inside the review dialog', async ({ page }) => {
await page.setContent(`
<main>
<textarea placeholder="Add a comment">Main draft</textarea>
</main>
<div role="dialog" aria-labelledby="review-title">
<h2 id="review-title">Review changes</h2>
<textarea placeholder="Add a comment"></textarea>
<button>Submit review</button>
</div>
`);
const review = page.getByRole('dialog', { name: 'Review changes' });
await review.getByPlaceholder('Add a comment').fill('Verified in WebKit');
await expect(review.getByPlaceholder('Add a comment')).toHaveValue(
'Verified in WebKit'
);
await expect(page.getByRole('main').getByPlaceholder('Add a comment')).toHaveValue(
'Main draft'
);
});
The second assertion guards against an unintended edit to the background field. Scope can come from a dialog, region, form, article, row, or list item, as long as the container has a meaningful identity.
Avoid nth() when content or ordering can change. Position is appropriate only when position itself is the requirement, such as verifying that the first ranked result contains its expected search field. Even then, identify the result collection semantically before applying position.
8. Handle Dynamic and Conditional Placeholders
Applications sometimes change hints based on a selected country, search mode, or validation state. Test the transition only when the changing hint is a deliberate requirement. A Locator created for the old placeholder will not magically match the new text after the attribute changes, because its selector still describes the old value.
import { test, expect } from '@playwright/test';
test('updates the postal code hint for the selected country', async ({ page }) => {
await page.setContent(`
<label for="country">Country</label>
<select id="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
</select>
<label for="postal">Postal code</label>
<input id="postal" placeholder="Example: 98101">
<script>
document.querySelector('select').addEventListener('change', event => {
document.querySelector('#postal').placeholder =
event.target.value === 'ca' ? 'Example: K1A 0B1' : 'Example: 98101';
});
</script>
`);
await page.getByLabel('Country').selectOption('ca');
const canadianPostalCode = page.getByPlaceholder('Example: K1A 0B1');
await expect(canadianPostalCode).toBeVisible();
await canadianPostalCode.fill('K1A 0B1');
await expect(canadianPostalCode).toHaveValue('K1A 0B1');
});
The expectation waits for the new attribute state without a fixed delay. If the transition depends on a server response, consider routing that boundary for deterministic component coverage and keeping a smaller number of real integration tests.
Do not encode every temporary placeholder variation into one large regex. Explicit state transitions make failures easier to diagnose and show which copy belongs to each mode.
9. Compare Placeholder Locators with Other Strategies
Select a locator based on the assertion and the semantics exposed by the product. Placeholder is one useful option, not the default for every input.
| Locator | Best fit | Strength | Tradeoff |
|---|---|---|---|
| getByLabel | Persistently named form control | Tests label association | Requires correct labeling markup |
| getByRole | Searchbox, combobox, button, region | Tests role and accessible name | Name may come from several sources |
| getByPlaceholder | Input or textarea format hint | Concise and user-facing | Hint disappears after typing |
| getByText | Visible noninteractive copy | Mirrors rendered content | Does not associate text with a field |
| getByTestId | Explicit automation contract | Stable through copy edits | Does not validate user-facing identity |
| locator | Necessary low-level selection | Full CSS or XPath control | Often coupled to implementation |
For an accessible search field, page.getByRole('searchbox', { name: 'Site search' }) may better represent its identity, while getByPlaceholder('Search articles') verifies hint copy. The Playwright getByRole guide provides a broader decision framework.
When user-facing semantics are genuinely unstable or absent, a test ID may be the honest contract. Follow a consistent naming policy from the Playwright getByTestId guide rather than constructing long CSS paths.
10. Debug Playwright getByPlaceholder Failures
Start by checking the final rendered placeholder attribute. Component libraries may forward it to a nested input, translate it, replace it after hydration, or remove it when a floating label activates. The test must target the actual control state.
Run a focused test in Inspector:
npx playwright test tests/search.spec.ts --debug
Use the locator picker to evaluate the placeholder locator and inspect how many controls match. In CI, open the retained trace to examine the DOM snapshot, actionability, network activity, and preceding actions.
Classify the failure:
- Zero matches: the control did not render, the hint changed, or the attribute is on a different element.
- Multiple matches: the string is broad or semantic scope is missing.
- Not editable: the matched element is disabled, read-only, hidden, or covered.
- Value resets: application state or rerendering replaces user input after fill.
- Submission fails: the locator succeeded, but validation, network, or business logic produced a different result.
Do not increase timeouts before classification. A permanently wrong placeholder will wait longer and still fail. When evidence points to broader synchronization trouble, use the Playwright timeout debugging guide to investigate the page state and event boundary.
11. Build Maintainable Placeholder Helpers
Page objects can expose important inputs as typed Locator fields, but workflow methods should still show why values are entered and what state follows. Avoid a generic find-by-placeholder helper that simply wraps the built-in API without adding domain meaning.
import { type Locator, type Page, expect } from '@playwright/test';
export class KnowledgeBasePage {
readonly query: Locator;
constructor(private readonly page: Page) {
this.query = page.getByPlaceholder('Search documentation');
}
async searchFor(term: string): Promise<void> {
await this.query.fill(term);
await this.query.press('Enter');
}
async expectResult(title: string): Promise<void> {
await expect(
this.page.getByRole('link', { name: title, exact: true })
).toBeVisible();
}
}
This abstraction uses the placeholder for input, but asserts a result link through its role. It separates interaction from the expected result so no-result and error tests can reuse the same search action.
Centralization reduces mechanical updates, yet a placeholder copy change still deserves review. Ask whether the hint, label, accessible name, and tests remain aligned. A helper should not make a user-facing regression invisible.
12. Review Placeholder Locators in Component Libraries
Shared input components can spread one mistake across hundreds of fields. Add focused browser coverage for attribute forwarding, label association, disabled and read-only states, error descriptions, and any floating-label behavior. The component test can use getByPlaceholder to prove that consumer-supplied hint text reaches the actual editable control.
Test state transitions that can remove or alter the attribute. For example, a floating-label implementation may move the hint after focus, a country selector may replace a format example, or validation may swap guidance for an error. Assert the intended state explicitly rather than writing one regex that accepts every phase.
Keep component coverage separate from product workflows. A component suite can exhaust prop combinations and accessibility wiring quickly. A smaller end-to-end suite should verify that real pages choose appropriate labels, hints, defaults, and submission behavior. This division prevents every product test from repeating library mechanics.
During review, ensure the placeholder remains supplementary when users need a persistent name. Confirm that disabled fields are not mistaken for editable ones and that read-only display controls have an appropriate semantic contract. A passing placeholder locator is evidence about the attribute, not a complete accessibility verdict.
Include copy behavior in the component contract. A hint should remain understandable when validation appears, browser autofill supplies a value, zoom changes the layout, or a narrow viewport truncates surrounding content. Automation can cover the semantic state and a few representative responsive modes. Content and accessibility review should judge whether the example still helps a person complete the field.
Interview Questions and Answers
Q: What does getByPlaceholder return?
It returns a Locator for an input or textarea matched through its placeholder text. Resolution is lazy, and actions benefit from Playwright waiting and actionability. Assertions retry until they pass or time out.
Q: When should getByLabel be preferred?
Prefer getByLabel when a stable label identifies the field. A label persists after typing and establishes an accessible association. Use getByPlaceholder when the hint itself is the relevant stable contract.
Q: How do you handle two fields with the same placeholder?
I locate a named dialog, form, region, card, or row first and call getByPlaceholder within it. This describes context and survives reordering. I avoid first() unless position is explicitly under test.
Q: What does exact: true change?
It requires a string to match the entire placeholder with the same casing. It is useful when substring matching could identify several controls. A regex is better when accepted variation needs explicit boundaries or case handling.
Q: How would you test an autocomplete field?
I fill the query, wait for the semantic suggestion list or expected network-backed state, choose an option by role, and assert the selected value. I avoid fixed sleeps for debounce timing. I also cover no-result and error states based on risk.
Q: How do you debug a placeholder locator timeout?
I inspect the rendered placeholder attribute, count matches, and verify that the control is visible, enabled, and editable. Then I use a trace to determine whether rerendering reset the field or the downstream workflow failed. I change the selector or synchronization based on evidence.
Common Mistakes
- Treating a placeholder as a complete substitute for a visible or accessible label.
- Matching a generic word such as Search across several page controls.
- Hiding duplicate matches with first() rather than adding semantic container scope.
- Adding a fixed delay for search debounce instead of waiting for suggestions or results.
- Verifying only toHaveValue and never checking validation, results, submission, or persistence.
- Using a multilingual catch-all regex that allows the wrong locale to pass.
- Raising timeouts when the rendered placeholder is permanently different.
- Building a wrapper that adds no domain meaning and obscures the built-in locator.
Conclusion
Playwright getByPlaceholder is a readable, retryable locator for input hints. Use it when the placeholder copy is a deliberate product contract, control matching precision, and scope duplicates to containers users can understand.
Keep labels and roles at the center of accessible form design. Start with one placeholder-based search or support workflow, assert its visible result, and use a trace to investigate any ambiguity before weakening the locator.
Interview Questions and Answers
What is Playwright getByPlaceholder?
It is a user-facing locator that selects an input or textarea by placeholder text. It returns a lazy Locator rather than a fixed element. Actions and assertions therefore use Playwright waiting and strictness behavior.
When would you choose getByPlaceholder over getByLabel?
I choose it when the hint text itself is stable and relevant to the test, such as a search prompt or format example. If a persistent label exists, getByLabel is usually the stronger field identity. I may assert both when copy and accessibility are separate requirements.
How do exact and regex matching differ?
exact: true enforces the full string and casing. A regex controls its own boundaries, alternatives, and case flags. I keep either form narrow enough to expose duplicate or incorrect fields.
How do you make a placeholder-based autocomplete test deterministic?
I control the suggestion data at the appropriate boundary, fill the query, wait for the semantic options, and choose one by role. I assert the selected value and expanded state or resulting workflow. I do not rely on a fixed debounce sleep.
How do you resolve duplicate placeholders?
I scope to the smallest meaningful container, such as a dialog, form, search region, or card. Then I call getByPlaceholder from that locator. Positional selection is reserved for order-specific requirements.
How would you troubleshoot a getByPlaceholder failure in CI?
I open the trace, inspect the final placeholder attribute, and count matches. I check whether hydration replaced the input, application state reset its value, or the control was not actionable. I fix the identified product or synchronization issue before adjusting timeouts.
Frequently Asked Questions
What does Playwright getByPlaceholder do?
It locates an input or textarea through the placeholder attribute and returns a Locator. The locator supports standard actions, auto-waiting, strictness, and web-first assertions.
Is getByPlaceholder case-sensitive?
String matching is normally case-insensitive and can match a substring. Use exact: true for a full case-sensitive string or a regular expression for explicit matching behavior.
Can getByPlaceholder fill a textarea?
Yes. A textarea with matching placeholder text can be located and filled like a text input. Assert the submitted or visible outcome as well as the value when it matters.
Is a placeholder the same as a label?
No. A placeholder is an input hint that usually disappears after typing, while a label provides a persistent name and semantic association. Use both when the design needs a field name and a format example.
How do I find an exact placeholder in Playwright?
Pass the full string and { exact: true } to getByPlaceholder. Use a bounded regular expression when casing or a known copy variation must be flexible.
Why does getByPlaceholder match multiple inputs?
The hint may be repeated or the string may be too broad. Scope the locator to a named form, dialog, region, or card, or use a more precise expected phrase.
How should I wait for autocomplete results?
Wait for a visible listbox, option, result, or other observable application state with a locator assertion. Avoid sleeping for an assumed debounce duration.