Practice Hub
Score: 0 / 12
Challenge 1BeginnerWeb-First
Solved
What makes Playwright assertions "web-first"?
Compare two assertion styles. What is the key advantage of the Playwright web-first approach?
// Style A — traditional
const text = await page.locator('.status').textContent();
expect(text).toBe('Success');

// Style B — web-first
await expect(page.locator('.status')).toHaveText('Success');
Answer: B — auto-retry eliminates race conditions
Web-first assertions (Style B) automatically retry — polling the element state — until the condition is met or the timeout (default 5s) expires. Style A takes a snapshot at one moment; if the element hasn't updated yet, it fails immediately. Style B is resilient to timing issues caused by animations, API calls, and async updates — it's the Playwright way to write flake-free tests.
Challenge 2IntermediateVisibility
Solved
What is the difference between toBeVisible and toBeAttached?
An element has display:none but is in the DOM. Which assertion passes and which fails?
// Element: <div class="modal" style="display:none">Hidden</div>

await expect(page.locator('.modal')).toBeAttached();  // A
await expect(page.locator('.modal')).toBeVisible();   // B
Answer: C — toBeAttached passes, toBeVisible fails
toBeAttached checks that the element exists in the DOM (attached to the document), regardless of visibility. toBeVisible additionally checks that the element is visible — not hidden by display:none, visibility:hidden, zero opacity, or zero dimensions. With display:none, the element is attached but not visible, so only A passes.
Challenge 3IntermediateSoft Assertions
Solved
What happens after a soft assertion fails?
You use expect.soft() for multiple checks. What is the behaviour when one fails?
test('check profile page', async ({ page }) => {
  await page.goto('/profile');

  await expect.soft(page.locator('h1')).toHaveText('John Doe');    // fails
  await expect.soft(page.locator('.email')).toHaveText('john@example.com'); // runs?
  await expect.soft(page.locator('.role')).toHaveText('Admin');   // runs?

  console.log('Test continues here?');  // runs?
});
Answer: B — continues executing; reports all failures together
expect.soft() records failures but does not throw immediately. The test continues running all remaining steps. At the end of the test, Playwright reports all accumulated soft failures together. This is useful for form validation tests where you want to check all fields in one test run. Note: the test does fail in the end if any soft assertion failed — they are not warnings.
Challenge 4BeginnertoHaveCount
Solved
How do you assert that a table has exactly 5 rows?
After loading a page, a table should show exactly 5 data rows. Which assertion is correct?
const rows = page.locator('table tbody tr');
Answer: B — await expect(rows).toHaveCount(5)
toHaveCount is a web-first assertion that auto-retries. Option A works but is not web-first — count() reads the value once and the traditional expect().toBe() does not retry. Option C is wrong because locators are not arrays with a .length property. Option D uses the wrong method name. Always prefer web-first assertions like toHaveCount over manual count reads.
Challenge 5BeginnertoHaveAttribute
Solved
How do you assert a link's href attribute?
You want to verify that a link points to the correct URL. Which assertion is correct?
<a href="https://vibetestq.com" class="nav-link">Home</a>
Answer: B — toHaveAttribute('href', value)
toHaveAttribute(name, value) checks that the element has the specified attribute with the expected value. It auto-retries and is the correct web-first way to check element attributes. The value can also be a regex: toHaveAttribute('href', /vibetestq/). Option A checks the text content (the visible link text "Home"), not the href. Option C tries to access .href on a Locator object, which doesn't exist.
Challenge 6BeginnertoHaveValue
Solved
How do you verify an input field's value?
After filling a form field, you want to assert it contains the expected value. Which is the correct web-first assertion?
await page.getByLabel('Username').fill('admin');
// Assert the input has 'admin' as its value
Answer: B — toHaveValue('admin')
toHaveValue checks the value property of form elements (input, textarea, select). toHaveText checks text content — inputs don't have text content as such. toContainText is also for text content. Option D uses inputValue() which reads the value once (not web-first). Use toHaveValue for all form field value assertions.
Challenge 7BeginnertoBeChecked
Solved
How do you assert a checkbox is checked?
After a user interaction enables a "Remember Me" checkbox, which assertion verifies it is checked?
const checkbox = page.getByRole('checkbox', { name: 'Remember Me' });
Answer: C — toBeChecked()
toBeChecked() is the dedicated web-first assertion for checkbox and radio button state. It checks the checked property of the element (not the attribute). Option A is unreliable — the HTML checked attribute only reflects the default state, not the current state after user interaction. toBeEnabled() checks that the element is not disabled — unrelated to its checked state. Use toBeChecked({ checked: false }) to assert unchecked.
Challenge 8Advancedexpect.poll()
Solved
When should you use expect.poll()?
What is expect.poll() designed for, and when should you use it instead of a standard web-first assertion?
await expect.poll(async () => {
  const response = await page.request.get('/api/jobs/123');
  return (await response.json()).status;
}, { timeout: 30_000 }).toBe('completed');
Answer: C — for non-Playwright values that need polling
expect.poll(fn) repeatedly calls the provided async function until the return value satisfies the assertion, or the timeout expires. This is for scenarios where you need auto-retry behaviour on values that are NOT Playwright locators — like an API endpoint polling, a database query, or a computed value. Standard web-first assertions (toHaveText, etc.) only work on Playwright locators.
Challenge 9IntermediateSnapshots
Solved
What happens on the first run of toMatchSnapshot()?
You add a visual snapshot assertion. What happens when you run the test for the very first time?
await expect(page).toHaveScreenshot('login-page.png');
Answer: B — baseline is created and test passes
On the first run, Playwright creates the baseline screenshot file in the __snapshots__ folder and the test passes. Subsequent runs compare the current screenshot to the saved baseline — failing if pixel differences exceed the threshold. To update a baseline after intentional UI changes, run with --update-snapshots. This is Playwright's visual regression testing workflow.
Challenge 10IntermediateTimeouts
Solved
How do you set a custom timeout for a single assertion?
A loading indicator takes up to 15 seconds to disappear. How do you extend the timeout for just this one assertion?
// Loader takes up to 15 seconds to hide
await expect(page.locator('.loading')).toBeHidden(/* ??? */);
Answer: B — pass timeout option to the assertion method
Most Playwright web-first assertion methods accept an options object as their last argument, including { timeout: ms }. This overrides the global expect.timeout for just that one assertion. Option A is wrong — the timeout goes on the assertion method, not on expect(). Option C changes the action timeout globally (affects all following actions). Option D is not a valid Playwright API.
Challenge 11AdvancedtoHaveCSS
Solved
What does toHaveCSS check exactly?
You want to verify a button's background color after it becomes active. What is correct about toHaveCSS?
await expect(page.getByRole('button', { name: 'Save' }))
  .toHaveCSS('background-color', 'rgb(5, 150, 105)');
Answer: B — computed CSS value
toHaveCSS(property, value) checks the computed style — the final resolved value after all CSS rules (stylesheets, classes, inline styles) are applied by the browser. This is important: you cannot pass a CSS class name — you must pass the actual property and its resolved value. For colors, browsers return values in rgb() or rgba() format, so use those formats in your assertion.
Challenge 12BeginnerNegation
Solved
How do you assert an element is NOT visible?
After closing a modal, you want to verify it is gone. Which assertion is correct?
await page.getByRole('button', { name: 'Close' }).click();
// Assert modal is no longer visible
Answer: C — .not.toBeVisible() (and D is also valid)
Playwright assertions support the .not modifier for negation: expect(locator).not.toBeVisible(). Additionally, toBeHidden() is a dedicated assertion that passes when the element is not visible (hidden or detached). Both C and D are correct patterns — toBeHidden() is the semantic equivalent of not.toBeVisible(). Option A (toBeNotVisible) does not exist as a method name.