Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import path from "path";
import { fileURLToPath } from "url";
import stylistic from "@stylistic/eslint-plugin";
import importRules from "eslint-plugin-import";
import progressPlugin from "./utils/eslint-plugin-progress/index.js";
import { fixupConfigRules } from "@eslint/compat";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
Expand Down Expand Up @@ -420,6 +421,18 @@ export default [
...noBooleanCompareRules,
},
},
{
files: [
"packages/playwright-core/src/server/**/*.ts",
],
plugins: {
"progress": progressPlugin,
},
languageOptions: languageOptionsWithTsConfig,
rules: {
"progress/await-must-use-progress": "warn",
},
},
{
files: ["tests/**/*.spec.js", "tests/**/*.ts"],
languageOptions: {
Expand Down
7 changes: 7 additions & 0 deletions packages/html-reporter/src/testCaseView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const TestCaseView: React.FC<{
const searchParams = useSearchParams();

const visibleTestAnnotations = test.annotations.filter(a => !a.type.startsWith('_')) ?? [];
appendRepeatEachIndexAnnotation(visibleTestAnnotations, test.repeatEachIndex);

return <>
<HeaderView
Expand Down Expand Up @@ -78,6 +79,7 @@ export const TestCaseView: React.FC<{
</div>,
render: () => {
const visibleAnnotations = result.annotations.filter(annotation => !annotation.type.startsWith('_'));
appendRepeatEachIndexAnnotation(visibleAnnotations, test.repeatEachIndex);
return <>
{!!visibleAnnotations.length && <AutoChip header='Annotations' dataTestId='test-case-annotations'>
{visibleAnnotations.map((annotation, index) => <TestCaseAnnotationView key={index} annotation={annotation} />)}
Expand All @@ -98,6 +100,11 @@ function TestCaseAnnotationView({ annotation: { type, description } }: { annotat
);
}

function appendRepeatEachIndexAnnotation(annotations: TestAnnotation[], repeatEachIndex: number | undefined) {
if (repeatEachIndex)
annotations.push({ type: 'repeatEachIndex', description: String(repeatEachIndex) });
}

function retryLabel(index: number) {
if (!index)
return 'Run';
Expand Down
1 change: 1 addition & 0 deletions packages/html-reporter/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export type TestCaseSummary = {
duration: number;
ok: boolean;
results: TestResultSummary[];
repeatEachIndex?: number;
};

export type TestResultSummary = {
Expand Down
6 changes: 3 additions & 3 deletions packages/playwright-core/src/androidServerImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { PlaywrightServer } from './remote/playwrightServer';
import { createPlaywright } from './server/playwright';
import { createGuid } from './server/utils/crypto';
import { ws } from './utilsBundle';
import { ProgressController } from './server/progress';
import { nullProgress, ProgressController } from './server/progress';

import type { BrowserServer } from './client/browserType';
import type { LaunchAndroidServerOptions } from './client/types';
Expand Down Expand Up @@ -58,8 +58,8 @@ export class AndroidServerLauncherImpl {
// 3. Return the BrowserServer interface
const browserServer = new ws.EventEmitter() as (BrowserServer & WebSocketEventEmitter);
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => device.close();
browserServer.kill = () => device.close();
browserServer.close = () => device.close(nullProgress);
browserServer.kill = () => device.close(nullProgress);
device.on('close', () => {
server.close();
browserServer.emit('close');
Expand Down
10 changes: 5 additions & 5 deletions packages/playwright-core/src/remote/playwrightServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { getPlaywrightVersion } from '../server/utils/userAgent';
import { debugLogger, isUnderTest } from '../utils';
import { SocksProxy } from '../server/utils/socksProxy';
import { Browser } from '../server/browser';
import { ProgressController } from '../server/progress';
import { nullProgress, ProgressController } from '../server/progress';

import type { AndroidDevice } from '../server/android/android';
import type { Playwright } from '../server/playwright';
Expand Down Expand Up @@ -204,7 +204,7 @@ export class PlaywrightServer {
if (this._dontReuseBrowsers.has(b))
continue;
if (b.options.name === browserName && b.options.channel === launchOptions.channel)
await b.close({ reason: 'Connection terminated' });
await b.close(nullProgress, { reason: 'Connection terminated' });
}

if (!browser) {
Expand All @@ -225,7 +225,7 @@ export class PlaywrightServer {
// keep around browser so it can be reused by the next connection.
for (const context of browser.contexts()) {
if (!context.pages().length)
await context.close({ reason: 'Connection terminated' });
await context.close(nullProgress, { reason: 'Connection terminated' });
}
}
};
Expand Down Expand Up @@ -259,7 +259,7 @@ export class PlaywrightServer {
// In pre-launched mode, keep only the pre-launched browser.
for (const b of this._playwright.allBrowsers()) {
if (b !== browser)
await b.close({ reason: 'Connection terminated' });
await b.close(nullProgress, { reason: 'Connection terminated' });
}

return {
Expand Down Expand Up @@ -299,7 +299,7 @@ export class PlaywrightServer {
socksProxy,
denyLaunch: true,
dispose: async () => {
await browser.close({ reason: 'Connection terminated' });
await browser.close(nullProgress, { reason: 'Connection terminated' });
socksProxy?.close();
},
};
Expand Down
34 changes: 23 additions & 11 deletions packages/playwright-core/src/server/android/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,17 @@
this._pollingWebViews = setTimeout(() => this._refreshWebViews()
.then(poll)
.catch(() => {
this.close().catch(() => {});
this._close().catch(() => {});
}), 500);
};
poll();
}

async shell(command: string): Promise<Buffer> {
async shell(progress: Progress, command: string): Promise<Buffer> {
return await progress.race(this._shell(command));
}

private async _shell(command: string): Promise<Buffer> {
const result = await this._backend.runCommand(`shell:${command}`);
await this._refreshWebViews();
return result;
Expand All @@ -154,8 +158,8 @@
return await this._open(progress, command);
}

async screenshot(): Promise<Buffer> {
return await this._backend.runCommand(`shell:screencap -p`);
async screenshot(progress: Progress): Promise<Buffer> {
return await progress.race(this._backend.runCommand(`shell:screencap -p`));
}

private async _driver(): Promise<PipeTransport | undefined> {
Expand All @@ -170,13 +174,13 @@

private async _installDriver(progress: Progress): Promise<PipeTransport> {
debug('pw:android')('Stopping the old driver');
await progress.race(this.shell(`am force-stop com.microsoft.playwright.androiddriver`));
await progress.race(this._shell(`am force-stop com.microsoft.playwright.androiddriver`));

// uninstall and install driver on every execution
if (!this._options.omitDriverInstall) {
debug('pw:android')('Uninstalling the old driver');
await progress.race(this.shell(`cmd package uninstall com.microsoft.playwright.androiddriver`));
await progress.race(this.shell(`cmd package uninstall com.microsoft.playwright.androiddriver.test`));
await this.shell(progress, `cmd package uninstall com.microsoft.playwright.androiddriver`);
await this.shell(progress, `cmd package uninstall com.microsoft.playwright.androiddriver.test`);

debug('pw:android')('Installing the new driver');
const executable = registry.findExecutable('android')!;
Expand All @@ -192,7 +196,7 @@
}

debug('pw:android')('Starting the new driver');
this.shell('am instrument -w com.microsoft.playwright.androiddriver.test/androidx.test.runner.AndroidJUnitRunner').catch(e => debug('pw:android')(e));
this._shell('am instrument -w com.microsoft.playwright.androiddriver.test/androidx.test.runner.AndroidJUnitRunner').catch(e => debug('pw:android')(e));
const socket = await this._waitForLocalAbstract(progress, 'playwright_android_driver_socket');
const transport = new PipeTransport(socket, socket, socket, 'be');
transport.onmessage = message => {
Expand All @@ -219,14 +223,18 @@
} catch (e) {
if (isAbortError(e))
throw e;
await progress.wait(250);

Check warning on line 226 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 226 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
}
}
debug('pw:android')(`Connected to localabstract:${socketName}`);
return socket;
}

async send(method: string, params: any = {}): Promise<any> {
async send(progress: Progress, method: string, params: any = {}): Promise<any> {
return await progress.race(this._send(method, params));
}

private async _send(method: string, params: any = {}): Promise<any> {
params = {
...params,
// Patch the timeout in, just in case it's missing in one of the commands.
Expand All @@ -245,7 +253,11 @@
return result;
}

async close() {
async close(progress: Progress) {
await progress.race(this._close());
}

private async _close() {
if (this._isClosed)
return;
this._isClosed = true;
Expand All @@ -264,7 +276,7 @@

async launchBrowser(progress: Progress, pkg: string = 'com.android.chrome', options: channels.AndroidDeviceLaunchBrowserParams): Promise<BrowserContext> {
debug('pw:android')('Force-stopping', pkg);
await this._backend.runCommand(`shell:am force-stop ${pkg}`);

Check warning on line 279 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 279 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
const socketName = isUnderTest() ? 'webview_devtools_remote_playwright_test' : ('playwright_' + createGuid() + '_devtools_remote');
const commandLine = this._defaultArgs(options, socketName).join(' ');
debug('pw:android')('Starting', pkg, commandLine);
Expand All @@ -276,7 +288,7 @@
await progress.race(this._backend.runCommand(`shell:rm /data/local/tmp/chrome-command-line`));
return browserContext;
} catch (error) {
await browserContext.close({ reason: 'Failed to launch' }).catch(() => {});
await browserContext.close(progress, { reason: 'Failed to launch' }).catch(() => {});

Check warning on line 291 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 291 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
throw error;
}
}
Expand Down Expand Up @@ -392,11 +404,11 @@
await sendHeader(command, data.length);
await progress.race(socket.write(data));
};
await send('SEND', Buffer.from(`${path},${mode}`));

Check warning on line 407 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 407 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
const maxChunk = 65535;
for (let i = 0; i < content.length; i += maxChunk)
await send('DATA', content.slice(i, i + maxChunk));

Check warning on line 410 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 410 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
await sendHeader('DONE', (Date.now() / 1000) | 0);

Check warning on line 411 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 411 in packages/playwright-core/src/server/android/android.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
const result = await progress.race(new Promise<Buffer>(f => socket.once('data', f)));
const code = result.slice(0, 4).toString();
if (code !== 'OKAY')
Expand Down
44 changes: 31 additions & 13 deletions packages/playwright-core/src/server/artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { TargetClosedError } from './errors';
import { SdkObject } from './instrumentation';
import { ManualPromise } from '../utils/isomorphic/manualPromise';

import type { Progress } from '@protocol/progress';

type SaveCallback = (localPath: string, error?: Error) => Promise<void>;
type CancelCallback = () => Promise<void>;

Expand All @@ -32,7 +34,7 @@ export class Artifact extends SdkObject {
private _saveCallbacks: SaveCallback[] = [];
private _finished: boolean = false;
private _deleted = false;
private _failureError: Error | undefined;
private _failureErrorValue: Error | undefined;

constructor(parent: SdkObject, localPath: string, unaccessibleErrorMessage?: string, cancelCallback?: CancelCallback) {
super(parent, 'artifact');
Expand All @@ -41,26 +43,42 @@ export class Artifact extends SdkObject {
this._cancelCallback = cancelCallback;
}

async localPathAfterFinished(progress: Progress): Promise<string> {
return await progress.race(this._localPathAfterFinished());
}

async failureError(progress: Progress): Promise<string | null> {
return await progress.race(this._failureError());
}

async cancel(progress: Progress): Promise<void> {
return await progress.race(this._cancel());
}

async delete(progress: Progress): Promise<void> {
return await progress.race(this._delete());
}

localPath() {
return this._localPath;
}

async localPathAfterFinished(): Promise<string> {
private async _localPathAfterFinished(): Promise<string> {
if (this._unaccessibleErrorMessage)
throw new Error(this._unaccessibleErrorMessage);
await this._finishedPromise;
if (this._failureError)
throw this._failureError;
if (this._failureErrorValue)
throw this._failureErrorValue;
return this._localPath;
}

saveAs(saveCallback: SaveCallback) {
saveAs(progress: Progress, saveCallback: SaveCallback) {
if (this._unaccessibleErrorMessage)
throw new Error(this._unaccessibleErrorMessage);
if (this._deleted)
throw new Error(`File already deleted. Save before deleting.`);
if (this._failureError)
throw this._failureError;
if (this._failureErrorValue)
throw this._failureErrorValue;

if (this._finished) {
saveCallback(this._localPath).catch(() => {});
Expand All @@ -69,22 +87,22 @@ export class Artifact extends SdkObject {
this._saveCallbacks.push(saveCallback);
}

async failureError(): Promise<string | null> {
async _failureError(): Promise<string | null> {
if (this._unaccessibleErrorMessage)
return this._unaccessibleErrorMessage;
await this._finishedPromise;
return this._failureError?.message || null;
return this._failureErrorValue?.message || null;
}

async cancel(): Promise<void> {
async _cancel(): Promise<void> {
assert(this._cancelCallback !== undefined);
return this._cancelCallback();
}

async delete(): Promise<void> {
async _delete(): Promise<void> {
if (this._unaccessibleErrorMessage)
return;
const fileName = await this.localPathAfterFinished();
const fileName = await this._localPathAfterFinished();
if (this._deleted)
return;
this._deleted = true;
Expand All @@ -107,7 +125,7 @@ export class Artifact extends SdkObject {
if (this._finished)
return;
this._finished = true;
this._failureError = error;
this._failureErrorValue = error;

if (error) {
for (const callback of this._saveCallbacks)
Expand Down
5 changes: 2 additions & 3 deletions packages/playwright-core/src/server/bidi/bidiBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,11 +507,10 @@ export class BidiBrowserContext extends BrowserContext {
override async clearCache(): Promise<void> {
}

async doClose(reason: string | undefined) {
async doClose(reason: string | undefined): Promise<void | 'close-browser'> {
if (!this._browserContextId) {
// Closing persistent context should close the browser.
await this._browser.close({ reason });
return;
return 'close-browser';
}
await this._browser._browserSession.send('browser.removeUserContext', {
userContext: this._browserContextId
Expand Down
7 changes: 4 additions & 3 deletions packages/playwright-core/src/server/bidi/bidiPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import { BidiNetworkManager } from './bidiNetworkManager';
import { BidiPDF } from './bidiPdf';
import * as bidi from './third_party/bidiProtocol';
import { nullProgress } from '../progress';

import * as network from '../network';
import type { RegisteredListener } from '../utils/eventsHelper';
Expand Down Expand Up @@ -532,8 +533,8 @@
private async _framePosition(frame: frames.Frame): Promise<types.Point | null> {
if (frame === this._page.mainFrame())
return { x: 0, y: 0 };
const element = await frame.frameElement();
const box = await element.boundingBox();
const element = await frame.frameElement(nullProgress);
const box = await element.boundingBox(nullProgress);
if (!box)
return null;
const style = await element.evaluateInUtility(([injected, iframe]) => injected.describeIFrameStyle(iframe as Element), {}).catch(e => 'error:notconnected' as const);
Expand Down Expand Up @@ -598,11 +599,11 @@
return quads as types.Quad[];
}

async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> {
async setInputFilePaths(progress: Progress, handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> {
const fromContext = toBidiExecutionContext(handle._context);
await this._session.send('input.setFiles', {

Check warning on line 604 in packages/playwright-core/src/server/bidi/bidiPage.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 604 in packages/playwright-core/src/server/bidi/bidiPage.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
context: this._session.sessionId,
element: await fromContext.nodeIdForElementHandle(handle),

Check warning on line 606 in packages/playwright-core/src/server/bidi/bidiPage.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts

Check warning on line 606 in packages/playwright-core/src/server/bidi/bidiPage.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Awaited async call must either pass `progress` as first argument or be wrapped in `progress.race()`. See packages/protocol/src/progress.d.ts
files: paths,
});
}
Expand Down
Loading
Loading