Merge branch 'main' into react-18

Signed-off-by: Simon Knott <info@simonknott.de>
This commit is contained in:
Simon Knott 2024-08-12 09:20:08 +02:00 committed by GitHub
commit 93a4c700e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 112 additions and 98 deletions

View file

@ -1,6 +1,6 @@
# 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-128.0.6613.27-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-128.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord)
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-128.0.6613.27-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-129.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord)
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
@ -10,7 +10,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->128.0.6613.27<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->128.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->129.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.

View file

@ -14,8 +14,6 @@ A few examples of problems this can catch include:
The following examples rely on the [`com.deque.html.axe-core/playwright`](https://mvnrepository.com/artifact/com.deque.html.axe-core/playwright) Maven package which adds support for running the [axe accessibility testing engine](https://www.deque.com/axe/) as part of your Playwright tests.
<!-- TOC -->
## Disclaimer
Automated accessibility tests can detect some common accessibility problems such as missing or invalid properties. But many accessibility problems can only be discovered through manual testing. We recommend using a combination of automated testing, manual accessibility assessments, and inclusive user testing.

View file

@ -18,8 +18,6 @@ All of that could be achieved via [APIRequestContext] methods.
The following examples rely on the [`Microsoft.Playwright.MSTest`](./test-runners.md) package which creates a Playwright and Page instance for each test.
<!-- TOC -->
## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network.

View file

@ -16,8 +16,6 @@ A few examples where it may come in handy:
All of that could be achieved via [APIRequestContext] methods.
<!-- TOC -->
## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network.

View file

@ -16,8 +16,6 @@ A few examples where it may come in handy:
All of that could be achieved via [APIRequestContext] methods.
<!-- TOC3 -->
## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network.

View file

@ -18,8 +18,6 @@ All of that could be achieved via [APIRequestContext] methods.
The following examples rely on the [`pytest-playwright`](./test-runners.md) package which add Playwright fixtures to the Pytest test-runner.
<!-- TOC -->
## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network.

View file

@ -10,8 +10,6 @@ With a few lines of code, you can hook up Playwright to your favorite Java test
In [JUnit](https://junit.org/junit5/), you can use Playwright [fixtures](./junit.md#fixtures) to automatically initialize [Playwright], [Browser], [BrowserContext] or [Page]. In the example below, all three test methods use the same
[Browser]. Each test uses its own [BrowserContext] and [Page].
<!-- TOC -->
```java
package org.example;

View file

@ -33,8 +33,6 @@ await Page.ScreenshotAsync(new()
[Screenshots API](./api/class-page#page-screenshot) accepts many parameters for image format, clip area, quality, etc. Make sure to check them out.
<!-- TOC -->
## Full page screenshots
Full page screenshot is a screenshot of a full scrollable page, as if you had a very

View file

@ -11,8 +11,6 @@ Playwright and Browser instances can be reused between tests for better performa
recommend running each test case in a new BrowserContext, this way browser state will be
isolated between the tests.
<!-- TOC -->
## JUnit
In [JUnit](https://junit.org/junit5/) you can initialize [Playwright] and [Browser] in [@BeforeAll](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/BeforeAll.html) method and

View file

@ -9,25 +9,25 @@
},
{
"name": "chromium-tip-of-tree",
"revision": "1247",
"revision": "1248",
"installByDefault": false,
"browserVersion": "129.0.6640.0"
"browserVersion": "129.0.6644.0"
},
{
"name": "firefox",
"revision": "1458",
"revision": "1462",
"installByDefault": true,
"browserVersion": "128.0"
"browserVersion": "129.0"
},
{
"name": "firefox-beta",
"revision": "1461",
"revision": "1462",
"installByDefault": false,
"browserVersion": "130.0b2"
},
{
"name": "webkit",
"revision": "2056",
"revision": "2061",
"installByDefault": true,
"revisionOverrides": {
"mac10.14": "1446",

View file

@ -1592,7 +1592,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0",
"screen": {
"width": 1792,
"height": 1120
@ -1652,7 +1652,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0",
"screen": {
"width": 1920,
"height": 1080

View file

@ -220,11 +220,6 @@ export module Protocol {
}|null;
};
export type setDefaultViewportReturnValue = void;
export type setScrollbarsHiddenParameters = {
browserContextId?: string;
hidden: boolean;
};
export type setScrollbarsHiddenReturnValue = void;
export type setInitScriptsParameters = {
browserContextId?: string;
scripts: {
@ -1116,7 +1111,6 @@ export module Protocol {
"Browser.setDownloadOptions": Browser.setDownloadOptionsParameters;
"Browser.setTouchOverride": Browser.setTouchOverrideParameters;
"Browser.setDefaultViewport": Browser.setDefaultViewportParameters;
"Browser.setScrollbarsHidden": Browser.setScrollbarsHiddenParameters;
"Browser.setInitScripts": Browser.setInitScriptsParameters;
"Browser.addBinding": Browser.addBindingParameters;
"Browser.grantPermissions": Browser.grantPermissionsParameters;
@ -1197,7 +1191,6 @@ export module Protocol {
"Browser.setDownloadOptions": Browser.setDownloadOptionsReturnValue;
"Browser.setTouchOverride": Browser.setTouchOverrideReturnValue;
"Browser.setDefaultViewport": Browser.setDefaultViewportReturnValue;
"Browser.setScrollbarsHidden": Browser.setScrollbarsHiddenReturnValue;
"Browser.setInitScripts": Browser.setInitScriptsReturnValue;
"Browser.addBinding": Browser.addBindingReturnValue;
"Browser.grantPermissions": Browser.grantPermissionsReturnValue;

View file

@ -31,7 +31,7 @@ import * as accessibility from './accessibility';
import { FileChooser } from './fileChooser';
import type { Progress } from './progress';
import { ProgressController } from './progress';
import { LongStandingScope, assert, createGuid, isError } from '../utils';
import { LongStandingScope, assert, createGuid } from '../utils';
import { ManualPromise } from '../utils/manualPromise';
import { debugLogger } from '../utils/debugLogger';
import type { ImageComparatorOptions } from '../utils/comparators';
@ -851,10 +851,7 @@ export class PageBinding {
}
context.evaluate(deliverResult, { name, seq, result }).catch(e => debugLogger.log('error', e));
} catch (error) {
if (isError(error))
context.evaluate(deliverError, { name, seq, message: error.message, stack: error.stack }).catch(e => debugLogger.log('error', e));
else
context.evaluate(deliverErrorValue, { name, seq, error }).catch(e => debugLogger.log('error', e));
context.evaluate(deliverResult, { name, seq, error }).catch(e => debugLogger.log('error', e));
}
function takeHandle(arg: { name: string, seq: number }) {
@ -863,21 +860,13 @@ export class PageBinding {
return handle;
}
function deliverResult(arg: { name: string, seq: number, result: any }) {
(globalThis as any)[arg.name]['callbacks'].get(arg.seq).resolve(arg.result);
(globalThis as any)[arg.name]['callbacks'].delete(arg.seq);
}
function deliverError(arg: { name: string, seq: number, message: string, stack: string | undefined }) {
const error = new Error(arg.message);
error.stack = arg.stack;
(globalThis as any)[arg.name]['callbacks'].get(arg.seq).reject(error);
(globalThis as any)[arg.name]['callbacks'].delete(arg.seq);
}
function deliverErrorValue(arg: { name: string, seq: number, error: any }) {
(globalThis as any)[arg.name]['callbacks'].get(arg.seq).reject(arg.error);
(globalThis as any)[arg.name]['callbacks'].delete(arg.seq);
function deliverResult(arg: { name: string, seq: number, result?: any, error?: any }) {
const callbacks = (globalThis as any)[arg.name]['callbacks'];
if ('error' in arg)
callbacks.get(arg.seq).reject(arg.error);
else
callbacks.get(arg.seq).resolve(arg.result);
callbacks.delete(arg.seq);
}
}
}

View file

@ -4452,19 +4452,23 @@ might return multiple quads for inline nodes.
savedResultIndex?: number;
}
/**
* Sets whether the given URL should be in the list of blackboxed scripts, which are ignored when pausing/stepping/debugging.
* Sets whether the given URL should be in the list of blackboxed scripts, which are ignored when pausing.
*/
export type setShouldBlackboxURLParameters = {
url: string;
shouldBlackbox: boolean;
/**
* If true, <code>url</code> is case sensitive.
* If <code>true</code>, <code>url</code> is case sensitive.
*/
caseSensitive?: boolean;
/**
* If true, treat <code>url</code> as regular expression.
* If <code>true</code>, treat <code>url</code> as regular expression.
*/
isRegex?: boolean;
/**
* If provided, limits where in the script the debugger will skip pauses. Expected structure is a repeated <code>[startLine, startColumn, endLine, endColumn]</code>. Ignored if <code>shouldBlackbox</code> is <code>false</code>.
*/
sourceRanges?: number[];
}
export type setShouldBlackboxURLReturnValue = {
}

View file

@ -4815,9 +4815,9 @@ export type Fixtures<T extends KeyValue = {}, W extends KeyValue = {}, PT extend
} & {
[K in keyof PT]?: TestFixtureValue<PT[K], T & W & PT & PW> | [TestFixtureValue<PT[K], T & W & PT & PW>, { scope: 'test', timeout?: number | undefined, title?: string, box?: boolean }];
} & {
[K in Exclude<keyof W, keyof PW>]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
[K in keyof W]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
} & {
[K in Exclude<keyof T, keyof PT>]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
[K in keyof T]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
};
type BrowserName = 'chromium' | 'firefox' | 'webkit';

View file

@ -17,11 +17,10 @@
import '@web/common.css';
import { applyTheme } from '@web/theme';
import '@web/third_party/vscode/codicon.css';
import * as ReactDOM from 'react-dom';
import * as ReactDOM from 'react-dom/client';
import { Main } from './main';
(async () => {
applyTheme();
// TODO: we'd like to migrate this to React 18, but concurrent mode seems to break some of our tests.
ReactDOM.render(<Main/>, document.querySelector('#root'));
ReactDOM.createRoot(document.querySelector('#root')!).render(<Main/>);
})();

View file

@ -30,12 +30,14 @@ export const Main: React.FC = ({
window.playwrightSetSources = setSources;
window.playwrightSetPaused = setPaused;
window.playwrightUpdateLogs = callLogs => {
const newLog = new Map<string, CallLog>(log);
for (const callLog of callLogs) {
callLog.reveal = !log.has(callLog.id);
newLog.set(callLog.id, callLog);
}
setLog(newLog);
setLog(log => {
const newLog = new Map<string, CallLog>(log);
for (const callLog of callLogs) {
callLog.reveal = !log.has(callLog.id);
newLog.set(callLog.id, callLog);
}
return newLog;
});
};
window.playwrightSourcesEchoForTest = sources;

View file

@ -131,7 +131,7 @@ export const AttachmentsTab: React.FunctionComponent<{
})}
{attachments.size ? <div className='attachments-section'>Attachments</div> : undefined}
{[...attachments.values()].map((a, i) => {
return <div className='attachment-item' key={`attachment-${i}`}>
return <div className='attachment-item' key={attachmentKey(a, i)}>
<ExpandableAttachment attachment={a} />
</div>;
})}
@ -154,3 +154,7 @@ function downloadURL(attachment: Attachment) {
params.dct = attachment.contentType;
return attachmentURL(attachment, params);
}
function attachmentKey(attachment: Attachment, index: number) {
return index + '-' + (attachment.sha1 ? `sha1-` + attachment.sha1 : `path-` + attachment.path);
}

View file

@ -63,6 +63,6 @@ export const LogTab: React.FunctionComponent<{
<span className='log-list-duration'>{entry.time}</span>
{entry.message}
</div>}
noHighlightOnHover={true}
notSelectable={true}
/>;
};

View file

@ -29,3 +29,9 @@
align-items: center;
flex: 1 1 auto;
}
.source-tab-file-name div {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View file

@ -95,6 +95,7 @@ export const SourceTab: React.FunctionComponent<{
}, [onOpenExternally, location]);
const showStackFrames = (stack?.length ?? 0) > 1;
const shortFileName = getFileName(fileName);
return <SplitView
sidebarSize={200}
@ -102,8 +103,10 @@ export const SourceTab: React.FunctionComponent<{
sidebarHidden={!showStackFrames}
main={<div className='vbox' data-testid='source-code'>
{ fileName && <Toolbar>
<span className='source-tab-file-name'>{fileName}</span>
<CopyToClipboard description='Copy filename' value={getFileName(fileName)}/>
<div className='source-tab-file-name' title={fileName}>
<div>{shortFileName}</div>
</div>
<CopyToClipboard description='Copy filename' value={shortFileName}/>
{location && <ToolbarButton icon='link-external' title='Open in VS Code' onClick={openExternally}></ToolbarButton>}
</Toolbar> }
<CodeMirrorWrapper text={source.content || ''} language='javascript' highlight={highlight} revealLine={targetLine} readOnly={true} lineNumbers={true} />

View file

@ -121,7 +121,7 @@ export function GridView<T>(model: GridViewProps<T>) {
onIconClicked={model.onIconClicked}
noItemsMessage={model.noItemsMessage}
dataTestId={model.dataTestId}
noHighlightOnHover={model.noHighlightOnHover}
notSelectable={model.notSelectable}
></ListView>
</div>
</div>;

View file

@ -34,6 +34,10 @@
padding-left: 5px;
}
.list-view-content.not-selectable > .list-view-entry {
cursor: inherit;
}
.list-view-entry.highlighted:not(.selected) {
background-color: var(--vscode-list-inactiveSelectionBackground) !important;
}

View file

@ -16,6 +16,7 @@
import * as React from 'react';
import './listView.css';
import { clsx } from '@web/uiUtils';
export type ListViewProps<T> = {
name: string,
@ -36,7 +37,7 @@ export type ListViewProps<T> = {
onIconClicked?: (item: T, index: number) => void,
noItemsMessage?: string,
dataTestId?: string,
noHighlightOnHover?: boolean,
notSelectable?: boolean,
};
const scrollPositions = new Map<string, number>();
@ -60,7 +61,7 @@ export function ListView<T>({
onIconClicked,
noItemsMessage,
dataTestId,
noHighlightOnHover,
notSelectable,
}: ListViewProps<T>) {
const itemListRef = React.useRef<HTMLDivElement>(null);
const [highlightedItem, setHighlightedItem] = React.useState<any>();
@ -85,9 +86,9 @@ export function ListView<T>({
itemListRef.current.scrollTop = scrollPositions.get(name) || 0;
}, [name]);
return <div className={`list-view vbox ` + name + '-list-view' } role={items.length > 0 ? 'list' : undefined} data-testid={dataTestId || (name + '-list')}>
return <div className={clsx(`list-view vbox`, name + '-list-view')} role={items.length > 0 ? 'list' : undefined} data-testid={dataTestId || (name + '-list')}>
<div
className='list-view-content'
className={clsx('list-view-content', notSelectable && 'not-selectable')}
tabIndex={0}
onKeyDown={event => {
if (selectedItem && event.key === 'Enter') {
@ -134,18 +135,19 @@ export function ListView<T>({
>
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
{items.map((item, index) => {
const selectedSuffix = selectedItem === item ? ' selected' : '';
const highlightedSuffix = !noHighlightOnHover && highlightedItem === item ? ' highlighted' : '';
const errorSuffix = isError?.(item, index) ? ' error' : '';
const warningSuffix = isWarning?.(item, index) ? ' warning' : '';
const infoSuffix = isInfo?.(item, index) ? ' info' : '';
const indentation = indent?.(item, index) || 0;
const rendered = render(item, index);
return <div
key={id?.(item, index) || index}
onDoubleClick={() => onAccepted?.(item, index)}
role='listitem'
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix + infoSuffix}
className={clsx(
'list-view-entry',
selectedItem === item && 'selected',
!notSelectable && highlightedItem === item && 'highlighted',
isError?.(item, index) && 'error',
isWarning?.(item, index) && 'warning',
isInfo?.(item, index) && 'info')}
onClick={() => onSelected?.(item, index)}
onMouseEnter={() => setHighlightedItem(item)}
onMouseLeave={() => setHighlightedItem(undefined)}

View file

@ -21,7 +21,7 @@ import { expect, playwrightTest as base } from '../config/browserTest';
import { kTargetClosedErrorMessage } from 'tests/config/errors';
const it = base.extend({
context: async () => {
context: async ({}, use) => {
throw new Error('global fetch tests should not use context');
}
});

View file

@ -179,7 +179,9 @@ it('should work with Cross-Origin-Opener-Policy after redirect', async ({ page,
expect(firstRequest.url()).toBe(server.PREFIX + '/redirect');
});
it('should properly cancel Cross-Origin-Opener-Policy navigation', async ({ page, server }) => {
it('should properly cancel Cross-Origin-Opener-Policy navigation', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32107' },
}, async ({ page, server }) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.end();

View file

@ -393,9 +393,12 @@ it('should continue preload link requests', async ({ page, server, browserName }
expect(color).toBe('rgb(255, 192, 203)');
});
it('continue should propagate headers to redirects', async ({ page, server, browserName }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28758' });
it.fixme(browserName === 'firefox');
it('continue should propagate headers to redirects', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28758' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32045' },
]
}, async ({ page, server }) => {
await server.setRedirect('/redirect', '/empty.html');
await page.route('**/redirect', route => {
void route.continue({
@ -413,9 +416,11 @@ it('continue should propagate headers to redirects', async ({ page, server, brow
});
it('redirected requests should report overridden headers', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31351' }
}, async ({ page, server, browserName }) => {
it.fixme(browserName === 'firefox');
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31351' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32045' },
]
}, async ({ page, server }) => {
await server.setRedirect('/redirect', '/empty.html');
await page.route('**/redirect', route => {
const headers = route.request().headers();
@ -433,9 +438,12 @@ it('redirected requests should report overridden headers', {
expect((await response.request().allHeaders())['custom']).toBe('value');
});
it('continue should delete headers on redirects', async ({ page, server, browserName }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' });
it.fixme(browserName === 'firefox');
it('continue should delete headers on redirects', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' },
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32045' },
]
}, async ({ page, server }) => {
await page.goto(server.PREFIX + '/empty.html');
server.setRoute('/something', (request, response) => {
response.writeHead(200, { 'Access-Control-Allow-Origin': '*' });

View file

@ -19,7 +19,7 @@ import { test, expect } from './playwright-test-fixtures';
test('should check types of fixtures', async ({ runTSC }) => {
const result = await runTSC({
'helper.ts': `
import { test as base, expect } from '@playwright/test';
import { test as base, expect, Page } from '@playwright/test';
export type MyOptions = { foo: string, bar: number };
export const test = base.extend<{ foo: string }, { bar: number }>({
foo: 'foo',
@ -71,7 +71,7 @@ test('should check types of fixtures', async ({ runTSC }) => {
// @ts-expect-error
baz: true,
});
const fail9 = test.extend<{ foo: string }>({
const fail9 = test.extend({
foo: [ async ({}, use) => {
await use('foo');
// @ts-expect-error
@ -100,7 +100,21 @@ test('should check types of fixtures', async ({ runTSC }) => {
return y;
});
},
})
});
const chain1 = base.extend({
page: async ({ page }, use) => {
await use(page);
},
});
const chain2 = chain1.extend<{ pageAsUser: Page }>({
pageAsUser: async ({ page }, use) => {
// @ts-expect-error
const x: number = page;
// @ts-expect-error
await use(x);
},
});
`,
'playwright.config.ts': `
import { MyOptions } from './helper';

View file

@ -144,9 +144,9 @@ export type Fixtures<T extends KeyValue = {}, W extends KeyValue = {}, PT extend
} & {
[K in keyof PT]?: TestFixtureValue<PT[K], T & W & PT & PW> | [TestFixtureValue<PT[K], T & W & PT & PW>, { scope: 'test', timeout?: number | undefined, title?: string, box?: boolean }];
} & {
[K in Exclude<keyof W, keyof PW>]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
[K in keyof W]?: [WorkerFixtureValue<W[K], W & PW>, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
} & {
[K in Exclude<keyof T, keyof PT>]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
[K in keyof T]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }];
};
type BrowserName = 'chromium' | 'firefox' | 'webkit';