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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/playwright-core/src/server/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { PlaywrightPipeServer } from '../remote/playwrightPipeServer';
import { PlaywrightWebSocketServer } from '../remote/playwrightWebSocketServer';
import { BrowserInfo, serverRegistry } from '../serverRegistry';
import { nullProgress } from './progress';
import { TargetClosedError } from './errors';

import type * as types from './types';
import type { ProxySettings } from './types';
Expand Down Expand Up @@ -174,6 +175,8 @@ export abstract class Browser extends SdkObject {
context.browserClosed();
if (this._defaultContext)
this._defaultContext.browserClosed();
for (const download of this._downloads.values())
download.artifact.reportFinished(new TargetClosedError(undefined));
this.stopServer(nullProgress).catch(() => {});
this.emit(Browser.Events.Disconnected);
this.instrumentation.onBrowserClose(this);
Expand Down
5 changes: 0 additions & 5 deletions packages/playwright-core/src/server/dispatchers/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ export class Dispatcher<Type extends SdkObject, ChannelType, ParentScopeType ext

private _disposeRecursively(error: Error) {
assert(!this._disposed, `${this._guid} is disposed more than once`);
for (const controller of this._activeProgressControllers) {
const metainfo = getMetainfo({ type: controller.metadata.type, method: controller.metadata.method });
if (!metainfo?.potentiallyClosesScope)
controller.abort(error).catch(() => {});
}
this._onDispose();
this._disposed = true;
eventsHelper.removeEventListeners(this._eventListeners);
Expand Down
8 changes: 7 additions & 1 deletion packages/playwright-core/src/server/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { getUserAgent } from './userAgent';
import { BrowserContext, verifyClientCertificates } from './browserContext';
import { Cookie, CookieStore, domainMatches, parseRawCookie } from './cookieStore';
import { MultipartFormData } from './formData';
import { TargetClosedError } from './errors';
import { SdkObject } from './instrumentation';
import { isAbortError } from './progress';
import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor';
Expand Down Expand Up @@ -109,6 +110,7 @@ export abstract class APIRequestContext extends SdkObject {
readonly fetchLog: Map<string, string[]> = new Map();
protected static allInstances: Set<APIRequestContext> = new Set();
_closeReason: string | undefined;
private _disposed = false;

static findResponseBody(guid: string): Buffer | undefined {
for (const request of APIRequestContext.allInstances) {
Expand Down Expand Up @@ -147,6 +149,7 @@ export abstract class APIRequestContext extends SdkObject {
abstract cookies(progress: Progress, url: URL): Promise<channels.NetworkCookie[]>;

protected _disposeImpl() {
this._disposed = true;
APIRequestContext.allInstances.delete(this);
this.fetchResponses.clear();
this.fetchLog.clear();
Expand Down Expand Up @@ -328,6 +331,9 @@ export abstract class APIRequestContext extends SdkObject {
};
this.emit(APIRequestContext.Events.Request, requestEvent);

if (this._disposed)
throw new TargetClosedError(this._closeReason || 'Request context disposed.');

let destroyRequest: (() => void) | undefined;
progress.setAllowConcurrentOrNestedRaces(true);
const resultPromise = new Promise<SendRequestResult>((fulfill, reject) => {
Expand Down Expand Up @@ -530,7 +536,7 @@ export abstract class APIRequestContext extends SdkObject {

listeners.push(
eventsHelper.addEventListener(this, APIRequestContext.Events.Dispose, () => {
reject(new Error('Request context disposed.'));
reject(new TargetClosedError(this._closeReason || 'Request context disposed.'));
request.destroy();
})
);
Expand Down
12 changes: 11 additions & 1 deletion packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1447,7 +1447,17 @@ export class Frame extends SdkObject<FrameEventMap> {
}

async waitForTimeout(progress: Progress, timeout: number) {
return progress.wait(timeout);
let timer: NodeJS.Timeout;
const promise = new Promise<void>(f => timer = setTimeout(f, timeout));
try {
// Make sure we react to page close or frame detach.
await progress.race(LongStandingScope.raceMultiple([
this._page.openScope,
this._detachedScope,
], promise));
} finally {
clearTimeout(timer!);
}
}

async expect(progress: Progress, selector: string | undefined, options: FrameExpectParams): Promise<ExpectResult> {
Expand Down
23 changes: 15 additions & 8 deletions packages/playwright-core/src/server/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,19 @@ export class Request extends SdkObject {
this._isFavicon = url.endsWith('/favicon.ico') || !!redirectedFrom?._isFavicon;
}

async raceWithPageClosure<T>(progress: Progress, promise: Promise<T>): Promise<T> {
const scope = this._serviceWorker?.openScope ?? this._frame?._page.openScope;
if (scope)
return await progress.race(scope.race(promise));
return await progress.race(promise);
}

async rawRequestHeaders(progress: Progress): Promise<HeadersArray> {
return await progress.race(this._rawRequestHeaders());
return await this.raceWithPageClosure(progress, this._rawRequestHeaders());
}

async response(progress: Progress): Promise<Response | null> {
return await progress.race(this._waitForResponse());
return await this.raceWithPageClosure(progress, this._waitForResponse());
}

_setFailureText(failureText: string) {
Expand Down Expand Up @@ -539,27 +546,27 @@ export class Response extends SdkObject {
}

async body(progress: Progress): Promise<Buffer> {
return await progress.race(this.internalBody());
return await this._request.raceWithPageClosure(progress, this.internalBody());
}

async securityDetails(progress: Progress): Promise<SecurityDetails | null> {
return await progress.race(this.internalSecurityDetails());
return await this._request.raceWithPageClosure(progress, this.internalSecurityDetails());
}

async serverAddr(progress: Progress): Promise<RemoteAddr | null> {
return (await progress.race(this._serverAddrPromise)) || null;
return (await this._request.raceWithPageClosure(progress, this._serverAddrPromise)) || null;
}

async rawResponseHeaders(progress: Progress): Promise<NameValue[]> {
return await progress.race(this._rawResponseHeadersPromise);
return await this._request.raceWithPageClosure(progress, this._rawResponseHeadersPromise);
}

async httpVersion(progress: Progress): Promise<string> {
return await progress.race(this._httpVersion());
return await this._request.raceWithPageClosure(progress, this._httpVersion());
}

async sizes(progress: Progress): Promise<ResourceSizes> {
return await progress.race(this._sizes());
return await this._request.raceWithPageClosure(progress, this._sizes());
}

_serverAddrFinished(addr?: RemoteAddr) {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export class Recorder extends EventEmitter<RecorderEventMap> implements Instrume
await this.setMode('inspecting');
return await selectorPromise;
};
return await progress.race(doPickLocator());
return await progress.race(page.openScope.race(doPickLocator()));
} finally {
eventsHelper.removeEventListeners(listeners);
this._pickLocatorPage = undefined;
Expand Down
2 changes: 1 addition & 1 deletion tests/library/browsercontext-fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1256,7 +1256,7 @@ it('should abort requests when browser context closes', async ({ contextFactory,
server.waitForRequest('/empty.html').then(() => context.close())
]);
expect(error instanceof Error).toBeTruthy();
expect(error.message).toContain(kTargetClosedErrorMessage);
expect(error.message).toMatch(/Request context disposed|Target page, context or browser has been closed/);
await connectionClosed;
});

Expand Down
3 changes: 1 addition & 2 deletions tests/library/global-fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import os from 'os';
import * as util from 'util';
import { getPlaywrightVersion } from '../../packages/playwright-core/lib/coreBundle';
import { expect, playwrightTest as base } from '../config/browserTest';
import { kTargetClosedErrorMessage } from '../config/errors';

const it = base.extend({
context: async ({}, use) => {
Expand Down Expand Up @@ -324,7 +323,7 @@ it('should abort redirected requests when context is disposed', async ({ playwri
server.waitForRequest('/test').then(() => request.dispose())
]);
expect(result instanceof Error).toBeTruthy();
expect(result.message).toContain(kTargetClosedErrorMessage);
expect(result.message).toMatch(/Request context disposed|Target page, context or browser has been closed/);
await connectionClosed;
await request.dispose();
});
Expand Down
4 changes: 3 additions & 1 deletion tests/playwright-test/playwright.fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ test('should hint unrouteAll if failed in the handler', async ({ runInlineTest,
await page.route('**/empty.html', async route => {
await route.continue();
await closedPromise;
// Wait for the page to actually close before calling route.fetch().
await new Promise(f => setTimeout(f, 500));
await route.fetch();
});
await page.goto('${server.EMPTY_PAGE}');
Expand All @@ -99,7 +101,7 @@ test('should hint unrouteAll if failed in the handler', async ({ runInlineTest,

test('second test', async ({ page }) => {
// Wait enough for the worker to be killed.
await new Promise(f => setTimeout(f, 1000));
await new Promise(f => setTimeout(f, 2000));
});
`,
}, { workers: 1 });
Expand Down
Loading