Playwright Scenarios Based Questions

Senior Playwright interviews are built around situations: "a test fails randomly", "it passes locally but fails in Jenkins", "two users need to interact". For each scenario below you'll find the root cause, the answer to give, and Playwright code for the fix. All code type-checks against Playwright 1.63.

Advertisement

1. A Test Fails Randomly

Answer

"I treat it as flaky until proven otherwise. I open the trace from the failed run to see the exact step, the DOM at that moment and the network calls. Usually it's timing — a one-shot check instead of a web-first assertion, or a missing wait for an API response — or shared test data between parallel tests. I fix the cause rather than adding retries or timeouts."

How to investigate

  1. Reproduce: npx playwright test flaky.spec.ts --repeat-each=20 (add --workers=4 to add parallel pressure).
  2. Open the trace (trace: 'on-first-retry') and compare the failing step's DOM snapshot with what you expected.
  3. Check the usual causes: await locator.textContent() or isVisible() checks that don't retry, waitForTimeout, animations, shared data, test order dependencies.
// ❌ one-shot — fails if the text arrives 100 ms later
expect(await page.locator('#status').textContent()).toBe('Paid');
// ✅ web-first — retries until it matches
await expect(page.locator('#status')).toHaveText('Paid');

More: Playwright Auto-Wait and Trace Viewer.

2. The Element Is Visible but the Click Fails

Visible isn't the same as clickable. Before clicking, Playwright checks the element is visible, stable (not animating), enabled and receives events (not covered). The error message says which check failed — for example <div class="overlay"> intercepts pointer events.

  • Overlay or loader — wait for it to go: await expect(page.getByTestId('loader')).toBeHidden().
  • Disabled until the form is valid — assert toBeEnabled(); if it never enables, that may be the bug.
  • Covered by a cookie banner or sticky header — accept the banner in setup; Playwright scrolls automatically, but fixed headers can still cover elements.
  • Wrong element — a strict-mode error or clicking a hidden duplicate; make the locator specific.

Avoid { force: true } except as a last resort — it skips the checks and can hide a real bug that users would hit.

3. Passes Locally, Fails in Jenkins

Answer

"The environment differs, so I compare them: headless mode, viewport, browser version, CPU speed, test data, time zone and locale, and parallel workers. I download the trace, screenshot and video from the CI artifacts, reproduce with the same settings locally, and fix the underlying cause."

// playwright.config.ts — make local and CI behave the same
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 2 : undefined,
  use: {
    viewport: { width: 1366, height: 768 },
    locale: 'en-IN',
    timezoneId: 'Asia/Kolkata',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
});

Also install the matching browsers in CI (npx playwright install --with-deps) or use the official Playwright Docker image. More: Test Runner, Workers & Retries.

4. Dynamic Elements

React and Angular apps generate IDs and class names that change between builds. Use locators based on what users see or on test IDs:

page.getByRole('button', { name: 'Place order' });
page.getByLabel('Delivery address');
page.getByTestId('order-total');
page.getByRole('row').filter({ hasText: 'Order #1043' }).getByRole('button', { name: 'Track' });

Avoid generated classes (.css-1x9k2p) and positional XPath. More: Playwright Locators.

5. CAPTCHA

"CAPTCHA exists to stop automation, so we don't try to solve it. In test environments it's disabled with a feature flag, or the provider's test keys are used (reCAPTCHA has keys that always pass); many flows skip the UI login entirely by authenticating through the API and reusing storage state. Production keeps the real CAPTCHA." See Storage State & Session Reuse.

6. Toast Notifications

await page.getByLabel('Phone').fill('9876543210');
await page.getByRole('button', { name: 'Save' }).click();

const toast = page.getByRole('status');                 // many toast libraries use role="status" or "alert"
await expect(toast).toHaveText('Profile updated');
await expect(toast).toBeHidden({ timeout: 10_000 });    // it disappears on its own

Check the right message for each outcome (success, validation error, server error), its type/color if it matters, and that it disappears. Assert immediately after the action — web-first assertions catch short-lived toasts that manual checks would miss.

7. Multi-User Scenarios ⭐

Each browser context is an isolated session with its own cookies and storage — like two different browsers — so two users can act in one test:

test('admin approves what a customer requested', async ({ browser }) => {
  const adminContext = await browser.newContext({ storageState: 'auth/admin.json' });
  const customerContext = await browser.newContext({ storageState: 'auth/customer.json' });
  const admin = await adminContext.newPage();
  const customer = await customerContext.newPage();

  await customer.goto('/loans/new');
  await customer.getByRole('button', { name: 'Submit request' }).click();

  await admin.goto('/approvals');
  await admin.getByRole('row', { name: /Pending/ }).getByRole('button', { name: 'Approve' }).click();

  await customer.reload();
  await expect(customer.getByText('Approved')).toBeVisible();

  await adminContext.close();
  await customerContext.close();
});

Contexts are cheap — far faster than launching a second browser — which is why Playwright handles chat, approval workflows and role-based access tests so well.

8. File Downloads

const downloadPromise = page.waitForEvent('download');           // start waiting before the click
await page.getByRole('button', { name: 'Download invoice' }).click();
const download = await downloadPromise;

expect(download.suggestedFilename()).toBe('invoice-1043.pdf');
const path = test.info().outputPath(download.suggestedFilename());
await download.saveAs(path);
expect(fs.statSync(path).size).toBeGreaterThan(0);                 // not an empty file

For stronger checks, parse the file (a CSV's rows, a PDF's text) and compare key values with what the UI showed.

9. Test Data Setup and Cleanup

test.describe('orders', () => {
  let orderId: number;

  test.beforeEach(async ({ request }) => {                          // create data through the API — fast and reliable
    const res = await request.post('/api/orders', { data: { item: 'Backpack', qty: 1 } });
    expect(res.ok()).toBeTruthy();
    orderId = (await res.json()).id;
  });

  test.afterEach(async ({ request }) => {                           // clean up even if the test failed
    await request.delete(`/api/orders/${orderId}`);
  });

  test('cancel an order', async ({ page }) => {
    await page.goto(`/orders/${orderId}`);
    await page.getByRole('button', { name: 'Cancel order' }).click();
    await expect(page.getByText('Cancelled')).toBeVisible();
  });
});

Each test creates its own data, so tests stay independent and safe to run in parallel. Use unique values (timestamps, random suffixes) where records must be unique.

10. Testing a Slow or Failing API

await page.route('**/api/orders', async route => {
  await new Promise(r => setTimeout(r, 3000));                     // simulate a slow backend
  await route.fulfill({ json: [{ id: 1, item: 'Backpack' }] });
});
await page.goto('/orders');
await expect(page.getByTestId('spinner')).toBeVisible();
await expect(page.getByRole('row', { name: /Backpack/ })).toBeVisible();

Route mocking lets you test loading states, empty lists and error messages (route.fulfill({ status: 500 })) that are hard to trigger on a real server.

11. Cross-Browser Runs

Define projects for Chromium, Firefox and WebKit (Safari's engine) in the config and run the same tests on each; use --project=webkit to run one. Skip genuinely unsupported cases with test.skip(browserName === 'webkit', 'reason') rather than weakening the test. Note that WebKit is close to, but not identical to, branded Safari.

Practice answering these scenarios out loud in the Playwright Interview Simulator.

From Real Projects

I've built my automation skills on Selenium with Java and TestNG, and that background maps well onto Playwright: page classes become page objects, TestNG groups and parallel runs become projects and workers, and explicit waits become auto-waiting and web-first assertions. If you're coming from Selenium too, focus on those differences first. Testing Testsigma — a platform where users write automated tests in plain English — meant thinking like its users, who are testers themselves. Answer with the problem, the Playwright feature you'd use, and why. You may also find JavaScript for Playwright Automation useful.

📚 Official documentation: Playwright documentation · ISTQB Glossary of testing terms

FAQs

How do you debug a randomly failing Playwright test?

Reproduce with --repeat-each, open the trace from the failure, and look for one-shot checks, fixed timeouts, missing response waits or shared test data.

Why does a click fail when the element is visible?

It may be covered, animating, disabled or a duplicate. The error names the failed actionability check; fix that cause instead of forcing the click.

Why do tests pass locally but fail in Jenkins?

Environment differences — headless mode, viewport, speed, browser versions, data, time zone, parallel workers. Align the config and use CI traces.

How do you test two users at once?

Create a separate browser context for each user (optionally with its own storage state) and a page in each.

How do you validate a file download?

Wait for the download event, check suggestedFilename(), save it with saveAs() and verify its size or contents.

How do you clean up test data?

Create data through the API in beforeEach or a fixture and delete it in afterEach, so every test is independent.