mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-27 13:50:25 +03:00
chore: plumb the target close reason when test fails (#27640)
This commit is contained in:
parent
4e845e7b1d
commit
a54dbfdadf
@ -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.
|
||||
|
||||
### 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
|
||||
* since: v1.8
|
||||
- returns: <[Array]<[BrowserContext]>>
|
||||
|
@ -94,6 +94,12 @@ Emitted when Browser context gets closed. This might happen because of one of th
|
||||
* Browser application is closed or crashed.
|
||||
* 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
|
||||
* since: v1.34
|
||||
* langs:
|
||||
|
@ -826,6 +826,12 @@ if [`option: runBeforeUnload`] is passed as true, a `beforeunload` dialog might
|
||||
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
|
||||
* since: v1.8
|
||||
- `runBeforeUnload` <[boolean]>
|
||||
|
@ -130,12 +130,12 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
async close(options: { reason?: string } = {}): Promise<void> {
|
||||
try {
|
||||
if (this._shouldCloseConnectionOnClose)
|
||||
this._connection.close();
|
||||
else
|
||||
await this._channel.close();
|
||||
await this._channel.close(options);
|
||||
await this._closedPromise;
|
||||
} catch (e) {
|
||||
if (isTargetClosedError(e))
|
||||
|
@ -383,7 +383,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
this.emit(Events.BrowserContext.Close, this);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
async close(options: { reason?: string } = {}): Promise<void> {
|
||||
if (this._closeWasCalled)
|
||||
return;
|
||||
this._closeWasCalled = true;
|
||||
@ -404,7 +404,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
await artifact.delete();
|
||||
}
|
||||
}, true);
|
||||
await this._channel.close();
|
||||
await this._channel.close(options);
|
||||
await this._closedPromise;
|
||||
}
|
||||
|
||||
|
@ -588,7 +588,9 @@ scheme.BrowserInitializer = tObject({
|
||||
name: tString,
|
||||
});
|
||||
scheme.BrowserCloseEvent = tOptional(tObject({}));
|
||||
scheme.BrowserCloseParams = tOptional(tObject({}));
|
||||
scheme.BrowserCloseParams = tObject({
|
||||
reason: tOptional(tString),
|
||||
});
|
||||
scheme.BrowserCloseResult = tOptional(tObject({}));
|
||||
scheme.BrowserKillForTestsParams = tOptional(tObject({}));
|
||||
scheme.BrowserKillForTestsResult = tOptional(tObject({}));
|
||||
@ -831,7 +833,9 @@ scheme.BrowserContextClearCookiesParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextClearCookiesResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextClearPermissionsParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextClearPermissionsResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextCloseParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextCloseParams = tObject({
|
||||
reason: tOptional(tString),
|
||||
});
|
||||
scheme.BrowserContextCloseResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextCookiesParams = tObject({
|
||||
urls: tArray(tString),
|
||||
@ -1000,6 +1004,7 @@ scheme.PageAddInitScriptParams = tObject({
|
||||
scheme.PageAddInitScriptResult = tOptional(tObject({}));
|
||||
scheme.PageCloseParams = tObject({
|
||||
runBeforeUnload: tOptional(tBoolean),
|
||||
reason: tOptional(tString),
|
||||
});
|
||||
scheme.PageCloseResult = tOptional(tObject({}));
|
||||
scheme.PageEmulateMediaParams = tObject({
|
||||
|
@ -116,7 +116,7 @@ export class PlaywrightConnection {
|
||||
|
||||
this._cleanups.push(async () => {
|
||||
for (const browser of playwright.allBrowsers())
|
||||
await browser.close();
|
||||
await browser.close({ reason: 'Connection terminated' });
|
||||
});
|
||||
browser.on(Browser.Events.Disconnected, () => {
|
||||
// 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.
|
||||
for (const b of playwright.allBrowsers()) {
|
||||
if (b !== browser)
|
||||
await b.close();
|
||||
await b.close({ reason: 'Connection terminated' });
|
||||
}
|
||||
this._cleanups.push(() => playwrightDispatcher.cleanup());
|
||||
return playwrightDispatcher;
|
||||
@ -189,7 +189,7 @@ export class PlaywrightConnection {
|
||||
if (b === browser)
|
||||
continue;
|
||||
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) {
|
||||
@ -209,12 +209,12 @@ export class PlaywrightConnection {
|
||||
for (const browser of playwright.allBrowsers()) {
|
||||
for (const context of browser.contexts()) {
|
||||
if (!context.pages().length)
|
||||
await context.close(serverSideCallMetadata());
|
||||
await context.close({ reason: 'Connection terminated' });
|
||||
else
|
||||
await context.stopPendingOperations('Connection closed');
|
||||
}
|
||||
if (!browser.contexts())
|
||||
await browser.close();
|
||||
await browser.close({ reason: 'Connection terminated' });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -197,7 +197,7 @@ export class PlaywrightServer {
|
||||
|
||||
debugLogger.log('server', 'closing browsers');
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ export abstract class Browser extends SdkObject {
|
||||
private _startedClosing = false;
|
||||
readonly _idToVideo = new Map<string, { context: BrowserContext, artifact: Artifact }>();
|
||||
private _contextForReuse: { context: BrowserContext, hash: string } | undefined;
|
||||
_closeReason: string | undefined;
|
||||
|
||||
constructor(parent: SdkObject, options: BrowserOptions) {
|
||||
super(parent, 'browser');
|
||||
@ -90,7 +91,7 @@ export abstract class Browser extends SdkObject {
|
||||
const hash = BrowserContext.reusableContextHash(params);
|
||||
if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) {
|
||||
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 };
|
||||
return { context: this._contextForReuse.context, needsReset: false };
|
||||
}
|
||||
@ -149,8 +150,10 @@ export abstract class Browser extends SdkObject {
|
||||
this.instrumentation.onBrowserClose(this);
|
||||
}
|
||||
|
||||
async close() {
|
||||
async close(options: { reason?: string }) {
|
||||
if (!this._startedClosing) {
|
||||
if (options.reason)
|
||||
this._closeReason = options.reason;
|
||||
this._startedClosing = true;
|
||||
await this.options.browserProcess.close();
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
readonly initScripts: string[] = [];
|
||||
private _routesInFlight = new Set<network.Route>();
|
||||
private _debugger!: Debugger;
|
||||
_closeReason: string | undefined;
|
||||
|
||||
constructor(browser: Browser, options: channels.BrowserNewContextParams, browserContextId: string | undefined) {
|
||||
super(browser, 'browser-context');
|
||||
@ -272,7 +273,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
protected abstract doExposeBinding(binding: PageBinding): Promise<void>;
|
||||
protected abstract doRemoveExposedBindings(): Promise<void>;
|
||||
protected abstract doUpdateRequestInterception(): Promise<void>;
|
||||
protected abstract doClose(): Promise<void>;
|
||||
protected abstract doClose(reason: string | undefined): Promise<void>;
|
||||
protected abstract onClosePersistent(): void;
|
||||
|
||||
async cookies(urls: string | string[] | undefined = []): Promise<channels.NetworkCookie[]> {
|
||||
@ -412,8 +413,10 @@ export abstract class BrowserContext extends SdkObject {
|
||||
this._customCloseHandler = handler;
|
||||
}
|
||||
|
||||
async close(metadata: CallMetadata) {
|
||||
async close(options: { reason?: string }) {
|
||||
if (this._closedStatus === 'open') {
|
||||
if (options.reason)
|
||||
this._closeReason = options.reason;
|
||||
this.emit(BrowserContext.Events.BeforeClose);
|
||||
this._closedStatus = 'closing';
|
||||
|
||||
@ -433,7 +436,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
await this._customCloseHandler();
|
||||
} else {
|
||||
// Close the context.
|
||||
await this.doClose();
|
||||
await this.doClose(options.reason);
|
||||
}
|
||||
|
||||
// We delete downloads after context closure
|
||||
|
@ -510,7 +510,7 @@ export class CRBrowserContext extends BrowserContext {
|
||||
await (sw as CRServiceWorker).updateRequestInterception();
|
||||
}
|
||||
|
||||
async doClose() {
|
||||
async doClose(reason: string | undefined) {
|
||||
// Headful chrome cannot dispose browser context with opened 'beforeunload'
|
||||
// dialogs, so we should close all that are currently opened.
|
||||
// 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) {
|
||||
await Promise.all(this._crPages().map(crPage => crPage._mainFrameSession._stopVideoRecording()));
|
||||
// Closing persistent context should close the browser.
|
||||
await this._browser.close();
|
||||
await this._browser.close({ reason });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ export class DebugController extends SdkObject {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -210,10 +210,10 @@ export class DebugController extends SdkObject {
|
||||
for (const browser of this._playwright.allBrowsers()) {
|
||||
for (const context of browser.contexts()) {
|
||||
if (!context.pages().length)
|
||||
await context.close(serverSideCallMetadata());
|
||||
await context.close({ reason: 'Browser collected' });
|
||||
}
|
||||
if (!browser.contexts())
|
||||
await browser.close();
|
||||
await browser.close({ reason: 'Browser collected' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -24,7 +24,6 @@ import { Dispatcher } from './dispatcher';
|
||||
import type { CRBrowser } from '../chromium/crBrowser';
|
||||
import type { PageDispatcher } from './pageDispatcher';
|
||||
import type { CallMetadata } from '../instrumentation';
|
||||
import { serverSideCallMetadata } from '../instrumentation';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { Selectors } from '../selectors';
|
||||
import type { BrowserTypeDispatcher } from './browserTypeDispatcher';
|
||||
@ -56,8 +55,8 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
|
||||
await this._object.stopPendingOperations(params.reason);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this._object.close();
|
||||
async close(params: channels.BrowserCloseParams): Promise<void> {
|
||||
await this._object.close(params);
|
||||
}
|
||||
|
||||
async killForTests(): Promise<void> {
|
||||
@ -155,7 +154,7 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
|
||||
}
|
||||
|
||||
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)' })));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ 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, rewriteErrorMessage } from '../../utils';
|
||||
import { TargetClosedError, kTargetClosedErrorMessage, kTargetCrashedErrorMessage } from '../../common/errors';
|
||||
import { TargetClosedError, isTargetClosedError, kTargetClosedErrorMessage, kTargetCrashedErrorMessage } from '../../common/errors';
|
||||
import type { CallMetadata } from '../instrumentation';
|
||||
import { SdkObject } from '../instrumentation';
|
||||
import type { PlaywrightDispatcher } from './playwrightDispatcher';
|
||||
@ -330,9 +330,13 @@ export class DispatcherConnection {
|
||||
const validator = findValidator(dispatcher._type, method, 'Result');
|
||||
callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' });
|
||||
} catch (e) {
|
||||
if (isTargetClosedError(e) && sdkObject)
|
||||
rewriteErrorMessage(e, closeReason(sdkObject));
|
||||
if (isProtocolError(e)) {
|
||||
if (e.type === 'closed')
|
||||
rewriteErrorMessage(e, kTargetClosedErrorMessage + e.browserLogMessage());
|
||||
if (e.type === 'closed') {
|
||||
const closedReason = sdkObject ? closeReason(sdkObject) : kTargetClosedErrorMessage;
|
||||
rewriteErrorMessage(e, closedReason + e.browserLogMessage());
|
||||
}
|
||||
if (e.type === 'crashed')
|
||||
rewriteErrorMessage(e, kTargetCrashedErrorMessage + e.browserLogMessage());
|
||||
}
|
||||
@ -352,3 +356,9 @@ export class DispatcherConnection {
|
||||
this.onmessage(response);
|
||||
}
|
||||
}
|
||||
|
||||
function closeReason(sdkObject: SdkObject) {
|
||||
return sdkObject.attribution.page?._closeReason ||
|
||||
sdkObject.attribution.context?._closeReason ||
|
||||
sdkObject.attribution.browser?._closeReason || kTargetClosedErrorMessage;
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ export class ElectronApplication extends SdkObject {
|
||||
async close() {
|
||||
const progressController = new ProgressController(serverSideCallMetadata(), this);
|
||||
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();
|
||||
await closed;
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ export abstract class APIRequestContext extends SdkObject {
|
||||
readonly fetchLog: Map<string, string[]> = new Map();
|
||||
protected static allInstances: Set<APIRequestContext> = new Set();
|
||||
readonly _activeProgressControllers = new Set<ProgressController>();
|
||||
_closeReason: string | undefined;
|
||||
|
||||
static findResponseBody(guid: string): Buffer | undefined {
|
||||
for (const request of APIRequestContext.allInstances) {
|
||||
|
@ -376,7 +376,7 @@ export class FFBrowserContext extends BrowserContext {
|
||||
await this._browser.session.send('Browser.clearCache');
|
||||
}
|
||||
|
||||
async doClose() {
|
||||
async doClose(reason: string | undefined) {
|
||||
if (!this._browserContextId) {
|
||||
if (this._options.recordVideo) {
|
||||
await this._browser.session.send('Browser.setVideoRecordingOptions', {
|
||||
@ -385,7 +385,7 @@ export class FFBrowserContext extends BrowserContext {
|
||||
});
|
||||
}
|
||||
// Closing persistent context should close the browser.
|
||||
await this._browser.close();
|
||||
await this._browser.close({ reason });
|
||||
} else {
|
||||
await this._browser.session.send('Browser.removeBrowserContext', { browserContextId: this._browserContextId });
|
||||
this._browser._contexts.delete(this._browserContextId);
|
||||
|
@ -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.
|
||||
// When throttling for tracing, 200ms between frames, except for 10 frames around the action.
|
||||
private _frameThrottler = new FrameThrottler(10, 35, 200);
|
||||
_closeReason: string | undefined;
|
||||
|
||||
constructor(delegate: PageDelegate, browserContext: BrowserContext) {
|
||||
super(browserContext, 'page');
|
||||
@ -596,10 +597,12 @@ export class Page extends SdkObject {
|
||||
this._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async close(metadata: CallMetadata, options?: { runBeforeUnload?: boolean }) {
|
||||
async close(metadata: CallMetadata, options: { runBeforeUnload?: boolean, reason?: string } = {}) {
|
||||
if (this._closedState === 'closed')
|
||||
return;
|
||||
const runBeforeUnload = !!options && !!options.runBeforeUnload;
|
||||
if (options.reason)
|
||||
this._closeReason = options.reason;
|
||||
const runBeforeUnload = !!options.runBeforeUnload;
|
||||
if (this._closedState !== 'closing') {
|
||||
this._closedState = 'closing';
|
||||
// This might throw if the browser context containing the page closes
|
||||
@ -609,7 +612,7 @@ export class Page extends SdkObject {
|
||||
if (!runBeforeUnload)
|
||||
await this._closedPromise;
|
||||
if (this._ownedContext)
|
||||
await this._ownedContext.close(metadata);
|
||||
await this._ownedContext.close(options);
|
||||
}
|
||||
|
||||
private _setIsError(error: Error) {
|
||||
|
@ -75,7 +75,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||
}
|
||||
|
||||
async close() {
|
||||
await this._page.context().close(serverSideCallMetadata());
|
||||
await this._page.context().close({ reason: 'Recorder window closed' });
|
||||
}
|
||||
|
||||
private async _init() {
|
||||
@ -105,7 +105,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||
|
||||
this._page.once('close', () => {
|
||||
this.emit('close');
|
||||
this._page.context().close(serverSideCallMetadata()).catch(() => {});
|
||||
this._page.context().close({ reason: 'Recorder window closed' }).catch(() => {});
|
||||
});
|
||||
|
||||
const mainFrame = this._page.mainFrame();
|
||||
|
@ -157,7 +157,7 @@ export async function openTraceViewerApp(traceUrls: string[], browserName: strin
|
||||
await syncLocalStorageWithSettings(page, 'traceviewer');
|
||||
|
||||
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);
|
||||
return page;
|
||||
|
@ -349,11 +349,11 @@ export class WKBrowserContext extends BrowserContext {
|
||||
});
|
||||
}
|
||||
|
||||
async doClose() {
|
||||
async doClose(reason: string | undefined) {
|
||||
if (!this._browserContextId) {
|
||||
await Promise.all(this._wkPages().map(wkPage => wkPage._stopVideo()));
|
||||
// Closing persistent context should close the browser.
|
||||
await this._browser.close();
|
||||
await this._browser.close({ reason });
|
||||
} else {
|
||||
await this._browser._browserSession.send('Playwright.deleteContext', { browserContextId: this._browserContextId });
|
||||
this._browser._contexts.delete(this._browserContextId);
|
||||
|
95
packages/playwright-core/types/types.d.ts
vendored
95
packages/playwright-core/types/types.d.ts
vendored
@ -2001,6 +2001,11 @@ export interface Page {
|
||||
* @param 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
|
||||
* [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
|
||||
* 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;
|
||||
|
||||
@ -7556,7 +7562,7 @@ export interface BrowserContext {
|
||||
* Emitted when Browser context gets closed. This might happen because of one of the following:
|
||||
* - Browser context is closed.
|
||||
* - 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;
|
||||
|
||||
@ -7748,7 +7754,7 @@ export interface BrowserContext {
|
||||
* Emitted when Browser context gets closed. This might happen because of one of the following:
|
||||
* - Browser context is closed.
|
||||
* - 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;
|
||||
|
||||
@ -7995,7 +8001,7 @@ export interface BrowserContext {
|
||||
* Emitted when Browser context gets closed. This might happen because of one of the following:
|
||||
* - Browser context is closed.
|
||||
* - 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;
|
||||
|
||||
@ -8208,8 +8214,14 @@ export interface BrowserContext {
|
||||
* Closes the browser context. All the pages that belong to the browser context will 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
|
||||
@ -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
|
||||
* 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;
|
||||
|
||||
@ -8587,7 +8600,7 @@ export interface BrowserContext {
|
||||
* Emitted when Browser context gets closed. This might happen because of one of the following:
|
||||
* - Browser context is closed.
|
||||
* - 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>;
|
||||
|
||||
@ -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.
|
||||
* 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
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* the HAR to be saved.
|
||||
*/
|
||||
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.
|
||||
* Make sure to await
|
||||
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* videos to be saved.
|
||||
*/
|
||||
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.
|
||||
* 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
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* the HAR to be saved.
|
||||
*/
|
||||
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.
|
||||
* Make sure to await
|
||||
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* videos to be saved.
|
||||
*/
|
||||
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
|
||||
* following:
|
||||
* - 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;
|
||||
|
||||
@ -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
|
||||
* following:
|
||||
* - 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;
|
||||
|
||||
@ -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
|
||||
* following:
|
||||
* - 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;
|
||||
|
||||
@ -16012,14 +16025,20 @@ export interface Browser extends EventEmitter {
|
||||
* the browser server.
|
||||
*
|
||||
* **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}'s you explicitly created earlier with
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) on
|
||||
* any {@link BrowserContext}'s you explicitly created earlier with
|
||||
* [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.
|
||||
* @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.
|
||||
@ -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
|
||||
* the returned context via
|
||||
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) when 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
|
||||
* closed gracefully and any artifacts—like HARs and videos—are fully flushed and saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) when
|
||||
* your code is done with the {@link BrowserContext}, and before calling
|
||||
* [browser.close([options])](https://playwright.dev/docs/api/class-browser#browser-close). This will ensure the
|
||||
* `context` is closed gracefully and any artifacts—like HARs and videos—are fully flushed and saved.
|
||||
*
|
||||
* **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.
|
||||
* 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
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* the HAR to be saved.
|
||||
*/
|
||||
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.
|
||||
* Make sure to await
|
||||
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* videos to be saved.
|
||||
*/
|
||||
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.
|
||||
* 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
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* the HAR to be saved.
|
||||
*/
|
||||
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.
|
||||
* Make sure to await
|
||||
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* videos to be saved.
|
||||
*/
|
||||
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.
|
||||
* 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
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* the HAR to be saved.
|
||||
*/
|
||||
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.
|
||||
* Make sure to await
|
||||
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for videos to
|
||||
* be saved.
|
||||
* [browserContext.close([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-close) for
|
||||
* videos to be saved.
|
||||
*/
|
||||
recordVideo?: {
|
||||
/**
|
||||
|
@ -112,7 +112,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
});
|
||||
await use(browser);
|
||||
await (browser as any)._wrapApiCall(async () => {
|
||||
await browser.close();
|
||||
await browser.close({ reason: 'Test ended.' });
|
||||
}, true);
|
||||
return;
|
||||
}
|
||||
@ -120,7 +120,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
const browser = await playwright[browserName].launch();
|
||||
await use(browser);
|
||||
await (browser as any)._wrapApiCall(async () => {
|
||||
await browser.close();
|
||||
await browser.close({ reason: 'Test ended.' });
|
||||
}, true);
|
||||
}, { scope: 'worker', timeout: 0 }],
|
||||
|
||||
@ -331,10 +331,11 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
});
|
||||
|
||||
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 => {
|
||||
(context as any)[kStartedContextTearDown] = true;
|
||||
await (context as any)._wrapApiCall(async () => {
|
||||
await context.close();
|
||||
await context.close({ reason: closeReason });
|
||||
}, true);
|
||||
const testFailed = testInfo.status !== testInfo.expectedStatus;
|
||||
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);
|
||||
(context as any)[kIsReusedContext] = true;
|
||||
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) => {
|
||||
|
@ -1080,7 +1080,7 @@ export interface BrowserEventTarget {
|
||||
}
|
||||
export interface BrowserChannel extends BrowserEventTarget, Channel {
|
||||
_type_Browser: boolean;
|
||||
close(params?: BrowserCloseParams, metadata?: CallMetadata): Promise<BrowserCloseResult>;
|
||||
close(params: BrowserCloseParams, metadata?: CallMetadata): Promise<BrowserCloseResult>;
|
||||
killForTests(params?: BrowserKillForTestsParams, metadata?: CallMetadata): Promise<BrowserKillForTestsResult>;
|
||||
defaultUserAgentForTest(params?: BrowserDefaultUserAgentForTestParams, metadata?: CallMetadata): Promise<BrowserDefaultUserAgentForTestResult>;
|
||||
newContext(params: BrowserNewContextParams, metadata?: CallMetadata): Promise<BrowserNewContextResult>;
|
||||
@ -1091,8 +1091,12 @@ export interface BrowserChannel extends BrowserEventTarget, Channel {
|
||||
stopTracing(params?: BrowserStopTracingParams, metadata?: CallMetadata): Promise<BrowserStopTracingResult>;
|
||||
}
|
||||
export type BrowserCloseEvent = {};
|
||||
export type BrowserCloseParams = {};
|
||||
export type BrowserCloseOptions = {};
|
||||
export type BrowserCloseParams = {
|
||||
reason?: string,
|
||||
};
|
||||
export type BrowserCloseOptions = {
|
||||
reason?: string,
|
||||
};
|
||||
export type BrowserCloseResult = void;
|
||||
export type BrowserKillForTestsParams = {};
|
||||
export type BrowserKillForTestsOptions = {};
|
||||
@ -1426,7 +1430,7 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
|
||||
addInitScript(params: BrowserContextAddInitScriptParams, metadata?: CallMetadata): Promise<BrowserContextAddInitScriptResult>;
|
||||
clearCookies(params?: BrowserContextClearCookiesParams, metadata?: CallMetadata): Promise<BrowserContextClearCookiesResult>;
|
||||
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>;
|
||||
exposeBinding(params: BrowserContextExposeBindingParams, metadata?: CallMetadata): Promise<BrowserContextExposeBindingResult>;
|
||||
grantPermissions(params: BrowserContextGrantPermissionsParams, metadata?: CallMetadata): Promise<BrowserContextGrantPermissionsResult>;
|
||||
@ -1524,8 +1528,12 @@ export type BrowserContextClearCookiesResult = void;
|
||||
export type BrowserContextClearPermissionsParams = {};
|
||||
export type BrowserContextClearPermissionsOptions = {};
|
||||
export type BrowserContextClearPermissionsResult = void;
|
||||
export type BrowserContextCloseParams = {};
|
||||
export type BrowserContextCloseOptions = {};
|
||||
export type BrowserContextCloseParams = {
|
||||
reason?: string,
|
||||
};
|
||||
export type BrowserContextCloseOptions = {
|
||||
reason?: string,
|
||||
};
|
||||
export type BrowserContextCloseResult = void;
|
||||
export type BrowserContextCookiesParams = {
|
||||
urls: string[],
|
||||
@ -1841,9 +1849,11 @@ export type PageAddInitScriptOptions = {
|
||||
export type PageAddInitScriptResult = void;
|
||||
export type PageCloseParams = {
|
||||
runBeforeUnload?: boolean,
|
||||
reason?: string,
|
||||
};
|
||||
export type PageCloseOptions = {
|
||||
runBeforeUnload?: boolean,
|
||||
reason?: string,
|
||||
};
|
||||
export type PageCloseResult = void;
|
||||
export type PageEmulateMediaParams = {
|
||||
|
@ -904,6 +904,8 @@ Browser:
|
||||
commands:
|
||||
|
||||
close:
|
||||
parameters:
|
||||
reason: string?
|
||||
|
||||
killForTests:
|
||||
|
||||
@ -1030,6 +1032,8 @@ BrowserContext:
|
||||
clearPermissions:
|
||||
|
||||
close:
|
||||
parameters:
|
||||
reason: string?
|
||||
|
||||
cookies:
|
||||
parameters:
|
||||
@ -1282,6 +1286,7 @@ Page:
|
||||
close:
|
||||
parameters:
|
||||
runBeforeUnload: boolean?
|
||||
reason: string?
|
||||
|
||||
emulateMedia:
|
||||
parameters:
|
||||
|
@ -111,7 +111,7 @@ export const traceViewerFixtures: Fixtures<TraceViewerFixtures, {}, BaseTestFixt
|
||||
for (const browser of browsers)
|
||||
await browser.close();
|
||||
for (const contextImpl of contextImpls)
|
||||
await contextImpl._browser.close();
|
||||
await contextImpl._browser.close({ reason: 'Trace viewer closed' });
|
||||
},
|
||||
|
||||
runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => {
|
||||
|
@ -328,10 +328,7 @@ test('should report error and pending operations on timeout', async ({ runInline
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('timedout', async ({ page }) => {
|
||||
await page.setContent('<div>Click me</div>');
|
||||
await Promise.all([
|
||||
page.getByText('Missing').click(),
|
||||
page.getByText('More missing').textContent(),
|
||||
]);
|
||||
await page.getByText('Missing').click();
|
||||
});
|
||||
`,
|
||||
}, { 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.passed).toBe(0);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.output).toContain('Error: locator.textContent: Target page, context or browser has been closed');
|
||||
expect(result.output).toContain('a.test.ts:7:42');
|
||||
expect(result.output).toContain('Error: locator.click: Test timeout of 2000ms exceeded.');
|
||||
expect(result.output).toContain('a.test.ts:5:41');
|
||||
});
|
||||
|
||||
test('should report error on timeout with shared page', async ({ runInlineTest }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user