chore: plumb the target close reason when test fails (#27640)

This commit is contained in:
Pavel Feldman 2023-10-16 20:32:13 -07:00 committed by GitHub
parent 4e845e7b1d
commit a54dbfdadf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 171 additions and 96 deletions

View File

@ -102,6 +102,12 @@ This is similar to force quitting the browser. Therefore, you should call [`meth
The [Browser] object itself is considered to be disposed and cannot be used anymore. The [Browser] object itself is considered to be disposed and cannot be used anymore.
### option: Browser.close.reason
* since: v1.40
- `reason` <[string]>
The reason to be reported to the operations interrupted by the browser closure.
## method: Browser.contexts ## method: Browser.contexts
* since: v1.8 * since: v1.8
- returns: <[Array]<[BrowserContext]>> - returns: <[Array]<[BrowserContext]>>

View File

@ -94,6 +94,12 @@ Emitted when Browser context gets closed. This might happen because of one of th
* Browser application is closed or crashed. * Browser application is closed or crashed.
* The [`method: Browser.close`] method was called. * The [`method: Browser.close`] method was called.
### option: BrowserContext.close.reason
* since: v1.40
- `reason` <[string]>
The reason to be reported to the operations interrupted by the context closure.
## event: BrowserContext.console ## event: BrowserContext.console
* since: v1.34 * since: v1.34
* langs: * langs:

View File

@ -826,6 +826,12 @@ if [`option: runBeforeUnload`] is passed as true, a `beforeunload` dialog might
manually via [`event: Page.dialog`] event. manually via [`event: Page.dialog`] event.
::: :::
### option: Page.close.reason
* since: v1.40
- `reason` <[string]>
The reason to be reported to the operations interrupted by the page closure.
### option: Page.close.runBeforeUnload ### option: Page.close.runBeforeUnload
* since: v1.8 * since: v1.8
- `runBeforeUnload` <[boolean]> - `runBeforeUnload` <[boolean]>

View File

@ -130,12 +130,12 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
return buffer; return buffer;
} }
async close(): Promise<void> { async close(options: { reason?: string } = {}): Promise<void> {
try { try {
if (this._shouldCloseConnectionOnClose) if (this._shouldCloseConnectionOnClose)
this._connection.close(); this._connection.close();
else else
await this._channel.close(); await this._channel.close(options);
await this._closedPromise; await this._closedPromise;
} catch (e) { } catch (e) {
if (isTargetClosedError(e)) if (isTargetClosedError(e))

View File

@ -383,7 +383,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
this.emit(Events.BrowserContext.Close, this); this.emit(Events.BrowserContext.Close, this);
} }
async close(): Promise<void> { async close(options: { reason?: string } = {}): Promise<void> {
if (this._closeWasCalled) if (this._closeWasCalled)
return; return;
this._closeWasCalled = true; this._closeWasCalled = true;
@ -404,7 +404,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await artifact.delete(); await artifact.delete();
} }
}, true); }, true);
await this._channel.close(); await this._channel.close(options);
await this._closedPromise; await this._closedPromise;
} }

View File

@ -588,7 +588,9 @@ scheme.BrowserInitializer = tObject({
name: tString, name: tString,
}); });
scheme.BrowserCloseEvent = tOptional(tObject({})); scheme.BrowserCloseEvent = tOptional(tObject({}));
scheme.BrowserCloseParams = tOptional(tObject({})); scheme.BrowserCloseParams = tObject({
reason: tOptional(tString),
});
scheme.BrowserCloseResult = tOptional(tObject({})); scheme.BrowserCloseResult = tOptional(tObject({}));
scheme.BrowserKillForTestsParams = tOptional(tObject({})); scheme.BrowserKillForTestsParams = tOptional(tObject({}));
scheme.BrowserKillForTestsResult = tOptional(tObject({})); scheme.BrowserKillForTestsResult = tOptional(tObject({}));
@ -831,7 +833,9 @@ scheme.BrowserContextClearCookiesParams = tOptional(tObject({}));
scheme.BrowserContextClearCookiesResult = tOptional(tObject({})); scheme.BrowserContextClearCookiesResult = tOptional(tObject({}));
scheme.BrowserContextClearPermissionsParams = tOptional(tObject({})); scheme.BrowserContextClearPermissionsParams = tOptional(tObject({}));
scheme.BrowserContextClearPermissionsResult = tOptional(tObject({})); scheme.BrowserContextClearPermissionsResult = tOptional(tObject({}));
scheme.BrowserContextCloseParams = tOptional(tObject({})); scheme.BrowserContextCloseParams = tObject({
reason: tOptional(tString),
});
scheme.BrowserContextCloseResult = tOptional(tObject({})); scheme.BrowserContextCloseResult = tOptional(tObject({}));
scheme.BrowserContextCookiesParams = tObject({ scheme.BrowserContextCookiesParams = tObject({
urls: tArray(tString), urls: tArray(tString),
@ -1000,6 +1004,7 @@ scheme.PageAddInitScriptParams = tObject({
scheme.PageAddInitScriptResult = tOptional(tObject({})); scheme.PageAddInitScriptResult = tOptional(tObject({}));
scheme.PageCloseParams = tObject({ scheme.PageCloseParams = tObject({
runBeforeUnload: tOptional(tBoolean), runBeforeUnload: tOptional(tBoolean),
reason: tOptional(tString),
}); });
scheme.PageCloseResult = tOptional(tObject({})); scheme.PageCloseResult = tOptional(tObject({}));
scheme.PageEmulateMediaParams = tObject({ scheme.PageEmulateMediaParams = tObject({

View File

@ -116,7 +116,7 @@ export class PlaywrightConnection {
this._cleanups.push(async () => { this._cleanups.push(async () => {
for (const browser of playwright.allBrowsers()) for (const browser of playwright.allBrowsers())
await browser.close(); await browser.close({ reason: 'Connection terminated' });
}); });
browser.on(Browser.Events.Disconnected, () => { browser.on(Browser.Events.Disconnected, () => {
// Underlying browser did close for some reason - force disconnect the client. // Underlying browser did close for some reason - force disconnect the client.
@ -143,7 +143,7 @@ export class PlaywrightConnection {
// In pre-launched mode, keep only the pre-launched browser. // In pre-launched mode, keep only the pre-launched browser.
for (const b of playwright.allBrowsers()) { for (const b of playwright.allBrowsers()) {
if (b !== browser) if (b !== browser)
await b.close(); await b.close({ reason: 'Connection terminated' });
} }
this._cleanups.push(() => playwrightDispatcher.cleanup()); this._cleanups.push(() => playwrightDispatcher.cleanup());
return playwrightDispatcher; return playwrightDispatcher;
@ -189,7 +189,7 @@ export class PlaywrightConnection {
if (b === browser) if (b === browser)
continue; continue;
if (b.options.name === this._options.browserName && b.options.channel === this._options.launchOptions.channel) if (b.options.name === this._options.browserName && b.options.channel === this._options.launchOptions.channel)
await b.close(); await b.close({ reason: 'Connection terminated' });
} }
if (!browser) { if (!browser) {
@ -209,12 +209,12 @@ export class PlaywrightConnection {
for (const browser of playwright.allBrowsers()) { for (const browser of playwright.allBrowsers()) {
for (const context of browser.contexts()) { for (const context of browser.contexts()) {
if (!context.pages().length) if (!context.pages().length)
await context.close(serverSideCallMetadata()); await context.close({ reason: 'Connection terminated' });
else else
await context.stopPendingOperations('Connection closed'); await context.stopPendingOperations('Connection closed');
} }
if (!browser.contexts()) if (!browser.contexts())
await browser.close(); await browser.close({ reason: 'Connection terminated' });
} }
}); });

View File

@ -197,7 +197,7 @@ export class PlaywrightServer {
debugLogger.log('server', 'closing browsers'); debugLogger.log('server', 'closing browsers');
if (this._preLaunchedPlaywright) if (this._preLaunchedPlaywright)
await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close())); await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close({ reason: 'Playwright Server stopped' })));
debugLogger.log('server', 'closed browsers'); debugLogger.log('server', 'closed browsers');
} }
} }

View File

@ -64,6 +64,7 @@ export abstract class Browser extends SdkObject {
private _startedClosing = false; private _startedClosing = false;
readonly _idToVideo = new Map<string, { context: BrowserContext, artifact: Artifact }>(); readonly _idToVideo = new Map<string, { context: BrowserContext, artifact: Artifact }>();
private _contextForReuse: { context: BrowserContext, hash: string } | undefined; private _contextForReuse: { context: BrowserContext, hash: string } | undefined;
_closeReason: string | undefined;
constructor(parent: SdkObject, options: BrowserOptions) { constructor(parent: SdkObject, options: BrowserOptions) {
super(parent, 'browser'); super(parent, 'browser');
@ -90,7 +91,7 @@ export abstract class Browser extends SdkObject {
const hash = BrowserContext.reusableContextHash(params); const hash = BrowserContext.reusableContextHash(params);
if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) { if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) {
if (this._contextForReuse) if (this._contextForReuse)
await this._contextForReuse.context.close(metadata); await this._contextForReuse.context.close({ reason: 'Context reused' });
this._contextForReuse = { context: await this.newContext(metadata, params), hash }; this._contextForReuse = { context: await this.newContext(metadata, params), hash };
return { context: this._contextForReuse.context, needsReset: false }; return { context: this._contextForReuse.context, needsReset: false };
} }
@ -149,8 +150,10 @@ export abstract class Browser extends SdkObject {
this.instrumentation.onBrowserClose(this); this.instrumentation.onBrowserClose(this);
} }
async close() { async close(options: { reason?: string }) {
if (!this._startedClosing) { if (!this._startedClosing) {
if (options.reason)
this._closeReason = options.reason;
this._startedClosing = true; this._startedClosing = true;
await this.options.browserProcess.close(); await this.options.browserProcess.close();
} }

View File

@ -86,6 +86,7 @@ export abstract class BrowserContext extends SdkObject {
readonly initScripts: string[] = []; readonly initScripts: string[] = [];
private _routesInFlight = new Set<network.Route>(); private _routesInFlight = new Set<network.Route>();
private _debugger!: Debugger; private _debugger!: Debugger;
_closeReason: string | undefined;
constructor(browser: Browser, options: channels.BrowserNewContextParams, browserContextId: string | undefined) { constructor(browser: Browser, options: channels.BrowserNewContextParams, browserContextId: string | undefined) {
super(browser, 'browser-context'); super(browser, 'browser-context');
@ -272,7 +273,7 @@ export abstract class BrowserContext extends SdkObject {
protected abstract doExposeBinding(binding: PageBinding): Promise<void>; protected abstract doExposeBinding(binding: PageBinding): Promise<void>;
protected abstract doRemoveExposedBindings(): Promise<void>; protected abstract doRemoveExposedBindings(): Promise<void>;
protected abstract doUpdateRequestInterception(): Promise<void>; protected abstract doUpdateRequestInterception(): Promise<void>;
protected abstract doClose(): Promise<void>; protected abstract doClose(reason: string | undefined): Promise<void>;
protected abstract onClosePersistent(): void; protected abstract onClosePersistent(): void;
async cookies(urls: string | string[] | undefined = []): Promise<channels.NetworkCookie[]> { async cookies(urls: string | string[] | undefined = []): Promise<channels.NetworkCookie[]> {
@ -412,8 +413,10 @@ export abstract class BrowserContext extends SdkObject {
this._customCloseHandler = handler; this._customCloseHandler = handler;
} }
async close(metadata: CallMetadata) { async close(options: { reason?: string }) {
if (this._closedStatus === 'open') { if (this._closedStatus === 'open') {
if (options.reason)
this._closeReason = options.reason;
this.emit(BrowserContext.Events.BeforeClose); this.emit(BrowserContext.Events.BeforeClose);
this._closedStatus = 'closing'; this._closedStatus = 'closing';
@ -433,7 +436,7 @@ export abstract class BrowserContext extends SdkObject {
await this._customCloseHandler(); await this._customCloseHandler();
} else { } else {
// Close the context. // Close the context.
await this.doClose(); await this.doClose(options.reason);
} }
// We delete downloads after context closure // We delete downloads after context closure

View File

@ -510,7 +510,7 @@ export class CRBrowserContext extends BrowserContext {
await (sw as CRServiceWorker).updateRequestInterception(); await (sw as CRServiceWorker).updateRequestInterception();
} }
async doClose() { async doClose(reason: string | undefined) {
// Headful chrome cannot dispose browser context with opened 'beforeunload' // Headful chrome cannot dispose browser context with opened 'beforeunload'
// dialogs, so we should close all that are currently opened. // dialogs, so we should close all that are currently opened.
// We also won't get new ones since `Target.disposeBrowserContext` does not trigger // We also won't get new ones since `Target.disposeBrowserContext` does not trigger
@ -525,7 +525,7 @@ export class CRBrowserContext extends BrowserContext {
if (!this._browserContextId) { if (!this._browserContextId) {
await Promise.all(this._crPages().map(crPage => crPage._mainFrameSession._stopVideoRecording())); await Promise.all(this._crPages().map(crPage => crPage._mainFrameSession._stopVideoRecording()));
// Closing persistent context should close the browser. // Closing persistent context should close the browser.
await this._browser.close(); await this._browser.close({ reason });
return; return;
} }

View File

@ -172,7 +172,7 @@ export class DebugController extends SdkObject {
} }
async closeAllBrowsers() { async closeAllBrowsers() {
await Promise.all(this.allBrowsers().map(browser => browser.close())); await Promise.all(this.allBrowsers().map(browser => browser.close({ reason: 'Close all browsers requested' })));
} }
private _emitSnapshot() { private _emitSnapshot() {
@ -210,10 +210,10 @@ export class DebugController extends SdkObject {
for (const browser of this._playwright.allBrowsers()) { for (const browser of this._playwright.allBrowsers()) {
for (const context of browser.contexts()) { for (const context of browser.contexts()) {
if (!context.pages().length) if (!context.pages().length)
await context.close(serverSideCallMetadata()); await context.close({ reason: 'Browser collected' });
} }
if (!browser.contexts()) if (!browser.contexts())
await browser.close(); await browser.close({ reason: 'Browser collected' });
} }
} }
} }

View File

@ -270,7 +270,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
} }
async close(params: channels.BrowserContextCloseParams, metadata: CallMetadata): Promise<void> { async close(params: channels.BrowserContextCloseParams, metadata: CallMetadata): Promise<void> {
await this._context.close(metadata); await this._context.close(params);
} }
async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> { async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {

View File

@ -24,7 +24,6 @@ import { Dispatcher } from './dispatcher';
import type { CRBrowser } from '../chromium/crBrowser'; import type { CRBrowser } from '../chromium/crBrowser';
import type { PageDispatcher } from './pageDispatcher'; import type { PageDispatcher } from './pageDispatcher';
import type { CallMetadata } from '../instrumentation'; import type { CallMetadata } from '../instrumentation';
import { serverSideCallMetadata } from '../instrumentation';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
import { Selectors } from '../selectors'; import { Selectors } from '../selectors';
import type { BrowserTypeDispatcher } from './browserTypeDispatcher'; import type { BrowserTypeDispatcher } from './browserTypeDispatcher';
@ -56,8 +55,8 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
await this._object.stopPendingOperations(params.reason); await this._object.stopPendingOperations(params.reason);
} }
async close(): Promise<void> { async close(params: channels.BrowserCloseParams): Promise<void> {
await this._object.close(); await this._object.close(params);
} }
async killForTests(): Promise<void> { async killForTests(): Promise<void> {
@ -155,7 +154,7 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
} }
async cleanupContexts() { async cleanupContexts() {
await Promise.all(Array.from(this._contexts).map(context => context.close(serverSideCallMetadata()))); await Promise.all(Array.from(this._contexts).map(context => context.close({ reason: 'Global context cleanup (connection terminated)' })));
} }
} }

View File

@ -19,7 +19,7 @@ import type * as channels from '@protocol/channels';
import { serializeError } from '../../protocol/serializers'; import { serializeError } from '../../protocol/serializers';
import { findValidator, ValidationError, createMetadataValidator, type ValidatorContext } from '../../protocol/validator'; import { findValidator, ValidationError, createMetadataValidator, type ValidatorContext } from '../../protocol/validator';
import { assert, isUnderTest, monotonicTime, rewriteErrorMessage } from '../../utils'; import { assert, isUnderTest, monotonicTime, rewriteErrorMessage } from '../../utils';
import { TargetClosedError, kTargetClosedErrorMessage, kTargetCrashedErrorMessage } from '../../common/errors'; import { TargetClosedError, isTargetClosedError, kTargetClosedErrorMessage, kTargetCrashedErrorMessage } from '../../common/errors';
import type { CallMetadata } from '../instrumentation'; import type { CallMetadata } from '../instrumentation';
import { SdkObject } from '../instrumentation'; import { SdkObject } from '../instrumentation';
import type { PlaywrightDispatcher } from './playwrightDispatcher'; import type { PlaywrightDispatcher } from './playwrightDispatcher';
@ -330,9 +330,13 @@ export class DispatcherConnection {
const validator = findValidator(dispatcher._type, method, 'Result'); const validator = findValidator(dispatcher._type, method, 'Result');
callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' });
} catch (e) { } catch (e) {
if (isTargetClosedError(e) && sdkObject)
rewriteErrorMessage(e, closeReason(sdkObject));
if (isProtocolError(e)) { if (isProtocolError(e)) {
if (e.type === 'closed') if (e.type === 'closed') {
rewriteErrorMessage(e, kTargetClosedErrorMessage + e.browserLogMessage()); const closedReason = sdkObject ? closeReason(sdkObject) : kTargetClosedErrorMessage;
rewriteErrorMessage(e, closedReason + e.browserLogMessage());
}
if (e.type === 'crashed') if (e.type === 'crashed')
rewriteErrorMessage(e, kTargetCrashedErrorMessage + e.browserLogMessage()); rewriteErrorMessage(e, kTargetCrashedErrorMessage + e.browserLogMessage());
} }
@ -352,3 +356,9 @@ export class DispatcherConnection {
this.onmessage(response); this.onmessage(response);
} }
} }
function closeReason(sdkObject: SdkObject) {
return sdkObject.attribution.page?._closeReason ||
sdkObject.attribution.context?._closeReason ||
sdkObject.attribution.browser?._closeReason || kTargetClosedErrorMessage;
}

View File

@ -100,7 +100,7 @@ export class ElectronApplication extends SdkObject {
async close() { async close() {
const progressController = new ProgressController(serverSideCallMetadata(), this); const progressController = new ProgressController(serverSideCallMetadata(), this);
const closed = progressController.run(progress => helper.waitForEvent(progress, this, ElectronApplication.Events.Close).promise); const closed = progressController.run(progress => helper.waitForEvent(progress, this, ElectronApplication.Events.Close).promise);
await this._browserContext.close(serverSideCallMetadata()); await this._browserContext.close({ reason: 'Application exited' });
this._nodeConnection.close(); this._nodeConnection.close();
await closed; await closed;
} }

View File

@ -91,6 +91,7 @@ export abstract class APIRequestContext extends SdkObject {
readonly fetchLog: Map<string, string[]> = new Map(); readonly fetchLog: Map<string, string[]> = new Map();
protected static allInstances: Set<APIRequestContext> = new Set(); protected static allInstances: Set<APIRequestContext> = new Set();
readonly _activeProgressControllers = new Set<ProgressController>(); readonly _activeProgressControllers = new Set<ProgressController>();
_closeReason: string | undefined;
static findResponseBody(guid: string): Buffer | undefined { static findResponseBody(guid: string): Buffer | undefined {
for (const request of APIRequestContext.allInstances) { for (const request of APIRequestContext.allInstances) {

View File

@ -376,7 +376,7 @@ export class FFBrowserContext extends BrowserContext {
await this._browser.session.send('Browser.clearCache'); await this._browser.session.send('Browser.clearCache');
} }
async doClose() { async doClose(reason: string | undefined) {
if (!this._browserContextId) { if (!this._browserContextId) {
if (this._options.recordVideo) { if (this._options.recordVideo) {
await this._browser.session.send('Browser.setVideoRecordingOptions', { await this._browser.session.send('Browser.setVideoRecordingOptions', {
@ -385,7 +385,7 @@ export class FFBrowserContext extends BrowserContext {
}); });
} }
// Closing persistent context should close the browser. // Closing persistent context should close the browser.
await this._browser.close(); await this._browser.close({ reason });
} else { } else {
await this._browser.session.send('Browser.removeBrowserContext', { browserContextId: this._browserContextId }); await this._browser.session.send('Browser.removeBrowserContext', { browserContextId: this._browserContextId });
this._browser._contexts.delete(this._browserContextId); this._browser._contexts.delete(this._browserContextId);

View File

@ -170,6 +170,7 @@ export class Page extends SdkObject {
// Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms. // Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
// When throttling for tracing, 200ms between frames, except for 10 frames around the action. // When throttling for tracing, 200ms between frames, except for 10 frames around the action.
private _frameThrottler = new FrameThrottler(10, 35, 200); private _frameThrottler = new FrameThrottler(10, 35, 200);
_closeReason: string | undefined;
constructor(delegate: PageDelegate, browserContext: BrowserContext) { constructor(delegate: PageDelegate, browserContext: BrowserContext) {
super(browserContext, 'page'); super(browserContext, 'page');
@ -596,10 +597,12 @@ export class Page extends SdkObject {
this._timeoutSettings.timeout(options)); this._timeoutSettings.timeout(options));
} }
async close(metadata: CallMetadata, options?: { runBeforeUnload?: boolean }) { async close(metadata: CallMetadata, options: { runBeforeUnload?: boolean, reason?: string } = {}) {
if (this._closedState === 'closed') if (this._closedState === 'closed')
return; return;
const runBeforeUnload = !!options && !!options.runBeforeUnload; if (options.reason)
this._closeReason = options.reason;
const runBeforeUnload = !!options.runBeforeUnload;
if (this._closedState !== 'closing') { if (this._closedState !== 'closing') {
this._closedState = 'closing'; this._closedState = 'closing';
// This might throw if the browser context containing the page closes // This might throw if the browser context containing the page closes
@ -609,7 +612,7 @@ export class Page extends SdkObject {
if (!runBeforeUnload) if (!runBeforeUnload)
await this._closedPromise; await this._closedPromise;
if (this._ownedContext) if (this._ownedContext)
await this._ownedContext.close(metadata); await this._ownedContext.close(options);
} }
private _setIsError(error: Error) { private _setIsError(error: Error) {

View File

@ -75,7 +75,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
} }
async close() { async close() {
await this._page.context().close(serverSideCallMetadata()); await this._page.context().close({ reason: 'Recorder window closed' });
} }
private async _init() { private async _init() {
@ -105,7 +105,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
this._page.once('close', () => { this._page.once('close', () => {
this.emit('close'); this.emit('close');
this._page.context().close(serverSideCallMetadata()).catch(() => {}); this._page.context().close({ reason: 'Recorder window closed' }).catch(() => {});
}); });
const mainFrame = this._page.mainFrame(); const mainFrame = this._page.mainFrame();

View File

@ -157,7 +157,7 @@ export async function openTraceViewerApp(traceUrls: string[], browserName: strin
await syncLocalStorageWithSettings(page, 'traceviewer'); await syncLocalStorageWithSettings(page, 'traceviewer');
if (isUnderTest()) if (isUnderTest())
page.on('close', () => context.close(serverSideCallMetadata()).catch(() => {})); page.on('close', () => context.close({ reason: 'Trace viewer closed' }).catch(() => {}));
await page.mainFrame().goto(serverSideCallMetadata(), url); await page.mainFrame().goto(serverSideCallMetadata(), url);
return page; return page;

View File

@ -349,11 +349,11 @@ export class WKBrowserContext extends BrowserContext {
}); });
} }
async doClose() { async doClose(reason: string | undefined) {
if (!this._browserContextId) { if (!this._browserContextId) {
await Promise.all(this._wkPages().map(wkPage => wkPage._stopVideo())); await Promise.all(this._wkPages().map(wkPage => wkPage._stopVideo()));
// Closing persistent context should close the browser. // Closing persistent context should close the browser.
await this._browser.close(); await this._browser.close({ reason });
} else { } else {
await this._browser._browserSession.send('Playwright.deleteContext', { browserContextId: this._browserContextId }); await this._browser._browserSession.send('Playwright.deleteContext', { browserContextId: this._browserContextId });
this._browser._contexts.delete(this._browserContextId); this._browser._contexts.delete(this._browserContextId);

View File

@ -2001,6 +2001,11 @@ export interface Page {
* @param options * @param options
*/ */
close(options?: { close(options?: {
/**
* The reason to be reported to the operations interrupted by the page closure.
*/
reason?: string;
/** /**
* Defaults to `false`. Whether to run the * Defaults to `false`. Whether to run the
* [before unload](https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload) page handlers. * [before unload](https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload) page handlers.
@ -3627,7 +3632,8 @@ export interface Page {
/** /**
* If specified, updates the given HAR with the actual network information instead of serving from file. The file is * If specified, updates the given HAR with the actual network information instead of serving from file. The file is
* written to disk when * written to disk when
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) is called. * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) is
* called.
*/ */
update?: boolean; update?: boolean;
@ -7556,7 +7562,7 @@ export interface BrowserContext {
* Emitted when Browser context gets closed. This might happen because of one of the following: * Emitted when Browser context gets closed. This might happen because of one of the following:
* - Browser context is closed. * - Browser context is closed.
* - Browser application is closed or crashed. * - Browser application is closed or crashed.
* - The [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close) method was called. * - The [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close) method was called.
*/ */
on(event: 'close', listener: (browserContext: BrowserContext) => void): this; on(event: 'close', listener: (browserContext: BrowserContext) => void): this;
@ -7748,7 +7754,7 @@ export interface BrowserContext {
* Emitted when Browser context gets closed. This might happen because of one of the following: * Emitted when Browser context gets closed. This might happen because of one of the following:
* - Browser context is closed. * - Browser context is closed.
* - Browser application is closed or crashed. * - Browser application is closed or crashed.
* - The [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close) method was called. * - The [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close) method was called.
*/ */
addListener(event: 'close', listener: (browserContext: BrowserContext) => void): this; addListener(event: 'close', listener: (browserContext: BrowserContext) => void): this;
@ -7995,7 +8001,7 @@ export interface BrowserContext {
* Emitted when Browser context gets closed. This might happen because of one of the following: * Emitted when Browser context gets closed. This might happen because of one of the following:
* - Browser context is closed. * - Browser context is closed.
* - Browser application is closed or crashed. * - Browser application is closed or crashed.
* - The [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close) method was called. * - The [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close) method was called.
*/ */
prependListener(event: 'close', listener: (browserContext: BrowserContext) => void): this; prependListener(event: 'close', listener: (browserContext: BrowserContext) => void): this;
@ -8208,8 +8214,14 @@ export interface BrowserContext {
* Closes the browser context. All the pages that belong to the browser context will be closed. * Closes the browser context. All the pages that belong to the browser context will be closed.
* *
* **NOTE** The default browser context cannot be closed. * **NOTE** The default browser context cannot be closed.
* @param options
*/ */
close(): Promise<void>; close(options?: {
/**
* The reason to be reported to the operations interrupted by the context closure.
*/
reason?: string;
}): Promise<void>;
/** /**
* If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those * If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those
@ -8395,7 +8407,8 @@ export interface BrowserContext {
/** /**
* If specified, updates the given HAR with the actual network information instead of serving from file. The file is * If specified, updates the given HAR with the actual network information instead of serving from file. The file is
* written to disk when * written to disk when
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) is called. * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) is
* called.
*/ */
update?: boolean; update?: boolean;
@ -8587,7 +8600,7 @@ export interface BrowserContext {
* Emitted when Browser context gets closed. This might happen because of one of the following: * Emitted when Browser context gets closed. This might happen because of one of the following:
* - Browser context is closed. * - Browser context is closed.
* - Browser application is closed or crashed. * - Browser application is closed or crashed.
* - The [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close) method was called. * - The [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close) method was called.
*/ */
waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: (browserContext: BrowserContext) => boolean | Promise<boolean>, timeout?: number } | ((browserContext: BrowserContext) => boolean | Promise<boolean>)): Promise<BrowserContext>; waitForEvent(event: 'close', optionsOrPredicate?: { predicate?: (browserContext: BrowserContext) => boolean | Promise<boolean>, timeout?: number } | ((browserContext: BrowserContext) => boolean | Promise<boolean>)): Promise<BrowserContext>;
@ -12968,8 +12981,8 @@ export interface BrowserType<Unused = {}> {
/** /**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. * Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file.
* If not specified, the HAR is not recorded. Make sure to await * If not specified, the HAR is not recorded. Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for the HAR to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * the HAR to be saved.
*/ */
recordHar?: { recordHar?: {
/** /**
@ -13009,8 +13022,8 @@ export interface BrowserType<Unused = {}> {
/** /**
* Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. * Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded.
* Make sure to await * Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * videos to be saved.
*/ */
recordVideo?: { recordVideo?: {
/** /**
@ -14379,8 +14392,8 @@ export interface AndroidDevice {
/** /**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. * Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file.
* If not specified, the HAR is not recorded. Make sure to await * If not specified, the HAR is not recorded. Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for the HAR to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * the HAR to be saved.
*/ */
recordHar?: { recordHar?: {
/** /**
@ -14420,8 +14433,8 @@ export interface AndroidDevice {
/** /**
* Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. * Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded.
* Make sure to await * Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * videos to be saved.
*/ */
recordVideo?: { recordVideo?: {
/** /**
@ -15963,7 +15976,7 @@ export interface Browser extends EventEmitter {
* Emitted when Browser gets disconnected from the browser application. This might happen because of one of the * Emitted when Browser gets disconnected from the browser application. This might happen because of one of the
* following: * following:
* - Browser application is closed or crashed. * - Browser application is closed or crashed.
* - The [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close) method was called. * - The [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close) method was called.
*/ */
on(event: 'disconnected', listener: (browser: Browser) => void): this; on(event: 'disconnected', listener: (browser: Browser) => void): this;
@ -15976,7 +15989,7 @@ export interface Browser extends EventEmitter {
* Emitted when Browser gets disconnected from the browser application. This might happen because of one of the * Emitted when Browser gets disconnected from the browser application. This might happen because of one of the
* following: * following:
* - Browser application is closed or crashed. * - Browser application is closed or crashed.
* - The [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close) method was called. * - The [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close) method was called.
*/ */
addListener(event: 'disconnected', listener: (browser: Browser) => void): this; addListener(event: 'disconnected', listener: (browser: Browser) => void): this;
@ -15994,7 +16007,7 @@ export interface Browser extends EventEmitter {
* Emitted when Browser gets disconnected from the browser application. This might happen because of one of the * Emitted when Browser gets disconnected from the browser application. This might happen because of one of the
* following: * following:
* - Browser application is closed or crashed. * - Browser application is closed or crashed.
* - The [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close) method was called. * - The [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close) method was called.
*/ */
prependListener(event: 'disconnected', listener: (browser: Browser) => void): this; prependListener(event: 'disconnected', listener: (browser: Browser) => void): this;
@ -16012,14 +16025,20 @@ export interface Browser extends EventEmitter {
* the browser server. * the browser server.
* *
* **NOTE** This is similar to force quitting the browser. Therefore, you should call * **NOTE** This is similar to force quitting the browser. Therefore, you should call
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) on any {@link * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) on
* BrowserContext}'s you explicitly created earlier with * any {@link BrowserContext}'s you explicitly created earlier with
* [browser.newContext([options])](https://playwright.dev/docs/api/class-browser#browser-new-context) **before** * [browser.newContext([options])](https://playwright.dev/docs/api/class-browser#browser-new-context) **before**
* calling [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close). * calling [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close).
* *
* The {@link Browser} object itself is considered to be disposed and cannot be used anymore. * The {@link Browser} object itself is considered to be disposed and cannot be used anymore.
* @param options
*/ */
close(): Promise<void>; close(options?: {
/**
* The reason to be reported to the operations interrupted by the browser closure.
*/
reason?: string;
}): Promise<void>;
/** /**
* Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts. * Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts.
@ -16054,10 +16073,10 @@ export interface Browser extends EventEmitter {
* *
* **NOTE** If directly using this method to create {@link BrowserContext}s, it is best practice to explicitly close * **NOTE** If directly using this method to create {@link BrowserContext}s, it is best practice to explicitly close
* the returned context via * the returned context via
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) when your code * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) when
* is done with the {@link BrowserContext}, and before calling * your code is done with the {@link BrowserContext}, and before calling
* [browser.close()](https://playwright.dev/docs/api/class-browser#browser-close). This will ensure the `context` is * [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close). This will ensure the
* closed gracefully and any artifactslike HARs and videosare fully flushed and saved. * `context` is closed gracefully and any artifactslike HARs and videosare fully flushed and saved.
* *
* **Usage** * **Usage**
* *
@ -16258,8 +16277,8 @@ export interface Browser extends EventEmitter {
/** /**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. * Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file.
* If not specified, the HAR is not recorded. Make sure to await * If not specified, the HAR is not recorded. Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for the HAR to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * the HAR to be saved.
*/ */
recordHar?: { recordHar?: {
/** /**
@ -16299,8 +16318,8 @@ export interface Browser extends EventEmitter {
/** /**
* Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. * Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded.
* Make sure to await * Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * videos to be saved.
*/ */
recordVideo?: { recordVideo?: {
/** /**
@ -17093,8 +17112,8 @@ export interface Electron {
/** /**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. * Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file.
* If not specified, the HAR is not recorded. Make sure to await * If not specified, the HAR is not recorded. Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for the HAR to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * the HAR to be saved.
*/ */
recordHar?: { recordHar?: {
/** /**
@ -17134,8 +17153,8 @@ export interface Electron {
/** /**
* Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. * Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded.
* Make sure to await * Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * videos to be saved.
*/ */
recordVideo?: { recordVideo?: {
/** /**
@ -19472,8 +19491,8 @@ export interface BrowserContextOptions {
/** /**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. * Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file.
* If not specified, the HAR is not recorded. Make sure to await * If not specified, the HAR is not recorded. Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for the HAR to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * the HAR to be saved.
*/ */
recordHar?: { recordHar?: {
/** /**
@ -19513,8 +19532,8 @@ export interface BrowserContextOptions {
/** /**
* Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. * Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded.
* Make sure to await * Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to * [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
* be saved. * videos to be saved.
*/ */
recordVideo?: { recordVideo?: {
/** /**

View File

@ -112,7 +112,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
}); });
await use(browser); await use(browser);
await (browser as any)._wrapApiCall(async () => { await (browser as any)._wrapApiCall(async () => {
await browser.close(); await browser.close({ reason: 'Test ended.' });
}, true); }, true);
return; return;
} }
@ -120,7 +120,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
const browser = await playwright[browserName].launch(); const browser = await playwright[browserName].launch();
await use(browser); await use(browser);
await (browser as any)._wrapApiCall(async () => { await (browser as any)._wrapApiCall(async () => {
await browser.close(); await browser.close({ reason: 'Test ended.' });
}, true); }, true);
}, { scope: 'worker', timeout: 0 }], }, { scope: 'worker', timeout: 0 }],
@ -331,10 +331,11 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
}); });
let counter = 0; let counter = 0;
const closeReason = testInfo.status === 'timedOut' ? 'Test timeout of ' + testInfo.timeout + 'ms exceeded.' : 'Test ended.';
await Promise.all([...contexts.keys()].map(async context => { await Promise.all([...contexts.keys()].map(async context => {
(context as any)[kStartedContextTearDown] = true; (context as any)[kStartedContextTearDown] = true;
await (context as any)._wrapApiCall(async () => { await (context as any)._wrapApiCall(async () => {
await context.close(); await context.close({ reason: closeReason });
}, true); }, true);
const testFailed = testInfo.status !== testInfo.expectedStatus; const testFailed = testInfo.status !== testInfo.expectedStatus;
const preserveVideo = captureVideo && (videoMode === 'on' || (testFailed && videoMode === 'retain-on-failure') || (videoMode === 'on-first-retry' && testInfo.retry === 1)); const preserveVideo = captureVideo && (videoMode === 'on' || (testFailed && videoMode === 'retain-on-failure') || (videoMode === 'on-first-retry' && testInfo.retry === 1));
@ -374,7 +375,8 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
const context = await (browser as any)._newContextForReuse(defaultContextOptions); const context = await (browser as any)._newContextForReuse(defaultContextOptions);
(context as any)[kIsReusedContext] = true; (context as any)[kIsReusedContext] = true;
await use(context); await use(context);
await (browser as any)._stopPendingOperations('Test ended'); const closeReason = testInfo.status === 'timedOut' ? 'Test timeout of ' + testInfo.timeout + 'ms exceeded.' : 'Test ended.';
await (browser as any)._stopPendingOperations(closeReason);
}, },
page: async ({ context, _reuseContext }, use) => { page: async ({ context, _reuseContext }, use) => {

View File

@ -1080,7 +1080,7 @@ export interface BrowserEventTarget {
} }
export interface BrowserChannel extends BrowserEventTarget, Channel { export interface BrowserChannel extends BrowserEventTarget, Channel {
_type_Browser: boolean; _type_Browser: boolean;
close(params?: BrowserCloseParams, metadata?: CallMetadata): Promise<BrowserCloseResult>; close(params: BrowserCloseParams, metadata?: CallMetadata): Promise<BrowserCloseResult>;
killForTests(params?: BrowserKillForTestsParams, metadata?: CallMetadata): Promise<BrowserKillForTestsResult>; killForTests(params?: BrowserKillForTestsParams, metadata?: CallMetadata): Promise<BrowserKillForTestsResult>;
defaultUserAgentForTest(params?: BrowserDefaultUserAgentForTestParams, metadata?: CallMetadata): Promise<BrowserDefaultUserAgentForTestResult>; defaultUserAgentForTest(params?: BrowserDefaultUserAgentForTestParams, metadata?: CallMetadata): Promise<BrowserDefaultUserAgentForTestResult>;
newContext(params: BrowserNewContextParams, metadata?: CallMetadata): Promise<BrowserNewContextResult>; newContext(params: BrowserNewContextParams, metadata?: CallMetadata): Promise<BrowserNewContextResult>;
@ -1091,8 +1091,12 @@ export interface BrowserChannel extends BrowserEventTarget, Channel {
stopTracing(params?: BrowserStopTracingParams, metadata?: CallMetadata): Promise<BrowserStopTracingResult>; stopTracing(params?: BrowserStopTracingParams, metadata?: CallMetadata): Promise<BrowserStopTracingResult>;
} }
export type BrowserCloseEvent = {}; export type BrowserCloseEvent = {};
export type BrowserCloseParams = {}; export type BrowserCloseParams = {
export type BrowserCloseOptions = {}; reason?: string,
};
export type BrowserCloseOptions = {
reason?: string,
};
export type BrowserCloseResult = void; export type BrowserCloseResult = void;
export type BrowserKillForTestsParams = {}; export type BrowserKillForTestsParams = {};
export type BrowserKillForTestsOptions = {}; export type BrowserKillForTestsOptions = {};
@ -1426,7 +1430,7 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
addInitScript(params: BrowserContextAddInitScriptParams, metadata?: CallMetadata): Promise<BrowserContextAddInitScriptResult>; addInitScript(params: BrowserContextAddInitScriptParams, metadata?: CallMetadata): Promise<BrowserContextAddInitScriptResult>;
clearCookies(params?: BrowserContextClearCookiesParams, metadata?: CallMetadata): Promise<BrowserContextClearCookiesResult>; clearCookies(params?: BrowserContextClearCookiesParams, metadata?: CallMetadata): Promise<BrowserContextClearCookiesResult>;
clearPermissions(params?: BrowserContextClearPermissionsParams, metadata?: CallMetadata): Promise<BrowserContextClearPermissionsResult>; clearPermissions(params?: BrowserContextClearPermissionsParams, metadata?: CallMetadata): Promise<BrowserContextClearPermissionsResult>;
close(params?: BrowserContextCloseParams, metadata?: CallMetadata): Promise<BrowserContextCloseResult>; close(params: BrowserContextCloseParams, metadata?: CallMetadata): Promise<BrowserContextCloseResult>;
cookies(params: BrowserContextCookiesParams, metadata?: CallMetadata): Promise<BrowserContextCookiesResult>; cookies(params: BrowserContextCookiesParams, metadata?: CallMetadata): Promise<BrowserContextCookiesResult>;
exposeBinding(params: BrowserContextExposeBindingParams, metadata?: CallMetadata): Promise<BrowserContextExposeBindingResult>; exposeBinding(params: BrowserContextExposeBindingParams, metadata?: CallMetadata): Promise<BrowserContextExposeBindingResult>;
grantPermissions(params: BrowserContextGrantPermissionsParams, metadata?: CallMetadata): Promise<BrowserContextGrantPermissionsResult>; grantPermissions(params: BrowserContextGrantPermissionsParams, metadata?: CallMetadata): Promise<BrowserContextGrantPermissionsResult>;
@ -1524,8 +1528,12 @@ export type BrowserContextClearCookiesResult = void;
export type BrowserContextClearPermissionsParams = {}; export type BrowserContextClearPermissionsParams = {};
export type BrowserContextClearPermissionsOptions = {}; export type BrowserContextClearPermissionsOptions = {};
export type BrowserContextClearPermissionsResult = void; export type BrowserContextClearPermissionsResult = void;
export type BrowserContextCloseParams = {}; export type BrowserContextCloseParams = {
export type BrowserContextCloseOptions = {}; reason?: string,
};
export type BrowserContextCloseOptions = {
reason?: string,
};
export type BrowserContextCloseResult = void; export type BrowserContextCloseResult = void;
export type BrowserContextCookiesParams = { export type BrowserContextCookiesParams = {
urls: string[], urls: string[],
@ -1841,9 +1849,11 @@ export type PageAddInitScriptOptions = {
export type PageAddInitScriptResult = void; export type PageAddInitScriptResult = void;
export type PageCloseParams = { export type PageCloseParams = {
runBeforeUnload?: boolean, runBeforeUnload?: boolean,
reason?: string,
}; };
export type PageCloseOptions = { export type PageCloseOptions = {
runBeforeUnload?: boolean, runBeforeUnload?: boolean,
reason?: string,
}; };
export type PageCloseResult = void; export type PageCloseResult = void;
export type PageEmulateMediaParams = { export type PageEmulateMediaParams = {

View File

@ -904,6 +904,8 @@ Browser:
commands: commands:
close: close:
parameters:
reason: string?
killForTests: killForTests:
@ -1030,6 +1032,8 @@ BrowserContext:
clearPermissions: clearPermissions:
close: close:
parameters:
reason: string?
cookies: cookies:
parameters: parameters:
@ -1282,6 +1286,7 @@ Page:
close: close:
parameters: parameters:
runBeforeUnload: boolean? runBeforeUnload: boolean?
reason: string?
emulateMedia: emulateMedia:
parameters: parameters:

View File

@ -111,7 +111,7 @@ export const traceViewerFixtures: Fixtures<TraceViewerFixtures, {}, BaseTestFixt
for (const browser of browsers) for (const browser of browsers)
await browser.close(); await browser.close();
for (const contextImpl of contextImpls) for (const contextImpl of contextImpls)
await contextImpl._browser.close(); await contextImpl._browser.close({ reason: 'Trace viewer closed' });
}, },
runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => { runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => {

View File

@ -328,10 +328,7 @@ test('should report error and pending operations on timeout', async ({ runInline
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test('timedout', async ({ page }) => { test('timedout', async ({ page }) => {
await page.setContent('<div>Click me</div>'); await page.setContent('<div>Click me</div>');
await Promise.all([ await page.getByText('Missing').click();
page.getByText('Missing').click(),
page.getByText('More missing').textContent(),
]);
}); });
`, `,
}, { workers: 1, timeout: 2000 }); }, { workers: 1, timeout: 2000 });
@ -339,8 +336,8 @@ test('should report error and pending operations on timeout', async ({ runInline
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0); expect(result.passed).toBe(0);
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
expect(result.output).toContain('Error: locator.textContent: Target page, context or browser has been closed'); expect(result.output).toContain('Error: locator.click: Test timeout of 2000ms exceeded.');
expect(result.output).toContain('a.test.ts:7:42'); expect(result.output).toContain('a.test.ts:5:41');
}); });
test('should report error on timeout with shared page', async ({ runInlineTest }) => { test('should report error on timeout with shared page', async ({ runInlineTest }) => {