1. Which locator does the Playwright team recommend first?
Answer
getByRole with an accessible name, e.g. getByRole('button', { name: 'Save' })
Correct. Role-based locators match how users and assistive technology see the page, and survive DOM/styling changes.
Recommended order: getByRole / getByLabel / getByPlaceholder / getByText, then getByTestId, and raw CSS/XPath only as a last resort.
Why the other answers are wrong
- page.locator with a long CSS path. Deep CSS paths are tied to structure and break on layout changes.
- XPath with indexes, e.g. //div[3]/button[2]. Indexed XPath is the most fragile option, any reorder breaks it.
- Locating by the element's inline style. Styles change constantly and say nothing about meaning.
2. What is special about await expect(locator).toBeVisible() compared to a normal assertion?
Answer
It re-checks the condition until it passes or the timeout is reached
Correct. Web-first assertions poll the page, absorbing loading time without any explicit waiting code.
Web-first assertions are retrying assertions. They are the reason well-written Playwright tests need no sleeps.
Why the other answers are wrong
- It checks the element exactly once, immediately. That is how classic assertions work, Playwright's web-first assertions retry instead.
- It only works inside test.describe blocks. Assertions work anywhere in a test regardless of grouping.
- It hides test failures. If the condition never becomes true, the assertion fails loudly with a useful message.
3. A locator matches 3 elements and you call .click() on it in strict mode. What happens in real Playwright?
Answer
The test fails with a strict mode violation telling you the locator is ambiguous
Correct. Strict mode forces one-element locators for actions, which catches selector bugs early.
If multiple matches are legitimate, be explicit: .first(), .nth(i), or narrow with .filter(). The error is a feature, not an annoyance.
Why the other answers are wrong
- It clicks all three elements. Actions never fan out to multiple elements.
- It silently clicks the first one. Silent first-match clicking is old-Selenium behavior; Playwright fails instead so you notice the ambiguity.
- It clicks a random one. Playwright is deterministic, it errors rather than guessing.
4. Why is page.getByLabel('Email') a good way to find a form field?
Answer
It finds the field through its visible label, and proves the field is properly labelled for assistive technology
Correct. If getByLabel cannot find the field, neither can a screen reader. The locator doubles as an accessibility check.
getByLabel ties the test to what users read. A failing locator here is an accessibility bug report for free.
Why the other answers are wrong
- Labels are faster to query than ids. Speed is not the point. Meaning and accessibility are.
- It works even when the field has no label. It needs a label association, that requirement is the feature.
- It matches the placeholder text. Placeholder matching is getByPlaceholder. getByLabel uses the label element or aria-label.
5. What is the difference between toHaveText('1') and toContainText('1')?
Answer
toHaveText matches the full text exactly; toContainText matches a part of it
Correct. Use the exact form when the whole value matters (a count), the contains form for messages with variable parts.
A badge that must read exactly '1' needs toHaveText, toContainText('1') would also pass on '10' or '21'.
Why the other answers are wrong
- They are identical. Exact versus partial matching is a real difference, '10' passes toContainText('1') but fails toHaveText('1').
- toContainText is deprecated. Both are current API with different matching rules.
- toHaveText only works on inputs. It works on any element with text content. Inputs use toHaveValue.
6. After filtering a table, which assertion proves the filter removed the non-matching rows?
Answer
await expect(page.getByTestId('contact-row')).toHaveCount(1)
Correct. The count is what distinguishes a working filter from one that was ignored.
Filtering is defined by what it removes. toHaveCount asserts the size of the result, not just one member's presence.
Why the other answers are wrong
- await expect(page.getByText('Maya Chen')).toBeVisible(). Maya is visible whether or not the other rows were removed. Presence does not prove filtering.
- await expect(page.getByTestId('search-input')).toHaveValue('Maya'). That checks what you typed, not what the table did.
- No assertion is needed after typing. Without an assertion the test proves nothing about the filter.
7. How do you assert that the welcome message did NOT appear after a failed login?
Answer
await expect(page.getByTestId('welcome-message')).toBeHidden()
Correct. toBeHidden retries and passes when the element is absent or invisible. The right way to assert absence.
toBeHidden (or not.toBeVisible) is the retrying absence check. Prove the error showed and the door stayed shut.
Why the other answers are wrong
- Skip the check, if the error shows, the welcome cannot show. Assert both: the error appeared AND the forbidden thing did not happen. Apps have shown both at once.
- await page.getByTestId('welcome-message').click() and expect a crash. Acting on an element to prove absence fails the test for the wrong reason and reads badly.
- assert(page.html.indexOf('welcome') === -1). String-searching raw HTML is fragile and does not retry. Use the assertion built for absence.
8. When is .first() a reasonable choice on a locator that matches many elements?
Answer
When any matching element is equally valid for the test, e.g. 'add any product to the cart'
Correct. If the test does not care which item, .first() states that honestly. If a specific item matters, target it directly.
The test's intent decides: 'any product' → .first() is honest; 'the Trail Runner product' → locate that product.
Why the other answers are wrong
- Never, .first() is always fragile. It is only fragile when you rely on a specific element being first.
- Only with XPath locators. .first() works on any locator; the question is intent, not syntax.
- Whenever strict mode complains. Silencing strict mode with .first() without thinking hides real ambiguity. Decide whether identity matters first.
9. What is the difference between fill() and pressSequentially()?
Answer
fill() sets the value in one step; pressSequentially() types character by character
Correct. fill() is faster and is what you want almost always. Type character by character only when the app reacts to each keystroke, like a search-as-you-type box.
Default to fill(). Reach for pressSequentially() when the app responds to each character.
Why the other answers are wrong
- They are identical. One sets the value directly, the other simulates real typing. That matters for fields with per-keystroke behaviour.
- fill() only works on textareas. fill() works on inputs, textareas and editable elements.
- pressSequentially() is deprecated. It is current API, meant for the cases where per-keystroke events matter.
10. A test needs to check a value that only exists inside an iframe. What does Playwright require?
Answer
Use frameLocator to enter the frame first, then locate inside it
Correct. page.frameLocator('#payment').getByLabel('Card number') scopes the search to that frame.
Payment forms are often iframes. frameLocator is how you reach inside one.
Why the other answers are wrong
- Nothing special, page locators search inside iframes automatically. They do not. A page locator only searches the main document.
- Iframes cannot be tested. They can. frameLocator exists exactly for this.
- You must switch the whole page context to the frame permanently. frameLocator scopes a single chain; there is no permanent switch to undo.
11. What does test.beforeEach do that putting the same code at the top of each test does not?
Answer
It runs before every test automatically, so setup stays in one place and cannot be forgotten
Correct. One change updates every test, and a new test gets the setup without the author remembering it.
beforeEach is the main tool for keeping tests independent while avoiding repeated setup code.
Why the other answers are wrong
- It makes the tests run faster. It changes where the code lives, not how long it takes.
- It runs only once for the whole file. That is beforeAll. beforeEach runs before each test.
- It allows tests to share state. The opposite: it gives each test the same fresh starting state.
12. A locator matches an element that is present in the DOM but hidden behind a modal. What happens on click()?
Answer
Playwright waits for it to become actionable, then fails with a timeout explaining what blocked the click
Correct. Actionability checks include visibility and being able to receive the event. The failure is usually a real finding about the app.
An actionability failure often means a real user would also be blocked. Read it before assuming the test is wrong.
Why the other answers are wrong
- It clicks anyway, because the element exists. Playwright will not click something a user could not click.
- It closes the modal automatically. Playwright never changes the page to make an action succeed.
- It clicks the modal instead. It acts on the element you located, or it fails.