Skip to content

Commit 29f4673

Browse files
committed
feat(fixture): support Page in within fixture (returns Screen)
Support `Page` in addition to `Locator` in `within()` fixture. When a `Page` instance is passed to `within()`, a `Screen` instance will be returned which includes the entire `Page` API in addition to the query methods. ```ts test('within page', async ({ page }) => { // Passing `page` to `within` returns a `Screen` instance const screen = within(page) await screen.goto(/* ... */) const form = screen.queryByRole('form', {name: 'User'}) // Passing a `Locator` returns scoped queries const {queryByLabelText} = within(form) // ... }) ```
1 parent 8a50a9f commit 29f4673

File tree

4 files changed

+46
-20
lines changed

4 files changed

+46
-20
lines changed

Diff for: lib/fixture/locator/fixtures.ts

+13-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {Locator, PlaywrightTestArgs, TestFixture} from '@playwright/test'
2-
import {selectors} from '@playwright/test'
2+
import {Page, selectors} from '@playwright/test'
33

44
import type {
55
Config,
@@ -8,10 +8,11 @@ import type {
88
SelectorEngine,
99
SynchronousQuery,
1010
Within,
11+
WithinReturn,
1112
} from '../types'
1213

13-
import {buildTestingLibraryScript, includes, queryToSelector} from './helpers'
14-
import {allQueryNames, isAllQuery, queriesFor, synchronousQueryNames} from './queries'
14+
import {buildTestingLibraryScript, queryToSelector} from './helpers'
15+
import {isAllQuery, queriesFor, screenFor, synchronousQueryNames} from './queries'
1516

1617
type TestArguments = PlaywrightTestArgs & Config
1718

@@ -34,26 +35,21 @@ const screenFixture: TestFixture<Screen, TestArguments> = async (
3435
{page, asyncUtilExpectedState, asyncUtilTimeout},
3536
use,
3637
) => {
37-
const queries = queriesFor(page, {asyncUtilExpectedState, asyncUtilTimeout})
38-
const revocable = Proxy.revocable(page, {
39-
get(target, property, receiver) {
40-
return includes(allQueryNames, property)
41-
? queries[property]
42-
: Reflect.get(target, property, receiver)
43-
},
44-
})
45-
46-
await use(revocable.proxy as Screen)
47-
48-
revocable.revoke()
38+
const {proxy, revoke} = screenFor(page, {asyncUtilExpectedState, asyncUtilTimeout})
39+
40+
await use(proxy)
41+
42+
revoke()
4943
}
5044

5145
const withinFixture: TestFixture<Within, TestArguments> = async (
5246
{asyncUtilExpectedState, asyncUtilTimeout},
5347
use,
5448
) =>
55-
use(
56-
(locator: Locator): Queries => queriesFor(locator, {asyncUtilExpectedState, asyncUtilTimeout}),
49+
use(<Root extends Page | Locator>(root: Root) =>
50+
'goto' in root
51+
? screenFor(root, {asyncUtilExpectedState, asyncUtilTimeout}).proxy
52+
: (queriesFor(root, {asyncUtilExpectedState, asyncUtilTimeout}) as WithinReturn<Root>),
5753
)
5854

5955
declare const queryName: SynchronousQuery

Diff for: lib/fixture/locator/queries.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import type {
1111
LocatorQueries as Queries,
1212
Query,
1313
QueryQuery,
14+
Screen,
1415
SynchronousQuery,
1516
} from '../types'
1617

17-
import {queryToSelector} from './helpers'
18+
import {includes, queryToSelector} from './helpers'
1819

1920
const isAllQuery = (query: Query): query is AllQuery => query.includes('All')
2021

@@ -109,4 +110,13 @@ const queriesFor = (pageOrLocator: Page | Locator, config?: Partial<Config>) =>
109110
{} as Queries,
110111
)
111112

112-
export {allQueryNames, isAllQuery, isNotFindQuery, queriesFor, synchronousQueryNames}
113+
const screenFor = (page: Page, config: Partial<Config>) =>
114+
Proxy.revocable(page, {
115+
get(target, property, receiver) {
116+
return includes(allQueryNames, property)
117+
? queriesFor(page, config)[property]
118+
: Reflect.get(target, property, receiver)
119+
},
120+
}) as {proxy: Screen; revoke: () => void}
121+
122+
export {allQueryNames, isAllQuery, isNotFindQuery, queriesFor, screenFor, synchronousQueryNames}

Diff for: lib/fixture/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ type KebabCase<S> = S extends `${infer C}${infer T}`
5252

5353
export type LocatorQueries = {[K in keyof Queries]: ConvertQuery<Queries[K]>}
5454

55-
export type Within = (locator: Locator) => LocatorQueries
55+
export type WithinReturn<Root extends Locator | Page> = Root extends Page ? Screen : LocatorQueries
5656
export type Screen = LocatorQueries & Page
57+
export type Within = <Root extends Locator | Page>(locator: Root) => WithinReturn<Root>
5758

5859
export type Query = keyof Queries
5960

Diff for: test/fixture/locators.test.ts

+19
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,25 @@ test.describe('lib/fixture.ts (locators)', () => {
149149
expect(await innerLocator.count()).toBe(1)
150150
})
151151

152+
test('when `Page` instance is provided, `within` returns a `Screen`', async ({
153+
page,
154+
within,
155+
}) => {
156+
const screen = within(page)
157+
158+
await screen.goto(`file://${path.join(__dirname, '../fixtures/page.html')}`)
159+
160+
const form = screen.queryByRole('form', {name: 'User'})
161+
162+
const {queryByLabelText} = within(form)
163+
164+
const outerLocator = queryByLabelText('Name')
165+
const innerLocator = queryByLabelText('Username')
166+
167+
expect(await outerLocator.count()).toBe(0)
168+
expect(await innerLocator.count()).toBe(1)
169+
})
170+
152171
test.describe('configuration', () => {
153172
test.describe('custom data-testid', () => {
154173
test.use({testIdAttribute: 'data-id'})

0 commit comments

Comments
 (0)