π² Heads up β Playwright introduced native Testing Library queries in version 1.27 .
π¬ #558 β We're discussing what this means for Playwright Testing Library in this issue. You can find a more detailed comparison of the new Playwright API and this library here. Please ask any questions you may have or share thoughts and suggestions!
π Archived β This project has officially been archived. Use Playwright 1.27+ with the built-in Testing Library support. See the above issues for more details and this comment for help migrating.
All of your favorite user-centric querying functions from @testing-library/react and @testing-library/dom available from within Playwright!
- Test fixture for @playwright/test via @playwright-testing-library/test
- Standalone queries for playwright via playwright-testing-library
ElementHandle
queries (getDocument
+queries
) β- Asynchronous
waitFor
assertion helper (via wait-for-expect )
# For use with Playwright Test (@playwright/test) npm install --save-dev @playwright-testing-library/test # For use with Playwright (playwright) npm install --save-dev playwright-testing-library
There are currently a few different ways to use Playwright Testing Library, depending on how you use Playwright. However, the recommended approach is to use the Locator
queries fixture with Playwright Test (@playwright/test).
β οΈ TheElementHandle
query APIs were created before Playwright introduced itsLocator
API and will be replaced in the next major version of Playwright Testing Library. If you can't use @playwright/test at the moment, you'll need to use theElementHandle
query API, but a migration path will be provided when we switch to the newLocator
APIs.
π Added in 4.4.0
Using the Locator
Playwright Test (@playwright/test) fixture with @playwright-testing-library/test.
import {test as base} from '@playwright/test' import { locatorFixtures as fixtures, LocatorFixtures as TestingLibraryFixtures, } from '@playwright-testing-library/test/fixture' const test = base.extend<TestingLibraryFixtures>(fixtures) const {expect} = test test('my form', async ({screen, within}) => { // Screen provides `Locator` queries scoped to current Playwright `Page` const formLocator = screen.getByTestId('my-form') // Scope queries to `Locator` with `within` // (note that this is a fixture from `test`, not the `within` import) const emailInputLocator = within(formLocator).getByLabelText('Email') // Interact via `Locator` API π₯³ await emailInputLocator.fill('email@playwright.dev') await emailInputLocator.press('Enter') // Screen also provides Playwright's `Page` API screen.goto('/account') const emailLocator = screen.getByRole('heading', {level: 2}) // Assert via `Locator` APIs π await expect(emailLocator).toHaveText('email@playwright.dev') })
The findBy
queries work the same way as they do in Testing Library core in that they return Promise<Locator>
and are intended to be used to defer test execution until an element appears on the page.
test('my modal', async ({screen, within}) => { // Here we wait for a modal to appear asynchronously before continuing // Note: the timeout for `findBy` queries is configured with `asyncUtilTimeout` const modalLocator = await screen.findByRole('dialog') // Once the modal is visible, we can interact with its contents and assert await expect(modalLocator).toHaveText(/MyModal/) await within(modalLocator).getByRole('button', {name: 'Okay'}).click() // We can also use `queryBy` methods to take advantage of Playwright's `Locator` auto-waiting // See: https://playwright.dev/docs/actionability // Note: this will use Playwright's timeout, not `asyncUtilTimeout` await expect(screen.queryByRole('dialog')).toBeHidden() })
π Added in 4.5.0
As an alternative to the within(locator: Locator)
function you're familiar with from Testing Library, Playwright Testing Library also supports chaining queries together.
All synchronous queries (get*
+ query*
) return Locator
instances augmented with a .within()
method (TestingLibraryLocator
). All asynchronous queries (find*
) return a special LocatorPromise
that also supports .within()
. This makes it possible to chain queries, including chaining get*
, query*
and find*
interchangeably.
β οΈ Note that including anyfind*
query in the chain will make the entire chain asynchronous
test('chaining synchronous queries', async ({screen}) => { const locator = screen.getByRole('figure').within().findByRole('img') expect(await locator.getAttribute('alt')).toEqual('Some image') })
test('chaining synchronous queries + asynchronous queries', ({screen}) => { // βββββ including any `find*` queries makes the whole chain asynchronous const locator = await screen .getByTestId('modal-container') // Get "modal container" or throw (sync) .within() .findByRole('dialog') // Wait for modal to appear (async, until `asyncUtilTimeout`) .within() .getByRole('button', {name: 'Close'}) // Get close button within modal (sync) expect(await locator.textContent()).toEqual('Close') })
The Locator
query API is configured using Playwright's use
API. See Playwright's documentation for global, project, and test.
Configuring Testing Library globally in playwright.config.ts
import type {PlaywrightTestConfig} from '@playwright/test' const config: PlaywrightTestConfig = { use: { // These are the defaults testIdAttribute: 'data-testid', asyncUtilTimeout: 1000, asyncUtilExpectedState: 'visible', }, } export default config
Scoping Testing Library configuration to test suites or describe
blocks
import {test as base} from '@playwright/test' import { locatorFixtures as fixtures, LocatorFixtures as TestingLibraryFixtures, } from '@playwright-testing-library/test/fixture' const test = base.extend<TestingLibraryFixtures>(fixtures) const {describe, expect, use} = test // Entire test suite use({testIdAttribute: 'data-custom-test-id'}) describe(() => { // Specific block use({ testIdAttribute: 'some-other-test-id', asyncUtilsTimeout: 5000, asyncUtilExpectedState: 'attached', }) test('my form', async ({screen}) => { // ... }) })
Using the ElementHandle
Playwright Test (@playwright/test) fixture with @playwright-testing-library/test.
β οΈ See note in Usage as you should be using theLocator
fixture if possible
import {test as base} from '@playwright/test' import {fixtures, within, TestingLibraryFixtures} from '@playwright-testing-library/test/fixture' const test = base.extend<TestingLibraryFixtures>(fixtures) const {expect} = test test('my form', async ({page, queries}) => { // Query methods are available in `test` blocks const formHandle = await queries.getByTestId('my-form') // Scope queries to an `ElementHandle` with `within` const emailInputHandle = await within(formHandle).getByLabelText('Email') // Interact via `ElementHandle` API await emailInputHandle.fill('email@playwright.dev') await emailInputHandle.press('Enter') page.goto('/account') const emailHandle = queries.getByRole('heading', {level: 2}) // Assert via `ElementHandle` APIs expect(await emailHandle.textContent()).toEqual('email@playwright.dev') })
import {test as base} from '@playwright/test' import { configure, fixtures, within, TestingLibraryFixtures, } from '@playwright-testing-library/test/fixture' const test = base.extend<TestingLibraryFixtures>(fixtures) const {beforeEach, describe, expect} = test // Global (these are the defaults) configure({asyncUtilTimeout: 1000, testIdAttribute: 'data-testid'}) // Specific block describe('my page', () => { beforeEach(() => configure({asyncUtilTimeout: 5000, testIdAttribute: 'data-custom-test-id'})) afterEach(() => configure({})) test('my form', async ({page, queries}) => { // ... }) })
Using the ElementHandle
queries with Playwright (playwright) and playwright-testing-library.
β οΈ See note in Usage as you should be using @playwright/test with theLocator
fixture if possible. TheLocator
queries will be made available for standalone playwright in the next major release.
import {beforeAll, expect, jest, test} from '@jest/globals' import {webkit} from 'playwright' // or 'firefox' or 'chromium' import {getDocument, queries, within} from 'playwright-testing-library' let browser: playwright.Browser let page: playwright.Page beforeAll(() => { const browser = await webkit.launch() const page = await browser.newPage() }) test('my form', () => { // Get `ElementHandle` for document from `Page` const documentHandle = await getDocument(page) // Global query methods take document handle as the first parameter const formHandle = await queries.getByTestId(documentHandle, 'my-form') // Scope queries to an `ElementHandle` with `within` const emailInputHandle = await within(formHandle).getByLabelText('Email') // Interact via `ElementHandle` API await emailInputHandle.fill('email@playwright.dev') await emailInputHandle.press('Enter') page.goto('/account') const accountHandle = getDocument(page) const emailHandle = queries.getByRole(accountHandle, 'heading', {level: 2}) // Assert via `ElementHandle` APIs expect(await emailHandle.textContent()).toEqual('email@playwright.dev') })
import {beforeEach, afterEach, expect, jest, test} from '@jest/globals' import {configure, getDocument, queries, within} from 'playwright-testing-library' // Global (these are the defaults) configure({asyncUtilTimeout: 1000, testIdAttribute: 'data-testid'}) // Specific block describe('my page', () => { beforeEach(() => configure({asyncUtilTimeout: 5000, testIdAttribute: 'data-custom-test-id'})) afterEach(() => configure({})) test('my form', async ({page, queries}) => { // ... }) })
All queries from @testing-library/dom are supported.
π The
find*
queries for theLocator
queries returnPromise<Locator>
which resolves when the element is found before the timeout specified viaasyncUtilTimeout
Unique methods, not part of @testing-library/dom
β οΈ These only apply to theElementHandle
queries
-
Get an
ElementHandle
for the documentgetDocument(page: playwright.Page): ElementHandle
-
Wait for an assertion (wrapper around wait-for-expect)
waitFor( expectation: () => void | Promise<void>, timeout?: number, interval?: number ): Promise<{}>
- Only
testIdAttribute
andasyncUtilTimeout
are supported as configuration options - Async utilities
waitForElement
,waitForElementToBeRemoved
andwaitForDomChange
are not exposed. Consider using afind*
query or a Playwright built-in likeLocator.waitFor()
. - The
fireEvent
method is not exposed, use Playwright's built-ins instead. - Assertion extensions from jest-dom are not compatible, use Playwright Test if possible.
-
The
getNodeText()
function is currently unsupported. -
When using a function for
TextMatch
, the function cannot reference its closure scope// β This is supported screen.getByText(content => content.startsWith('Foo')) // β This is not supported const startsWithFoo = (content: string) => content.startsWith('Foo') screen.getByText(content => startsWithFoo(content))
MIT
This project (ει€) is (ει€γγγΎγ§) was actively maintained by engineers at
@hoverinc π.