From 1b41bd5e5de2c22d618b07ff5982997da043bd7c Mon Sep 17 00:00:00 2001 From: Jamie Rolfs Date: Sun, 18 Sep 2022 04:44:27 -0700 Subject: [PATCH] docs(readme): add `Locator` documentation and clarify different APIs --- README.md | 328 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 228 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index ee3021b..e76a97f 100644 --- a/README.md +++ b/README.md @@ -22,17 +22,18 @@
-## ✨ Features +## 🎛 Features -All of your favorite user-centric querying functions from **@testing-library/react** and **@testing-library/dom** available from Playwright! +All of your favorite user-centric querying functions from **@testing-library/react** and **@testing-library/dom** available from within Playwright! -- Playwright Test [fixture](https://playwright.dev/docs/test-fixtures) — **`@playwright-testing-library/test/fixture`** or... -- Standalone queries — **`playwright-testing-library`**/**`@playwright-testing-library/test`** -- Asynchronous assertion helper (via **[wait-for-expect](https://github.com/TheBrainFamily/wait-for-expect)**) +- Playwright Test [fixture](https://playwright.dev/docs/test-fixtures) for **@playwright/test** via **@playwright-testing-library/test** + - ✨ **New** — `Locator` queries fixture (`locatorFixtures`) [↓](#playwright-test-locator-fixture) + - `ElementHandle` queries fixture (`fixtures`) [↓](#legacy-playwright-test-fixture) +- Standalone queries for **playwright** via **playwright-testing-library** + - `ElementHandle` queries (`getDocument` + `queries`) [↓](#standalone-playwright-queries) + - Asynchronous `waitFor` assertion helper (via **[wait-for-expect](https://github.com/TheBrainFamily/wait-for-expect)**) -## 🌱 Getting Started - -### 1. Install +## 🌱 Installation ```bash # For use with Playwright Test (@playwright/test) @@ -40,76 +41,252 @@ npm install --save-dev @playwright-testing-library/test # For use with Playwright (playwright) npm install --save-dev playwright-testing-library - ``` -### 2a. Use _Playwright Test [fixture](https://playwright.dev/docs/test-fixtures)_ +## 📝 Usage + +There are currently a few different ways to use Playwright Testing Library, depending, however using the `Locator` queries fixture with Playwright Test (**@playwright/test**) is the recommended approach. + +> ⚠️ 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 + +Using the `Locator` Playwright Test (**@playwright/test**) fixture with **@playwright-testing-library/test**. + +#### Setup ```ts -import {test as baseTest} from '@playwright/test' -import {fixtures, within, TestingLibraryFixtures} from '@playwright-testing-library/test/fixture' +import {test as base} from '@playwright/test' +import { + locatorFixtures as fixtures, + LocatorFixtures as TestingLibraryFixtures, +} from '@playwright-testing-library/test/fixture' + +const test = base.extend(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') +}) +``` + +#### Configuration + +The `Locator` query API is configured using Playwright's `use` API. See Playwright's documentation for [global](https://playwright.dev/docs/api/class-testconfig#test-config-use), [project](https://playwright.dev/docs/api/class-testproject#test-project-use), and [test](https://playwright.dev/docs/api/class-test#test-use). -// As only fixture -const test = baseTest.extend(fixtures) +##### Global -// Alternatively, with other fixtures -interface Fixtures extends TestingLibraryFixtures { - // ... additional fixture types +Configuring Testing Library globally in `playwright.config.ts` + +```ts +import type {PlaywrightTestConfig} from '@playwright/test' + +const config: PlaywrightTestConfig = { + use: { + // These are the defaults + testIdAttribute: 'data-testid', + asyncUtilTimeout: 1000, + asyncUtilExpectedState: 'visible', + }, } -const test = baseTest.extend({ - ...fixtures, - // ... additional fixtures +export default config +``` + +##### Local + +Scoping Testing Library configuration to test suites or `describe` blocks + +```ts +import {test as base} from '@playwright/test' +import { + locatorFixtures as fixtures, + LocatorFixtures as TestingLibraryFixtures, +} from '@playwright-testing-library/test/fixture' + +const test = base.extend(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](#-usage) as you should be using the `Locator` fixture if possible + +#### Setup + +```ts +import {test as base} from '@playwright/test' +import {fixtures, within, TestingLibraryFixtures} from '@playwright-testing-library/test/fixture' + +const test = base.extend(fixtures) const {expect} = test -// Query methods are available in `test` blocks -test('my form', async ({queries: {getByTestId}}) => { - const $form = await getByTestId('my-form') +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 - // Scope queries with `within` - const {getByLabelText} = within($form) +```ts +import {test as base} from '@playwright/test' +import { + configure, + fixtures, + within, + TestingLibraryFixtures, +} from '@playwright-testing-library/test/fixture' + +const test = base.extend(fixtures) + +const {beforeEach, describe, expect} = test - const $email = await getByLabelText('Email') +// Global (these are the defaults) +configure({asyncUtilTimeout: 1000, testIdAttribute: 'data-testid'}) - // Interact with Playwright like usual - await $email.type('playwright@example.com') +// Specific block +describe('my page', () => { + beforeEach(() => configure({asyncUtilTimeout: 5000, testIdAttribute: 'data-custom-test-id'})) - // ... + afterEach(() => configure({})) + + test('my form', async ({page, queries}) => { + // ... + }) }) ``` -### 2b. Use _standalone queries_ +### Standalone Playwright Queries + +Using the `ElementHandle` queries with Playwright (**playwright**) and **playwright-testing-library**. -```js -const {webkit} = require('playwright') // or 'firefox' or 'chromium' -const {getDocument, queries} = require('playwright-testing-library') +> ⚠️ See note in [Usage](#-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. -const {getByTestId, getByLabelText} = queries +```ts +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() +}) -const browser = await webkit.launch() -const page = await browser.newPage() +test('my form', () => { + // Get `ElementHandle` for document from `Page` + const documentHandle = await getDocument(page) -// Grab ElementHandle for document -const $document = await getDocument(page) + // Global query methods take document handle as the first parameter + const formHandle = await queries.getByTestId(documentHandle, 'my-form') -// Your favorite query methods are available -const $form = await getByTestId($document, 'my-form') + // Scope queries to an `ElementHandle` with `within` + const emailInputHandle = await within(formHandle).getByLabelText('Email') -// Returned elements are ElementHandles too! -const $email = await getByLabelText($form, 'Email') + // Interact via `ElementHandle` API + await emailInputHandle.fill('email@playwright.dev') + await emailInputHandle.press('Enter') -// Interact with playwright like usual -await $email.type('playwright@example.com') + 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 + +```ts +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](https://github.com/testing-library/dom-testing-library#usage)** are supported. + +> 📝 The **`find*`** queries for the `Locator` queries return `Promise` 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 ```ts @@ -126,72 +303,23 @@ Unique methods, not part of **@testing-library/dom** ): Promise<{}> ``` ---- - -The **[@testing-library/dom](https://github.com/testing-library/dom-testing-library#usage)** — All **`get*`**, **`query*`**, and **`find*`** methods are supported. - -- `getQueriesForElement(handle: ElementHandle): ElementHandle & QueryUtils` - extend the input object with the query API and return it -- `getNodeText(handle: ElementHandle): Promise` - get the text content of the element -- `queries: QueryUtils` - the query subset of `@testing-library/dom` exports - - `queryByPlaceholderText` - - `queryAllByPlaceholderText` - - `getByPlaceholderText` - - `getAllByPlaceholderText` - - `findByPlaceholderText` - - `findAllByPlaceholderText` - - `queryByText` - - `queryAllByText` - - `getByText` - - `getAllByText` - - `findByText` - - `findAllByText` - - `queryByLabelText` - - `queryAllByLabelText` - - `getByLabelText` - - `getAllByLabelText` - - `findByLabelText` - - `findAllByLabelText` - - `queryByAltText` - - `queryAllByAltText` - - `getByAltText` - - `getAllByAltText` - - `findByAltText` - - `findAllByAltText` - - `queryByTestId` - - `queryAllByTestId` - - `getByTestId` - - `getAllByTestId` - - `findByTestId` - - `findAllByTestId` - - `queryByTitle` - - `queryAllByTitle` - - `getByTitle` - - `getAllByTitle` - - `findByTitle` - - `findAllByTitle` - - `queryByDisplayValue`, - - `queryAllByDisplayValue`, - - `getByDisplayValue`, - - `getAllByDisplayValue`, - - `findByDisplayValue`, - - `findAllByDisplayValue`, - ## Known Limitations -- Async utilities `waitForElement`, `waitForElementToBeRemoved` and `waitForDomChange` are not exposed. Consider using a `find*` query. -- `fireEvent` method is not exposed, use Playwright's built-ins instead. -- `expect` assertion extensions are not available. +- 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()`](https://playwright.dev/docs/api/class-locator#locator-wait-for). +- The `fireEvent` method is not exposed, use Playwright's built-ins instead. +- Assertion extensions from [**jest-dom**](https://testing-library.com/docs/ecosystem-jest-dom/) are not compatible, use Playwright Test if possible. +- The [`getNodeText()`](https://testing-library.com/docs/dom-testing-library/api-custom-queries/#getnodetext) function is not currently supported for `Locator`. ## Special Thanks - [pptr-testing-library](https://github.com/testing-library/pptr-testing-library) -- [@testing-library/dom](https://github.com/testing-library/dom-testing-library) of course! +- [@testing-library/dom](https://github.com/testing-library/dom-testing-library) ## Related Playwright Test Utilities - [jest-playwright](https://github.com/playwright-community/jest-playwright) - [expect-playwright](https://github.com/playwright-community/expect-playwright) -- Yours! Name TBD, PR welcome ;) ## LICENSE