Prepare for Playwright interview questions grouped by experience level.
Playwright Interview Question & Answers
0-2 Years
Playwright is a genuinely modern browser automation framework, built by Microsoft, for actually testing web applications across Chromium, Firefox, and WebKit. It was built to solve genuinely common pain points teams historically experienced with older automation tools, like flaky waits and genuinely inconsistent cross-browser behavior.
Playwright genuinely includes built-in auto-waiting, communicates with the browser through a genuinely modern protocol rather than the older WebDriver protocol, and ships with its own genuinely bundled browser binaries. Selenium genuinely supports a broader range of browsers and languages but generally requires more genuinely manual handling of waits and driver setup.
Playwright genuinely provides official support for JavaScript and TypeScript, Python, Java, and .NET (C#), letting a team actually write tests in the genuinely specific language their own project already uses.
Playwright genuinely supports Chromium (covering Chrome and Edge), Firefox, and WebKit (covering Safari), letting a genuinely single test suite run across all three major browser engines without needing a genuinely separate driver for each one.
Auto-waiting automatically genuinely waits for an element to actually become ready, visible, enabled, and stable, before performing an action on it, without requiring the test author to genuinely write an explicit wait statement. It solves the genuine problem of a flaky test failing because it tried to interact with an element too early.
npm init playwright@latest genuinely sets up a new Playwright project, installing the genuine necessary dependencies, downloading the browser binaries, and creating a genuinely basic project structure with an example test and a configuration file.
It genuinely creates a playwright.config.ts (or .js) configuration file, an example test file inside a genuine tests directory, and a genuinely separate tests-examples folder demonstrating common patterns, along with installing the actual browser binaries needed to run tests.
The config file genuinely defines project-wide settings, like which browsers to actually test against, the base URL, timeout values, and reporter configuration, letting a team actually manage test settings centrally in one place rather than repeating them in every single test file.
npx playwright test genuinely runs every test file within the configured tests directory, executing them against every genuine browser project defined in the configuration file.
npx playwright test tests/login.spec.ts genuinely runs only that specific file, rather than the entire test suite, useful when you're actually focused on debugging or verifying a genuinely specific test in isolation.
It genuinely downloads the actual browser binaries Playwright needs, Chromium, Firefox, and WebKit, that Playwright itself manages and bundles rather than relying on browsers already genuinely installed on the machine.
Headless mode genuinely runs the browser without an actual visible window, which is faster and typically used in CI. Running headed shows the genuinely actual browser window during test execution, useful for actually watching a test run live while debugging it locally.
A locator represents a genuinely lazy way to actually find an element, only resolving to the genuine, actual DOM element at the exact moment an action is performed. Unlike a Selenium WebElement, which genuinely represents a specific element found at a fixed point in time, a Playwright locator automatically genuinely re-finds the element fresh every single time it's actually used.
getByRole('button', { name: 'Submit' }) genuinely finds an element by its accessible role and name, mirroring how a genuine screen reader or an actual user would identify it. It's genuinely recommended because it tests the application the way a real user actually perceives it, and it's generally more genuinely resilient to a styling change than a CSS class-based selector.
getByText('Welcome back') genuinely finds an element containing the specified visible text, letting you actually locate an element based on what a real user would genuinely see displayed on the page, rather than an internal implementation detail like a class name.
getByTestId() is a genuinely dedicated, built-in Playwright method specifically designed for locating an element by a test ID attribute, using a genuinely configurable default attribute name, offering a slightly cleaner, more genuinely purpose-built syntax than manually writing an equivalent CSS attribute selector yourself.
locator.click() genuinely operates on a Locator object, which lazily re-finds the element, and is the genuinely recommended, modern approach. page.click() is a genuinely older, shorthand method that combines finding and clicking in one single call, but doesn't offer the exact same genuine reusability a stored Locator object provides.
Selectors based on visible role or text tend to be meaningfully more genuinely resilient to a change in the underlying implementation, like a CSS class being renamed during a refactor, since they're genuinely based on what a real user would actually perceive rather than a genuinely fragile internal implementation detail.
await page.getByRole('button', { name: 'Submit' }).click() genuinely locates the button by its accessible role and name, and clicks it, with Playwright's own automatic waiting genuinely ensuring the button is actually ready to be clicked before the action actually happens.
await page.getByLabel('Email').fill('test@example.com') genuinely fills the specified input field with the given text, first genuinely clearing any existing value already present in that field.
fill() genuinely sets an input's value directly and instantly, which is faster and works fine for most cases. pressSequentially() genuinely simulates actual, individual keystrokes one at a time, useful specifically when testing a feature that genuinely reacts to each individual keystroke, like a live search suggestion.
await page.keyboard.press('Enter') genuinely simulates pressing that specific key, useful for a scenario like submitting a form by pressing Enter inside a text field, rather than clicking a separate submit button element directly.
await page.goto('https://example.com') genuinely navigates the browser to the specified URL, and Playwright's own automatic waiting genuinely ensures the page has actually finished its initial load before the test genuinely proceeds to its next step.
await page.getByLabel('Country').selectOption('Canada') genuinely selects the specified option from a standard HTML select element, identified by the dropdown's own genuine accessible label.
await page.screenshot({ path: 'screenshot.png' }) genuinely captures an image of the current page's visible viewport and saves it to the specified file path, useful for actually documenting a test's own state or for later use in a genuine visual comparison.
await page.getByLabel('Accept terms').check() genuinely checks the checkbox if it isn't already checked. Playwright's own check() method is genuinely idempotent, meaning calling it again on an already-checked box has genuinely no additional effect.
expect() genuinely creates an assertion, comparing an actual value against an expected one, and the entire test genuinely fails if that assertion doesn't actually hold true, providing the genuine pass/fail signal that gives a test its actual value.
An assertion like expect(locator).toBeVisible() genuinely retries automatically for a defined timeout period rather than failing genuinely immediately, giving the application a chance to actually reach the expected state, which meaningfully reduces flakiness compared to a genuinely single, immediate check.
await expect(page.getByRole('heading')).toHaveText('Welcome') genuinely asserts the located heading element's text content actually matches the specified string exactly, using Playwright's own genuinely built-in, auto-retrying assertion.
await expect(page.getByText('Success')).toBeVisible() genuinely asserts the specified element is actually present and visible, waiting automatically for it to actually appear rather than failing genuinely immediately if it hasn't loaded yet.
toHaveText() genuinely requires the element's text content to actually match the specified string exactly, in full. toContainText() genuinely only requires the specified string to actually be present somewhere within the element's text, allowing for genuinely additional surrounding text.
await expect(page).toHaveURL('https://example.com/dashboard') genuinely asserts the browser's current URL actually matches the specified value, waiting automatically for a genuine navigation to actually complete if one is still in progress.
test('user can log in', async ({ page }) => { ... }) defines a genuinely single test, with the page fixture automatically genuinely provided by Playwright, giving the test direct access to a genuinely fresh browser page to actually interact with.
test.describe('Login flow', () => { ... }) genuinely groups a set of related tests together under one shared, descriptive name, making genuinely large test suites easier to actually organize and read in a genuine test report.
test.beforeEach() genuinely runs before every single individual test within its scope. test.beforeAll() genuinely runs just once, before the genuinely entire group of tests in that scope actually begins, useful for a genuine one-time setup step that doesn't need to repeat before each test.
test.skip('this test needs fixing', async ({ page }) => { ... }) genuinely marks that specific test to be skipped, and it will genuinely show up as skipped in the test report rather than actually running or being reported as failed.
The page fixture represents a genuinely single browser tab, automatically provided fresh for each individual test by Playwright's own test runner, giving the test a genuinely clean, isolated browser page to actually interact with, without needing to manually create or clean it up yourself.
3-6 Years
Before actually clicking or filling an element, Playwright genuinely verifies it's attached to the DOM, visible, stable (not currently animating), enabled, and, for a fill action, genuinely editable, automatically waiting for all of these to actually be true before proceeding.
The action genuinely fails with a timeout error, and Playwright's own error message typically identifies exactly which specific actionability condition, like visible or stable, was genuinely never satisfied, helping you actually diagnose why the action ultimately failed.
Because Playwright genuinely, automatically waits for actionability before every single action and for a defined condition before every single assertion, a test author genuinely doesn't need to manually insert an explicit wait in most common, ordinary scenarios the way an older tool often required.
The stability check genuinely verifies an element's position on the page hasn't actually changed across two genuinely consecutive animation frames, ensuring Playwright doesn't actually click on an element that's still genuinely mid-animation and about to move to a different, final position.
A genuinely custom condition not directly tied to a standard action or assertion, like waiting for a genuinely specific network request to actually complete, or a custom JavaScript condition on the page, might still require an explicit page.waitForResponse() or page.waitForFunction() call.
A browser context represents a genuinely isolated browser session, with its own cookies, storage, and cache, similar to a genuinely separate incognito window. It lets multiple genuinely independent test sessions run within the exact same browser instance without interfering with each other.
A fresh context genuinely ensures each test starts from a genuinely clean, isolated state, with no leftover cookie or local storage value from a genuinely previous test, which meaningfully reduces the real risk of one test's own state accidentally affecting another test's genuine outcome.
A browser context represents a genuinely isolated session, similar to an incognito window. A page represents a genuinely single tab within that context, and a genuinely single context can actually contain multiple separate pages open simultaneously.
Creating two genuinely separate browser contexts, each with its own genuine page, lets you actually simulate two genuinely independent users interacting with the application simultaneously, since each context is genuinely isolated from the other, just like two genuinely separate real users would be.
A new context genuinely isolates cookies, localStorage, sessionStorage, and cache, ensuring a genuine value set during one test's own execution doesn't accidentally, unexpectedly carry over and affect a genuinely separate, later test.
A fixture provides a genuinely reusable piece of test setup, like the page or browser object, automatically provided to a test function as a parameter. It solves the genuine problem of needing to repeat the exact same setup and teardown logic manually inside every single individual test.
page provides a genuinely fresh browser page. context provides the genuinely isolated browser context that page belongs to. browser provides the underlying genuine browser instance. request provides an API testing client for actually making a direct HTTP request without a browser.
Extending the base test object with test.extend({ myFixture: async ({ page }, use) => { ... } }) lets you actually define a genuinely custom, reusable piece of setup, like a logged-in page state, that multiple different tests can then actually reuse without duplicating that same setup logic in each individual test.
A test-scoped fixture is genuinely created fresh for every single individual test. A worker-scoped fixture is genuinely created once and shared across every test running within the exact same worker process, useful for a genuinely expensive setup step that doesn't need to actually be repeated for every single test.
If genuinely many tests all need to start from an already-logged-in state, a custom authentication fixture handles that login step genuinely once, in one central, reusable place, so a change to the login flow later only genuinely needs to be updated in that one single fixture, rather than every single individual test.
Network interception, using page.route(), lets you actually intercept, modify, or mock a network request the page makes, letting a test genuinely control the exact response an API call receives, without needing a genuinely real, live backend to actually be running during the test.
await page.route('**/api/users', route => route.fulfill({ json: [{ name: 'Anu' }] })) genuinely intercepts any request matching that URL pattern and returns the specified mock response instead of letting the request actually reach a real, live server.
Mocking makes the test genuinely faster and more reliable, since it doesn't depend on a real, live backend actually being available and returning genuinely consistent data, and it lets you actually test a genuinely specific edge case, like an error response, that might be hard to actually reproduce reliably against a real, live system.
await page.waitForResponse('**/api/save') genuinely waits until a request matching that specific URL pattern actually receives a response, letting a test actually confirm a background API call genuinely finished before actually proceeding to its next assertion.
A project defines a genuinely specific test configuration, like running against a particular browser or device, and Playwright's config file can genuinely define several separate projects, letting the exact same test suite actually run against multiple genuinely different browser or device combinations.
Defining separate entries in the projects array of the config file, each specifying a genuinely different browser, lets npx playwright test automatically genuinely run the entire test suite against every configured browser project.
Setting baseURL once lets every test genuinely use a relative path, like page.goto('/login'), rather than needing to actually write out the entire, full URL in every single test, and it also makes it genuinely easy to switch the target environment by just changing that one, single, central value.
Using an environment variable to actually set the baseURL value dynamically, read from within the configuration file itself, lets the exact same test suite genuinely target a genuinely different environment simply by changing that one environment variable at runtime.
6-8 Years
page.getByRole('listitem').filter({ hasText: 'Anu' }).getByRole('button') genuinely narrows the search to just the specific list item containing that text, then locates the button within that specific, narrowed-down item, letting you actually target an element precisely within a genuinely repeated structure.
The Trace Viewer lets you actually replay a completed test run visually, step by step, including a genuine screenshot at every action, network requests, and console logs, solving the genuine problem of a test failing in CI with no genuinely easy way to actually see exactly what happened during that specific run.
Setting trace: 'on-first-retry' (or another genuine trace option) in the configuration file tells Playwright to actually record a trace, which can then be actually opened and inspected in detail using the Trace Viewer whenever a test genuinely fails.
npx playwright codegen genuinely opens a real browser and records every action you actually perform in it, automatically generating the genuinely corresponding Playwright test code, solving the genuine problem of writing a first, initial draft of a test manually from scratch by hand.
npx playwright test --debug genuinely opens the Playwright Inspector, letting you actually step through the test action by action, pausing at each one to actually inspect the page's own real state at that exact moment, rather than only seeing the genuinely final pass or fail result.
By default, Playwright can genuinely capture a screenshot, a video recording, and a trace at the exact moment of failure, depending on the configuration, giving you genuinely rich, detailed context to actually diagnose exactly why that specific test actually failed.
retain-on-failure genuinely records a video or trace for every single test but only keeps the file if that specific test actually failed, discarding it for a genuinely passing test to save disk space. The on setting genuinely keeps the recording for every test regardless of outcome, which uses meaningfully more storage but can be useful when you specifically need to review a passing test's own behavior too.
Playwright genuinely, automatically distributes test files across multiple worker processes running simultaneously, letting genuinely independent test files actually execute concurrently rather than one strictly after another, meaningfully reducing the overall total test suite execution time.
By default, Playwright genuinely runs different test files in parallel across separate workers, but tests within the exact same single file genuinely run sequentially by default, unless you explicitly genuinely enable full parallelism for tests within that same file using test.describe.configure({ mode: 'parallel' }).
Sharding splits the genuinely entire test suite across multiple, genuinely separate machines (or CI jobs) running simultaneously, letting a genuinely very large test suite complete in a fraction of the time it would take running entirely on just one single machine.
npx playwright test --shard=1/4 genuinely runs only the first quarter of the total test suite, letting you actually run four genuinely separate shard commands (1/4, 2/4, 3/4, 4/4) across four separate CI jobs, each running in genuine parallel to complete the full suite faster overall.
Each test genuinely needs to be independent, not relying on shared, mutable state or a genuinely fixed execution order, since tests running in genuinely different parallel workers could execute in essentially any order relative to each other.
8-10 Years
Visual regression testing genuinely compares a screenshot taken during a test run against a genuinely previously approved, baseline screenshot, using toHaveScreenshot(), flagging a genuinely meaningful pixel difference as a failure, catching a visual bug a purely functional assertion wouldn't actually detect.
Font rendering and anti-aliasing can genuinely differ subtly between operating systems and browser versions, producing a genuinely false-positive pixel difference even though nothing meaningfully actually changed, which is exactly why visual regression tests are typically genuinely run consistently within one specific, controlled environment, like a Docker container.
Component Testing lets you actually test a genuinely individual UI component in isolation, mounting it directly rather than testing it through the entire, complete application. It's genuinely faster than a full end-to-end test and lets you actually test a component's own behavior independent of the rest of the application around it.
Running npx playwright test --update-snapshots genuinely regenerates the baseline screenshots to actually match the current state, which should genuinely only be done deliberately, after actually confirming the new appearance is genuinely correct and intentional.
Masking a genuinely specific dynamic region of the page before taking the screenshot, using Playwright's own mask option, excludes that genuinely variable content from the comparison entirely, letting the visual test genuinely focus only on the actual, stable parts of the page that should genuinely stay consistent.
I'd prioritize genuinely visually critical, high-traffic pages and reusable shared components where a genuinely subtle visual regression would actually affect many different pages at once, rather than attempting to genuinely visually test every single page in an application, which would create a genuinely large, high-maintenance baseline set.
The pipeline genuinely installs Playwright's browsers and dependencies, then runs npx playwright test as a genuine build step, typically publishing the generated HTML report as a genuine build artifact so a team can actually review results, including any captured trace, after the run completes.
The official Docker image genuinely bundles the exact correct, tested versions of every browser dependency, ensuring genuinely consistent behavior between a developer's own local machine and the CI environment, avoiding a genuinely subtle difference caused by a mismatched system library or browser version.
Configuring a genuinely limited number of retries, like retries: 2, in the CI configuration specifically (while typically keeping retries at zero locally during active development) lets an occasional, genuinely transient flake pass on a retry, while still surfacing a genuinely, consistently failing test as an actual failure.
Playwright's own built-in HTML reporter genuinely generates a detailed, interactive report including a screenshot and trace for any failure, which the CI pipeline can then actually publish as a downloadable artifact or host directly for the team to actually review after each run.
Combining test sharding across multiple genuinely parallel CI jobs with a genuinely fast, prioritized subset of critical tests run on every commit, while reserving the genuinely full, complete suite for a scheduled run or before an actual production deployment, balances real speed against real, thorough coverage.
Using Playwright's own storageState feature, you can actually log in once, save the resulting authenticated browser state to a file, and then reuse that genuinely saved state across every subsequent test, avoiding the genuine overhead and real risk of flakiness from repeating the login flow before every single test.
The saved state file genuinely contains real session tokens or cookies capable of authenticating as that test account, so it needs to be treated with the same genuine care as any other credential, stored securely, excluded from version control, and scoped to a dedicated test account rather than a genuinely real, actual user's own account.
10+ Years
I'd weigh Playwright's genuine advantages, auto-waiting, faster execution, built-in tracing, against the real cost and disruption of migrating a genuinely large, already-working existing Selenium suite. A genuinely new project often benefits from starting fresh with Playwright, while a genuinely large, stable existing suite might not justify a full migration without a genuinely specific, concrete pain point driving it.
I'd migrate incrementally, starting with genuinely newly-written tests in Playwright while the existing Selenium suite continues running, gradually porting a genuinely high-value existing test over time, rather than attempting a genuinely large, disruptive, all-at-once rewrite of the entire existing suite.
I check whether locators genuinely favor a resilient, role-based approach over a genuinely fragile CSS selector, whether custom fixtures are genuinely used appropriately to avoid duplicated setup logic, and whether the design accounts for genuine parallel execution from the very start.
Automate what can genuinely be automated, an ESLint plugin or a custom lint rule flagging a genuinely risky pattern, like a raw CSS selector where a role-based locator would be more resilient, directly in code review, rather than relying purely on manual review.
I'd weigh the genuine time and infrastructure cost saved by not maintaining a genuinely in-house, scaled parallel execution setup against the real, ongoing subscription cost, and whether the organization's own current pain, limited local execution capacity, genuinely justifies that specific investment.
I'd look for genuinely gradual accumulation, growing test data volume never actually cleaned up, an increasing number of genuinely flaky tests nobody's fixed, or an infrastructure that hasn't genuinely scaled to match the suite's own growing size, since this kind of gradual degradation often traces back to accumulated technical debt rather than one obvious cause.
Track pipeline execution time and genuine pass rate trends over time, alerting if either degrades meaningfully, since a test suite that's quietly becoming slower or less reliable eventually erodes the whole team's trust in it and their overall willingness to genuinely rely on it before a real deployment.
Treat the fixture's actual signature and behavior as a genuine contract with every consuming test. Adding something new is generally safe. Changing or removing an existing capability needs a documented deprecation period and direct communication with every dependent team before actual removal.
I'd first genuinely check whether the failures share a genuine common root cause, a breaking change documented in the new version's own release notes, rather than assuming genuinely many separate, unrelated bugs suddenly appeared at the exact same coincidental moment, and consider rolling back the version while properly investigating.
I'd load test the genuine parallel execution infrastructure itself under a realistically larger, expected test count, identifying whether worker capacity, browser binary provisioning, or reporting infrastructure is genuinely likely to become the actual bottleneck first, and address that specific constraint proactively.
This is a judgment question interviewers use to see how you reason under genuine uncertainty, not to test a specific textbook fact. A strong answer names the actual constraint that forced the decision, the realistic options that were genuinely on the table, why you picked one knowing it wasn't guaranteed to be right, and what you'd do differently with what you know now.
I'd walk through an actual, real scenario together, showing concretely how a genuinely minor CSS class rename during a refactor broke their specific test, rather than explaining locator resilience as an abstract best practice in isolation. Seeing that genuinely real, concrete breakage tends to build that habit far more effectively.
I'd point to a specific, real, already-experienced incident where the team's own habit of ignoring or rerunning a flaky test caused a genuinely real bug to slip through into production, and show concretely how properly fixing that specific flaky test, and using the Trace Viewer to actually diagnose it, would have genuinely caught it instead.
I'd bring the actual, concrete question of what the test is genuinely, specifically trying to verify into the discussion, rather than a general, abstract preference for one approach over the other. Grounding the discussion in the specific, real goal of that particular test resolves it faster than an abstract debate.
I'd translate the investment into terms leadership already tracks: the engineering hours currently lost waiting on a slow, unparallelized test suite each day, and the cost of a specific past incident that a flaky or skipped test allowed through to production. Framed as recovered engineering time and reduced production risk, it competes far better for prioritization than framed as tooling spend for its own sake.




