chore: unify target closed errors (#27540)
This commit is contained in:
parent
8595a6c25f
commit
f212fd1a83
|
@ -10,7 +10,7 @@ export default defineConfig({
|
|||
testDir: './tests',
|
||||
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 30 * 1000,
|
||||
timeout: 15_000,
|
||||
|
||||
expect: {
|
||||
|
||||
|
@ -18,7 +18,7 @@ export default defineConfig({
|
|||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 5000
|
||||
timeout: 5_000
|
||||
},
|
||||
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
|
|
|
@ -17,9 +17,8 @@ const TODO_ITEMS = [
|
|||
|
||||
test.describe('New Todo', () => {
|
||||
test('should allow me to add todo items', async ({ page }) => {
|
||||
test.setTimeout(5000);
|
||||
// create a new todo locator
|
||||
const newTodo = page.getByPlaceholder('What needs to be completed?');
|
||||
const newTodo = page.getByPlaceholder('What needs to be done?');
|
||||
// Create 1st todo.
|
||||
await newTodo.fill(TODO_ITEMS[0]);
|
||||
await newTodo.press('Enter');
|
||||
|
|
|
@ -34,6 +34,7 @@ import { spawn } from 'child_process';
|
|||
import { wrapInASCIIBox, isLikelyNpxGlobal, assert, gracefullyProcessExitDoNotHang, getPackageManagerExecCommand } from '../utils';
|
||||
import type { Executable } from '../server';
|
||||
import { registry, writeDockerVersion } from '../server';
|
||||
import { isTargetClosedError } from '../common/errors';
|
||||
|
||||
const packageJSON = require('../../package.json');
|
||||
|
||||
|
@ -530,7 +531,7 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
|
|||
else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:'))
|
||||
url = 'http://' + url;
|
||||
await page.goto(url).catch(error => {
|
||||
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN && error.message.includes('Navigation failed because page was closed')) {
|
||||
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN && isTargetClosedError(error)) {
|
||||
// Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
|
||||
// in a stray navigation aborted error. We should ignore it.
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,7 @@ import { TimeoutSettings } from '../common/timeoutSettings';
|
|||
import { Waiter } from './waiter';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Connection } from './connection';
|
||||
import { isSafeCloseError, kBrowserClosedError } from '../common/errors';
|
||||
import { isTargetClosedError, TargetClosedError } from '../common/errors';
|
||||
import { raceAgainstDeadline } from '../utils/timeoutRunner';
|
||||
import type { AndroidServerLauncherImpl } from '../androidServerImpl';
|
||||
|
||||
|
@ -76,10 +76,10 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
|||
connection.on('close', closePipe);
|
||||
|
||||
let device: AndroidDevice;
|
||||
let closeError: string | undefined;
|
||||
let closeError: Error | undefined;
|
||||
const onPipeClosed = () => {
|
||||
device?._didClose();
|
||||
connection.close(closeError || kBrowserClosedError);
|
||||
connection.close(closeError);
|
||||
};
|
||||
pipe.on('closed', onPipeClosed);
|
||||
connection.onmessage = message => pipe.send({ message }).catch(onPipeClosed);
|
||||
|
@ -88,7 +88,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
|||
try {
|
||||
connection!.dispatch(message);
|
||||
} catch (e) {
|
||||
closeError = e.toString();
|
||||
closeError = e;
|
||||
closePipe();
|
||||
}
|
||||
});
|
||||
|
@ -237,11 +237,11 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
|||
async close() {
|
||||
try {
|
||||
if (this._shouldCloseConnectionOnClose)
|
||||
this._connection.close(kBrowserClosedError);
|
||||
this._connection.close();
|
||||
else
|
||||
await this._channel.close();
|
||||
} catch (e) {
|
||||
if (isSafeCloseError(e))
|
||||
if (isTargetClosedError(e))
|
||||
return;
|
||||
throw e;
|
||||
}
|
||||
|
@ -281,7 +281,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
|||
const waiter = Waiter.createForEvent(this, event);
|
||||
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||
if (event !== Events.AndroidDevice.Close)
|
||||
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed'));
|
||||
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new TargetClosedError());
|
||||
const result = await waiter.waitForEvent(this, event, predicate as any);
|
||||
waiter.dispose();
|
||||
return result;
|
||||
|
|
|
@ -21,7 +21,7 @@ import type { Page } from './page';
|
|||
import { ChannelOwner } from './channelOwner';
|
||||
import { Events } from './events';
|
||||
import type { LaunchOptions, BrowserContextOptions, HeadersArray } from './types';
|
||||
import { isSafeCloseError, kBrowserClosedError } from '../common/errors';
|
||||
import { isTargetClosedError } from '../common/errors';
|
||||
import type * as api from '../../types/types';
|
||||
import { CDPSession } from './cdpSession';
|
||||
import type { BrowserType } from './browserType';
|
||||
|
@ -133,12 +133,12 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
|||
async close(): Promise<void> {
|
||||
try {
|
||||
if (this._shouldCloseConnectionOnClose)
|
||||
this._connection.close(kBrowserClosedError);
|
||||
this._connection.close();
|
||||
else
|
||||
await this._channel.close();
|
||||
await this._closedPromise;
|
||||
} catch (e) {
|
||||
if (isSafeCloseError(e))
|
||||
if (isTargetClosedError(e))
|
||||
return;
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import { ConsoleMessage } from './consoleMessage';
|
|||
import { Dialog } from './dialog';
|
||||
import { WebError } from './webError';
|
||||
import { parseError } from '../protocol/serializers';
|
||||
import { TargetClosedError } from '../common/errors';
|
||||
|
||||
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
|
||||
_pages = new Set<Page>();
|
||||
|
@ -343,7 +344,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
const waiter = Waiter.createForEvent(this, event);
|
||||
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||
if (event !== Events.BrowserContext.Close)
|
||||
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed'));
|
||||
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new TargetClosedError());
|
||||
const result = await waiter.waitForEvent(this, event, predicate as any);
|
||||
waiter.dispose();
|
||||
return result;
|
||||
|
|
|
@ -25,7 +25,6 @@ import type { ChildProcess } from 'child_process';
|
|||
import { envObjectToArray } from './clientHelper';
|
||||
import { assert, headersObjectToArray, monotonicTime } from '../utils';
|
||||
import type * as api from '../../types/types';
|
||||
import { kBrowserClosedError } from '../common/errors';
|
||||
import { raceAgainstDeadline } from '../utils/timeoutRunner';
|
||||
import type { Playwright } from './playwright';
|
||||
|
||||
|
@ -144,7 +143,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
|||
connection.on('close', closePipe);
|
||||
|
||||
let browser: Browser;
|
||||
let closeError: string | undefined;
|
||||
let closeError: Error | undefined;
|
||||
const onPipeClosed = () => {
|
||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||
for (const context of browser?.contexts() || []) {
|
||||
|
@ -153,7 +152,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
|||
context._onClose();
|
||||
}
|
||||
browser?._didClose();
|
||||
connection.close(closeError || kBrowserClosedError);
|
||||
connection.close(closeError);
|
||||
};
|
||||
pipe.on('closed', onPipeClosed);
|
||||
connection.onmessage = message => pipe.send({ message }).catch(onPipeClosed);
|
||||
|
@ -162,7 +161,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
|||
try {
|
||||
connection!.dispatch(message);
|
||||
} catch (e) {
|
||||
closeError = e.toString();
|
||||
closeError = e;
|
||||
closePipe();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,7 +35,6 @@ import { WritableStream } from './writableStream';
|
|||
import { debugLogger } from '../common/debugLogger';
|
||||
import { SelectorsOwner } from './selectors';
|
||||
import { Android, AndroidSocket, AndroidDevice } from './android';
|
||||
import { captureLibraryStackText } from '../utils/stackTrace';
|
||||
import { Artifact } from './artifact';
|
||||
import { EventEmitter } from 'events';
|
||||
import { JsonPipe } from './jsonPipe';
|
||||
|
@ -45,6 +44,8 @@ import { Tracing } from './tracing';
|
|||
import { findValidator, ValidationError, type ValidatorContext } from '../protocol/validator';
|
||||
import { createInstrumentation } from './clientInstrumentation';
|
||||
import type { ClientInstrumentation } from './clientInstrumentation';
|
||||
import { TargetClosedError } from '../common/errors';
|
||||
import { formatCallLog, rewriteErrorMessage } from '../utils';
|
||||
|
||||
class Root extends ChannelOwner<channels.RootChannel> {
|
||||
constructor(connection: Connection) {
|
||||
|
@ -67,7 +68,7 @@ export class Connection extends EventEmitter {
|
|||
private _lastId = 0;
|
||||
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void, apiName: string | undefined, type: string, method: string }>();
|
||||
private _rootObject: Root;
|
||||
private _closedErrorMessage: string | undefined;
|
||||
private _closedError: Error | undefined;
|
||||
private _isRemote = false;
|
||||
private _localUtils?: LocalUtils;
|
||||
// Some connections allow resolving in-process dispatchers.
|
||||
|
@ -110,8 +111,8 @@ export class Connection extends EventEmitter {
|
|||
}
|
||||
|
||||
async sendMessageToServer(object: ChannelOwner, method: string, params: any, apiName: string | undefined, frames: channels.StackFrame[], wallTime: number | undefined): Promise<any> {
|
||||
if (this._closedErrorMessage)
|
||||
throw new Error(this._closedErrorMessage);
|
||||
if (this._closedError)
|
||||
throw this._closedError;
|
||||
if (object._wasCollected)
|
||||
throw new Error('The object has been collected to prevent unbounded heap growth.');
|
||||
|
||||
|
@ -132,10 +133,10 @@ export class Connection extends EventEmitter {
|
|||
}
|
||||
|
||||
dispatch(message: object) {
|
||||
if (this._closedErrorMessage)
|
||||
if (this._closedError)
|
||||
return;
|
||||
|
||||
const { id, guid, method, params, result, error } = message as any;
|
||||
const { id, guid, method, params, result, error, log } = message as any;
|
||||
if (id) {
|
||||
if (debugLogger.isEnabled('channel'))
|
||||
debugLogger.log('channel', '<RECV ' + JSON.stringify(message));
|
||||
|
@ -144,7 +145,9 @@ export class Connection extends EventEmitter {
|
|||
throw new Error(`Cannot find command to respond: ${id}`);
|
||||
this._callbacks.delete(id);
|
||||
if (error && !result) {
|
||||
callback.reject(parseError(error));
|
||||
const parsedError = parseError(error);
|
||||
rewriteErrorMessage(parsedError, parsedError.message + formatCallLog(log));
|
||||
callback.reject(parsedError);
|
||||
} else {
|
||||
const validator = findValidator(callback.type, callback.method, 'Result');
|
||||
callback.resolve(validator(result, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this.isRemote() ? 'fromBase64' : 'buffer' }));
|
||||
|
@ -180,13 +183,12 @@ export class Connection extends EventEmitter {
|
|||
(object._channel as any).emit(method, validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this.isRemote() ? 'fromBase64' : 'buffer' }));
|
||||
}
|
||||
|
||||
close(errorMessage: string = 'Connection closed') {
|
||||
const stack = captureLibraryStackText();
|
||||
if (stack)
|
||||
errorMessage += '\n ==== Closed by ====\n' + stack + '\n';
|
||||
this._closedErrorMessage = errorMessage;
|
||||
close(cause?: Error) {
|
||||
this._closedError = cause || new TargetClosedError();
|
||||
if (cause)
|
||||
rewriteErrorMessage(this._closedError, this._closedError.message + '\nCaused by: ' + cause.toString());
|
||||
for (const callback of this._callbacks.values())
|
||||
callback.reject(new Error(errorMessage));
|
||||
callback.reject(this._closedError);
|
||||
this._callbacks.clear();
|
||||
this.emit('close');
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
|||
import type { Page } from './page';
|
||||
import type { Env, WaitForEventOptions, Headers, BrowserContextOptions } from './types';
|
||||
import { Waiter } from './waiter';
|
||||
import { TargetClosedError } from '../common/errors';
|
||||
|
||||
type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHeaders'|'recordHar'|'colorScheme'|'acceptDownloads'> & {
|
||||
env?: Env,
|
||||
|
@ -120,7 +121,7 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
|||
const waiter = Waiter.createForEvent(this, event);
|
||||
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
|
||||
if (event !== Events.ElectronApplication.Close)
|
||||
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed'));
|
||||
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new TargetClosedError());
|
||||
const result = await waiter.waitForEvent(this, event, predicate as any);
|
||||
waiter.dispose();
|
||||
return result;
|
||||
|
|
|
@ -21,7 +21,6 @@ import type { Serializable } from '../../types/structs';
|
|||
import type * as api from '../../types/types';
|
||||
import type { HeadersArray, NameValue } from '../common/types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import { kBrowserOrContextClosedError } from '../common/errors';
|
||||
import { assert, headersObjectToArray, isString } from '../utils';
|
||||
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
|
@ -29,6 +28,7 @@ import { RawHeaders } from './network';
|
|||
import type { FilePayload, Headers, StorageState } from './types';
|
||||
import type { Playwright } from './playwright';
|
||||
import { Tracing } from './tracing';
|
||||
import { isTargetClosedError } from '../common/errors';
|
||||
|
||||
export type FetchOptions = {
|
||||
params?: { [key: string]: string; },
|
||||
|
@ -272,7 +272,7 @@ export class APIResponse implements api.APIResponse {
|
|||
throw new Error('Response has been disposed');
|
||||
return result.binary;
|
||||
} catch (e) {
|
||||
if (e.message.includes(kBrowserOrContextClosedError))
|
||||
if (isTargetClosedError(e))
|
||||
throw new Error('Response has been disposed');
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import { urlMatches } from '../utils/network';
|
|||
import type * as api from '../../types/types';
|
||||
import type * as structs from '../../types/structs';
|
||||
import { debugLogger } from '../common/debugLogger';
|
||||
import { TargetClosedError } from '../common/errors';
|
||||
|
||||
export type WaitForNavigationOptions = {
|
||||
timeout?: number,
|
||||
|
@ -104,8 +105,8 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
|||
private _setupNavigationWaiter(options: { timeout?: number }): Waiter {
|
||||
const waiter = new Waiter(this._page!, '');
|
||||
if (this._page!.isClosed())
|
||||
waiter.rejectImmediately(new Error('Navigation failed because page was closed!'));
|
||||
waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!'));
|
||||
waiter.rejectImmediately(new TargetClosedError());
|
||||
waiter.rejectOnEvent(this._page!, Events.Page.Close, new TargetClosedError());
|
||||
waiter.rejectOnEvent(this._page!, Events.Page.Crash, new Error('Navigation failed because page crashed!'));
|
||||
waiter.rejectOnEvent<Frame>(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this);
|
||||
const timeout = this._page!._timeoutSettings.navigationTimeout(options);
|
||||
|
|
|
@ -34,6 +34,7 @@ import { MultiMap } from '../utils/multimap';
|
|||
import { APIResponse } from './fetch';
|
||||
import type { Serializable } from '../../types/structs';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import { TargetClosedError } from '../common/errors';
|
||||
|
||||
export type NetworkCookie = {
|
||||
name: string,
|
||||
|
@ -610,7 +611,7 @@ export class WebSocket extends ChannelOwner<channels.WebSocketChannel> implement
|
|||
waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error'));
|
||||
if (event !== Events.WebSocket.Close)
|
||||
waiter.rejectOnEvent(this, Events.WebSocket.Close, new Error('Socket closed'));
|
||||
waiter.rejectOnEvent(this._page, Events.Page.Close, new Error('Page closed'));
|
||||
waiter.rejectOnEvent(this._page, Events.Page.Close, new TargetClosedError());
|
||||
const result = await waiter.waitForEvent(this, event, predicate as any);
|
||||
waiter.dispose();
|
||||
return result;
|
||||
|
|
|
@ -19,7 +19,7 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import type * as structs from '../../types/structs';
|
||||
import type * as api from '../../types/types';
|
||||
import { isSafeCloseError, kBrowserOrContextClosedError } from '../common/errors';
|
||||
import { isTargetClosedError, TargetClosedError, kTargetClosedErrorMessage } from '../common/errors';
|
||||
import { urlMatches } from '../utils/network';
|
||||
import { TimeoutSettings } from '../common/timeoutSettings';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
@ -140,8 +140,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
|
||||
this.coverage = new Coverage(this._channel);
|
||||
|
||||
this.once(Events.Page.Close, () => this._closedOrCrashedScope.close(kBrowserOrContextClosedError));
|
||||
this.once(Events.Page.Crash, () => this._closedOrCrashedScope.close(kBrowserOrContextClosedError));
|
||||
this.once(Events.Page.Close, () => this._closedOrCrashedScope.close(kTargetClosedErrorMessage));
|
||||
this.once(Events.Page.Crash, () => this._closedOrCrashedScope.close(kTargetClosedErrorMessage));
|
||||
|
||||
this._setEventToSubscriptionMapping(new Map<string, channels.PageUpdateSubscriptionParams['event']>([
|
||||
[Events.Page.Console, 'console'],
|
||||
|
@ -398,7 +398,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
if (event !== Events.Page.Crash)
|
||||
waiter.rejectOnEvent(this, Events.Page.Crash, new Error('Page crashed'));
|
||||
if (event !== Events.Page.Close)
|
||||
waiter.rejectOnEvent(this, Events.Page.Close, new Error('Page closed'));
|
||||
waiter.rejectOnEvent(this, Events.Page.Close, new TargetClosedError());
|
||||
const result = await waiter.waitForEvent(this, event, predicate as any);
|
||||
waiter.dispose();
|
||||
return result;
|
||||
|
@ -520,7 +520,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
else
|
||||
await this._channel.close(options);
|
||||
} catch (e) {
|
||||
if (isSafeCloseError(e) && !options.runBeforeUnload)
|
||||
if (isTargetClosedError(e) && !options.runBeforeUnload)
|
||||
return;
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import type { BrowserContext } from './browserContext';
|
|||
import type * as api from '../../types/types';
|
||||
import type * as structs from '../../types/structs';
|
||||
import { LongStandingScope } from '../utils';
|
||||
import { kBrowserOrContextClosedError } from '../common/errors';
|
||||
import { kTargetClosedErrorMessage } from '../common/errors';
|
||||
|
||||
export class Worker extends ChannelOwner<channels.WorkerChannel> implements api.Worker {
|
||||
_page: Page | undefined; // Set for web workers.
|
||||
|
@ -43,7 +43,7 @@ export class Worker extends ChannelOwner<channels.WorkerChannel> implements api.
|
|||
this._context._serviceWorkers.delete(this);
|
||||
this.emit(Events.Worker.Close, this);
|
||||
});
|
||||
this.once(Events.Worker.Close, () => this._closedScope.close(kBrowserOrContextClosedError));
|
||||
this.once(Events.Worker.Close, () => this._closedScope.close(kTargetClosedErrorMessage));
|
||||
}
|
||||
|
||||
url(): string {
|
||||
|
|
|
@ -25,9 +25,15 @@ class CustomError extends Error {
|
|||
|
||||
export class TimeoutError extends CustomError {}
|
||||
|
||||
export const kBrowserClosedError = 'Browser has been closed';
|
||||
export const kBrowserOrContextClosedError = 'Target page, context or browser has been closed';
|
||||
export const kTargetClosedErrorMessage = 'Target page, context or browser has been closed';
|
||||
|
||||
export function isSafeCloseError(error: Error) {
|
||||
return error.message.endsWith(kBrowserClosedError) || error.message.endsWith(kBrowserOrContextClosedError);
|
||||
export class TargetClosedError extends Error {
|
||||
constructor() {
|
||||
super(kTargetClosedErrorMessage);
|
||||
this.name = this.constructor.name;
|
||||
}
|
||||
}
|
||||
|
||||
export function isTargetClosedError(error: Error) {
|
||||
return error instanceof TargetClosedError || error.message.includes(kTargetClosedErrorMessage);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { debugLogger } from '../../common/debugLogger';
|
|||
import type { ProtocolLogger } from '../types';
|
||||
import { helper } from '../helper';
|
||||
import { ProtocolError } from '../protocolError';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
|
||||
export const ConnectionEvents = {
|
||||
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
||||
|
@ -140,12 +141,10 @@ export class CRSession extends EventEmitter {
|
|||
private _closedErrorMessage() {
|
||||
if (this._crashed)
|
||||
return 'Target crashed';
|
||||
if (this._connection._browserDisconnectedLogs !== undefined)
|
||||
return `Browser closed.` + this._connection._browserDisconnectedLogs;
|
||||
if (this._closed)
|
||||
return `Target closed`;
|
||||
if (this._connection._closed)
|
||||
return 'Browser closed';
|
||||
if (this._connection._browserDisconnectedLogs)
|
||||
return kTargetClosedErrorMessage + '\nBrowser logs: ' + this._connection._browserDisconnectedLogs;
|
||||
if (this._closed || this._connection._closed)
|
||||
return kTargetClosedErrorMessage;
|
||||
}
|
||||
|
||||
async send<T extends keyof Protocol.CommandParameters>(
|
||||
|
|
|
@ -45,6 +45,7 @@ import { platformToFontFamilies } from './defaultFontFamilies';
|
|||
import type { Protocol } from './protocol';
|
||||
import { VideoRecorder } from './videoRecorder';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { TargetClosedError } from '../../common/errors';
|
||||
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
@ -574,7 +575,7 @@ class FrameSession {
|
|||
}
|
||||
|
||||
dispose() {
|
||||
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
|
||||
this._firstNonInitialNavigationCommittedReject(new TargetClosedError());
|
||||
for (const childSession of this._childSessions)
|
||||
childSession.dispose();
|
||||
if (this._parentSession)
|
||||
|
|
|
@ -19,10 +19,9 @@ import type * as channels from '@protocol/channels';
|
|||
import { serializeError } from '../../protocol/serializers';
|
||||
import { findValidator, ValidationError, createMetadataValidator, type ValidatorContext } from '../../protocol/validator';
|
||||
import { assert, isUnderTest, monotonicTime } from '../../utils';
|
||||
import { kBrowserOrContextClosedError } from '../../common/errors';
|
||||
import { TargetClosedError } from '../../common/errors';
|
||||
import type { CallMetadata } from '../instrumentation';
|
||||
import { SdkObject } from '../instrumentation';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import type { PlaywrightDispatcher } from './playwrightDispatcher';
|
||||
import { eventsHelper } from '../..//utils/eventsHelper';
|
||||
import type { RegisteredListener } from '../..//utils/eventsHelper';
|
||||
|
@ -262,7 +261,7 @@ export class DispatcherConnection {
|
|||
const { id, guid, method, params, metadata } = message as any;
|
||||
const dispatcher = this._dispatchers.get(guid);
|
||||
if (!dispatcher) {
|
||||
this.onmessage({ id, error: serializeError(new Error(kBrowserOrContextClosedError)) });
|
||||
this.onmessage({ id, error: serializeError(new TargetClosedError()) });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -324,20 +323,13 @@ export class DispatcherConnection {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
let error: any;
|
||||
await sdkObject?.instrumentation.onBeforeCall(sdkObject, callMetadata);
|
||||
try {
|
||||
const result = await (dispatcher as any)[method](validParams, callMetadata);
|
||||
const validator = findValidator(dispatcher._type, method, 'Result');
|
||||
callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' });
|
||||
} catch (e) {
|
||||
// Dispatching error
|
||||
// We want original, unmodified error in metadata.
|
||||
callMetadata.error = serializeError(e);
|
||||
if (callMetadata.log.length)
|
||||
rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log));
|
||||
error = serializeError(e);
|
||||
} finally {
|
||||
callMetadata.endTime = monotonicTime();
|
||||
await sdkObject?.instrumentation.onAfterCall(sdkObject, callMetadata);
|
||||
|
@ -346,18 +338,10 @@ export class DispatcherConnection {
|
|||
const response: any = { id };
|
||||
if (callMetadata.result)
|
||||
response.result = callMetadata.result;
|
||||
if (error)
|
||||
response.error = error;
|
||||
if (callMetadata.error) {
|
||||
response.error = callMetadata.error;
|
||||
response.log = callMetadata.log;
|
||||
}
|
||||
this.onmessage(response);
|
||||
}
|
||||
}
|
||||
|
||||
function formatLogRecording(log: string[]): string {
|
||||
if (!log.length)
|
||||
return '';
|
||||
const header = ` logs `;
|
||||
const headerLength = 60;
|
||||
const leftLength = (headerLength - header.length) / 2;
|
||||
const rightLength = headerLength - header.length - leftLength;
|
||||
return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { kBrowserClosedError } from '../../common/errors';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
import { assert } from '../../utils';
|
||||
import type { BrowserOptions } from '../browser';
|
||||
import { Browser } from '../browser';
|
||||
|
@ -159,7 +159,7 @@ export class FFBrowser extends Browser {
|
|||
|
||||
_onDisconnect() {
|
||||
for (const video of this._idToVideo.values())
|
||||
video.artifact.reportFinished(kBrowserClosedError);
|
||||
video.artifact.reportFinished(kTargetClosedErrorMessage);
|
||||
this._idToVideo.clear();
|
||||
for (const ffPage of this._ffPages.values())
|
||||
ffPage.didClose();
|
||||
|
|
|
@ -24,6 +24,7 @@ import { debugLogger } from '../../common/debugLogger';
|
|||
import type { ProtocolLogger } from '../types';
|
||||
import { helper } from '../helper';
|
||||
import { ProtocolError } from '../protocolError';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
|
||||
export const ConnectionEvents = {
|
||||
Disconnected: Symbol('Disconnected'),
|
||||
|
@ -134,12 +135,10 @@ export class FFSession extends EventEmitter {
|
|||
private _closedErrorMessage() {
|
||||
if (this._crashed)
|
||||
return 'Target crashed';
|
||||
if (this._connection._browserDisconnectedLogs !== undefined)
|
||||
return `Browser closed.` + this._connection._browserDisconnectedLogs;
|
||||
if (this._disposed)
|
||||
return `Target closed`;
|
||||
if (this._connection._closed)
|
||||
return 'Browser closed';
|
||||
if (this._connection._browserDisconnectedLogs)
|
||||
return kTargetClosedErrorMessage + '\nBrowser logs: ' + this._connection._browserDisconnectedLogs;
|
||||
if (this._disposed || this._connection._closed)
|
||||
return kTargetClosedErrorMessage;
|
||||
}
|
||||
|
||||
async send<T extends keyof Protocol.CommandParameters>(
|
||||
|
|
|
@ -35,6 +35,7 @@ import { splitErrorMessage } from '../../utils/stackTrace';
|
|||
import { debugLogger } from '../../common/debugLogger';
|
||||
import { ManualPromise } from '../../utils/manualPromise';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { TargetClosedError } from '../../common/errors';
|
||||
|
||||
export const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
|
@ -342,7 +343,7 @@ export class FFPage implements PageDelegate {
|
|||
}
|
||||
|
||||
didClose() {
|
||||
this._markAsError(new Error('Page closed'));
|
||||
this._markAsError(new TargetClosedError());
|
||||
this._session.dispose();
|
||||
eventsHelper.removeEventListeners(this._eventListeners);
|
||||
this._networkManager.dispose();
|
||||
|
|
|
@ -66,7 +66,7 @@ export class FrameSelectors {
|
|||
const resolved = await this.resolveInjectedForSelector(selector, { mainWorld: true }, scope);
|
||||
// Be careful, |this.frame| can be different from |resolved.frame|.
|
||||
if (!resolved)
|
||||
throw new Error(`Error: failed to find frame for selector "${selector}"`);
|
||||
throw new Error(`Failed to find frame for selector "${selector}"`);
|
||||
return await resolved.injected.evaluateHandle((injected, { info, scope }) => {
|
||||
return injected.querySelectorAll(info.parsed, scope || document);
|
||||
}, { info: resolved.info, scope: resolved.scope });
|
||||
|
@ -76,7 +76,7 @@ export class FrameSelectors {
|
|||
const resolved = await this.resolveInjectedForSelector(selector);
|
||||
// Be careful, |this.frame| can be different from |resolved.frame|.
|
||||
if (!resolved)
|
||||
throw new Error(`Error: failed to find frame for selector "${selector}"`);
|
||||
throw new Error(`Failed to find frame for selector "${selector}"`);
|
||||
return await resolved.injected.evaluate((injected, { info }) => {
|
||||
return injected.querySelectorAll(info.parsed, document).length;
|
||||
}, { info: resolved.info });
|
||||
|
|
|
@ -834,7 +834,7 @@ export class Frame extends SdkObject {
|
|||
async evalOnSelector(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any, scope?: dom.ElementHandle): Promise<any> {
|
||||
const handle = await this.selectors.query(selector, { strict }, scope);
|
||||
if (!handle)
|
||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
||||
throw new Error(`Failed to find element matching selector "${selector}"`);
|
||||
const result = await handle.evaluateExpression(expression, { isFunction }, arg);
|
||||
handle.dispose();
|
||||
return result;
|
||||
|
|
|
@ -98,7 +98,7 @@ class Helper {
|
|||
static formatBrowserLogs(logs: string[]) {
|
||||
if (!logs.length)
|
||||
return '';
|
||||
return '\n' + '='.repeat(20) + ' Browser output: ' + '='.repeat(20) + '\n' + logs.join('\n');
|
||||
return '\n' + logs.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import type { TimeoutOptions } from '../common/types';
|
|||
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
|
||||
import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSerializers';
|
||||
import type { SerializedValue } from './isomorphic/utilityScriptSerializers';
|
||||
import { kTargetClosedErrorMessage } from '../common/errors';
|
||||
|
||||
export interface PageDelegate {
|
||||
readonly rawMouse: input.RawMouse;
|
||||
|
@ -275,7 +276,7 @@ export class Page extends SdkObject {
|
|||
this.emit(Page.Events.Close);
|
||||
this._closedPromise.resolve();
|
||||
this.instrumentation.onPageClose(this);
|
||||
this.openScope.close('Page closed');
|
||||
this.openScope.close(kTargetClosedErrorMessage);
|
||||
}
|
||||
|
||||
_didCrash() {
|
||||
|
|
|
@ -30,7 +30,7 @@ import type { Protocol } from './protocol';
|
|||
import type { PageProxyMessageReceivedPayload } from './wkConnection';
|
||||
import { kPageProxyMessageReceived, WKConnection, WKSession } from './wkConnection';
|
||||
import { WKPage } from './wkPage';
|
||||
import { kBrowserClosedError } from '../../common/errors';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
import type { SdkObject } from '../instrumentation';
|
||||
|
||||
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15';
|
||||
|
@ -81,7 +81,7 @@ export class WKBrowser extends Browser {
|
|||
wkPage.didClose();
|
||||
this._wkPages.clear();
|
||||
for (const video of this._idToVideo.values())
|
||||
video.artifact.reportFinished(kBrowserClosedError);
|
||||
video.artifact.reportFinished(kTargetClosedErrorMessage);
|
||||
this._idToVideo.clear();
|
||||
this._didClose();
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ export class WKBrowser extends Browser {
|
|||
context = this._defaultContext as WKBrowserContext;
|
||||
if (!context)
|
||||
return;
|
||||
const pageProxySession = new WKSession(this._connection, pageProxyId, `Target closed`, (message: any) => {
|
||||
const pageProxySession = new WKSession(this._connection, pageProxyId, kTargetClosedErrorMessage, (message: any) => {
|
||||
this._connection.rawSend({ ...message, pageProxyId });
|
||||
});
|
||||
const opener = event.openerId ? this._wkPages.get(event.openerId) : undefined;
|
||||
|
|
|
@ -24,7 +24,7 @@ import type { RecentLogsCollector } from '../../common/debugLogger';
|
|||
import { debugLogger } from '../../common/debugLogger';
|
||||
import type { ProtocolLogger } from '../types';
|
||||
import { helper } from '../helper';
|
||||
import { kBrowserClosedError } from '../../common/errors';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
import { ProtocolError } from '../protocolError';
|
||||
|
||||
// WKPlaywright uses this special id to issue Browser.close command which we
|
||||
|
@ -51,7 +51,7 @@ export class WKConnection {
|
|||
this._onDisconnect = onDisconnect;
|
||||
this._protocolLogger = protocolLogger;
|
||||
this._browserLogsCollector = browserLogsCollector;
|
||||
this.browserSession = new WKSession(this, '', kBrowserClosedError, (message: any) => {
|
||||
this.browserSession = new WKSession(this, '', kTargetClosedErrorMessage, (message: any) => {
|
||||
this.rawSend(message);
|
||||
});
|
||||
this._transport.onmessage = this._dispatchMessage.bind(this);
|
||||
|
@ -137,7 +137,7 @@ export class WKSession extends EventEmitter {
|
|||
if (this._crashed)
|
||||
throw new ProtocolError(true, 'Target crashed');
|
||||
if (this._disposed)
|
||||
throw new ProtocolError(true, `Target closed`);
|
||||
throw new ProtocolError(true, kTargetClosedErrorMessage);
|
||||
const id = this.connection.nextMessageId();
|
||||
const messageObj = { id, method, params };
|
||||
this._rawSend(messageObj);
|
||||
|
@ -160,7 +160,7 @@ export class WKSession extends EventEmitter {
|
|||
|
||||
dispose() {
|
||||
if (this.connection._browserDisconnectedLogs)
|
||||
this.errorText = 'Browser closed.' + this.connection._browserDisconnectedLogs;
|
||||
this.errorText = kTargetClosedErrorMessage + '\nBrowser logs: ' + this.connection._browserDisconnectedLogs;
|
||||
for (const callback of this._callbacks.values()) {
|
||||
callback.error.sessionClosed = true;
|
||||
callback.reject(rewriteErrorMessage(callback.error, this.errorText));
|
||||
|
|
|
@ -45,6 +45,7 @@ import { WKWorkers } from './wkWorkers';
|
|||
import { debugLogger } from '../../common/debugLogger';
|
||||
import { ManualPromise } from '../../utils/manualPromise';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { TargetClosedError, kTargetClosedErrorMessage } from '../../common/errors';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
|
@ -271,7 +272,7 @@ export class WKPage implements PageDelegate {
|
|||
this._provisionalPage.dispose();
|
||||
this._provisionalPage = null;
|
||||
}
|
||||
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
|
||||
this._firstNonInitialNavigationCommittedReject(new TargetClosedError());
|
||||
this._page._didClose();
|
||||
}
|
||||
|
||||
|
@ -303,7 +304,7 @@ export class WKPage implements PageDelegate {
|
|||
|
||||
private async _onTargetCreated(event: Protocol.Target.targetCreatedPayload) {
|
||||
const { targetInfo } = event;
|
||||
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, `Target closed`, (message: any) => {
|
||||
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, kTargetClosedErrorMessage, (message: any) => {
|
||||
this._pageProxySession.send('Target.sendMessageToTarget', {
|
||||
message: JSON.stringify(message), targetId: targetInfo.targetId
|
||||
}).catch(e => {
|
||||
|
@ -524,7 +525,7 @@ export class WKPage implements PageDelegate {
|
|||
|
||||
async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
||||
if (this._pageProxySession.isDisposed())
|
||||
throw new Error('Target closed');
|
||||
throw new TargetClosedError();
|
||||
const pageProxyId = this._pageProxySession.sessionId;
|
||||
const result = await this._pageProxySession.connection.browserSession.send('Playwright.navigate', { url, pageProxyId, frameId: frame._id, referrer });
|
||||
return { newDocumentId: result.loaderId };
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { rewriteErrorMessage } from './stackTrace';
|
||||
import { captureRawStack } from './stackTrace';
|
||||
|
||||
export class ManualPromise<T = void> extends Promise<T> {
|
||||
private _resolve!: (t: T) => void;
|
||||
|
@ -59,7 +59,7 @@ export class ManualPromise<T = void> extends Promise<T> {
|
|||
export class LongStandingScope {
|
||||
private _terminateError: Error | undefined;
|
||||
private _terminateErrorMessage: string | undefined;
|
||||
private _terminatePromises = new Map<ManualPromise<Error>, Error>();
|
||||
private _terminatePromises = new Map<ManualPromise<Error>, string[]>();
|
||||
private _isClosed = false;
|
||||
|
||||
reject(error: Error) {
|
||||
|
@ -72,9 +72,10 @@ export class LongStandingScope {
|
|||
close(errorMessage: string) {
|
||||
this._isClosed = true;
|
||||
this._terminateErrorMessage = errorMessage;
|
||||
for (const [p, e] of this._terminatePromises) {
|
||||
rewriteErrorMessage(e, errorMessage);
|
||||
p.resolve(e);
|
||||
for (const [p, frames] of this._terminatePromises) {
|
||||
const error = new Error(errorMessage);
|
||||
error.stack = [error.name + ':' + errorMessage, ...frames].join('\n');
|
||||
p.resolve(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +101,7 @@ export class LongStandingScope {
|
|||
terminatePromise.resolve(this._terminateError);
|
||||
if (this._terminateErrorMessage)
|
||||
terminatePromise.resolve(new Error(this._terminateErrorMessage));
|
||||
this._terminatePromises.set(terminatePromise, new Error(''));
|
||||
this._terminatePromises.set(terminatePromise, captureRawStack());
|
||||
try {
|
||||
return await Promise.race([
|
||||
terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
|
||||
|
|
|
@ -18,6 +18,7 @@ import path from 'path';
|
|||
import { parseStackTraceLine } from '../utilsBundle';
|
||||
import { isUnderTest } from './';
|
||||
import type { StackFrame } from '@protocol/channels';
|
||||
import { colors } from '../utilsBundle';
|
||||
|
||||
export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string): E {
|
||||
const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at '));
|
||||
|
@ -131,6 +132,15 @@ export function splitErrorMessage(message: string): { name: string, message: str
|
|||
};
|
||||
}
|
||||
|
||||
export function formatCallLog(log: string[] | undefined): string {
|
||||
if (!log || !log.some(l => !!l))
|
||||
return '';
|
||||
return `
|
||||
Call log:
|
||||
${colors.dim('- ' + (log || []).join('\n - '))}
|
||||
`;
|
||||
}
|
||||
|
||||
export type ExpectZone = {
|
||||
title: string;
|
||||
wallTime: number;
|
||||
|
|
|
@ -20,7 +20,8 @@ import type { StackFrame } from '@protocol/channels';
|
|||
import util from 'util';
|
||||
import path from 'path';
|
||||
import url from 'url';
|
||||
import { colors, debug, minimatch, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
|
||||
import { debug, minimatch, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
|
||||
import { formatCallLog } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoError } from './../types/test';
|
||||
import type { Location } from './../types/testReporter';
|
||||
import { calculateSha1, isRegExp, isString, sanitizeForFilePath, stringifyStackFrames } from 'playwright-core/lib/utils';
|
||||
|
@ -213,14 +214,7 @@ export function getContainedPath(parentPath: string, subPath: string = ''): stri
|
|||
|
||||
export const debugTest = debug('pw:test');
|
||||
|
||||
export function callLogText(log: string[] | undefined): string {
|
||||
if (!log)
|
||||
return '';
|
||||
return `
|
||||
Call log:
|
||||
${colors.dim('- ' + (log || []).join('\n - '))}
|
||||
`;
|
||||
}
|
||||
export const callLogText = formatCallLog;
|
||||
|
||||
const folderToPackageJsonPath = new Map<string, string>();
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const kTargetClosedErrorMessage = 'Target page, context or browser has been closed';
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { kTargetClosedErrorMessage } from '../config/errors';
|
||||
import { browserTest as test, expect } from '../config/browserTest';
|
||||
|
||||
test('should return browserType', function({ browser, browserType }) {
|
||||
|
@ -59,5 +60,5 @@ test('should dispatch page.on(close) upon browser.close and reject evaluate', as
|
|||
await browser.close();
|
||||
expect(closed).toBe(true);
|
||||
const error = await promise;
|
||||
expect(error.message).toMatch(/(Target|Browser) closed/);
|
||||
expect(error.message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { kTargetClosedErrorMessage } from '../config/errors';
|
||||
import { browserTest as it, expect } from '../config/browserTest';
|
||||
import { attachFrame, verifyViewport } from '../config/utils';
|
||||
|
||||
|
@ -129,7 +130,7 @@ it('close() should abort waitForEvent', async ({ browser }) => {
|
|||
const promise = context.waitForEvent('page').catch(e => e);
|
||||
await context.close();
|
||||
const error = await promise;
|
||||
expect(error.message).toContain('Context closed');
|
||||
expect(error.message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
it('close() should be callable twice', async ({ browser }) => {
|
||||
|
|
|
@ -27,6 +27,7 @@ import { parseTrace, suppressCertificateWarning } from '../config/utils';
|
|||
import formidable from 'formidable';
|
||||
import type { Browser, ConnectOptions } from 'playwright-core';
|
||||
import { createHttpServer } from '../../packages/playwright-core/lib/utils/network';
|
||||
import { kTargetClosedErrorMessage } from '../config/errors';
|
||||
|
||||
type ExtraFixtures = {
|
||||
connect: (wsEndpoint: string, options?: ConnectOptions, redirectPortForTest?: number) => Promise<Browser>,
|
||||
|
@ -336,7 +337,7 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
|||
]);
|
||||
expect(browser.isConnected()).toBe(false);
|
||||
const error = await page.waitForNavigation().catch(e => e);
|
||||
expect(error.message).toContain('Navigation failed because page was closed');
|
||||
expect(error.message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
test('should reject navigation when browser closes', async ({ connect, startRemoteServer, server }) => {
|
||||
|
@ -391,7 +392,7 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
|||
]);
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const message = results[i].message;
|
||||
expect(message).toContain('Page closed');
|
||||
expect(message).toContain(kTargetClosedErrorMessage);
|
||||
expect(message).not.toContain('Timeout');
|
||||
}
|
||||
});
|
||||
|
@ -549,13 +550,11 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
|||
await disconnectedPromise;
|
||||
expect(browser.isConnected()).toBe(false);
|
||||
|
||||
const navMessage = (await navigationPromise).message;
|
||||
expect(navMessage).toContain('Connection closed');
|
||||
expect(navMessage).toContain('Closed by');
|
||||
expect(navMessage).toContain(__filename);
|
||||
expect((await waitForNavigationPromise).message).toContain('Navigation failed because page was closed');
|
||||
const navError = await navigationPromise;
|
||||
expect(navError.message).toContain(kTargetClosedErrorMessage);
|
||||
expect((await waitForNavigationPromise).message).toContain(kTargetClosedErrorMessage);
|
||||
expect((await page.goto(server.EMPTY_PAGE).catch(e => e)).message).toContain('has been closed');
|
||||
expect((await page.waitForNavigation().catch(e => e)).message).toContain('Navigation failed because page was closed');
|
||||
expect((await page.waitForNavigation().catch(e => e)).message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
test('should be able to connect when the wsEndpoint is passed as an option', async ({ browserType, startRemoteServer }) => {
|
||||
|
@ -894,9 +893,9 @@ test.describe('launchServer only', () => {
|
|||
expect(browser.isConnected()).toBe(false);
|
||||
|
||||
expect((await navigationPromise).message).toContain('has been closed');
|
||||
expect((await waitForNavigationPromise).message).toContain('Navigation failed because page was closed');
|
||||
expect((await waitForNavigationPromise).message).toContain(kTargetClosedErrorMessage);
|
||||
expect((await page.goto(server.EMPTY_PAGE).catch(e => e)).message).toContain('has been closed');
|
||||
expect((await page.waitForNavigation().catch(e => e)).message).toContain('Navigation failed because page was closed');
|
||||
expect((await page.waitForNavigation().catch(e => e)).message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
test('should be able to reconnect to a browser 12 times without warnings', async ({ connect, startRemoteServer, server }) => {
|
||||
|
|
|
@ -64,7 +64,7 @@ it('should reject if launched browser fails immediately', async ({ mode, browser
|
|||
|
||||
let waitError = null;
|
||||
await browserType.launch({ executablePath: asset('dummy_bad_browser_executable.js') }).catch(e => waitError = e);
|
||||
expect(waitError.message).toContain('== logs ==');
|
||||
expect(waitError.message).toContain('Browser logs:');
|
||||
});
|
||||
|
||||
it('should reject if executable path is invalid', async ({ browserType, mode }) => {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { kTargetClosedErrorMessage } from '../../config/errors';
|
||||
import { contextTest as it, expect } from '../../config/browserTest';
|
||||
import { browserTest } from '../../config/browserTest';
|
||||
|
||||
|
@ -139,7 +140,7 @@ browserTest('should reject protocol calls when page closes', async function({ br
|
|||
const promise = session.send('Runtime.evaluate', { expression: 'new Promise(() => {})', awaitPromise: true }).catch(e => e);
|
||||
await page.close();
|
||||
const error1 = await promise;
|
||||
expect(error1.message).toContain('Target closed');
|
||||
expect(error1.message).toContain(kTargetClosedErrorMessage);
|
||||
const error2 = await session.send('Runtime.evaluate', { expression: 'new Promise(() => {})', awaitPromise: true }).catch(e => e);
|
||||
expect(error2.message).toContain('Target page, context or browser has been closed');
|
||||
await context.close();
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { kTargetClosedErrorMessage } from '../config/errors';
|
||||
import { contextTest as it, expect } from '../config/browserTest';
|
||||
import { Server as WebSocketServer } from 'ws';
|
||||
|
||||
|
@ -196,7 +197,7 @@ it('should reject waitForEvent on page close', async ({ page, server }) => {
|
|||
]);
|
||||
const error = ws.waitForEvent('framesent').catch(e => e);
|
||||
await page.close();
|
||||
expect((await error).message).toContain('Page closed');
|
||||
expect((await error).message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
it('should turn off when offline', async ({ page }) => {
|
||||
|
|
|
@ -37,7 +37,7 @@ it('should throw in case of missing selector', async ({ page, server }) => {
|
|||
await page.setContent(htmlContent);
|
||||
const elementHandle = await page.$('#myId');
|
||||
const errorMessage = await elementHandle.$eval('.a', node => (node as HTMLElement).innerText).catch(error => error.message);
|
||||
expect(errorMessage).toContain(`Error: failed to find element matching selector ".a"`);
|
||||
expect(errorMessage).toContain(`elementHandle.$eval: Failed to find element matching selector ".a"`);
|
||||
});
|
||||
|
||||
it('should work for all', async ({ page, server }) => {
|
||||
|
|
|
@ -99,7 +99,7 @@ it('should accept ElementHandles as arguments', async ({ page, server }) => {
|
|||
it('should throw error if no element is found', async ({ page, server }) => {
|
||||
let error = null;
|
||||
await page.$eval('section', e => e.id).catch(e => error = e);
|
||||
expect(error.message).toContain('failed to find element matching selector "section"');
|
||||
expect(error.message).toContain('Failed to find element matching selector "section"');
|
||||
});
|
||||
|
||||
it('should support >> syntax', async ({ page, server }) => {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { kTargetClosedErrorMessage } from '../config/errors';
|
||||
import { test as it, expect } from './pageTest';
|
||||
|
||||
it('should reject all promises when page is closed', async ({ page, isWebView2, isAndroid }) => {
|
||||
|
@ -26,7 +27,7 @@ it('should reject all promises when page is closed', async ({ page, isWebView2,
|
|||
page.evaluate(() => new Promise(r => {})).catch(e => error = e),
|
||||
page.close(),
|
||||
]);
|
||||
expect(error.message).toContain('Target closed');
|
||||
expect(error.message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
it('should set the page close state', async ({ page, isWebView2 }) => {
|
||||
|
@ -59,7 +60,7 @@ it('should terminate network waiters', async ({ page, server, isAndroid, isWebVi
|
|||
]);
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const message = results[i].message;
|
||||
expect(message).toContain('Page closed');
|
||||
expect(message).toContain(kTargetClosedErrorMessage);
|
||||
expect(message).not.toContain('Timeout');
|
||||
}
|
||||
});
|
||||
|
@ -142,7 +143,7 @@ it('should fail with error upon disconnect', async ({ page, isAndroid, isWebView
|
|||
const waitForPromise = page.waitForEvent('download').catch(e => error = e);
|
||||
await page.close();
|
||||
await waitForPromise;
|
||||
expect(error.message).toContain('Page closed');
|
||||
expect(error.message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
it('page.url should work', async ({ page, server }) => {
|
||||
|
|
|
@ -198,7 +198,7 @@ it('should throw nice error without injected script stack when element is not an
|
|||
let error = null;
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
await page.fill('body', '').catch(e => error = e);
|
||||
expect(error.message).toContain('page.fill: Error: Element is not an <input>, <textarea> or [contenteditable] element\n=========================== logs');
|
||||
expect(error.message).toContain('page.fill: Error: Element is not an <input>, <textarea> or [contenteditable] element\nCall log:');
|
||||
});
|
||||
|
||||
it('should throw if passed a non-string value', async ({ page, server }) => {
|
||||
|
|
|
@ -113,12 +113,12 @@ it('$eval should throw for missing frame', async ({ page, server }) => {
|
|||
await page.goto(server.EMPTY_PAGE);
|
||||
{
|
||||
const error = await page.$eval('iframe >> internal:control=enter-frame >> canvas', e => 1).catch(e => e);
|
||||
expect(error.message).toContain('Error: failed to find element matching selector');
|
||||
expect(error.message).toContain('page.$eval: Failed to find element matching selector');
|
||||
}
|
||||
{
|
||||
const body = await page.$('body');
|
||||
const error = await body.$eval('iframe >> internal:control=enter-frame >> canvas', e => 1).catch(e => e);
|
||||
expect(error.message).toContain('Error: failed to find element matching selector');
|
||||
expect(error.message).toContain('elementHandle.$eval: Failed to find element matching selector');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -126,12 +126,12 @@ it('$$eval should throw for missing frame', async ({ page, server }) => {
|
|||
await page.goto(server.EMPTY_PAGE);
|
||||
{
|
||||
const error = await page.$$eval('iframe >> internal:control=enter-frame >> canvas', e => 1).catch(e => e);
|
||||
expect(error.message).toContain('Error: failed to find frame for selector');
|
||||
expect(error.message).toContain('page.$$eval: Failed to find frame for selector');
|
||||
}
|
||||
{
|
||||
const body = await page.$('body');
|
||||
const error = await body.$$eval('iframe >> internal:control=enter-frame >> canvas', e => 1).catch(e => e);
|
||||
expect(error.message).toContain('Error: failed to find frame for selector');
|
||||
expect(error.message).toContain('Failed to find frame for selector');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import type { Worker as PwWorker } from '@playwright/test';
|
|||
import { attachFrame } from '../config/utils';
|
||||
import type { ConsoleMessage } from 'playwright-core';
|
||||
import fs from 'fs';
|
||||
import { kTargetClosedErrorMessage } from '../config/errors';
|
||||
|
||||
it('Page.workers @smoke', async function({ page, server }) {
|
||||
await Promise.all([
|
||||
|
@ -43,7 +44,8 @@ it('should emit created and destroyed events', async function({ page }) {
|
|||
await page.evaluate(workerObj => workerObj.terminate(), workerObj);
|
||||
expect(await workerDestroyedPromise).toBe(worker);
|
||||
const error = await workerThisObj.getProperty('self').catch(error => error);
|
||||
expect(error.message).toMatch(/jsHandle.getProperty: (Worker was closed|Target closed)/);
|
||||
expect(error.message).toContain('jsHandle.getProperty');
|
||||
expect(error.message).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
it('should report console logs', async function({ page }) {
|
||||
|
|
|
@ -339,7 +339,7 @@ test('should report error and pending operations on timeout', async ({ runInline
|
|||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.output).toContain('Error: locator.textContent: Page closed');
|
||||
expect(result.output).toContain('Error: locator.textContent: Target page, context or browser has been closed');
|
||||
expect(result.output).toContain('a.test.ts:7:42');
|
||||
});
|
||||
|
||||
|
|
|
@ -454,7 +454,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
`begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
|
||||
`end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
|
||||
`begin {\"title\":\"page.click(input)\",\"category\":\"pw:api\"}`,
|
||||
`end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"page.click: Timeout 1ms exceeded.\\n=========================== logs ===========================\\nwaiting for locator('input')\\n============================================================\",\"stack\":\"<stack>\",\"location\":\"<location>\",\"snippet\":\"<snippet>\"}}`,
|
||||
`end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"page.click: Timeout 1ms exceeded.\\nCall log:\\n \\u001b[2m- waiting for locator('input')\\u001b[22m\\n\",\"stack\":\"<stack>\",\"location\":\"<location>\",\"snippet\":\"<snippet>\"}}`,
|
||||
`begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`,
|
||||
`end {\"title\":\"fixture: page\",\"category\":\"fixture\"}`,
|
||||
|
|
Loading…
Reference in New Issue