playwright-testing-library
🔍 Find elements in playwright like your users with queries from @testing-library/dom
🎛 Features
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
- ✨ New —
Locator
queries fixture (locatorFixtures
) ↓ ElementHandle
queries fixture (fixtures
) ↓
- Standalone queries for playwright via playwright-testing-library
ElementHandle
queries (getDocument
+ queries
) ↓- Asynchronous
waitFor
assertion helper (via wait-for-expect)
🌱 Installation
npm install --save-dev @playwright-testing-library/test
npm install --save-dev playwright-testing-library
📝 Usage
There are currently a few different ways to use Playwright Testing Library, depending on how you use Playwright. However, the recommended approach is using the Locator
queries fixture with Playwright Test (@playwright/test).
⚠️ The ElementHandle
query APIs were created before Playwright introduced its Locator
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 the ElementHandle
query API, but a migration path will be provided when we switch to the new Locator
APIs.
Playwright Test Fixture
🔖 Added in 4.4.0
Using the Locator
Playwright Test (@playwright/test) fixture with @playwright-testing-library/test.
Setup
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}) => {
const formLocator = screen.getByTestId('my-form')
const emailInputLocator = within(formLocator).getByLabelText('Email')
await emailInputLocator.fill('email@playwright.dev')
await emailInputLocator.press('Enter')
screen.goto('/account')
const emailLocator = screen.getByRole('heading', {level: 2})
await expect(emailLocator).toHaveText('email@playwright.dev')
})
Async Methods
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}) => {
const modalLocator = await screen.findByRole('dialog')
await expect(modalLocator).toHaveText(/My Modal/)
await within(modalLocator).getByRole('button', {name: 'Okay'}).click()
await expect(screen.queryByRole('dialog')).toBeHidden()
})
Chaining
🔖 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 are 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 any find*
query in the chain will make the entire chain asynchronous
Synchronous
test('chaining synchronous queries', async ({screen}) => {
const locator = screen.getByRole('figure').within().findByRole('img')
expect(await locator.getAttribute('alt')).toEqual('Some image')
})
Synchronous + Asynchronous
test('chaining synchronous queries + asynchronous queries', ({screen}) => {
const locator = await screen
.getByTestId('modal-container')
.within()
.findByRole('dialog')
.within()
.getByRole('button', {name: 'Close'})
expect(await locator.textContent()).toEqual('Close')
})
Configuration
The Locator
query API is configured using Playwright's use
API. See Playwright's documentation for global, project, and test.
Global
Configuring Testing Library globally in playwright.config.ts
import type {PlaywrightTestConfig} from '@playwright/test'
const config: PlaywrightTestConfig = {
use: {
testIdAttribute: 'data-testid',
asyncUtilTimeout: 1000,
asyncUtilExpectedState: 'visible',
},
}
export default config
Local
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
use({testIdAttribute: 'data-custom-test-id'})
describe(() => {
use({
testIdAttribute: 'some-other-test-id',
asyncUtilsTimeout: 5000,
asyncUtilExpectedState: 'attached',
})
test('my form', async ({screen}) => {
})
})
Legacy Playwright Test Fixture
Using the ElementHandle
Playwright Test (@playwright/test) fixture with @playwright-testing-library/test.
⚠️ See note in Usage as you should be using the Locator
fixture if possible
Setup
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}) => {
const formHandle = await queries.getByTestId('my-form')
const emailInputHandle = await within(formHandle).getByLabelText('Email')
await emailInputHandle.fill('email@playwright.dev')
await emailInputHandle.press('Enter')
page.goto('/account')
const emailHandle = queries.getByRole('heading', {level: 2})
expect(await emailHandle.textContent()).toEqual('email@playwright.dev')
})
Configuration
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
configure({asyncUtilTimeout: 1000, testIdAttribute: 'data-testid'})
describe('my page', () => {
beforeEach(() => configure({asyncUtilTimeout: 5000, testIdAttribute: 'data-custom-test-id'}))
afterEach(() => configure({}))
test('my form', async ({page, queries}) => {
})
})
Standalone Playwright Queries
Using the ElementHandle
queries with Playwright (playwright) and playwright-testing-library.
⚠️ See note in Usage as you should be using @playwright/test with the Locator
fixture if possible. The Locator
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'
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', () => {
const documentHandle = await getDocument(page)
const formHandle = await queries.getByTestId(documentHandle, 'my-form')
const emailInputHandle = await within(formHandle).getByLabelText('Email')
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})
expect(await emailHandle.textContent()).toEqual('email@playwright.dev')
})
Configuration
import {beforeEach, afterEach, expect, jest, test} from '@jest/globals'
import {configure, getDocument, queries, within} from 'playwright-testing-library'
configure({asyncUtilTimeout: 1000, testIdAttribute: 'data-testid'})
describe('my page', () => {
beforeEach(() => configure({asyncUtilTimeout: 5000, testIdAttribute: 'data-custom-test-id'}))
afterEach(() => configure({}))
test('my form', async ({page, queries}) => {
})
})
🔌 API
Testing Library
All queries from @testing-library/dom are supported.
📝 The find*
queries for the Locator
queries return Promise<Locator>
which resolves when the element is found before the timeout specified via asyncUtilTimeout
Additional
Unique methods, not part of @testing-library/dom
⚠️ These only apply to the ElementHandle
queries
-
Get an ElementHandle
for the document
getDocument(page: playwright.Page): ElementHandle
-
Wait for an assertion (wrapper around wait-for-expect)
waitFor(
expectation: () => void | Promise<void>,
timeout?: number,
interval?: number
): Promise<{}>
Known Limitations
- Only
testIdAttribute
and asyncUtilTimeout
are supported as configuration options - Async utilities
waitForElement
, waitForElementToBeRemoved
and waitForDomChange
are not exposed. Consider using a find*
query or a Playwright built-in like Locator.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.
Locator Queries
-
The getNodeText()
function is currently unsupported.
-
When using a function for TextMatch
, the function cannot reference its closure scope
screen.getByText(content => content.startsWith('Foo'))
const startsWithFoo = (content: string) => content.startsWith('Foo')
screen.getByText(content => startsWithFoo(content))
Special Thanks
Related Playwright Test Utilities
LICENSE
MIT
Maintenance
This project is actively maintained by engineers at
@hoverinc 😀.