Q&A
Playwright questions, answered
Real Playwright and JavaScript questions answered with direct explanations, working code snippets, and citations to official documentation.
Locators
- What is a locator in Playwright?A locator is Playwright's way of finding an element on the page at the moment you act on it. Every action and assertion through a locator waits and retries until the element is ready, which is why locators are the centre of Playwright's auto-waiting.
- What is the difference between getByRole and getByText in Playwright?
getByRolefinds an element by what it is (a button, link or heading) and its accessible name, whilegetByTextfinds any element containing the text, whatever kind of element it is. UsegetByRolefor things a user interacts with, andgetByTextfor plain text such as a message or a note. - What does strict mode mean for Playwright locators?Strict mode means a locator used for an action, or for an assertion about one element, must match exactly one element; if it matches two or more, Playwright throws a strict mode violation instead of guessing. Fix it by making the locator say which element you mean, for example with
exact: trueor by finding the element inside its card. - How do I select an element by data-testid in Playwright?Use page.getByTestId('your-id') to select an element by its dedicated test attribute. By default, Playwright matches elements whose data-testid attribute equals the provided string.
- When should I use first() or nth() on a Playwright locator?Use first() or nth() only when DOM order is the actual test requirement, such as verifying the topmost search result or table header. When targeting a specific item, use semantic filters like filter({ hasText }) instead.
- How do I find an element inside another element in Playwright?Chain locators together by invoking locator methods on an existing locator. For example, parentLocator.getByRole('button') searches strictly inside the DOM subtree of parentLocator.
- What is the difference between page.locator() and getByRole() in Playwright?page.locator() takes a CSS or XPath selector, so it finds elements by how the page is built. getByRole() finds elements by their role and accessible name, the way a user or a screen reader perceives them, which is why Playwright recommends it first.
- Should I use CSS or XPath selectors in Playwright?Prefer neither: Playwright recommends role, text, label and test id locators first, because CSS and XPath tend to break when the markup changes. When you do need a raw selector, choose a short CSS selector on a stable attribute, because CSS reaches into open shadow roots and XPath does not.
- How do I find an input by its label in Playwright?Use page.getByLabel('Label text'), which finds the form control associated with that label. It works with a <label for> pointing at the input's id, a <label> wrapped around the input, an aria-labelledby reference, or an aria-label attribute.
- What does getByPlaceholder do in Playwright?page.getByPlaceholder('text') finds an input by the hint text in its placeholder attribute. Playwright suggests it for form fields that have no label but do have a placeholder.
- How do I filter a list of elements by text in Playwright?Call filter({ hasText: 'some text' }) on a locator that matches the whole list; it keeps only the elements that contain that text somewhere inside them. Use hasNotText to keep the ones that do not.
- How do I find a row or card that contains a specific element in Playwright?Use filter({ has: innerLocator }) on the list of rows or cards; it keeps only the ones that contain an element matching the inner locator. The inner locator is searched inside each row or card, not from the top of the page.
- How do I count elements in Playwright?To check a count, use await expect(locator).toHaveCount(n), which retries until the number matches. To read the number into a variable, use await locator.count(), which returns how many elements match at that moment.
- How do I get the text of every item in a list in Playwright?To check the text, pass an array to await expect(locator).toHaveText([...]), which compares every item in order and retries. To get the strings into a variable, use await locator.allTextContents(), which returns an array of each element's text.
- How do I match exact text instead of partial text in Playwright?Pass { exact: true } to the locator, for example getByRole('heading', { name: 'Halcyon Burr Grinder', exact: true }), which makes the match case-sensitive and whole-string. A regular expression with ^ and $ anchors does the same job.
- How do I select an option from a dropdown in Playwright?For a native <select>, call selectOption() on its locator, for example getByLabel('City').selectOption('Kochi'). Pass a string to match the option's value or label, or { label: '...' } to match the visible label only.
- How do I check a checkbox or radio button in Playwright?Locate the control, then call check() to tick it or uncheck() to clear it, and assert the state with toBeChecked(). setChecked(true or false) does either, which suits a test that receives the state as data.
- How do I work with an iframe in Playwright?Create a frame locator for the iframe with page.frameLocator('selector'), then locate elements through it as you would on the page. Locators that start from page only search the main document, so they never see what is inside an iframe.
- How does the name option in getByRole match text?By default, name matches the accessible name case-insensitively and as a substring, so { name: 'Export CSV' } also matches 'export csv'. With exact: true it becomes case-sensitive and whole-string, though surrounding whitespace is still trimmed; a regular expression is matched as written.
- How do I locate an element that can appear in one of two forms in Playwright?Combine the two possibilities with a.or(b), which matches elements that fit either locator, and wait on that. If both can be in the DOM at once, narrow the result, for example with .visible(), so strict mode still sees one element.
- Why does my locator match more than one element, and how do I fix it?Your locator describes something the page has several of, so make it more specific: add the role and accessible name, filter by text or by a child element, or scope it inside a container. Reach for first() or nth() only when position is genuinely what you are testing.
JavaScript basics
- Is JavaScript dynamically typed?Yes. In JavaScript a variable has no fixed type: the type belongs to the value it currently holds, and the same variable can hold a number now and a string later.
- What is a block statement in JavaScript?A block statement is a pair of curly braces that groups zero or more statements into one. Variables declared inside it with
letorconstexist only inside the block;varignores the block and leaks out. - What is the difference between let, const and var?const declares a block-scoped binding that cannot be reassigned. let declares a block-scoped variable that can be reassigned. var declares a function-scoped variable that hoists its declaration with undefined and ignores blocks.
- What is the difference between == and === in JavaScript?=== (strict equality) checks that both value and type are identical without coercion. == (loose equality) converts operands to a common type before comparing them, producing unexpected matches.
- What does async/await do in a Playwright test?async/await pauses test function execution until a Promise settles, allowing asynchronous browser actions like clicks and navigations to run in predictable sequential order without nested callbacks.
- What happens if I forget await in Playwright?If you forget await on a Playwright assertion or action, the test function completes immediately without waiting for the operation to settle. Playwright closes the browser context during teardown, causing the pending operation to fail with a session closed error.
- What is an arrow function in JavaScript?An arrow function is a compact syntax for writing functions using the => operator. Unlike standard function expressions, arrow functions inherit this lexically from their enclosing scope rather than binding their own this.
- What is the difference between null and undefined in JavaScript?
undefinedmeans a value was never set: an unassigned variable, a missing property or a function that returns nothing.nullis a value someone assigned on purpose to say "there is no object here". - What is a template literal in JavaScript?A template literal is a string written between backticks that can contain
${expression}placeholders. JavaScript evaluates each placeholder and puts the result into the string, and the string may span several lines. - What is the difference between map and forEach in JavaScript?
map()calls your function on every element and returns a new array of the results.forEach()calls your function on every element and always returnsundefined, so it is only for side effects. - What is the difference between for...of and for...in in JavaScript?
for...ofloops over the values of an array or other iterable, one value per round.for...inloops over the property names of an object, as strings, which makes it the wrong choice for arrays. - What is destructuring in JavaScript?Destructuring unpacks properties from an object, or items from an array, into separate variables in one statement. You can rename a property as you unpack it and give it a default for when it is
undefined. - What does the spread operator (...) do in JavaScript?The spread syntax
...expands an array or object in place, so its items or properties are copied into a new array, a new object or a function call's arguments. The copy is shallow: nested objects are shared, not copied. - What is a Promise in JavaScript?A Promise is an object standing in for the result of work that has not finished yet, such as a page load or reading text from an element. It starts pending, then becomes either fulfilled with a value or rejected with an error, and
awaitwaits for that to happen. - When should I use Promise.all instead of awaiting one at a time?Use
Promise.allwhen several pieces of async work do not depend on each other, so they can run at the same time and you wait once for all of them. Keep awaiting one at a time when each step needs the previous one to finish, which is true of most actions on a single page. - What are truthy and falsy values in JavaScript?When a value is used as a yes or no, as in an
if, JavaScript treats a short list of values as false:false,0,-0,0n,"",null,undefinedandNaN. Everything else counts as true, including"0",[]and{}. - What does optional chaining (?.) do in JavaScript?Optional chaining,
?., reads a property or calls a method only if the thing before it is notnullorundefined. If it is, the whole expression stops and returnsundefinedinstead of throwing a TypeError. - What is the difference between import and require in JavaScript?
importbelongs to ES modules, the standard module system built into JavaScript, whilerequire()belongs to CommonJS, the older system Node.js used before ES modules existed. Inside an ES modulerequireis not defined, so a file uses one system or the other. - What is a callback function in JavaScript?A callback is a function you pass into another function as an argument, so that the other function can call it at the right moment. Some callbacks run straight away, like the one given to
map(), and others run later, when an event happens or slow work finishes. - What is scope in JavaScript?Scope is the part of the code where a variable can be seen and used. Variables declared inside a function or a
{ }block exist only there, while code inside can still read the variables declared around it. - What is a class in JavaScript, and why do test frameworks use them?A class is a template for creating objects that bundle data with the methods that work on that data. Test frameworks use classes for page objects: one class per page holds that page's locators and actions, so a test reads as steps and a changed selector is fixed in one place.
- How do I handle errors with try/catch around await?Put the
awaitinside thetryblock: if the awaited Promise rejects,awaitthrows the error there and yourcatchblock receives it. A Promise that is not awaited inside thetryrejects later, after thetryhas finished, and thecatchnever sees it.
Assertions
- What are web-first assertions in Playwright?Web-first assertions are async assertion matchers provided by expect(locator) that wait and retry automatically until the expected condition is met or a timeout expires. They eliminate test flakiness caused by asynchronous rendering.
- What is the difference between toHaveText and toContainText?
toHaveTextpasses only when the element's whole text matches, whiletoContainTextpasses when the expected text appears anywhere inside it. UsetoHaveTextby default, becausetoContainTextalso passes on a longer text, such as the wrong product whose name starts the same way. - Why should I avoid page.waitForTimeout() in Playwright?page.waitForTimeout() introduces arbitrary hard-coded sleep delays that slow down test execution and create flaky tests when network or CI delays exceed the arbitrary sleep window. Use web-first assertions instead.
- What is auto-waiting in Playwright?Auto-waiting is Playwright's built-in mechanism that performs actionability checks before executing any user action on an element. It automatically waits for the target to be attached, visible, stable, enabled, and unobstructed before clicking or typing.
- Why is isVisible() not an assertion in Playwright?isVisible() is an immediate query method that returns a boolean representing the element's current state without polling. expect(locator).toBeVisible() is an assertion that auto-retries until the element appears or times out.
- How do I assert the page URL in Playwright?Use
await expect(page).toHaveURL(...), which retries until the page's URL matches a string, a regular expression or a predicate. Because it retries, it also covers the moment just after a redirect, when the URL has not changed yet. - What is the difference between toBeHidden() and not.toBeVisible()?In practice there is none: both pass when the locator matches no element or matches one that is not visible, and both retry until that is true. Pick whichever reads more clearly in your test.
- What is expect.soft in Playwright?A soft assertion records a failure and lets the test carry on, instead of stopping at the first failed check. The test is still marked as failed at the end, and every soft failure appears in the report.
- How do I change the assertion timeout in Playwright?Pass
{ timeout: ms }to a single assertion, or setexpect: { timeout: ms }inplaywright.config.jsto change the default for every assertion. The default is 5 seconds. - How do I check the value of an input field in Playwright?Use
await expect(locator).toHaveValue(...), which retries until the input's current value matches a string or a regular expression. Do not usetoHaveText()for this: the text of an input is empty, and what the user typed lives in its value. - How do I assert that an element has a CSS class in Playwright?To check one class among several, use
await expect(locator).toContainClass('name').toHaveClass('...')with a string must match the wholeclassattribute, so use it only when you mean every class, or pass it a regular expression. - How do I assert an attribute value in Playwright?Use
await expect(locator).toHaveAttribute('name', 'value'), which retries until the attribute has that value. Pass only the name,toHaveAttribute('name'), to check that the attribute is present at all. - How do I assert a button is disabled or enabled in Playwright?Use
await expect(button).toBeDisabled()andawait expect(button).toBeEnabled(). Both retry, so they wait for validation code to switch the button on or off. - How do I assert the page title in Playwright?Use
await expect(page).toHaveTitle(...)with the exact title as a string, or a regular expression for part of it. It retries, so it waits for the title of a page that is still loading.
The test runner
- How do I run a single Playwright test?To run one specific test, add its line number to the file name:
npx playwright test single-test.spec.js:8. Or match its title with-g:npx playwright test -g "second test". - What does test.describe do in Playwright?
test.describegroups related tests under one title, and that title becomes part of every test name in the report. Hooks such asbeforeEachdeclared inside the group run only for its tests, and a group can be nested, focused with.only, skipped with.skipor set up with.configure. - What is a fixture in Playwright?A fixture is a reusable environment setup that Playwright provides to tests as arguments, such as page or request. Fixtures manage their own lifecycle, initializing before a test runs and cleaning up automatically afterward.
- How do I take a screenshot when a Playwright test fails?Configure screenshot: 'only-on-failure' in the use options in playwright.config.ts or via test.use(). Playwright automatically captures a screenshot when a test fails and attaches it to the test results.
- What is a Playwright trace and how do I open it?A Playwright trace is an interactive recording of a test run containing DOM snapshots, network requests, console logs, and action timelines. You open it with npx playwright show-trace path/to/trace.zip or at trace.playwright.dev.
- What is baseURL in playwright.config and why use it?baseURL is a setting under
useinplaywright.config.jsthat gives every page in the test a starting address, sopage.goto('/catalog.html')becomes a full URL. You use it so tests name only the path, and the same suite can run against a different environment by changing one line. - How do I run Playwright tests in headed mode?Add the
--headedflag:npx playwright test --headed. To make it the default instead, setheadless: falseunderuseinplaywright.config.js. - How do I run Playwright tests in parallel, and what are workers?Playwright already runs test files in parallel, each in a worker: a separate operating-system process with its own browser. You set how many workers run at once with
--workersor theworkersconfig option, and you let tests inside one file run in parallel withfullyParallel: trueortest.describe.configure({ mode: 'parallel' }). - What are retries in Playwright, and when should I use them?Retries tell Playwright to run a failed test again, up to the number you set with
--retriesorretriesin the config; they are off by default. Use them on CI as a safety net with a report that flags every retry, never as a way to hide a test that fails for a reason you have not found. - How do I run code before each test in Playwright?Put it in
test.beforeEach(async ({ page }) => { ... }), which runs before every test in the same file, or the sametest.describeblock. When the same setup is needed in many files, or needs its own clean-up, a fixture is the better home. - How do I skip a test or run only one in Playwright?
test.skip()andtest.fixme()mark a test so Playwright does not run it, andtest.only()makes Playwright run only the tests marked with it. Useskipwhen a test does not apply,fixmewhen it is known to be broken, and remove everyonlybefore you commit. - How do I run Playwright tests in more than one browser?Add one project per browser to the
projectsarray inplaywright.config.js, using the ready-madedevicessettings such asdevices['Desktop Firefox']. Playwright runs every project by default, and--project=firefoxruns just one. - How do I tag tests and run only the smoke tests in Playwright?Give the test a tag in its details object,
test('title', { tag: '@smoke' }, async ({ page }) => { ... }), or put@smokein the title. Then runnpx playwright test --grep @smoke, and use--grep-invert @smoketo run everything else. - How do I log in once and reuse the session in Playwright?Sign in once in a setup project, save the browser state with
page.context().storageState({ path }), and point your other projects at that file withstorageStateanddependencies: ['setup']. Every test then starts already signed in, without going through the login form. - How do I mock an API response in Playwright?Call
page.route()with a pattern for the API address and answer the request withroute.fulfill({ json }), before the page makes the call. The page then receives your data instead of the server's, so you can test states such as an empty list or one particular product. - How do I run Playwright tests in GitHub Actions?Add a workflow file under
.github/workflows/that checks out the code, sets up Node, runsnpm ci,npx playwright install --with-depsandnpx playwright test, then uploads theplaywright-reportfolder as an artifact. GitHub runs it on every push and pull request you list underon:.
Reviewing AI-written tests
- How do I review a Playwright test an AI agent wrote?Review an AI-generated test by checking that it actually asserts user-visible outcomes with web-first assertions, uses resilient semantic locators instead of brittle selectors, and fails when the application behavior is broken.
- How can a passing test check nothing?A test passes while checking nothing if it performs actions and reads DOM state without calling expect(), because the test runner marks any test that exits without throwing an unhandled exception as green.
- How do I check that an AI-generated locator is stable?Run the locator against the real page and assert it matches exactly one element, then run it again under conditions that change, such as a reload or different data. A locator built on something that changes between loads, like a generated id, fails that second run; a role, label or test id survives it.
- What should I look for when reviewing a Playwright test in a pull request?Start with the assertions: for each test, ask what would have to break in the app for it to go red, then check the names, waits, locators and data against the diff. The fastest proof is to break the behaviour on purpose and see whether the test notices.
- Can AI write Playwright tests for me?Yes, AI tools can draft Playwright tests quickly, and Playwright can set up planner, generator and healer agents for your AI tool to do exactly that. What they cannot do is tell you whether a test can fail, so every generated test still needs a person to review it and run it.
Waiting & auto-waiting
- How do I wait for a network response in Playwright?Call
page.waitForResponse()before the action that sends the request, withoutawait, then perform the action and await the promise. It resolves with the matchingResponse, so you can check its status and read its JSON. - How do I wait for the page to navigate after a click in Playwright?Click, then call
await page.waitForURL(...)with a glob, a regular expression or a predicate for the address you expect. Alternatively, assertawait expect(page).toHaveURL(...)and then assert something on the new page. - What is expect.poll in Playwright?expect.poll calls a function you give it again and again, and applies an ordinary matcher to each result until it passes or the timeout runs out. It turns a check on any value, not only a locator, into a retrying assertion.
- What does toPass do in Playwright?toPass runs a block of code again and again until it completes without throwing, or until its timeout runs out. It is for retrying a sequence of steps, such as "click, then check the result", where retrying the check alone would not help.
- Why does my Playwright test time out, and which timeout is it?Playwright has separate timeouts for the whole test (30 seconds by default), for each assertion (5 seconds) and, optionally, for each action, and the error message names the one that ran out. Read the first line of the error before changing any number.
- How do I wait for a loading spinner to disappear in Playwright?Assert
await expect(spinner).toBeHidden(), which retries until the spinner is gone or no longer visible. Better still, assert on the content the spinner was covering, because that is what the test actually needs.
Debugging and reports
- How do I debug a Playwright test step by step?Run the test with
--debug, for examplenpx playwright test cart.spec.js:8 --debug, which opens the browser and the Playwright Inspector paused before the first action. You then step over one action at a time, or addawait page.pause()where you want the test to stop and press Resume to run up to it. - What is UI mode in Playwright?UI mode is an interactive window, opened with
npx playwright test --ui, where you pick tests from a list, run them, and step back through each action with a DOM snapshot of the page at that moment. It also has a watch mode that re-runs a test when you save its file. - How do I record a test with Playwright codegen?Run
npx playwright codegenfollowed by the page address, then click and type in the browser that opens while the Playwright Inspector writes the test code for each action. Copy the code, or save it with--output, and then read it critically before you keep it. - How do I open the Playwright HTML report?Run
npx playwright show-report, which serves the last report from theplaywright-reportfolder and opens it in your browser. The report must first be produced by running tests with the HTML reporter, for examplenpx playwright test --reporter=html.
Building a framework
- What is the Page Object Model in Playwright?The Page Object Model is a pattern where each page or part of your app gets a class that holds its locators and actions, so tests call
catalog.addToCart(name)instead of repeating selectors. When the page changes, you fix one class rather than every test that touches it. - Should page objects contain assertions?Keep the assertions that check behaviour in the test, and let a page object assert only that its own page is ready. A reader should see what a test claims without opening another file.
- How should I organise test data in Playwright?Keep fixed test data in its own files, such as a JSON file under
test-data/, and import it into the tests that need it. Tests then describe behaviour, and a price or a SKU changes in one place. - What is a flaky test, and how do I fix one in Playwright?A flaky test is one that passes on some runs and fails on others with no change to the code; Playwright's report labels a test
flakywhen it failed first and then passed on retry. You fix one by reproducing it with--repeat-each, finding what it depends on that changes between runs, and removing that dependency. - How do I run the same test with different data in Playwright?Put the cases in an array and loop over it, calling
test()once per item with the data in the title. Each item becomes its own test in the report, so one failing case does not hide the others. - What is test isolation in Playwright?Test isolation means each test runs in its own fresh browser context, with its own cookies, local storage and session storage, so nothing one test does can leak into the next. Every test can run alone, in any order, and give the same result.
- What is a browser context in Playwright?A browser context is an isolated, incognito-like browser profile with its own cookies, storage and pages, running inside one browser. You can open several in one test to act as different users at the same time.
Getting started
- What should a manual tester automate first?Start with a short check you already run by hand on every release, on a screen that has stopped changing, such as signing in and reaching the main page. Leave new features that are still moving, and anything where your judgement is the test, as manual for now.
- What is the difference between unit, API and end-to-end tests?A unit test checks one function directly, an API test sends HTTP requests to the server with no browser, and an end-to-end test drives a real browser through the app the way a user does. They trade speed for coverage: each level runs slower than the one below it but proves more of the system works together.
- How do I make an API request in a Playwright test?Ask for the built-in
requestfixture in your test and callrequest.get(),request.post()and the rest, with paths resolved against thebaseURLin your config. Check the response withawait expect(response).toBeOK()and read the body withawait response.json(). - Is Playwright free to use?Yes, Playwright is free and open source, released under the Apache License 2.0. You can use it for personal and commercial testing without paying for a licence.
- Do I need to learn JavaScript before Playwright?Yes, the basics, because every Playwright test you write or review is JavaScript. You do not need the whole language: variables, objects and arrays, functions,
ifand loops, andasync/awaitare enough to start.