Merge branch 'main' into decouple-testserverconnection-transport

This commit is contained in:
Simon Knott 2024-08-23 13:12:49 +02:00
commit 577602d95b
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
19 changed files with 92 additions and 100 deletions

View file

@ -439,9 +439,9 @@ const mockPath = { path: path.resolve(__dirname, '../mocks/mockRandom.js') };
// Passing 42 as an argument to the default export function.
await context.addInitScript({ path: mockPath }, 42);
// Make sure to pass undefined even if you do not need to pass an argument.
// Make sure to pass something even if you do not need to pass an argument.
// This instructs Playwright to treat the file as a commonjs module.
await context.addInitScript({ path: mockPath }, undefined);
await context.addInitScript({ path: mockPath }, '');
```
### param: BrowserContext.addInitScript.script

View file

@ -643,9 +643,9 @@ const mockPath = { path: path.resolve(__dirname, '../mocks/mockRandom.js') };
// Passing 42 as an argument to the default export function.
await page.addInitScript({ path: mockPath }, 42);
// Make sure to pass undefined even if you do not need to pass an argument.
// Make sure to pass something even if you do not need to pass an argument.
// This instructs Playwright to treat the file as a commonjs module.
await page.addInitScript({ path: mockPath }, undefined);
await page.addInitScript({ path: mockPath }, '');
```
### param: Page.addInitScript.script

View file

@ -9,9 +9,9 @@
},
{
"name": "chromium-tip-of-tree",
"revision": "1250",
"revision": "1253",
"installByDefault": false,
"browserVersion": "129.0.6658.0"
"browserVersion": "130.0.6670.0"
},
{
"name": "firefox",

View file

@ -308,7 +308,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
}
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> {
const source = await evaluationScript(script, arg, arguments.length > 1);
const source = await evaluationScript(script, arg);
await this._channel.addInitScript({ source });
}

View file

@ -28,7 +28,7 @@ export function envObjectToArray(env: types.Env): { name: string, value: string
return result;
}
export async function evaluationScript(fun: Function | string | { path?: string, content?: string }, arg: any, hasArg: boolean, addSourceUrl: boolean = true): Promise<string> {
export async function evaluationScript(fun: Function | string | { path?: string, content?: string }, arg: any, addSourceUrl: boolean = true): Promise<string> {
if (typeof fun === 'function') {
const source = fun.toString();
const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg);
@ -46,7 +46,7 @@ export async function evaluationScript(fun: Function | string | { path?: string,
}
if (fun.path !== undefined) {
let source = await fs.promises.readFile(fun.path, 'utf8');
if (hasArg) {
if (arg !== undefined) {
// Assume a CJS module that has a function default export.
source = `(() => {
var exports = {}; var module = { exports };

View file

@ -492,7 +492,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
}
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
const source = await evaluationScript(script, arg, arguments.length > 1);
const source = await evaluationScript(script, arg);
await this._channel.addInitScript({ source });
}

View file

@ -26,7 +26,7 @@ export class Selectors implements api.Selectors {
private _registrations: channels.SelectorsRegisterParams[] = [];
async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise<void> {
const source = await evaluationScript(script, undefined, false, false);
const source = await evaluationScript(script, undefined, false);
const params = { ...options, name, source };
for (const channel of this._channels)
await channel._channel.register(params);

View file

@ -312,9 +312,9 @@ export interface Page {
* // Passing 42 as an argument to the default export function.
* await page.addInitScript({ path: mockPath }, 42);
*
* // Make sure to pass undefined even if you do not need to pass an argument.
* // Make sure to pass something even if you do not need to pass an argument.
* // This instructs Playwright to treat the file as a commonjs module.
* await page.addInitScript({ path: mockPath }, undefined);
* await page.addInitScript({ path: mockPath }, '');
* ```
*
* @param script Script to be evaluated in the page.
@ -7723,9 +7723,9 @@ export interface BrowserContext {
* // Passing 42 as an argument to the default export function.
* await context.addInitScript({ path: mockPath }, 42);
*
* // Make sure to pass undefined even if you do not need to pass an argument.
* // Make sure to pass something even if you do not need to pass an argument.
* // This instructs Playwright to treat the file as a commonjs module.
* await context.addInitScript({ path: mockPath }, undefined);
* await context.addInitScript({ path: mockPath }, '');
* ```
*
* @param script Script to be evaluated in all pages in the browser context.

View file

@ -14,11 +14,24 @@
* limitations under the License.
*/
import { TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver';
import { statusEx } from '@testIsomorphic/testTree';
import type { ReporterV2 } from 'playwright/src/reporters/reporterV2';
import type * as reporterTypes from 'playwright/types/testReporter';
import type { Progress, TestModel } from './uiModeModel';
import { TeleReporterReceiver, TeleSuite } from './teleReceiver';
import { statusEx } from './testTree';
import type { ReporterV2 } from '../reporters/reporterV2';
import type * as reporterTypes from '../../types/testReporter';
export type TeleSuiteUpdaterProgress = {
total: number;
passed: number;
failed: number;
skipped: number;
};
export type TeleSuiteUpdaterTestModel = {
config: reporterTypes.FullConfig;
rootSuite: reporterTypes.Suite;
loadErrors: reporterTypes.TestError[];
progress: TeleSuiteUpdaterProgress;
};
export type TeleSuiteUpdaterOptions = {
onUpdate: (force?: boolean) => void,
@ -30,7 +43,7 @@ export class TeleSuiteUpdater {
rootSuite: TeleSuite | undefined;
config: reporterTypes.FullConfig | undefined;
readonly loadErrors: reporterTypes.TestError[] = [];
readonly progress: Progress = {
readonly progress: TeleSuiteUpdaterProgress = {
total: 0,
passed: 0,
failed: 0,
@ -118,11 +131,11 @@ export class TeleSuiteUpdater {
return false;
},
onStdOut: () => {},
onStdErr: () => {},
onExit: () => {},
onStepBegin: () => {},
onStepEnd: () => {},
onStdOut: () => { },
onStdErr: () => { },
onExit: () => { },
onStepBegin: () => { },
onStepEnd: () => { },
};
}
@ -134,7 +147,7 @@ export class TeleSuiteUpdater {
onError: (error: reporterTypes.TestError) => this._handleOnError(error)
});
for (const message of report)
receiver.dispatch(message);
void receiver.dispatch(message);
}
processListReport(report: any[]) {
@ -144,14 +157,14 @@ export class TeleSuiteUpdater {
this._testResultsSnapshot = new Map(tests.map(test => [test.id, test.results]));
this._receiver.reset();
for (const message of report)
this._receiver.dispatch(message);
void this._receiver.dispatch(message);
}
processTestReportEvent(message: any) {
// The order of receiver dispatches matters here, we want to assign `lastRunTestCount`
// before we use it.
this._lastRunReceiver?.dispatch(message)?.catch(() => {});
this._receiver.dispatch(message)?.catch(() => {});
this._lastRunReceiver?.dispatch(message)?.catch(() => { });
this._receiver.dispatch(message)?.catch(() => { });
}
private _handleOnError(error: reporterTypes.TestError) {
@ -160,7 +173,7 @@ export class TeleSuiteUpdater {
this._options.onUpdate();
}
asModel(): TestModel {
asModel(): TeleSuiteUpdaterTestModel {
return {
rootSuite: this.rootSuite || new TeleSuite('', 'root'),
config: this.config!,

View file

@ -113,13 +113,13 @@ export class WebServerPlugin implements TestRunnerPlugin {
debugWebServer(`Process started`);
launchedProcess.stderr!.on('data', line => {
launchedProcess.stderr!.on('data', data => {
if (debugWebServer.enabled || (this._options.stderr === 'pipe' || !this._options.stderr))
this._reporter!.onStdErr?.(colors.dim('[WebServer] ') + line.toString());
this._reporter!.onStdErr?.(prefixOutputLines(data.toString()));
});
launchedProcess.stdout!.on('data', line => {
launchedProcess.stdout!.on('data', data => {
if (debugWebServer.enabled || this._options.stdout === 'pipe')
this._reporter!.onStdOut?.(colors.dim('[WebServer] ') + line.toString());
this._reporter!.onStdOut?.(prefixOutputLines(data.toString()));
});
}
@ -201,3 +201,14 @@ export const webServerPluginsForConfig = (config: FullConfigInternal): TestRunne
return webServerPlugins;
};
function prefixOutputLines(output: string) {
const lastIsNewLine = output[output.length - 1] === '\n';
let lines = output.split('\n');
if (lastIsNewLine)
lines.pop();
lines = lines.map(line => colors.dim('[WebServer] ') + line);
if (lastIsNewLine)
lines.push('');
return lines.join('\n');
}

View file

@ -20,7 +20,7 @@ import '@web/third_party/vscode/codicon.css';
import { settings } from '@web/uiUtils';
import React from 'react';
import './uiModeFiltersView.css';
import type { TestModel } from './uiModeModel';
import type { TeleSuiteUpdaterTestModel } from '@testIsomorphic/teleSuiteUpdater';
export const FiltersView: React.FC<{
filterText: string;
@ -29,7 +29,7 @@ export const FiltersView: React.FC<{
setStatusFilters: (filters: Map<string, boolean>) => void;
projectFilters: Map<string, boolean>;
setProjectFilters: (filters: Map<string, boolean>) => void;
testModel: TestModel | undefined,
testModel: TeleSuiteUpdaterTestModel | undefined,
runTests: () => void;
}> = ({ filterText, setFilterText, statusFilters, setStatusFilters, projectFilters, setProjectFilters, testModel, runTests }) => {
const [expanded, setExpanded] = React.useState(false);

View file

@ -1,33 +0,0 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import type * as reporterTypes from 'playwright/types/testReporter';
export type Progress = {
total: number;
passed: number;
failed: number;
skipped: number;
};
export type TestModel = {
config: reporterTypes.FullConfig;
rootSuite: reporterTypes.Suite;
loadErrors: reporterTypes.TestError[];
progress: Progress;
};
export const pathSeparator = navigator.userAgent.toLowerCase().includes('windows') ? '\\' : '/';

View file

@ -27,10 +27,10 @@ import type * as reporterTypes from 'playwright/types/testReporter';
import React from 'react';
import type { SourceLocation } from './modelUtil';
import { testStatusIcon } from './testUtils';
import type { TestModel } from './uiModeModel';
import './uiModeTestListView.css';
import type { TestServerConnection } from '@testIsomorphic/testServerConnection';
import { TagView } from './tag';
import type { TeleSuiteUpdaterTestModel } from '@testIsomorphic/teleSuiteUpdater';
const TestTreeView = TreeView<TreeItem>;
@ -38,7 +38,7 @@ export const TestListView: React.FC<{
filterText: string,
testTree: TestTree,
testServerConnection: TestServerConnection | undefined,
testModel?: TestModel,
testModel?: TeleSuiteUpdaterTestModel,
runTests: (mode: 'bounce-if-busy' | 'queue-if-busy', testIds: Set<string>) => void,
runningState?: { testIds: Set<string>, itemSelectedByUser?: boolean, completed?: boolean },
watchAll: boolean,
@ -179,7 +179,7 @@ export const TestListView: React.FC<{
noItemsMessage={isLoading ? 'Loading\u2026' : 'No tests'} />;
};
function itemLocation(item: TreeItem | undefined, model: TestModel | undefined): SourceLocation | undefined {
function itemLocation(item: TreeItem | undefined, model: TeleSuiteUpdaterTestModel | undefined): SourceLocation | undefined {
if (!item || !model)
return;
return {

View file

@ -18,8 +18,7 @@ import '@web/third_party/vscode/codicon.css';
import '@web/common.css';
import React from 'react';
import { TeleSuite } from '@testIsomorphic/teleReceiver';
import { TeleSuiteUpdater } from './teleSuiteUpdater';
import type { Progress } from './uiModeModel';
import { TeleSuiteUpdater, type TeleSuiteUpdaterProgress, type TeleSuiteUpdaterTestModel } from '@testIsomorphic/teleSuiteUpdater';
import type { TeleTestCase } from '@testIsomorphic/teleReceiver';
import type * as reporterTypes from 'playwright/types/testReporter';
import { SplitView } from '@web/components/splitView';
@ -34,13 +33,13 @@ import { clsx, settings, useSetting } from '@web/uiUtils';
import { statusEx, TestTree } from '@testIsomorphic/testTree';
import type { TreeItem } from '@testIsomorphic/testTree';
import { TestServerConnection, WebSocketTestServerTransport } from '@testIsomorphic/testServerConnection';
import { pathSeparator } from './uiModeModel';
import type { TestModel } from './uiModeModel';
import { FiltersView } from './uiModeFiltersView';
import { TestListView } from './uiModeTestListView';
import { TraceView } from './uiModeTraceView';
import { SettingsView } from './settingsView';
const pathSeparator = navigator.userAgent.toLowerCase().includes('windows') ? '\\' : '/';
let xtermSize = { cols: 80, rows: 24 };
const xtermDataSource: XtermDataSource = {
pending: [],
@ -80,8 +79,8 @@ export const UIModeView: React.FC<{}> = ({
['skipped', false],
]));
const [projectFilters, setProjectFilters] = React.useState<Map<string, boolean>>(new Map());
const [testModel, setTestModel] = React.useState<TestModel>();
const [progress, setProgress] = React.useState<Progress & { total: number } | undefined>();
const [testModel, setTestModel] = React.useState<TeleSuiteUpdaterTestModel>();
const [progress, setProgress] = React.useState<TeleSuiteUpdaterProgress & { total: number } | undefined>();
const [selectedItem, setSelectedItem] = React.useState<{ treeItem?: TreeItem, testFile?: SourceLocation, testCase?: reporterTypes.TestCase }>({});
const [visibleTestIds, setVisibleTestIds] = React.useState<Set<string>>(new Set());
const [isLoading, setIsLoading] = React.useState<boolean>(false);

View file

@ -29,7 +29,7 @@
"htmlimports": false,
"history": true,
"ie8compat": false,
"applicationcache": true,
"applicationcache": false,
"blobconstructor": true,
"blob-constructor": true,
"cookies": true,
@ -166,7 +166,7 @@
"srcdoc": true,
"imgcrossorigin": true,
"hashchange": true,
"inputsearchevent": true,
"inputsearchevent": false,
"ambientlight": false,
"datalistelem": true,
"videoloop": true,

View file

@ -29,7 +29,7 @@
"htmlimports": false,
"history": true,
"ie8compat": false,
"applicationcache": true,
"applicationcache": false,
"blobconstructor": true,
"blob-constructor": true,
"cookies": true,
@ -166,7 +166,7 @@
"srcdoc": true,
"imgcrossorigin": true,
"hashchange": true,
"inputsearchevent": true,
"inputsearchevent": false,
"ambientlight": false,
"datalistelem": true,
"videoloop": true,

View file

@ -32,7 +32,6 @@ async function checkFeatures(name: string, context: any, server: any) {
it('safari-14-1', async ({ browser, browserName, platform, server, headless, isMac }) => {
it.skip(browserName !== 'webkit');
it.skip(browserName === 'webkit' && platform === 'darwin', 'WebKit for macOS 10.15 is frozen.');
it.skip(browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) === 22, 'Modernizr uses WebGL which is not available in macOS-13 - https://bugs.webkit.org/show_bug.cgi?id=278277');
const context = await browser.newContext({
deviceScaleFactor: 2
@ -41,6 +40,7 @@ it('safari-14-1', async ({ browser, browserName, platform, server, headless, isM
if (platform === 'linux') {
expected.subpixelfont = false;
expected.speechrecognition = false;
if (headless)
expected.todataurljpeg = false;
@ -56,7 +56,6 @@ it('safari-14-1', async ({ browser, browserName, platform, server, headless, isM
if (platform === 'win32') {
expected.datalistelem = false;
expected.fileinputdirectory = false;
expected.getusermedia = false;
expected.peerconnection = false;
expected.speechrecognition = false;
@ -64,6 +63,7 @@ it('safari-14-1', async ({ browser, browserName, platform, server, headless, isM
expected.todataurljpeg = false;
expected.unicode = false;
expected.webaudio = false;
expected.gamepads = false;
expected.input.list = false;
expected.inputtypes.color = false;
@ -72,10 +72,8 @@ it('safari-14-1', async ({ browser, browserName, platform, server, headless, isM
expected.inputtypes.time = false;
}
if (isMac && parseInt(os.release(), 10) > 20) {
if (isMac && parseInt(os.release(), 10) > 20)
expected.applicationcache = false;
expected.inputsearchevent = false;
}
expect(actual).toEqual(expected);
});
@ -99,6 +97,7 @@ it('mobile-safari-14-1', async ({ playwright, browser, browserName, platform, is
if (platform === 'linux') {
expected.subpixelfont = false;
expected.speechrecognition = false;
if (headless)
expected.todataurljpeg = false;
@ -114,7 +113,6 @@ it('mobile-safari-14-1', async ({ playwright, browser, browserName, platform, is
if (platform === 'win32') {
expected.datalistelem = false;
expected.fileinputdirectory = false;
expected.getusermedia = false;
expected.peerconnection = false;
expected.speechrecognition = false;
@ -122,6 +120,7 @@ it('mobile-safari-14-1', async ({ playwright, browser, browserName, platform, is
expected.todataurljpeg = false;
expected.unicode = false;
expected.webaudio = false;
expected.gamepads = false;
expected.input.list = false;
expected.inputtypes.color = false;
@ -133,11 +132,6 @@ it('mobile-safari-14-1', async ({ playwright, browser, browserName, platform, is
expected.inputtypes.time = false;
}
if (isMac && parseInt(os.release(), 10) > 20) {
expected.applicationcache = false;
expected.inputsearchevent = false;
}
expect(actual).toEqual(expected);
});

View file

@ -289,6 +289,20 @@ test('should filter network requests by resource type', async ({ page, runAndTra
await expect(traceViewer.networkRequests.getByText('font.woff2')).toBeVisible();
});
test('should show font preview', async ({ page, runAndTrace, server }) => {
const traceViewer = await runAndTrace(async () => {
await page.goto(`${server.PREFIX}/network-tab/network.html`);
});
await traceViewer.selectAction('http://localhost');
await traceViewer.showNetworkTab();
await traceViewer.page.getByText('Font', { exact: true }).click();
await expect(traceViewer.networkRequests).toHaveCount(1);
await traceViewer.networkRequests.getByText('font.woff2').click();
await traceViewer.page.getByTestId('network-request-details').getByTitle('Body').click();
await expect(traceViewer.page.locator('.network-request-details-tab')).toContainText('ABCDEF');
});
test('should filter network requests by url', async ({ page, runAndTrace, server }) => {
const traceViewer = await runAndTrace(async () => {
await page.goto(`${server.PREFIX}/network-tab/network.html`);

View file

@ -37,12 +37,6 @@ it('should assume CJS module with a path and arg', async ({ page, server, asset
expect(await page.evaluate(() => window['injected'])).toBe(17);
});
it('should assume CJS module with a path and undefined arg', async ({ page, server, asset }) => {
await page.addInitScript({ path: asset('injectedmodule.js') }, undefined);
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate(() => window['injected'])).toBe(42);
});
it('should work with content @smoke', async ({ page, server }) => {
await page.addInitScript({ content: 'window["injected"] = 123' });
await page.goto(server.PREFIX + '/tamperable.html');