Skip to content

Commit 9ede66a

Browse files
dgozmanaslushnikov
authored andcommitted
cherry-pick(#18997): fix(reuse): stop pending operations upon reuse/disconnect
SHA 503f8f5
1 parent 76dc43e commit 9ede66a

File tree

6 files changed

+41
-1
lines changed

6 files changed

+41
-1
lines changed

packages/playwright-core/src/remote/playwrightConnection.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ export class PlaywrightConnection {
208208
for (const context of browser.contexts()) {
209209
if (!context.pages().length)
210210
await context.close(serverSideCallMetadata());
211+
else
212+
await context.stopPendingOperations();
211213
}
212214
if (!browser.contexts())
213215
await browser.close();

packages/playwright-core/src/server/browser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export abstract class Browser extends SdkObject {
106106
this._contextForReuse = { context: await this.newContext(metadata, params), hash };
107107
return { context: this._contextForReuse.context, needsReset: false };
108108
}
109+
await this._contextForReuse.context.stopPendingOperations();
109110
return { context: this._contextForReuse.context, needsReset: true };
110111
}
111112

packages/playwright-core/src/server/browserContext.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { helper } from './helper';
2626
import * as network from './network';
2727
import type { PageDelegate } from './page';
2828
import { Page, PageBinding } from './page';
29-
import type { Progress } from './progress';
29+
import type { Progress, ProgressController } from './progress';
3030
import type { Selectors } from './selectors';
3131
import type * as types from './types';
3232
import type * as channels from '@protocol/channels';
@@ -56,6 +56,7 @@ export abstract class BrowserContext extends SdkObject {
5656

5757
readonly _timeoutSettings = new TimeoutSettings();
5858
readonly _pageBindings = new Map<string, PageBinding>();
59+
readonly _activeProgressControllers = new Set<ProgressController>();
5960
readonly _options: channels.BrowserNewContextParams;
6061
_requestInterceptor?: network.RouteHandler;
6162
private _isPersistentContext: boolean;
@@ -145,6 +146,11 @@ export abstract class BrowserContext extends SdkObject {
145146
return true;
146147
}
147148

149+
async stopPendingOperations() {
150+
for (const controller of this._activeProgressControllers)
151+
controller.abort(new Error(`Context was reset for reuse.`));
152+
}
153+
148154
static reusableContextHash(params: channels.BrowserNewContextForReuseParams): string {
149155
const paramsCopy = { ...params };
150156

packages/playwright-core/src/server/fetch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export abstract class APIRequestContext extends SdkObject {
7878
readonly fetchResponses: Map<string, Buffer> = new Map();
7979
readonly fetchLog: Map<string, string[]> = new Map();
8080
protected static allInstances: Set<APIRequestContext> = new Set();
81+
readonly _activeProgressControllers = new Set<ProgressController>();
8182

8283
static findResponseBody(guid: string): Buffer | undefined {
8384
for (const request of APIRequestContext.allInstances) {

packages/playwright-core/src/server/progress.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ export class ProgressController {
6363
return this._lastIntermediateResult;
6464
}
6565

66+
abort(error: Error) {
67+
this._forceAbortPromise.reject(error);
68+
}
69+
6670
async run<T>(task: (progress: Progress) => Promise<T>, timeout?: number): Promise<T> {
6771
if (timeout) {
6872
this._timeout = timeout;
@@ -71,6 +75,7 @@ export class ProgressController {
7175

7276
assert(this._state === 'before');
7377
this._state = 'running';
78+
this.sdkObject.attribution.context?._activeProgressControllers.add(this);
7479

7580
const progress: Progress = {
7681
log: message => {
@@ -117,6 +122,7 @@ export class ProgressController {
117122
await Promise.all(this._cleanups.splice(0).map(runCleanup));
118123
throw e;
119124
} finally {
125+
this.sdkObject.attribution.context?._activeProgressControllers.delete(this);
120126
clearTimeout(timer);
121127
}
122128
}

tests/playwright-test/playwright.reuse.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,3 +374,27 @@ test('should reuse context with beforeunload', async ({ runInlineTest }) => {
374374
expect(result.exitCode).toBe(0);
375375
expect(result.passed).toBe(2);
376376
});
377+
378+
test('should cancel pending operations upon reuse', async ({ runInlineTest }) => {
379+
const result = await runInlineTest({
380+
'src/reuse.test.ts': `
381+
const { test } = pwt;
382+
test('one', async ({ page }) => {
383+
await Promise.race([
384+
page.getByText('click me').click().catch(e => {}),
385+
page.waitForTimeout(2000),
386+
]);
387+
});
388+
389+
test('two', async ({ page }) => {
390+
await page.setContent('<button onclick="window._clicked=true">click me</button>');
391+
// Give it time to erroneously click.
392+
await page.waitForTimeout(2000);
393+
expect(await page.evaluate('window._clicked')).toBe(undefined);
394+
});
395+
`,
396+
}, { workers: 1 }, { PW_TEST_REUSE_CONTEXT: '1' });
397+
398+
expect(result.exitCode).toBe(0);
399+
expect(result.passed).toBe(2);
400+
});

0 commit comments

Comments
 (0)