How would you repair this profile UI test?
Instruction: Replace the fragile selectors and wait with a complete Playwright test. Explain what the assertion establishes.
The fictional /profile route exposes the DOM below. The unrelated first Save button can move or disappear. Submitting Profile updates status to exactly Saved Alex after a successful API request; it remains Ready beforehand. This functional test has a five-second observation budget starting after the click action completes. There is no end-to-end latency requirement in this exercise. An authenticated test session and base URL are provided. The existing test is flaky.
<button>Save</button>
<form aria-label="Profile">
<label for="display-name">Display name</label>
<input id="display-name" name="displayName">
<button type="submit">Save</button>
<p role="status">Ready</p>
</form>await page.locator('input').fill('Alex');
await page.locator('button').nth(1).click();
await page.waitForTimeout(500);
expect(await page.locator('[role="status"]').textContent()).toBe('Saved Alex');Updated
Example Answer
I would scope the interaction to the named Profile form, fill the input by its label and click that form’s Save button. Position two is not a stable identity, and a global input selector becomes ambiguous as the page grows.
I would replace the fixed sleep and one-time text read with a retrying assertion on the form’s status. After the click action completes, the assertion waits for the specific outcome for up to the configured five seconds and fails with a useful locator assertion if it never appears. This checks visible success within the assertion budget. It does not measure five seconds from the user click; a latency test needs a separately defined start event and deadline. If persistence is part of the acceptance criteria, I would additionally reload or read through an independent API and verify the saved value.
Worked exhibit
import { test, expect } from '@playwright/test';
test('saves the profile display name', async ({ page }) => {
await page.goto('/profile');
const profile = page.getByRole('form', { name: 'Profile', exact: true });
await profile.getByLabel('Display name', { exact: true }).fill('Alex');
await profile.getByRole('button', { name: 'Save', exact: true }).click();
await expect(profile.getByRole('status')).toHaveText(
'Saved Alex', { timeout: 5000 }
);
});
What the interviewer is assessing
Core checks (1 point each):
- Uses the named Profile form and labeled field/button.
- Awaits the retrying status assertion with a bounded observation budget.
- Distinguishes visible success from persistence and end-to-end latency proof.
Stronger (+1 after all core checks): Distinguishes action readiness from completion of the save and adds persistence verification when required. Stable explicit test IDs are also acceptable if they form an intentional UI contract.
Acceptable alternatives: Stable intentional test IDs are acceptable; the assertion still needs the defined functional budget.
Red flags: Keeps positional selectors, increases the sleep, uses force-click to bypass readiness or asserts a one-time text snapshot while the UI is updating.
Tempting answer that misses the point
Weak answer: Increase the fixed sleep to five seconds.
Why it fails: A sleep does not observe success. Wait for the named state within the defined observation budget.
Follow-up
What if there are two profile forms with the same accessible name?
Example response: I would inspect the intended user context and use a stable enclosing region or a unique accessible name to select the correct form. If they are truly indistinguishable, I would improve the markup or add an agreed test ID. Using first() merely to silence a strictness error could make the test save the wrong profile.
Related Questions
-
easy
-
easy
-
easy
-
easy
-
easy
-
easy