Resource library

Automation Interview

Playwright Coding Interview Questions with Answers

Practice Playwright coding interview questions with runnable TypeScript answers for locators, events, fixtures, APIs, polling, debugging, and framework design.

17 min read | 2,754 words

TL;DR

Prepare for Playwright coding rounds by practicing semantic locators, race-free event waits, retrying assertions, isolated fixtures, API-to-UI setup, polling, and code review. Explain both what your test proves and where its boundary ends.

Key Takeaways

  • Interviewers score behavioral intent, synchronization, isolation, and diagnostics in addition to syntax.
  • Select dynamic data by a stable business key, not by DOM position.
  • Create popup, download, and response waits before the triggering action.
  • Use fixtures to create uniquely owned records and guarantee teardown after failures.
  • Poll eventual state with a bound and avoid retrying non-idempotent side effects.
  • Explain what mocked, browser-only, and single-engine tests do not prove.
  • Use trace evidence and one hypothesis at a time when debugging live.

Strong playwright coding interview questions test whether a candidate can turn product risk into reliable code, not whether they can recite method names. Expect to write semantic locators, synchronize on observable outcomes, handle events without races, design isolated fixtures, and explain why a test will remain trustworthy in parallel CI.

This guide gives runnable TypeScript solutions and the reasoning interviewers listen for. Practice by writing each answer without copying it, running it, and then explaining failure modes. The explanation often distinguishes an SDET who understands automation from someone who only knows syntax.

TL;DR

Interview area What a strong solution shows Warning sign
Locators User-facing semantics and strict unique targeting Long CSS or XPath copied from DevTools
Synchronization Web-first assertions and event promises created before actions Fixed sleeps and broad timeout increases
Async TypeScript Every relevant promise awaited, errors preserved Floating promises or swallowed failures
Test data Unique ownership and reliable teardown Shared mutable accounts and ordered tests
Framework design Small domain abstractions with visible behavior Wrappers around every Playwright method
CI diagnosis Trace-first evidence and controlled reproduction Calling every failure flaky

1. Playwright coding interview questions and evaluation criteria

Coding rounds usually combine a small implementation with follow-up questions. The interviewer evaluates more than whether the test passes once. They look for correctness, readability, actionability, isolation, and diagnostic quality.

A useful scoring model is:

Dimension Strong evidence
Behavioral intent Test name and assertions describe user or system outcome
Locator quality Roles, labels, and scoped composition produce one clear target
Synchronization Assertions retry; event waits are registered before actions
Isolation Test creates or controls its own data and cleans it safely
Failure evidence Assertions are specific; traces and context can explain a failure
Maintainability Abstraction captures domain meaning without hiding Playwright

Before typing, restate the requirement. Ask yourself which layer owns the behavior, what visible state proves success, and what could race. You do not need to narrate every keystroke, but explain major choices such as why a role locator is more stable than a CSS class or why a response promise starts before a click.

For a live exercise, get a thin correct path running first. Then improve data isolation, negative behavior, and readability. If the environment is incomplete, state assumptions and create a minimal reproduction with page.setContent(). That shows you can separate a framework issue from application complexity.

Interviewers also value honest boundaries. A UI Clock test cannot prove server time. A mocked API test cannot prove the real service contract. A Chromium-only result is not cross-browser evidence. Say what the test proves and what complementary coverage is required.

2. Locator exercise: sign-in form without brittle selectors

Prompt: Write a Playwright test that submits a sign-in form and verifies a successful dashboard state. The DOM has generated CSS classes that change every build.

import { test, expect } from '@playwright/test';

test('signs in with valid credentials', async ({ page }) => {
  await page.setContent(`
    <form aria-label="Sign in">
      <label>Email <input type="email"></label>
      <label>Password <input type="password"></label>
      <button type="submit">Sign in</button>
    </form>
    <main hidden><h1>Dashboard</h1></main>
    <script>
      document.querySelector('form').addEventListener('submit', event => {
        event.preventDefault();
        document.querySelector('form').hidden = true;
        document.querySelector('main').hidden = false;
      });
    </script>
  `);

  const form = page.getByRole('form', { name: 'Sign in' });
  await form.getByLabel('Email').fill('candidate@example.com');
  await form.getByLabel('Password').fill('correct-password');
  await form.getByRole('button', { name: 'Sign in' }).click();

  await expect(page.getByRole('heading', { name: 'Dashboard' }))
    .toBeVisible();
  await expect(form).toBeHidden();
});

The solution scopes fields to a named form and locates them through labels. It does not wait for an arbitrary delay after Submit. The heading assertion waits for the outcome that matters.

A follow-up may introduce two Sign in buttons. Strictness should fail if the locator is ambiguous. Scope to the form rather than calling .first(). If the real product lacks labels or roles, identify that as an accessibility and testability defect. Use a test ID only when semantic locators do not represent the intended contract. The getByRole locator guide explains accessible-name behavior in depth.

Never put real credentials in source code. Use a secure environment or setup fixture, validate required variables, and avoid printing secrets in reports.

3. Coding task: select a dynamic table row by business key

Prompt: Find order ORD-1042, verify its status, and click its Refund button. Row order can change.

test('refunds a completed order by order ID', async ({ page }) => {
  await page.setContent(`
    <table>
      <caption>Orders</caption>
      <thead><tr><th>Order</th><th>Status</th><th>Action</th></tr></thead>
      <tbody>
        <tr><td>ORD-1041</td><td>Pending</td><td><button>Refund</button></td></tr>
        <tr><td>ORD-1042</td><td>Completed</td><td><button>Refund</button></td></tr>
      </tbody>
    </table>
    <p role="status"></p>
    <script>
      document.querySelectorAll('button').forEach(button => {
        button.addEventListener('click', () => {
          const id = button.closest('tr').cells[0].textContent;
          document.querySelector('[role=status]').textContent =
            id + ' refund requested';
        });
      });
    </script>
  `);

  const orderRow = page.getByRole('row').filter({
    has: page.getByRole('cell', { name: 'ORD-1042', exact: true })
  });

  await expect(orderRow.getByRole('cell', { name: 'Completed' }))
    .toBeVisible();
  await orderRow.getByRole('button', { name: 'Refund' }).click();
  await expect(page.getByRole('status'))
    .toHaveText('ORD-1042 refund requested');
});

This selects by a business key, not nth(2). The inner cell locator is evaluated within each row candidate. The final assertion proves the targeted order drove the action.

Follow-up questions may ask how to handle pagination or virtualization. For server pagination, navigate or query until the required page is loaded, then assert the row. For a virtual grid, locate the visible semantic row and use the component's supported scroll behavior. Do not scrape all DOM rows and assume unrendered data is absent from the product.

If the task asks to return table data, wait for an expected count or loading completion before calling allTextContents(). Collection extraction does not automatically wait for a final list length in the same way a locator assertion does.

4. Async event race: popup and download questions

Prompt: A click opens a report in a new tab. Write the test without missing the popup event.

test('opens a report preview in a new page', async ({ page }) => {
  await page.setContent(`
    <button>Preview report</button>
    <script>
      document.querySelector('button').addEventListener('click', () => {
        const popup = window.open('', '_blank');
        popup.document.title = 'Report Preview';
        popup.document.body.innerHTML = '<h1>Q2 Report</h1>';
      });
    </script>
  `);

  const popupPromise = page.waitForEvent('popup');
  await page.getByRole('button', { name: 'Preview report' }).click();
  const popup = await popupPromise;

  await expect(popup).toHaveTitle('Report Preview');
  await expect(popup.getByRole('heading', { name: 'Q2 Report' }))
    .toBeVisible();
});

The event promise is created before the click because the popup may open immediately. Starting the wait afterward introduces a race.

The equivalent download pattern is:

test('downloads the CSV report', async ({ page }) => {
  await page.setContent(`
    <a download="report.csv"
       href="data:text/csv;charset=utf-8,id%2Cstatus%0A1%2Cready">
      Download CSV
    </a>
  `);

  const downloadPromise = page.waitForEvent('download');
  await page.getByRole('link', { name: 'Download CSV' }).click();
  const download = await downloadPromise;

  expect(download.suggestedFilename()).toBe('report.csv');
  const target = test.info().outputPath('report.csv');
  await download.saveAs(target);
});

A strong follow-up answer notes that saving a file proves download mechanics, not CSV contents. Read and parse the artifact when its contract matters, and use a unique output path supplied by test.info() so parallel workers do not overwrite one shared file.

5. Network task: API setup and UI verification

Prompt: The UI lists recommendations from an API. Test the rendering deterministically and explain what the mock does not prove.

test('renders recommended courses from the API', async ({ page }) => {
  await page.route('https://api.example.test/recommendations', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      headers: { 'access-control-allow-origin': '*' },
      body: JSON.stringify({
        items: [
          { id: 'pw-1', title: 'Reliable Playwright Tests' },
          { id: 'api-1', title: 'API Contract Testing' }
        ]
      })
    });
  });

  await page.setContent(`
    <main>
      <h1>Recommended courses</h1>
      <ul aria-label="Recommendations"></ul>
    </main>
    <script>
      fetch('https://api.example.test/recommendations')
        .then(response => response.json())
        .then(data => {
          const list = document.querySelector('ul');
          for (const item of data.items) {
            const entry = document.createElement('li');
            entry.textContent = item.title;
            list.appendChild(entry);
          }
        });
    </script>
  `);

  const recommendations = page.getByRole('list', {
    name: 'Recommendations'
  }).getByRole('listitem');

  await expect(recommendations).toHaveText([
    'Reliable Playwright Tests',
    'API Contract Testing'
  ]);
});

The test proves the client renders a controlled valid payload. It does not prove the real service returns that schema, authenticates correctly, or integrates in the deployed environment. Add contract or integration coverage for those risks.

Follow-ups often include error handling. Fulfill a 500 response and assert a retry or error state. Return an empty list and verify the empty-state message. Return malformed data only if the product is expected to defend against it. Each case should state its business expectation rather than merely exercising a branch.

For API-created test records, use the built-in request fixture, verify the setup response, provide the record to the UI, and delete it during teardown. Do not use UI setup for every prerequisite when the interview is specifically about a downstream UI behavior.

6. Fixture task: unique data with reliable teardown

Prompt: Design a fixture that creates a unique project for each test and removes it afterward.

import { test as base, expect } from '@playwright/test';

type Project = { id: string; name: string };
type AppFixtures = { project: Project };

const testWithProject = base.extend<AppFixtures>({
  project: async ({ request }, use) => {
    const name = `e2e-${crypto.randomUUID()}`;
    const createResponse = await request.post('/api/projects', {
      data: { name }
    });
    expect(createResponse.ok()).toBeTruthy();
    const project = await createResponse.json() as Project;

    await use(project);

    const deleteResponse = await request.delete(`/api/projects/${project.id}`);
    expect([204, 404]).toContain(deleteResponse.status());
  }
});

testWithProject('shows the project details', async ({ page, project }) => {
  await page.goto(`/projects/${project.id}`);
  await expect(page.getByRole('heading', { name: project.name }))
    .toBeVisible();
});

use(project) separates fixture setup from teardown. Teardown runs even after the test body fails. Accepting 404 during cleanup can make deletion idempotent if the scenario itself deletes the project.

In a real framework, setup assertion messages should include safe context. If creation fails, report status and a redacted body. Do not log tokens or personal test data. Cleanup failure policy also deserves discussion. A leaked project can pollute future runs, while a cleanup assertion can obscure the primary test failure. Teams may report cleanup as a separate attachment or error while preserving the original cause.

Worker-scoped fixtures are useful for expensive read-only setup or accounts explicitly partitioned by worker. Test-scoped mutable data is safer by default. Browser isolation does not prevent two tests from editing the same server record.

7. Coding exercise: retry an eventually consistent API

Prompt: An export becomes ready asynchronously. Poll its status without writing a manual loop and without sleeping blindly.

test('waits for the export to become ready', async ({ page }) => {
  let attempts = 0;

  await page.route('https://api.example.test/exports/42', async route => {
    attempts += 1;
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      headers: { 'access-control-allow-origin': '*' },
      body: JSON.stringify({
        status: attempts >= 3 ? 'ready' : 'processing'
      })
    });
  });

  await expect.poll(async () => {
    return page.evaluate(async () => {
      const response = await fetch('https://api.example.test/exports/42');
      const body = await response.json();
      return body.status;
    });
  }, {
    timeout: 5_000,
    intervals: [100, 250, 500]
  }).toBe('ready');
});

expect.poll() retries the async producer and applies the matcher to each returned value. The timeout is bounded and the intervals express an intentional polling cadence. For an external production-like system, choose values aligned with service behavior and rate limits.

expect().toPass() is useful when a block contains multiple related assertions that must eventually pass. Do not wrap an entire end-to-end flow in toPass(), because repeated clicks or creates can produce duplicate side effects. Poll read-only status when possible.

A manual while loop can be valid if the policy needs jitter, cancellation, status-specific backoff, or rich attempt logging. Explain its termination condition and preserve the last error. An infinite loop or recursive retry without a deadline is unacceptable in CI.

The async and await guide for testers covers promise completion and error propagation expected in this exercise.

8. Code review question: find the defects

Prompt: Review this test and identify reliability problems.

test('updates settings', async ({ page }) => {
  page.goto('/settings');
  await page.locator('div:nth-child(3) button').click({ force: true });
  await page.waitForTimeout(5000);
  expect(await page.locator('.toast').textContent()).toBe('Saved');
});

A strong review identifies at least five issues:

  1. page.goto() is not awaited, so the click races navigation.
  2. The structural selector depends on DOM position and does not reveal intent.
  3. force: true bypasses actionability and may hide a covered or disabled button.
  4. waitForTimeout(5000) is slow and does not prove readiness.
  5. textContent() plus a generic assertion samples once instead of retrying the UI state.
  6. The selector .toast may match an unrelated or hidden notification.
  7. The test does not identify which setting changed.

An improved version is:

test('enables weekly summary emails', async ({ page }) => {
  await page.goto('/settings');

  const weeklySummary = page.getByLabel('Weekly summary emails');
  await weeklySummary.check();
  await page.getByRole('button', { name: 'Save settings' }).click();

  await expect(page.getByRole('status')).toHaveText('Settings saved');
  await expect(weeklySummary).toBeChecked();
});

If the Save action has a critical backend contract, create a waitForResponse() promise before clicking and inspect status or payload. Do not add network coupling when the visible saved state is already backed by a separately tested service and the current exercise focuses on UI behavior.

9. Playwright coding interview questions on framework design

Framework questions test tradeoffs, not folder memorization. A maintainable structure separates behavior from mechanics:

tests/
  ui/
  api/
fixtures/
  app-test.ts
pages/
  checkout-page.ts
clients/
  orders-client.ts
builders/
  order-builder.ts
playwright.config.ts

Page objects should expose meaningful operations or stable component boundaries. They should not hide every assertion, add arbitrary waits, or turn getByRole() into a custom wrapper with no domain value.

import { Page, Locator, expect } from '@playwright/test';

export class CheckoutPage {
  readonly page: Page;
  readonly total: Locator;

  constructor(page: Page) {
    this.page = page;
    this.total = page.getByTestId('order-total');
  }

  async placeOrder(): Promise<void> {
    await this.page.getByRole('button', { name: 'Place order' }).click();
    await expect(this.page.getByRole('heading', { name: 'Receipt' }))
      .toBeVisible();
  }
}

Whether assertions belong inside page objects is a team decision. A navigation guarantee inside placeOrder() can make the operation safe, while business-specific totals may belong in the test. State your convention and keep it consistent.

Configuration should define projects, evidence, retries, base URL, and environment inputs. It should not contain secrets. CI should use a lockfile install, aligned browsers, and artifacts on failure. Parallelism comes after isolation. Sharding a suite that shares one user increases collisions.

For advanced candidate expectations, compare your answers with Playwright interview questions for experienced engineers.

10. Live coding strategy and communication

Start with a behavioral test name and one happy-path assertion. Use the simplest locator that matches user semantics. Run the smallest test early so environment issues surface before the solution becomes large.

Narrate decisions at useful boundaries:

  • "I am registering the popup wait before the click to avoid missing the event."
  • "I am selecting the row by order ID because visual order is not stable."
  • "This mock proves rendering, so I would add contract coverage for the real service."
  • "The fixture owns cleanup, and the unique name makes parallel execution safe."

When code fails, read the first error and inspect actual state. Do not immediately increase timeouts or add force. Use Playwright Inspector, UI Mode, or trace evidence when available. State a hypothesis, make one change, and rerun the focused case.

If time runs short, leave the code in a correct smaller state and explain the next tests by risk. One deterministic happy path plus a precise negative-test plan is stronger than three unfinished tests. Call out security boundaries, data cleanup, and cross-browser scope where relevant.

After the round, review whether your code would survive rerendering, parallel workers, and a slower CI environment. The interviewer is evaluating production judgment through a small task.

Interview Questions and Answers

Q: What is Playwright auto-waiting?

Before actions, Playwright waits for relevant actionability conditions such as a unique resolved target, visibility, stability, enabled state, and whether the element can receive events. The exact checks depend on the action. Auto-waiting does not guarantee that a business workflow or backend process is complete.

Q: What is a web-first assertion?

A web-first assertion evaluates a locator and retries until the expected condition is met or its timeout expires. Examples include toBeVisible() and toHaveText(). This is safer for changing UI state than reading a value once and applying a generic assertion.

Q: Why is waitForTimeout() usually a poor synchronization choice?

It waits for elapsed time rather than evidence. The delay is wasteful when the system is fast and insufficient when it is slow. A specific UI state, request, response, URL, or event is a better completion signal.

Q: How do you avoid missing a popup, download, or response?

Create the event or response promise before the action that can trigger it, then perform the action and await the promise. This ordering closes the race in which the event occurs before the listener is registered.

Q: What causes a Playwright strictness violation?

An operation that expects one element received a locator resolving to multiple elements. I fix the locator by adding meaningful scope or a unique accessible name. I use first() or nth() only when position is genuinely the product requirement.

Q: How would you handle authentication in a large suite?

I would create authenticated storage state through a setup project or controlled fixture, protect the state file as sensitive, and use separate server-side accounts or data for parallel mutations. I would keep a smaller set of tests for the sign-in UI itself.

Q: What belongs in a page object?

Stable locators, component boundaries, and domain-meaningful operations can belong there. Page objects should not wrap every Playwright method, hide arbitrary sleeps, or swallow assertion failures. Tests should still make business intent visible.

Q: How do you test an eventually consistent API?

I poll a read-only status with a bounded timeout and intentional intervals, using expect.poll() or a small retry helper. I preserve the last failure and stop at a clear terminal condition. I avoid retrying side-effecting creates or clicks unless idempotency is guaranteed.

Q: How do you make test data safe for parallel execution?

Each test or worker gets uniquely named mutable records and accounts appropriate to its scope. The owner cleans only its own data through reliable teardown. Browser contexts alone do not isolate shared backend state.

Q: What evidence do you inspect for a CI-only failure?

I start with the first meaningful assertion or action error, then inspect the trace, screenshot, console, network, browser project, environment inputs, and data identity. I reproduce the smallest case with CI-like parallelism and configuration before changing timeouts.

Q: When is getByTestId() appropriate?

It is appropriate when no reliable user-facing locator represents the target or when the team wants an explicit automation contract. Roles and labels remain preferable for interactive UI because they align with how users and accessibility APIs identify controls.

Q: What is the difference between retries and polling?

Test retries rerun a failed test, often from fixture setup, and can reveal intermittent behavior. Polling repeatedly checks one eventually consistent condition within the same test. Neither should hide shared-state defects or uncontrolled dependencies.

Q: How would you test file upload?

I would call setInputFiles() on the file input or its associated label, using a fixture path or an in-memory buffer. Then I would assert the application's parsed filename, preview, validation, or backend result, not only that the input received a file.

Q: How do you decide what to mock?

I mock dependencies when the target risk is client behavior under controlled responses, such as loading, empty, and error states. I keep separate integration or contract tests for the real boundary. I do not mock the behavior the current test claims to prove.

Common Mistakes

  • Writing CSS or XPath chains before checking roles, labels, and scoped locators.
  • Forgetting await on navigation, actions, assertions, or cleanup.
  • Registering popup, download, or response waits after the triggering action.
  • Using .first() to silence ambiguity instead of creating a meaningful locator.
  • Adding force: true without explaining why a real user can complete the action.
  • Sleeping for fixed time rather than waiting for a business state.
  • Sharing one mutable account or output file across parallel tests.
  • Catching a Playwright error, logging it, and letting the test pass.
  • Mocking the backend and claiming the test proves end-to-end integration.
  • Creating abstraction layers that hide locators and useful failure stacks.
  • Retrying a side-effecting block that can create duplicate records.
  • Exposing passwords, cookies, tokens, or sensitive payloads in source and reports.
  • Increasing suite-wide timeouts before diagnosing the specific wait.
  • Describing every failure as flakiness without collecting trace evidence.

Conclusion

The best preparation for playwright coding interview questions is deliberate practice with runnable problems. Write semantic locators, register event waits before actions, use retrying assertions, own test data through fixtures, and state what each mock or Clock control does not prove.

Choose three exercises from this guide and solve them from a blank file. Run each in parallel, introduce a deliberate failure, and explain the trace. That practice builds the judgment interviewers need to see and the reliability production teams need after you are hired.

Interview Questions and Answers

What is Playwright auto-waiting?

Before an action, Playwright waits for the action's relevant conditions, which can include a unique target, visibility, stability, enabled state, and event reception. This reduces manual synchronization. It does not prove that an unrelated backend workflow is complete.

What is a web-first assertion?

A web-first assertion retries a locator or page observation until it matches or times out. toBeVisible() and toHaveText() are common examples. They are safer for changing UI state than reading a value once and applying a generic assertion.

Why avoid waitForTimeout in production tests?

A fixed sleep waits for time rather than evidence. It is slower than necessary on fast runs and may still be insufficient on slow runs. I wait for a specific UI state, URL, request, response, or event instead.

How do you avoid a popup or download race?

I create the waitForEvent promise before the action that triggers the event. Then I perform the action and await that promise. This ordering prevents a fast event from occurring before the listener exists.

What causes strict mode violations?

A single-target operation received a locator matching multiple elements. I add meaningful scope, accessible name, or a stable business key. I do not use first() unless the first position is explicitly the behavior.

How would you handle authentication in a Playwright suite?

I prepare storage state through a trusted setup project or fixture and keep the file protected because it may contain credentials. Tests that mutate server state receive separate accounts or records. A smaller suite still exercises the real sign-in UI.

What belongs in a page object?

Stable component boundaries and domain-meaningful operations are good candidates. The object should not wrap every Playwright call, hide arbitrary sleeps, or swallow assertion errors. Business intent should remain visible in the test.

How do you poll an eventually consistent API?

I retry a read-only status with a bounded timeout and deliberate intervals, often using expect.poll(). I stop at a clear terminal condition and preserve the last failure. I avoid retrying creates or other side effects unless they are idempotent.

How do you make test data parallel-safe?

Each test or worker owns unique mutable records and deletes only what it created. Names can include a UUID or worker-aware identifier. Browser contexts isolate client session state, but not a shared backend account.

What evidence helps diagnose CI-only failures?

I inspect the first meaningful error, trace, screenshot, console, network, browser project, configuration, and safe data identity. Then I reproduce the smallest test with CI-like workers and environment inputs. I change one hypothesis at a time.

When is getByTestId appropriate?

It is appropriate when user-facing semantics cannot identify the target reliably or when the team defines an explicit automation contract. Roles and labels remain preferable for normal controls because they align the test with accessibility behavior.

What is the difference between test retry and polling?

A test retry reruns a failed test, typically with fresh fixture lifecycle, and can expose intermittent behavior. Polling repeatedly reads one eventual condition inside a test. Neither mechanism should hide shared state or an uncontrolled race.

How would you test file upload?

I use setInputFiles() with a fixture path or an in-memory buffer. Then I assert the product result, such as parsed filename, preview, validation message, or backend record. Setting the input alone does not prove upload processing succeeded.

How do you decide what to mock?

I mock a dependency when the target risk is client behavior under controlled states such as loading, empty, success, and error. I add contract or integration coverage for the real boundary. I never mock the exact behavior the test claims to prove.

Frequently Asked Questions

What coding questions are asked in a Playwright interview?

Common tasks include writing robust locators, selecting dynamic rows, handling popups or downloads, mocking an API, creating fixtures, polling eventual state, and reviewing flaky code. Senior rounds add framework and CI tradeoffs.

Should I use JavaScript or TypeScript in a Playwright interview?

Use the language requested by the interviewer. TypeScript is common because Playwright provides strong types, but correct asynchronous behavior and test design matter more than adding complex type machinery.

How should I prepare for Playwright live coding?

Solve small runnable exercises from a blank file, run them in parallel, and introduce deliberate failures. Practice explaining locator choice, event ordering, data ownership, and the evidence you would inspect.

Are CSS selectors acceptable in a Playwright interview?

Yes, when structure is the real contract and a user-facing locator does not fit. For normal controls, roles and labels usually communicate intent and survive markup refactoring better than long CSS chains.

How do I answer a flaky test debugging question?

Start with the first meaningful error and trace, form a specific hypothesis, and reproduce the smallest case under CI-like settings. Check locator ambiguity, readiness, event races, data sharing, and environment inputs before increasing timeouts.

What framework design answer do interviewers expect?

They expect clear boundaries, domain-focused abstractions, typed fixtures, reliable cleanup, and visible test intent. Avoid claiming that one folder structure or wrapping every Playwright method is universally correct.

How many Playwright questions should I practice?

Depth matters more than a memorized count. Practice enough scenarios to explain locators, assertions, events, network control, fixtures, parallelism, debugging, and CI through runnable code.

Related Guides