QA How-To
Playwright getByPlaceholder: Examples and Best Practices
Use playwright getByPlaceholder examples for search, login, checkout, filters, textareas, and dynamic forms, with reliable TypeScript locator practices.
21 min read | 3,183 words
TL;DR
Useful Playwright getByPlaceholder examples combine a precise placeholder string or regex with fill, press, and web-first assertions. Keep repeated inputs scoped, treat placeholder copy as a hint rather than a label, and verify the workflow result after entering data.
Key Takeaways
- Use placeholder locators for stable hints, examples, and search prompts on inputs or textareas.
- Prefer persistent labels for field identity and use placeholders to test supplemental guidance.
- Scope repeated search and filter inputs to named regions, dialogs, or cards.
- Synchronize autocomplete and dynamic forms through visible state rather than fixed delays.
- Use exact matching or intentional regular expressions instead of broad fragments and first().
- Assert results, errors, or saved state so each example proves behavior beyond typing.
Playwright getByPlaceholder examples can make input tests read like the interface: search by a prompt, enter a format example, add a comment, then verify the result. The locator is especially practical for search fields and textareas whose hint copy is a stable part of the product.
Good examples also show where not to use it. Placeholder text disappears after typing, may change by locale or state, and does not replace a persistent label. This cookbook uses current Playwright Test APIs to cover common workflows while keeping strictness, accessibility, and business outcomes visible.
TL;DR
| Example | Locator | Outcome to assert |
|---|---|---|
| Global search | getByPlaceholder('Search jobs and skills') | Result heading or list |
| Password format hint | getByPlaceholder('At least 12 characters') | Validation or account state |
| Issue description | getByPlaceholder('Steps, expected, observed') | Created issue status |
| Filter inside a region | filters.getByPlaceholder('Filter by name') | Visible filtered rows |
| Locale-specific format | getByPlaceholder(expectedHint, { exact: true }) | Correct locale and accepted value |
Use a precise hint, act through the returned Locator, and wait for an observable product result. Scope first when the same prompt appears more than once.
1. Playwright getByPlaceholder Examples with Minimal Setup
The following test uses page.setContent so it can run without an application server. In a real suite, keep the same locator and replace the fixture with page.goto to the product route.
import { test, expect } from '@playwright/test';
test('filters a skills directory', async ({ page }) => {
await page.setContent(`
<label for="skill-search">Skill search</label>
<input id="skill-search" placeholder="Search skills">
<ul>
<li>API testing</li>
<li>Playwright</li>
<li>Performance testing</li>
</ul>
<script>
const input = document.querySelector('input');
input.addEventListener('input', () => {
document.querySelectorAll('li').forEach(item => {
item.hidden = !item.textContent.toLowerCase().includes(input.value.toLowerCase());
});
});
</script>
`);
await page.getByPlaceholder('Search skills').fill('play');
await expect(page.getByText('Playwright', { exact: true })).toBeVisible();
await expect(page.getByText('API testing', { exact: true })).toBeHidden();
});
The visible label identifies the field, and the placeholder describes the short search action. This test chooses the placeholder because the hint is the subject of the example, then verifies both a matching and nonmatching item.
Install Playwright Test through npm init playwright@latest or the package setup used by your repository. Run npx playwright test, or add --debug to inspect locators interactively. Locator actions already wait for actionability, and web-first assertions retry, so no sleep is necessary.
2. Use Exact and Regex Placeholder Matching
String matching is concise and normally case-insensitive with substring behavior. Add exact: true for a full case-sensitive match. Use a regular expression when the accepted variation has a real rule, such as optional punctuation or two approved nouns.
import { test, expect } from '@playwright/test';
test('matches the intended project filters', async ({ page }) => {
await page.setContent(`
<input placeholder="Search projects">
<input placeholder="Search archived projects">
<input placeholder="Filter by owner or tag">
`);
const activeProjects = page.getByPlaceholder('Search projects', {
exact: true
});
const archivedProjects = page.getByPlaceholder(/^search archived projects$/i);
const ownerOrTag = page.getByPlaceholder(/owner or tag$/i);
await expect(activeProjects).toHaveCount(1);
await expect(archivedProjects).toHaveCount(1);
await expect(ownerOrTag).toHaveCount(1);
});
The exact locator fails if the product changes the complete phrase or capitalization. That is desirable when the phrase is a reviewed content contract. The regex examples allow only deliberate flexibility.
Avoid /search/i on a dashboard that contains global, project, and archived search fields. Avoid following a broad match with first(), because a layout change can silently redirect input. Either make the text precise or scope to the region that gives the prompt meaning.
3. Test Login and Password Hint Workflows
Authentication forms often show format examples through placeholders. Use synthetic accounts and keep secrets out of source control, logs, traces shared outside the team, and failure attachments. The locator text can remain readable without exposing the entered value.
import { test, expect } from '@playwright/test';
test('shows password guidance after invalid registration', async ({ page }) => {
await page.setContent(`
<form aria-label="Register">
<label>Email address <input type="email" placeholder="name@company.com"></label>
<label>Password <input type="password" placeholder="At least 12 characters"></label>
<button>Create account</button>
<p role="alert"></p>
</form>
<script>
document.querySelector('form').addEventListener('submit', event => {
event.preventDefault();
const password = document.querySelector('[type=password]');
if (password.value.length < 12) {
document.querySelector('[role=alert]').textContent = 'Password must contain at least 12 characters';
password.focus();
}
});
</script>
`);
await page.getByPlaceholder('name@company.com').fill('new.user@example.com');
const password = page.getByPlaceholder('At least 12 characters');
await password.fill('short');
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('alert')).toHaveText(
'Password must contain at least 12 characters'
);
await expect(password).toBeFocused();
});
The test verifies the visible validation and focus behavior. It does not assert the raw password value in output. In a real product, add positive coverage for successful registration and test the full policy primarily at faster unit or service layers.
If labels exist, getByLabel is usually the strongest primary locator. The Playwright getByLabel examples show how to operate each form control while preserving the label relationship.
4. Exercise Global Search and Keyboard Submission
Search is a natural placeholder use case because the prompt often explains searchable content. Tests should verify the search contract: query submission, resulting scope, empty state, and relevant result selection.
import { test, expect } from '@playwright/test';
test('submits a global search with Enter', async ({ page }) => {
await page.setContent(`
<header>
<input type="search" aria-label="Global search" placeholder="Search jobs and skills">
</header>
<main>
<h1>Home</h1>
<p role="status"></p>
</main>
<script>
const input = document.querySelector('input');
input.addEventListener('keydown', event => {
if (event.key === 'Enter') {
document.querySelector('h1').textContent = 'Search results';
document.querySelector('[role=status]').textContent = '1 result for ' + input.value;
}
});
</script>
`);
const search = page.getByPlaceholder('Search jobs and skills');
await search.fill('Playwright');
await search.press('Enter');
await expect(page.getByRole('heading', { name: 'Search results' })).toBeVisible();
await expect(page.getByRole('status')).toHaveText('1 result for Playwright');
});
Pressing Enter is relevant because keyboard submission is part of the workflow. If the primary design uses a visible Search button, include a button path too based on risk. Do not use keyboard operations merely to avoid locating an available control.
For server-backed results, wait for result UI rather than network idle as a universal signal. Applications may keep analytics or streaming requests open. A heading, status, or result row is closer to the user's completion condition.
5. Select Autocomplete Suggestions Without Sleeping
Autocomplete combines input hints with a semantic popup. Use the placeholder to enter text, then roles to select an option and assert the final value. A fixed delay tied to debounce milliseconds makes the test environment-dependent.
import { test, expect } from '@playwright/test';
test('selects a repository suggestion', async ({ page }) => {
await page.setContent(`
<label for="repository">Repository</label>
<input id="repository" role="combobox" aria-expanded="false" placeholder="Type owner/repository">
<div role="listbox" hidden>
<div role="option">qa-team/web-tests</div>
<div role="option">qa-team/api-tests</div>
</div>
<script>
const input = document.querySelector('input');
const listbox = document.querySelector('[role=listbox]');
input.addEventListener('input', () => {
listbox.hidden = false;
input.setAttribute('aria-expanded', 'true');
});
document.querySelectorAll('[role=option]').forEach(option => {
option.addEventListener('click', () => {
input.value = option.textContent;
listbox.hidden = true;
input.setAttribute('aria-expanded', 'false');
});
});
</script>
`);
const repository = page.getByPlaceholder('Type owner/repository');
await repository.fill('qa-team/web');
await page.getByRole('option', { name: 'qa-team/web-tests' }).click();
await expect(repository).toHaveValue('qa-team/web-tests');
await expect(repository).toHaveAttribute('aria-expanded', 'false');
});
The option locator names the chosen result, so reordered suggestions do not break the intent. In product tests, make suggestion data deterministic at the appropriate boundary, then retain a smaller end-to-end case for the real integration.
Cover no results, request failure, and stale response ordering if they are meaningful risks. Those states need explicit user feedback rather than longer timeouts.
6. Verify Checkout Format Hints and Normalized Values
Checkout placeholders often demonstrate expected card, postal, or phone formats. Never use real payment or customer data. Use provider-approved test values, synthetic identities, and an environment that cannot create real charges.
import { test, expect } from '@playwright/test';
test('normalizes a phone number from a format hint', async ({ page }) => {
await page.setContent(`
<label for="phone">Phone number</label>
<input id="phone" placeholder="Example: (555) 123-4567">
<button>Continue</button>
<p role="status"></p>
<script>
document.querySelector('button').addEventListener('click', () => {
const input = document.querySelector('input');
const digits = input.value.replace(/[^0-9]/g, '');
document.querySelector('[role=status]').textContent =
digits.length === 10 ? 'Phone accepted' : 'Enter 10 digits';
});
</script>
`);
const phone = page.getByPlaceholder('Example: (555) 123-4567');
await phone.fill('555.123.4567');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('status')).toHaveText('Phone accepted');
});
The business result matters more than echoing the typed punctuation. If the UI formats the field on blur, add an expected value assertion after focus moves. Keep comprehensive normalization cases in lower layers and select representative browser boundaries.
Payment fields hosted in cross-origin iframes need frame-aware locators and provider-specific test guidance. Do not assume page.getByPlaceholder can cross into an iframe without first selecting the frame context.
7. Fill Multiline Textareas and Assert Created Content
Textareas commonly use prompts such as Add context or Describe expected behavior. fill handles multiline strings, but a useful test should assert the created comment, ticket, or note after submission.
import { test, expect } from '@playwright/test';
test('creates a defect note from a textarea', async ({ page }) => {
await page.setContent(`
<form aria-label="Add defect note">
<label for="note">Defect note</label>
<textarea id="note" placeholder="Steps, expected result, observed result"></textarea>
<button>Add note</button>
</form>
<section aria-label="Notes"></section>
<script>
document.querySelector('form').addEventListener('submit', event => {
event.preventDefault();
const note = document.querySelector('textarea').value;
const paragraph = document.createElement('p');
paragraph.textContent = note;
document.querySelector('section').append(paragraph);
});
</script>
`);
const noteText = 'Expected: confirmation\nObserved: blank page';
await page.getByPlaceholder('Steps, expected result, observed result').fill(noteText);
await page.getByRole('button', { name: 'Add note' }).click();
await expect(page.getByRole('region', { name: 'Notes' })).toContainText(
'Observed: blank page'
);
});
Do not assert an entire long note when a stable, meaningful fragment proves the result. Full text may be correct for an immutable audit record, but error messages become noisy. For rich text editors, the editable surface may expose role textbox without a placeholder attribute, so inspect the rendered semantics and choose the matching locator.
8. Scope Repeated Filters in Dashboards and Dialogs
Admin screens often repeat Filter by name in the page and an open dialog. Scope by named regions or dialogs before using the placeholder. This turns duplication into explicit context.
import { test, expect } from '@playwright/test';
test('filters members only inside the team dialog', async ({ page }) => {
await page.setContent(`
<main>
<input placeholder="Filter by name" value="Background value">
</main>
<div role="dialog" aria-labelledby="team-title">
<h2 id="team-title">Add team member</h2>
<input placeholder="Filter by name">
<ul><li>Jordan Kim</li><li>Alex Rivera</li></ul>
</div>
`);
const dialog = page.getByRole('dialog', { name: 'Add team member' });
await dialog.getByPlaceholder('Filter by name').fill('Jordan');
await expect(dialog.getByPlaceholder('Filter by name')).toHaveValue('Jordan');
await expect(page.getByRole('main').getByPlaceholder('Filter by name')).toHaveValue(
'Background value'
);
});
The background assertion ensures the test did not type into the wrong field. Named forms, regions, articles, and table rows can provide the same kind of scope.
If the container lacks accessible identity, decide whether adding one would improve the product. A stable test ID can be appropriate for a purely structural host, but it should not be the first response to missing semantics. See the Playwright getByTestId guide for deliberate contracts.
9. Test Dynamic Placeholder Changes by State
Dynamic hints can explain different modes, but they also make selectors state-dependent. Assert the transition, then create or use the locator that represents the new state.
import { test, expect } from '@playwright/test';
test('switches from email to employee ID lookup', async ({ page }) => {
await page.setContent(`
<button aria-pressed="false">Use employee ID</button>
<label for="lookup">Account lookup</label>
<input id="lookup" placeholder="Enter work email">
<script>
document.querySelector('button').addEventListener('click', event => {
event.target.setAttribute('aria-pressed', 'true');
document.querySelector('input').placeholder = 'Enter employee ID';
});
</script>
`);
await page.getByRole('button', { name: 'Use employee ID' }).click();
const employeeId = page.getByPlaceholder('Enter employee ID');
await expect(employeeId).toBeVisible();
await employeeId.fill('EMP-1042');
await expect(employeeId).toHaveValue('EMP-1042');
});
The assertion retries until the attribute change makes the new locator match. A fixed timeout would only guess when the update finishes. If state can toggle back, add coverage for value preservation or clearing based on the documented product rule.
Avoid a regex that matches both work email and employee ID when the mode itself is important. Distinct locators make a wrong transition fail at the right point.
10. Data-Driven Playwright getByPlaceholder Examples for Locales
Localization tests should pair the expected locale with its exact hint. They should also verify a separate locale signal so the placeholder fixture cannot mask a page rendered in the wrong language.
import { test, expect } from '@playwright/test';
const locales = [
{ language: 'English', placeholder: 'Search articles' },
{ language: 'Spanish', placeholder: 'Buscar artÃculos' }
];
for (const locale of locales) {
test('search placeholder in ' + locale.language, async ({ page }) => {
await page.setContent(
'<main><h1>' + locale.language + '</h1>' +
'<input placeholder="' + locale.placeholder + '"></main>'
);
await expect(page.getByRole('heading', { name: locale.language })).toBeVisible();
await expect(
page.getByPlaceholder(locale.placeholder, { exact: true })
).toBeVisible();
});
}
Real applications should obtain expectations from reviewed product resources or a deliberate test fixture, not scrape the same DOM value they intend to verify. If translations are frequently updated, separate a small copy contract suite from broader functional tests whose primary risk is behavior.
Do not solve locale maintenance with /search|buscar|chercher|suchen/i. Such a pattern can pass if the wrong locale renders and becomes more fragile as languages grow.
11. Compare Placeholder Examples with Alternative Locators
The best locator depends on what the test claims. A single field can support several valid assertions, each with a different locator.
| Test claim | Better locator | Why |
|---|---|---|
| Field is named Email address | getByLabel('Email address') | Validates persistent labeling |
| Field shows approved format example | getByPlaceholder('name@company.com') | Validates the hint directly |
| Search control has correct role and name | getByRole('searchbox', { name: 'Site search' }) | Validates semantic control identity |
| Hidden structural widget needs a stable hook | getByTestId('query-editor') | Uses an explicit automation contract |
| Visible no-result message appears | getByText('No matching projects') | Tests rendered feedback |
Use role for actionable controls when it best captures the interface. The Playwright getByRole guide explains accessible names, state filters, and container scoping. Use alternative text for meaningful imagery, as shown in Playwright getByAltText examples.
Avoid team rules that require placeholder locators for every input. Some inputs have no placeholder by design, and others use volatile examples. A small locator hierarchy plus reviewed exceptions produces clearer tests than a universal mandate.
12. Migrate Brittle Input Selectors Safely
Replace selectors in slices rather than performing a blind search and replace. For each field, identify its user-facing label, role, placeholder, and test contract. Then choose the locator that expresses the test's claim.
import { test, expect } from '@playwright/test';
test('migrates a brittle search selector', async ({ page }) => {
await page.setContent(`
<div class="toolbar">
<div>Sort controls</div>
<div><input class="search-input" placeholder="Search test runs"></div>
</div>
<table><tbody><tr><td>release-2026-07</td></tr></tbody></table>
`);
// Before: coupled to layout and styling
const brittleQuery = page.locator(
'.toolbar > div:nth-child(2) input.search-input'
);
await expect(brittleQuery).toHaveCount(1);
// After: tied to the approved input hint
const query = page.getByPlaceholder('Search test runs');
await query.fill('release-2026-07');
await expect(page.getByRole('row', { name: /release-2026-07/ })).toBeVisible();
});
Run the migrated test in all configured browsers, inspect strictness failures, and confirm that the final assertion still represents the original risk. Do not preserve first() or nth() mechanically. A new ambiguity may reveal missing scope that the CSS chain accidentally encoded.
Keep review diffs small enough to connect each selector change with its semantic reason. If a migration reveals unlabeled controls or misleading hints, create a product fix rather than compensating with increasingly complex automation.
13. Diagnose Example Failures by Layer
When an example fails, first determine which layer broke. A locator layer failure means the expected input cannot be found uniquely. An actionability failure means the control exists but cannot currently receive input. A component-state failure means the value is cleared, reformatted, or replaced unexpectedly. A workflow failure means the input action succeeded but the result, request, or persistence is wrong.
This classification keeps fixes narrow. Missing scope calls for a named container, not a longer timeout. An overlaid input calls for product-state investigation, not force. A stale autocomplete response calls for deterministic data and an observable option state, not a sleep. A server validation mismatch calls for contract analysis, not a different locator.
Collect the DOM snapshot, action log, request and response evidence, and final visible state from a trace. Reproduce with the smallest relevant test and configured browser. If only one browser fails, inspect native input behavior and component CSS before introducing browser-specific branches.
After the fix, strengthen the assertion that would have explained the original defect sooner. The best regression test preserves the user-facing signal and identifies the failing layer without embedding incidental implementation details.
Interview Questions and Answers
Q: What is the best use case for getByPlaceholder?
It fits input and textarea hints that are stable and meaningful, especially search prompts and format examples. It returns a retryable Locator. It should not be treated as a substitute for a persistent label.
Q: How do you avoid flaky autocomplete tests?
I make suggestion data deterministic, fill the query, and wait for a visible listbox or expected option. I select by role and name and assert the chosen value or workflow. I avoid sleeping for the debounce interval.
Q: How do you test repeated placeholders?
I scope to a named dialog, region, form, card, or row before locating the input. I often assert an unaffected duplicate retains its value. I use position only if order is explicitly required.
Q: When is exact: true appropriate?
It is appropriate when the entire placeholder and casing are stable and substring matching could be ambiguous. A bounded regex is better for approved variation. A broad regex plus first() is not a substitute for context.
Q: What should you assert after fill?
The assertion should follow the risk: filtered results, validation, submission status, normalized value, selected suggestion, or persistence. toHaveValue can be useful, but it only proves the field state. High-value tests verify the resulting behavior.
Q: How would you migrate a CSS input selector?
I identify the field's label, role, placeholder, and stability needs, then choose the most direct contract. I preserve semantic container scope that the CSS encoded and keep strictness enabled. I run the change across configured browsers and review the final business assertion.
Common Mistakes
- Treating placeholder text as the only field name when users need a persistent label.
- Using a broad search regex, then selecting first() to suppress strictness.
- Sleeping through autocomplete debounce rather than waiting for semantic results.
- Asserting a filled value without checking the filter, validation, created item, or saved state.
- Using real credentials, payment details, resumes, or customer records as form fixtures.
- Letting a multilingual regex pass the wrong localized page.
- Assuming page locators automatically search inside a cross-origin payment iframe.
- Mechanically replacing CSS selectors without preserving meaningful container context.
Conclusion
These Playwright getByPlaceholder examples cover the patterns that matter in working suites: precise matching, keyboard search, autocomplete, checkout hints, textareas, repeated forms, dynamic state, and localization. The recurring design is simple: use the hint to find the input, then assert the outcome users receive.
Review one brittle input test and state its claim in plain language. If the claim concerns hint copy, getByPlaceholder is a strong fit. If it concerns identity or interaction semantics, use label or role and keep placeholder assertions focused on guidance.
Interview Questions and Answers
Where does getByPlaceholder fit in a locator strategy?
It is a user-facing locator for stable input or textarea hints. I generally prefer a label or role for persistent control identity, then use placeholder when the hint is the direct contract. Test IDs remain an explicit fallback when semantics are not stable enough.
How do you use getByPlaceholder for search?
I locate the search input by its prompt, fill the query, and submit through the supported keyboard or button path. Then I assert a result heading, status, row, or empty state. I avoid using network idle as the only completion condition.
How do you make autocomplete tests deterministic?
I control the response at the correct test boundary, wait for semantic options, and choose by role and accessible name. I assert the selected value and component state. Fixed waits are avoided because debounce and network timing vary.
How do you handle the same placeholder in two places?
I identify a named container such as a dialog or region and call getByPlaceholder within it. I may also assert the other input was not changed. This is clearer and safer than first or nth.
What is wrong with a multilingual placeholder regex?
It can make the test pass when the application renders the wrong locale. I pair each locale with an exact expected hint and verify a second locale signal. Translation coverage should reveal, not absorb, language mistakes.
What would you check during a CSS-to-placeholder migration?
I verify that the placeholder is stable, user-facing, and unique within meaningful scope. I preserve the original business assertion and run configured browser projects. Strictness failures are investigated instead of hidden with positional selectors.
Frequently Asked Questions
What are practical getByPlaceholder examples in Playwright?
Common examples include global search, autocomplete, login format hints, checkout inputs, filter panels, and multiline textareas. Each should verify the resulting application behavior.
How do I use regex with getByPlaceholder?
Pass a JavaScript regular expression instead of a string, such as /^search projects$/i. Keep boundaries narrow so the pattern does not hide duplicate or incorrect inputs.
Can getByPlaceholder locate password inputs?
Yes, if the password input has the matching placeholder. Use synthetic safe values and avoid logging or attaching secrets in test reports.
How do I test autocomplete with getByPlaceholder?
Fill the query through the placeholder locator, wait for options by role, choose a named option, and assert the selected value. Do not wait with a fixed debounce sleep.
Can I scope getByPlaceholder to a dialog?
Yes. Locate the named dialog first and call getByPlaceholder on that Locator. This is the preferred way to distinguish the dialog field from a duplicate in the background page.
Should I use getByPlaceholder or getByLabel?
Use getByLabel for the persistent field identity and getByPlaceholder when the hint itself matters. A test can use one for interaction and assert the other when both are product requirements.
How do I debug a dynamic placeholder?
Assert the application state that triggers the change, then locate the new exact placeholder and wait for it to be visible. Inspect a trace if the attribute never reaches the expected value.
Related Guides
- Playwright drag and drop: Examples and Best Practices
- Playwright getByLabel: Examples and Best Practices
- Playwright mock date and time: Examples and Best Practices
- Playwright popup and new tab handling: Examples and Best Practices
- Playwright tag and grep filters: Examples and Best Practices
- Playwright APIRequestContext: Examples and Best Practices