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 # 🎭 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) ## [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: | | 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: | | 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. 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. 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 ## 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. 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. 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 ## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network. [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. All of that could be achieved via [APIRequestContext] methods.
<!-- TOC -->
## Writing API Test ## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network. [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. All of that could be achieved via [APIRequestContext] methods.
<!-- TOC3 -->
## Writing API Test ## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network. [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. 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 ## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network. [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 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]. [Browser]. Each test uses its own [BrowserContext] and [Page].
<!-- TOC -->
```java ```java
package org.example; 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. [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 screenshots
Full page screenshot is a screenshot of a full scrollable page, as if you had a very 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 recommend running each test case in a new BrowserContext, this way browser state will be
isolated between the tests. isolated between the tests.
<!-- TOC -->
## JUnit ## 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 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", "name": "chromium-tip-of-tree",
"revision": "1247", "revision": "1248",
"installByDefault": false, "installByDefault": false,
"browserVersion": "129.0.6640.0" "browserVersion": "129.0.6644.0"
}, },
{ {
"name": "firefox", "name": "firefox",
"revision": "1458", "revision": "1462",
"installByDefault": true, "installByDefault": true,
"browserVersion": "128.0" "browserVersion": "129.0"
}, },
{ {
"name": "firefox-beta", "name": "firefox-beta",
"revision": "1461", "revision": "1462",
"installByDefault": false, "installByDefault": false,
"browserVersion": "130.0b2" "browserVersion": "130.0b2"
}, },
{ {
"name": "webkit", "name": "webkit",
"revision": "2056", "revision": "2061",
"installByDefault": true, "installByDefault": true,
"revisionOverrides": { "revisionOverrides": {
"mac10.14": "1446", "mac10.14": "1446",

View file

@ -1592,7 +1592,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Desktop Firefox HiDPI": { "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": { "screen": {
"width": 1792, "width": 1792,
"height": 1120 "height": 1120
@ -1652,7 +1652,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Desktop Firefox": { "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": { "screen": {
"width": 1920, "width": 1920,
"height": 1080 "height": 1080

View file

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

View file

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

View file

@ -4452,19 +4452,23 @@ might return multiple quads for inline nodes.
savedResultIndex?: number; 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 = { export type setShouldBlackboxURLParameters = {
url: string; url: string;
shouldBlackbox: boolean; shouldBlackbox: boolean;
/** /**
* If true, <code>url</code> is case sensitive. * If <code>true</code>, <code>url</code> is case sensitive.
*/ */
caseSensitive?: boolean; caseSensitive?: boolean;
/** /**
* If true, treat <code>url</code> as regular expression. * If <code>true</code>, treat <code>url</code> as regular expression.
*/ */
isRegex?: boolean; 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 = { 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 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'; type BrowserName = 'chromium' | 'firefox' | 'webkit';

View file

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

View file

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

View file

@ -131,7 +131,7 @@ export const AttachmentsTab: React.FunctionComponent<{
})} })}
{attachments.size ? <div className='attachments-section'>Attachments</div> : undefined} {attachments.size ? <div className='attachments-section'>Attachments</div> : undefined}
{[...attachments.values()].map((a, i) => { {[...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} /> <ExpandableAttachment attachment={a} />
</div>; </div>;
})} })}
@ -154,3 +154,7 @@ function downloadURL(attachment: Attachment) {
params.dct = attachment.contentType; params.dct = attachment.contentType;
return attachmentURL(attachment, params); 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> <span className='log-list-duration'>{entry.time}</span>
{entry.message} {entry.message}
</div>} </div>}
noHighlightOnHover={true} notSelectable={true}
/>; />;
}; };

View file

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

View file

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

View file

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

View file

@ -21,7 +21,7 @@ import { expect, playwrightTest as base } from '../config/browserTest';
import { kTargetClosedErrorMessage } from 'tests/config/errors'; import { kTargetClosedErrorMessage } from 'tests/config/errors';
const it = base.extend({ const it = base.extend({
context: async () => { context: async ({}, use) => {
throw new Error('global fetch tests should not use context'); 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'); 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) => { server.setRoute('/empty.html', (req, res) => {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.end(); 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)'); expect(color).toBe('rgb(255, 192, 203)');
}); });
it('continue should propagate headers to redirects', async ({ page, server, browserName }) => { it('continue should propagate headers to redirects', {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28758' }); annotation: [
it.fixme(browserName === 'firefox'); { 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 server.setRedirect('/redirect', '/empty.html');
await page.route('**/redirect', route => { await page.route('**/redirect', route => {
void route.continue({ 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', { it('redirected requests should report overridden headers', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31351' } annotation: [
}, async ({ page, server, browserName }) => { { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31351' },
it.fixme(browserName === 'firefox'); { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32045' },
]
}, async ({ page, server }) => {
await server.setRedirect('/redirect', '/empty.html'); await server.setRedirect('/redirect', '/empty.html');
await page.route('**/redirect', route => { await page.route('**/redirect', route => {
const headers = route.request().headers(); const headers = route.request().headers();
@ -433,9 +438,12 @@ it('redirected requests should report overridden headers', {
expect((await response.request().allHeaders())['custom']).toBe('value'); expect((await response.request().allHeaders())['custom']).toBe('value');
}); });
it('continue should delete headers on redirects', async ({ page, server, browserName }) => { it('continue should delete headers on redirects', {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' }); annotation: [
it.fixme(browserName === 'firefox'); { 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'); await page.goto(server.PREFIX + '/empty.html');
server.setRoute('/something', (request, response) => { server.setRoute('/something', (request, response) => {
response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); 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 }) => { test('should check types of fixtures', async ({ runTSC }) => {
const result = await runTSC({ const result = await runTSC({
'helper.ts': ` '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 type MyOptions = { foo: string, bar: number };
export const test = base.extend<{ foo: string }, { bar: number }>({ export const test = base.extend<{ foo: string }, { bar: number }>({
foo: 'foo', foo: 'foo',
@ -71,7 +71,7 @@ test('should check types of fixtures', async ({ runTSC }) => {
// @ts-expect-error // @ts-expect-error
baz: true, baz: true,
}); });
const fail9 = test.extend<{ foo: string }>({ const fail9 = test.extend({
foo: [ async ({}, use) => { foo: [ async ({}, use) => {
await use('foo'); await use('foo');
// @ts-expect-error // @ts-expect-error
@ -100,7 +100,21 @@ test('should check types of fixtures', async ({ runTSC }) => {
return y; 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': ` 'playwright.config.ts': `
import { MyOptions } from './helper'; 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 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'; type BrowserName = 'chromium' | 'firefox' | 'webkit';