Skip to content

Latest commit

 

History

History
427 lines (301 loc) · 16.3 KB

README.md

File metadata and controls

427 lines (301 loc) · 16.3 KB

😲 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!




playwright-testing-library

🔍 Find elements in playwright like your users with queries from @testing-library/dom

Build Status Test Coverage Code Style Package Version
MIT License Conventional Commits Maintenance


🎛 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
    • NewLocator 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

# 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

📝 Usage

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).

⚠️ 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}) => {
  // 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')
})

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}) => {
  // 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(/My Modal/)
  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()
})

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 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}) => {
  //              ↓↓↓↓↓ 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')
})

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: {
    // These are the defaults
    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

// 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}) => {
    // ...
  })
})

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}) => {
  // 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')
})

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

// 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}) => {
    // ...
  })
})

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' // 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')
})

Configuration

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}) => {
    // ...
  })
})

🔌 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

    // ✅ 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))

Special Thanks

Related Playwright Test Utilities

LICENSE

MIT

Maintenance

This project is actively maintained by engineers at @hoverinc 😀.