Skip to content

Commit 9dd1777

Browse files
authored
feat: mark PlaywrightClient's connection as remote (microsoft#9477)
This makes artifacts work as expected for all remote scenarios.
1 parent a4d1412 commit 9dd1777

14 files changed

+77
-57
lines changed

Diff for: packages/playwright-core/src/client/artifact.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,21 @@ import { ChannelOwner } from './channelOwner';
2222
import { Readable } from 'stream';
2323

2424
export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.ArtifactInitializer> {
25-
_isRemote = false;
26-
2725
static from(channel: channels.ArtifactChannel): Artifact {
2826
return (channel as any)._object;
2927
}
3028

3129
async pathAfterFinished(): Promise<string | null> {
32-
if (this._isRemote)
33-
throw new Error(`Path is not available when using browserType.connect(). Use saveAs() to save a local copy.`);
30+
if (this._connection.isRemote())
31+
throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
3432
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
3533
return (await channel.pathAfterFinished()).value || null;
3634
});
3735
}
3836

3937
async saveAs(path: string): Promise<void> {
4038
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
41-
if (!this._isRemote) {
39+
if (!this._connection.isRemote()) {
4240
await channel.saveAs({ path });
4341
return;
4442
}

Diff for: packages/playwright-core/src/client/browser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
2929
readonly _contexts = new Set<BrowserContext>();
3030
private _isConnected = true;
3131
private _closedPromise: Promise<void>;
32-
_remoteType: 'owns-connection' | 'uses-connection' | null = null;
32+
_shouldCloseConnectionOnClose = false;
3333
private _browserType!: BrowserType;
3434
readonly _name: string;
3535

@@ -109,7 +109,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
109109
async close(): Promise<void> {
110110
try {
111111
await this._wrapApiCall(async (channel: channels.BrowserChannel) => {
112-
if (this._remoteType === 'owns-connection')
112+
if (this._shouldCloseConnectionOnClose)
113113
this._connection.close();
114114
else
115115
await channel.close();

Diff for: packages/playwright-core/src/client/browserContext.ts

-2
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
346346
if (this._options.recordHar) {
347347
const har = await this._channel.harExport();
348348
const artifact = Artifact.from(har.artifact);
349-
if (this.browser()?._remoteType)
350-
artifact._isRemote = true;
351349
await artifact.saveAs(this._options.recordHar.path);
352350
await artifact.delete();
353351
}

Diff for: packages/playwright-core/src/client/browserType.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
134134
const { pipe } = await channel.connect({ wsEndpoint, headers: params.headers, slowMo: params.slowMo, timeout: params.timeout });
135135
const closePipe = () => pipe.close().catch(() => {});
136136
const connection = new Connection(closePipe);
137+
connection.markAsRemote();
137138

138139
const onPipeClosed = () => {
139140
// Emulate all pages, contexts and the browser closing upon disconnect.
@@ -173,7 +174,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
173174
}
174175
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
175176
browser._logger = logger;
176-
browser._remoteType = 'owns-connection';
177+
browser._shouldCloseConnectionOnClose = true;
177178
browser._setBrowserType((playwright as any)[browser._name]);
178179
browser.on(Events.Browser.Disconnected, () => {
179180
playwright._cleanup();
@@ -221,7 +222,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
221222
const browser = Browser.from(result.browser);
222223
if (result.defaultContext)
223224
browser._contexts.add(BrowserContext.from(result.defaultContext));
224-
browser._remoteType = 'uses-connection';
225225
browser._logger = logger;
226226
browser._setBrowserType(this);
227227
return browser;

Diff for: packages/playwright-core/src/client/connection.ts

+9
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,22 @@ export class Connection extends EventEmitter {
6262
private _rootObject: Root;
6363
private _disconnectedErrorMessage: string | undefined;
6464
private _onClose?: () => void;
65+
private _isRemote = false;
6566

6667
constructor(onClose?: () => void) {
6768
super();
6869
this._rootObject = new Root(this);
6970
this._onClose = onClose;
7071
}
7172

73+
markAsRemote() {
74+
this._isRemote = true;
75+
}
76+
77+
isRemote() {
78+
return this._isRemote;
79+
}
80+
7281
async initializePlaywright(): Promise<Playwright> {
7382
return await this._rootObject.initialize();
7483
}

Diff for: packages/playwright-core/src/client/page.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
128128
this._channel.on('domcontentloaded', () => this.emit(Events.Page.DOMContentLoaded, this));
129129
this._channel.on('download', ({ url, suggestedFilename, artifact }) => {
130130
const artifactObject = Artifact.from(artifact);
131-
artifactObject._isRemote = !!this._browserContext._browser && !!this._browserContext._browser._remoteType;
132131
this.emit(Events.Page.Download, new Download(this, url, suggestedFilename, artifactObject));
133132
});
134133
this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple)));
@@ -250,7 +249,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
250249

251250
private _forceVideo(): Video {
252251
if (!this._video)
253-
this._video = new Video(this);
252+
this._video = new Video(this, this._connection);
254253
return this._video;
255254
}
256255

Diff for: packages/playwright-core/src/client/tracing.ts

-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ export class Tracing implements api.Tracing {
5757
if (!result.artifact)
5858
return;
5959
const artifact = Artifact.from(result.artifact);
60-
if (this._context._browser?._remoteType)
61-
artifact._isRemote = true;
6260
await artifact.saveAs(path!);
6361
await artifact.delete();
6462
}

Diff for: packages/playwright-core/src/client/video.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,28 @@
1717
import { Page } from './page';
1818
import * as api from '../../types/types';
1919
import { Artifact } from './artifact';
20+
import { Connection } from './connection';
2021

2122
export class Video implements api.Video {
2223
private _artifact: Promise<Artifact | null> | null = null;
2324
private _artifactCallback = (artifact: Artifact) => {};
2425
private _isRemote = false;
2526

26-
constructor(page: Page) {
27-
const browser = page.context()._browser;
28-
this._isRemote = !!browser && !!browser._remoteType;
27+
constructor(page: Page, connection: Connection) {
28+
this._isRemote = connection.isRemote();
2929
this._artifact = Promise.race([
3030
new Promise<Artifact>(f => this._artifactCallback = f),
3131
page._closedOrCrashedPromise.then(() => null),
3232
]);
3333
}
3434

3535
_artifactReady(artifact: Artifact) {
36-
artifact._isRemote = this._isRemote;
3736
this._artifactCallback(artifact);
3837
}
3938

4039
async path(): Promise<string> {
4140
if (this._isRemote)
42-
throw new Error(`Path is not available when using browserType.connect(). Use saveAs() to save a local copy.`);
41+
throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
4342
const artifact = await this._artifact;
4443
if (!artifact)
4544
throw new Error('Page did not produce any video frames');

Diff for: packages/playwright-core/src/remote/playwrightClient.ts

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class PlaywrightClient {
3232
static async connect(options: PlaywrightClientConnectOptions): Promise<PlaywrightClient> {
3333
const { wsEndpoint, timeout = 30000 } = options;
3434
const connection = new Connection();
35+
connection.markAsRemote();
3536
const ws = new WebSocket(wsEndpoint);
3637
const waitForNextTask = makeWaitForNextTask();
3738
connection.onmessage = message => {

Diff for: tests/browsertype-connect.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ test('should saveAs videos from remote browser', async ({ browserType, startRemo
389389
await page.video().saveAs(savedAsPath);
390390
expect(fs.existsSync(savedAsPath)).toBeTruthy();
391391
const error = await page.video().path().catch(e => e);
392-
expect(error.message).toContain('Path is not available when using browserType.connect(). Use saveAs() to save a local copy.');
392+
expect(error.message).toContain('Path is not available when connecting remotely. Use saveAs() to save a local copy.');
393393
});
394394

395395
test('should be able to connect 20 times to a single server without warnings', async ({ browserType, startRemoteServer }) => {
@@ -428,7 +428,7 @@ test('should save download', async ({ server, browserType, startRemoteServer },
428428
expect(fs.existsSync(nestedPath)).toBeTruthy();
429429
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
430430
const error = await download.path().catch(e => e);
431-
expect(error.message).toContain('Path is not available when using browserType.connect(). Use saveAs() to save a local copy.');
431+
expect(error.message).toContain('Path is not available when connecting remotely. Use saveAs() to save a local copy.');
432432
await browser.close();
433433
});
434434

Diff for: tests/defaultbrowsercontext-1.spec.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ it('should support offline option', async ({ server, launchPersistent }) => {
171171
expect(error).toBeTruthy();
172172
});
173173

174-
it('should support acceptDownloads option', async ({ server, launchPersistent }) => {
174+
it('should support acceptDownloads option', async ({ server, launchPersistent, mode }) => {
175+
it.skip(mode === 'service', 'download.path() is not avaialble in remote mode');
176+
175177
const { page } = await launchPersistent({ acceptDownloads: true });
176178
server.setRoute('/download', (req, res) => {
177179
res.setHeader('Content-Type', 'application/octet-stream');

Diff for: tests/download.spec.ts

+26-14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import crypto from 'crypto';
2121
import type { Download } from 'playwright-core';
2222

2323
it.describe('download event', () => {
24+
it.skip(({ mode }) => mode === 'service', 'download.path() is not available in remote mode');
25+
2426
it.beforeEach(async ({ server }) => {
2527
server.setRoute('/download', (req, res) => {
2628
res.setHeader('Content-Type', 'application/octet-stream');
@@ -156,20 +158,6 @@ it.describe('download event', () => {
156158
await page.close();
157159
});
158160

159-
it('should save to user-specified path', async ({ browser, server }, testInfo) => {
160-
const page = await browser.newPage({ acceptDownloads: true });
161-
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
162-
const [ download ] = await Promise.all([
163-
page.waitForEvent('download'),
164-
page.click('a')
165-
]);
166-
const userPath = testInfo.outputPath('download.txt');
167-
await download.saveAs(userPath);
168-
expect(fs.existsSync(userPath)).toBeTruthy();
169-
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
170-
await page.close();
171-
});
172-
173161
it('should save to user-specified path without updating original path', async ({ browser, server }, testInfo) => {
174162
const page = await browser.newPage({ acceptDownloads: true });
175163
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
@@ -621,6 +609,30 @@ it('should be able to download a inline PDF file', async ({ browser, server, ass
621609
await page.close();
622610
});
623611

612+
it('should save to user-specified path', async ({ browser, server, mode }, testInfo) => {
613+
server.setRoute('/download', (req, res) => {
614+
res.setHeader('Content-Type', 'application/octet-stream');
615+
res.setHeader('Content-Disposition', 'attachment');
616+
res.end(`Hello world`);
617+
});
618+
619+
const page = await browser.newPage({ acceptDownloads: true });
620+
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
621+
const [ download ] = await Promise.all([
622+
page.waitForEvent('download'),
623+
page.click('a')
624+
]);
625+
if (mode === 'service') {
626+
const error = await download.path().catch(e => e);
627+
expect(error.message).toContain('Path is not available when connecting remotely. Use saveAs() to save a local copy.');
628+
}
629+
const userPath = testInfo.outputPath('download.txt');
630+
await download.saveAs(userPath);
631+
expect(fs.existsSync(userPath)).toBeTruthy();
632+
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
633+
await page.close();
634+
});
635+
624636
async function assertDownloadToPDF(download: Download, filePath: string) {
625637
expect(download.suggestedFilename()).toBe(path.basename(filePath));
626638
const stream = await download.createReadStream();

Diff for: tests/downloads-path.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import fs from 'fs';
1919
import path from 'path';
2020

2121
it.describe('downloads path', () => {
22+
it.skip(({ mode }) => mode === 'service', 'download.path() is not available in remote mode');
23+
2224
it.beforeEach(async ({ server }) => {
2325
server.setRoute('/download', (req, res) => {
2426
res.setHeader('Content-Type', 'application/octet-stream');

Diff for: tests/video.spec.ts

+22-20
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ function expectRedFrames(videoFile: string, size: { width: number, height: numbe
152152

153153
it.describe('screencast', () => {
154154
it.slow();
155+
it.skip(({ mode }) => mode === 'service', 'video.path() is not avaialble in remote mode');
155156

156157
it('videoSize should require videosPath', async ({ browser }) => {
157158
const error = await browser.newContext({ videoSize: { width: 100, height: 100 } }).catch(e => e);
@@ -218,26 +219,6 @@ it.describe('screencast', () => {
218219
expect(fs.existsSync(path)).toBeTruthy();
219220
});
220221

221-
it('should saveAs video', async ({ browser }, testInfo) => {
222-
const videosPath = testInfo.outputPath('');
223-
const size = { width: 320, height: 240 };
224-
const context = await browser.newContext({
225-
recordVideo: {
226-
dir: videosPath,
227-
size
228-
},
229-
viewport: size,
230-
});
231-
const page = await context.newPage();
232-
await page.evaluate(() => document.body.style.backgroundColor = 'red');
233-
await page.waitForTimeout(1000);
234-
await context.close();
235-
236-
const saveAsPath = testInfo.outputPath('my-video.webm');
237-
await page.video().saveAs(saveAsPath);
238-
expect(fs.existsSync(saveAsPath)).toBeTruthy();
239-
});
240-
241222
it('saveAs should throw when no video frames', async ({ browser, browserName }, testInfo) => {
242223
const videosPath = testInfo.outputPath('');
243224
const size = { width: 320, height: 240 };
@@ -668,5 +649,26 @@ it.describe('screencast', () => {
668649
const files = fs.readdirSync(videoDir);
669650
expect(files.length).toBe(1);
670651
});
652+
});
671653

654+
it('should saveAs video', async ({ browser }, testInfo) => {
655+
it.slow();
656+
657+
const videosPath = testInfo.outputPath('');
658+
const size = { width: 320, height: 240 };
659+
const context = await browser.newContext({
660+
recordVideo: {
661+
dir: videosPath,
662+
size
663+
},
664+
viewport: size,
665+
});
666+
const page = await context.newPage();
667+
await page.evaluate(() => document.body.style.backgroundColor = 'red');
668+
await page.waitForTimeout(1000);
669+
await context.close();
670+
671+
const saveAsPath = testInfo.outputPath('my-video.webm');
672+
await page.video().saveAs(saveAsPath);
673+
expect(fs.existsSync(saveAsPath)).toBeTruthy();
672674
});

0 commit comments

Comments
 (0)