-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fixture): add
locatorFixtures
that provide Locator
-based que…
…ries This will likely replace the fixtures that provided `ElementHandle`-based queries in a future major release, but for now the `Locator` queries are exported as `locatorFixtures`: ```ts import { test as baseTest } from '@playwright/test' import { locatorFixtures as fixtures, LocatorFixtures as TestingLibraryFixtures, within } from '@playwright-testing-library/test/fixture'; const test = baseTest.extend<TestingLibraryFixtures>(fixtures); const {expect} = test; test('my form', async ({queries: {getByTestId}}) => { // Queries now return `Locator` const formLocator = getByTestId('my-form'); // Locator-based `within` support const {getByLabelText} = within(formLocator); const emailInputLocator = getByLabelText('Email'); // Interact via `Locator` API 🥳 await emailInputLocator.fill('[email protected]'); // Assert via `Locator` APIs 🎉 await expect(emailInputLocator).toHaveValue('[email protected]'); }) ```
- Loading branch information
Showing
9 changed files
with
409 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const replacer = (_: string, value: unknown) => { | ||
if (value instanceof RegExp) return `__REGEXP ${value.toString()}` | ||
|
||
return value | ||
} | ||
|
||
const reviver = (_: string, value: string) => { | ||
if (value.toString().includes('__REGEXP ')) { | ||
const match = /\/(.*)\/(.*)?/.exec(value.split('__REGEXP ')[1]) | ||
|
||
return new RegExp(match![1], match![2] || '') | ||
} | ||
|
||
return value | ||
} | ||
|
||
export {replacer, reviver} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,38 @@ | ||
import {Fixtures} from '@playwright/test' | ||
|
||
import type {Queries as ElementHandleQueries} from './element-handle' | ||
import {queriesFixture as elementHandleQueriesFixture} from './element-handle' | ||
import type {Queries as LocatorQueries} from './locator' | ||
import { | ||
Queries as ElementHandleQueries, | ||
queriesFixture as elementHandleQueriesFixture, | ||
} from './element-handle' | ||
installTestingLibraryFixture, | ||
queriesFixture as locatorQueriesFixture, | ||
registerSelectorsFixture, | ||
within, | ||
} from './locator' | ||
|
||
const elementHandleFixtures: Fixtures = {queries: elementHandleQueriesFixture} | ||
const locatorFixtures: Fixtures = { | ||
queries: locatorQueriesFixture, | ||
registerSelectors: registerSelectorsFixture, | ||
installTestingLibrary: installTestingLibraryFixture, | ||
} | ||
|
||
interface ElementHandleFixtures { | ||
queries: ElementHandleQueries | ||
} | ||
|
||
interface LocatorFixtures { | ||
queries: LocatorQueries | ||
registerSelectors: void | ||
installTestingLibrary: void | ||
} | ||
|
||
export type {ElementHandleFixtures as TestingLibraryFixtures} | ||
export {elementHandleQueriesFixture as fixture} | ||
export {elementHandleFixtures as fixtures} | ||
|
||
export type {LocatorFixtures} | ||
export {locatorQueriesFixture} | ||
export {locatorFixtures, within} | ||
|
||
export {configure} from '..' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import {promises as fs} from 'fs' | ||
|
||
import type {Locator, PlaywrightTestArgs, TestFixture} from '@playwright/test' | ||
import {selectors} from '@playwright/test' | ||
|
||
import {queryNames as allQueryNames} from '../common' | ||
|
||
import {replacer, reviver} from './helpers' | ||
import type { | ||
AllQuery, | ||
FindQuery, | ||
LocatorQueries as Queries, | ||
Query, | ||
Selector, | ||
SelectorEngine, | ||
SupportedQuery, | ||
} from './types' | ||
|
||
const isAllQuery = (query: Query): query is AllQuery => query.includes('All') | ||
const isNotFindQuery = (query: Query): query is Exclude<Query, FindQuery> => | ||
!query.startsWith('find') | ||
|
||
const queryNames = allQueryNames.filter(isNotFindQuery) | ||
|
||
const queryToSelector = (query: SupportedQuery) => | ||
query.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() as Selector | ||
|
||
const queriesFixture: TestFixture<Queries, PlaywrightTestArgs> = async ({page}, use) => { | ||
const queries = queryNames.reduce( | ||
(rest, query) => ({ | ||
...rest, | ||
[query]: (...args: Parameters<Queries[keyof Queries]>) => | ||
page.locator(`${queryToSelector(query)}=${JSON.stringify(args, replacer)}`), | ||
}), | ||
{} as Queries, | ||
) | ||
|
||
await use(queries) | ||
} | ||
|
||
const within = (locator: Locator): Queries => | ||
queryNames.reduce( | ||
(rest, query) => ({ | ||
...rest, | ||
[query]: (...args: Parameters<Queries[keyof Queries]>) => | ||
locator.locator(`${queryToSelector(query)}=${JSON.stringify(args, replacer)}`), | ||
}), | ||
{} as Queries, | ||
) | ||
|
||
declare const queryName: SupportedQuery | ||
|
||
const engine: () => SelectorEngine = () => ({ | ||
query(root, selector) { | ||
const args = JSON.parse(selector, window.__testingLibraryReviver) as unknown as Parameters< | ||
Queries[typeof queryName] | ||
> | ||
|
||
if (isAllQuery(queryName)) | ||
throw new Error( | ||
`PlaywrightTestingLibrary: the plural '${queryName}' was used to create this Locator`, | ||
) | ||
|
||
// @ts-expect-error | ||
const result = window.TestingLibraryDom[queryName](root, ...args) | ||
|
||
return result | ||
}, | ||
queryAll(root, selector) { | ||
const testingLibrary = window.TestingLibraryDom | ||
const args = JSON.parse(selector, window.__testingLibraryReviver) as unknown as Parameters< | ||
Queries[typeof queryName] | ||
> | ||
|
||
// @ts-expect-error | ||
const result = testingLibrary[queryName](root, ...args) | ||
|
||
if (!result) return [] | ||
|
||
return Array.isArray(result) ? result : [result] | ||
}, | ||
}) | ||
|
||
const registerSelectorsFixture: [ | ||
TestFixture<void, PlaywrightTestArgs>, | ||
{scope: 'worker'; auto?: boolean}, | ||
] = [ | ||
async ({}, use) => { | ||
try { | ||
await Promise.all( | ||
queryNames.map(async name => | ||
selectors.register( | ||
queryToSelector(name), | ||
`(${engine.toString().replace(/queryName/g, `"${name}"`)})()`, | ||
), | ||
), | ||
) | ||
} catch (error) { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
'PlaywrightTestingLibrary: failed to register Testing Library functions\n', | ||
error, | ||
) | ||
} | ||
await use() | ||
}, | ||
{scope: 'worker', auto: true}, | ||
] | ||
|
||
const installTestingLibraryFixture: [ | ||
TestFixture<void, PlaywrightTestArgs>, | ||
{scope: 'test'; auto?: boolean}, | ||
] = [ | ||
async ({context}, use) => { | ||
const testingLibraryDomUmdScript = await fs.readFile( | ||
require.resolve('@testing-library/dom/dist/@testing-library/dom.umd.js'), | ||
'utf8', | ||
) | ||
|
||
await context.addInitScript(` | ||
${testingLibraryDomUmdScript} | ||
window.__testingLibraryReviver = ${reviver.toString()}; | ||
`) | ||
|
||
await use() | ||
}, | ||
{scope: 'test', auto: true}, | ||
] | ||
|
||
export {queriesFixture, registerSelectorsFixture, installTestingLibraryFixture, within} | ||
export type {Queries} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import {Locator} from '@playwright/test' | ||
import type * as TestingLibraryDom from '@testing-library/dom' | ||
import {queries} from '@testing-library/dom' | ||
|
||
import {reviver} from './helpers' | ||
|
||
/** | ||
* This type was copied across from Playwright | ||
* | ||
* @see {@link https://github.com/microsoft/playwright/blob/82ff85b106e31ffd7b3702aef260c9c460cfb10c/packages/playwright-core/src/client/types.ts#L108-L117} | ||
*/ | ||
export type SelectorEngine = { | ||
/** | ||
* Returns the first element matching given selector in the root's subtree. | ||
*/ | ||
query(root: HTMLElement, selector: string): HTMLElement | null | ||
/** | ||
* Returns all elements matching given selector in the root's subtree. | ||
*/ | ||
queryAll(root: HTMLElement, selector: string): HTMLElement[] | ||
} | ||
|
||
type Queries = typeof queries | ||
|
||
type StripNever<T> = {[P in keyof T as T[P] extends never ? never : P]: T[P]} | ||
type ConvertQuery<Query extends Queries[keyof Queries]> = Query extends ( | ||
el: HTMLElement, | ||
...rest: infer Rest | ||
) => HTMLElement | (HTMLElement[] | null) | (HTMLElement | null) | ||
? (...args: Rest) => Locator | ||
: never | ||
|
||
type KebabCase<S> = S extends `${infer C}${infer T}` | ||
? T extends Uncapitalize<T> | ||
? `${Uncapitalize<C>}${KebabCase<T>}` | ||
: `${Uncapitalize<C>}-${KebabCase<T>}` | ||
: S | ||
|
||
export type LocatorQueries = StripNever<{[K in keyof Queries]: ConvertQuery<Queries[K]>}> | ||
|
||
export type Query = keyof Queries | ||
|
||
export type AllQuery = Extract<Query, `${string}All${string}`> | ||
export type FindQuery = Extract<Query, `find${string}`> | ||
export type SupportedQuery = Exclude<Query, FindQuery> | ||
|
||
export type Selector = KebabCase<SupportedQuery> | ||
|
||
declare global { | ||
interface Window { | ||
TestingLibraryDom: typeof TestingLibraryDom | ||
__testingLibraryReviver: typeof reviver | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.