feat: mark PlaywrightClient's connection as remote (#9477)
This makes artifacts work as expected for all remote scenarios.
This commit is contained in:
parent
a4d1412463
commit
9dd17773e6
|
|
@ -22,15 +22,13 @@ import { ChannelOwner } from './channelOwner';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.ArtifactInitializer> {
|
export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.ArtifactInitializer> {
|
||||||
_isRemote = false;
|
|
||||||
|
|
||||||
static from(channel: channels.ArtifactChannel): Artifact {
|
static from(channel: channels.ArtifactChannel): Artifact {
|
||||||
return (channel as any)._object;
|
return (channel as any)._object;
|
||||||
}
|
}
|
||||||
|
|
||||||
async pathAfterFinished(): Promise<string | null> {
|
async pathAfterFinished(): Promise<string | null> {
|
||||||
if (this._isRemote)
|
if (this._connection.isRemote())
|
||||||
throw new Error(`Path is not available when using browserType.connect(). Use saveAs() to save a local copy.`);
|
throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
|
||||||
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
|
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
|
||||||
return (await channel.pathAfterFinished()).value || null;
|
return (await channel.pathAfterFinished()).value || null;
|
||||||
});
|
});
|
||||||
|
|
@ -38,7 +36,7 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.Ar
|
||||||
|
|
||||||
async saveAs(path: string): Promise<void> {
|
async saveAs(path: string): Promise<void> {
|
||||||
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
|
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
|
||||||
if (!this._isRemote) {
|
if (!this._connection.isRemote()) {
|
||||||
await channel.saveAs({ path });
|
await channel.saveAs({ path });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
|
||||||
readonly _contexts = new Set<BrowserContext>();
|
readonly _contexts = new Set<BrowserContext>();
|
||||||
private _isConnected = true;
|
private _isConnected = true;
|
||||||
private _closedPromise: Promise<void>;
|
private _closedPromise: Promise<void>;
|
||||||
_remoteType: 'owns-connection' | 'uses-connection' | null = null;
|
_shouldCloseConnectionOnClose = false;
|
||||||
private _browserType!: BrowserType;
|
private _browserType!: BrowserType;
|
||||||
readonly _name: string;
|
readonly _name: string;
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this._wrapApiCall(async (channel: channels.BrowserChannel) => {
|
await this._wrapApiCall(async (channel: channels.BrowserChannel) => {
|
||||||
if (this._remoteType === 'owns-connection')
|
if (this._shouldCloseConnectionOnClose)
|
||||||
this._connection.close();
|
this._connection.close();
|
||||||
else
|
else
|
||||||
await channel.close();
|
await channel.close();
|
||||||
|
|
|
||||||
|
|
@ -346,8 +346,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
||||||
if (this._options.recordHar) {
|
if (this._options.recordHar) {
|
||||||
const har = await this._channel.harExport();
|
const har = await this._channel.harExport();
|
||||||
const artifact = Artifact.from(har.artifact);
|
const artifact = Artifact.from(har.artifact);
|
||||||
if (this.browser()?._remoteType)
|
|
||||||
artifact._isRemote = true;
|
|
||||||
await artifact.saveAs(this._options.recordHar.path);
|
await artifact.saveAs(this._options.recordHar.path);
|
||||||
await artifact.delete();
|
await artifact.delete();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||||
const { pipe } = await channel.connect({ wsEndpoint, headers: params.headers, slowMo: params.slowMo, timeout: params.timeout });
|
const { pipe } = await channel.connect({ wsEndpoint, headers: params.headers, slowMo: params.slowMo, timeout: params.timeout });
|
||||||
const closePipe = () => pipe.close().catch(() => {});
|
const closePipe = () => pipe.close().catch(() => {});
|
||||||
const connection = new Connection(closePipe);
|
const connection = new Connection(closePipe);
|
||||||
|
connection.markAsRemote();
|
||||||
|
|
||||||
const onPipeClosed = () => {
|
const onPipeClosed = () => {
|
||||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||||
|
|
@ -173,7 +174,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||||
}
|
}
|
||||||
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||||
browser._logger = logger;
|
browser._logger = logger;
|
||||||
browser._remoteType = 'owns-connection';
|
browser._shouldCloseConnectionOnClose = true;
|
||||||
browser._setBrowserType((playwright as any)[browser._name]);
|
browser._setBrowserType((playwright as any)[browser._name]);
|
||||||
browser.on(Events.Browser.Disconnected, () => {
|
browser.on(Events.Browser.Disconnected, () => {
|
||||||
playwright._cleanup();
|
playwright._cleanup();
|
||||||
|
|
@ -221,7 +222,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
|
||||||
const browser = Browser.from(result.browser);
|
const browser = Browser.from(result.browser);
|
||||||
if (result.defaultContext)
|
if (result.defaultContext)
|
||||||
browser._contexts.add(BrowserContext.from(result.defaultContext));
|
browser._contexts.add(BrowserContext.from(result.defaultContext));
|
||||||
browser._remoteType = 'uses-connection';
|
|
||||||
browser._logger = logger;
|
browser._logger = logger;
|
||||||
browser._setBrowserType(this);
|
browser._setBrowserType(this);
|
||||||
return browser;
|
return browser;
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ export class Connection extends EventEmitter {
|
||||||
private _rootObject: Root;
|
private _rootObject: Root;
|
||||||
private _disconnectedErrorMessage: string | undefined;
|
private _disconnectedErrorMessage: string | undefined;
|
||||||
private _onClose?: () => void;
|
private _onClose?: () => void;
|
||||||
|
private _isRemote = false;
|
||||||
|
|
||||||
constructor(onClose?: () => void) {
|
constructor(onClose?: () => void) {
|
||||||
super();
|
super();
|
||||||
|
|
@ -69,6 +70,14 @@ export class Connection extends EventEmitter {
|
||||||
this._onClose = onClose;
|
this._onClose = onClose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsRemote() {
|
||||||
|
this._isRemote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRemote() {
|
||||||
|
return this._isRemote;
|
||||||
|
}
|
||||||
|
|
||||||
async initializePlaywright(): Promise<Playwright> {
|
async initializePlaywright(): Promise<Playwright> {
|
||||||
return await this._rootObject.initialize();
|
return await this._rootObject.initialize();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,6 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
||||||
this._channel.on('domcontentloaded', () => this.emit(Events.Page.DOMContentLoaded, this));
|
this._channel.on('domcontentloaded', () => this.emit(Events.Page.DOMContentLoaded, this));
|
||||||
this._channel.on('download', ({ url, suggestedFilename, artifact }) => {
|
this._channel.on('download', ({ url, suggestedFilename, artifact }) => {
|
||||||
const artifactObject = Artifact.from(artifact);
|
const artifactObject = Artifact.from(artifact);
|
||||||
artifactObject._isRemote = !!this._browserContext._browser && !!this._browserContext._browser._remoteType;
|
|
||||||
this.emit(Events.Page.Download, new Download(this, url, suggestedFilename, artifactObject));
|
this.emit(Events.Page.Download, new Download(this, url, suggestedFilename, artifactObject));
|
||||||
});
|
});
|
||||||
this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple)));
|
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
|
||||||
|
|
||||||
private _forceVideo(): Video {
|
private _forceVideo(): Video {
|
||||||
if (!this._video)
|
if (!this._video)
|
||||||
this._video = new Video(this);
|
this._video = new Video(this, this._connection);
|
||||||
return this._video;
|
return this._video;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ export class Tracing implements api.Tracing {
|
||||||
if (!result.artifact)
|
if (!result.artifact)
|
||||||
return;
|
return;
|
||||||
const artifact = Artifact.from(result.artifact);
|
const artifact = Artifact.from(result.artifact);
|
||||||
if (this._context._browser?._remoteType)
|
|
||||||
artifact._isRemote = true;
|
|
||||||
await artifact.saveAs(path!);
|
await artifact.saveAs(path!);
|
||||||
await artifact.delete();
|
await artifact.delete();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,15 @@
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import { Artifact } from './artifact';
|
import { Artifact } from './artifact';
|
||||||
|
import { Connection } from './connection';
|
||||||
|
|
||||||
export class Video implements api.Video {
|
export class Video implements api.Video {
|
||||||
private _artifact: Promise<Artifact | null> | null = null;
|
private _artifact: Promise<Artifact | null> | null = null;
|
||||||
private _artifactCallback = (artifact: Artifact) => {};
|
private _artifactCallback = (artifact: Artifact) => {};
|
||||||
private _isRemote = false;
|
private _isRemote = false;
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page, connection: Connection) {
|
||||||
const browser = page.context()._browser;
|
this._isRemote = connection.isRemote();
|
||||||
this._isRemote = !!browser && !!browser._remoteType;
|
|
||||||
this._artifact = Promise.race([
|
this._artifact = Promise.race([
|
||||||
new Promise<Artifact>(f => this._artifactCallback = f),
|
new Promise<Artifact>(f => this._artifactCallback = f),
|
||||||
page._closedOrCrashedPromise.then(() => null),
|
page._closedOrCrashedPromise.then(() => null),
|
||||||
|
|
@ -33,13 +33,12 @@ export class Video implements api.Video {
|
||||||
}
|
}
|
||||||
|
|
||||||
_artifactReady(artifact: Artifact) {
|
_artifactReady(artifact: Artifact) {
|
||||||
artifact._isRemote = this._isRemote;
|
|
||||||
this._artifactCallback(artifact);
|
this._artifactCallback(artifact);
|
||||||
}
|
}
|
||||||
|
|
||||||
async path(): Promise<string> {
|
async path(): Promise<string> {
|
||||||
if (this._isRemote)
|
if (this._isRemote)
|
||||||
throw new Error(`Path is not available when using browserType.connect(). Use saveAs() to save a local copy.`);
|
throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
|
||||||
const artifact = await this._artifact;
|
const artifact = await this._artifact;
|
||||||
if (!artifact)
|
if (!artifact)
|
||||||
throw new Error('Page did not produce any video frames');
|
throw new Error('Page did not produce any video frames');
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export class PlaywrightClient {
|
||||||
static async connect(options: PlaywrightClientConnectOptions): Promise<PlaywrightClient> {
|
static async connect(options: PlaywrightClientConnectOptions): Promise<PlaywrightClient> {
|
||||||
const { wsEndpoint, timeout = 30000 } = options;
|
const { wsEndpoint, timeout = 30000 } = options;
|
||||||
const connection = new Connection();
|
const connection = new Connection();
|
||||||
|
connection.markAsRemote();
|
||||||
const ws = new WebSocket(wsEndpoint);
|
const ws = new WebSocket(wsEndpoint);
|
||||||
const waitForNextTask = makeWaitForNextTask();
|
const waitForNextTask = makeWaitForNextTask();
|
||||||
connection.onmessage = message => {
|
connection.onmessage = message => {
|
||||||
|
|
|
||||||
|
|
@ -389,7 +389,7 @@ test('should saveAs videos from remote browser', async ({ browserType, startRemo
|
||||||
await page.video().saveAs(savedAsPath);
|
await page.video().saveAs(savedAsPath);
|
||||||
expect(fs.existsSync(savedAsPath)).toBeTruthy();
|
expect(fs.existsSync(savedAsPath)).toBeTruthy();
|
||||||
const error = await page.video().path().catch(e => e);
|
const error = await page.video().path().catch(e => e);
|
||||||
expect(error.message).toContain('Path is not available when using browserType.connect(). Use saveAs() to save a local copy.');
|
expect(error.message).toContain('Path is not available when connecting remotely. Use saveAs() to save a local copy.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to connect 20 times to a single server without warnings', async ({ browserType, startRemoteServer }) => {
|
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 },
|
||||||
expect(fs.existsSync(nestedPath)).toBeTruthy();
|
expect(fs.existsSync(nestedPath)).toBeTruthy();
|
||||||
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
|
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
|
||||||
const error = await download.path().catch(e => e);
|
const error = await download.path().catch(e => e);
|
||||||
expect(error.message).toContain('Path is not available when using browserType.connect(). Use saveAs() to save a local copy.');
|
expect(error.message).toContain('Path is not available when connecting remotely. Use saveAs() to save a local copy.');
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,9 @@ it('should support offline option', async ({ server, launchPersistent }) => {
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support acceptDownloads option', async ({ server, launchPersistent }) => {
|
it('should support acceptDownloads option', async ({ server, launchPersistent, mode }) => {
|
||||||
|
it.skip(mode === 'service', 'download.path() is not avaialble in remote mode');
|
||||||
|
|
||||||
const { page } = await launchPersistent({ acceptDownloads: true });
|
const { page } = await launchPersistent({ acceptDownloads: true });
|
||||||
server.setRoute('/download', (req, res) => {
|
server.setRoute('/download', (req, res) => {
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import crypto from 'crypto';
|
||||||
import type { Download } from 'playwright-core';
|
import type { Download } from 'playwright-core';
|
||||||
|
|
||||||
it.describe('download event', () => {
|
it.describe('download event', () => {
|
||||||
|
it.skip(({ mode }) => mode === 'service', 'download.path() is not available in remote mode');
|
||||||
|
|
||||||
it.beforeEach(async ({ server }) => {
|
it.beforeEach(async ({ server }) => {
|
||||||
server.setRoute('/download', (req, res) => {
|
server.setRoute('/download', (req, res) => {
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
|
|
@ -156,20 +158,6 @@ it.describe('download event', () => {
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save to user-specified path', async ({ browser, server }, testInfo) => {
|
|
||||||
const page = await browser.newPage({ acceptDownloads: true });
|
|
||||||
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
|
|
||||||
const [ download ] = await Promise.all([
|
|
||||||
page.waitForEvent('download'),
|
|
||||||
page.click('a')
|
|
||||||
]);
|
|
||||||
const userPath = testInfo.outputPath('download.txt');
|
|
||||||
await download.saveAs(userPath);
|
|
||||||
expect(fs.existsSync(userPath)).toBeTruthy();
|
|
||||||
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
|
|
||||||
await page.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should save to user-specified path without updating original path', async ({ browser, server }, testInfo) => {
|
it('should save to user-specified path without updating original path', async ({ browser, server }, testInfo) => {
|
||||||
const page = await browser.newPage({ acceptDownloads: true });
|
const page = await browser.newPage({ acceptDownloads: true });
|
||||||
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
|
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
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should save to user-specified path', async ({ browser, server, mode }, testInfo) => {
|
||||||
|
server.setRoute('/download', (req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment');
|
||||||
|
res.end(`Hello world`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = await browser.newPage({ acceptDownloads: true });
|
||||||
|
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
|
||||||
|
const [ download ] = await Promise.all([
|
||||||
|
page.waitForEvent('download'),
|
||||||
|
page.click('a')
|
||||||
|
]);
|
||||||
|
if (mode === 'service') {
|
||||||
|
const error = await download.path().catch(e => e);
|
||||||
|
expect(error.message).toContain('Path is not available when connecting remotely. Use saveAs() to save a local copy.');
|
||||||
|
}
|
||||||
|
const userPath = testInfo.outputPath('download.txt');
|
||||||
|
await download.saveAs(userPath);
|
||||||
|
expect(fs.existsSync(userPath)).toBeTruthy();
|
||||||
|
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
async function assertDownloadToPDF(download: Download, filePath: string) {
|
async function assertDownloadToPDF(download: Download, filePath: string) {
|
||||||
expect(download.suggestedFilename()).toBe(path.basename(filePath));
|
expect(download.suggestedFilename()).toBe(path.basename(filePath));
|
||||||
const stream = await download.createReadStream();
|
const stream = await download.createReadStream();
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
it.describe('downloads path', () => {
|
it.describe('downloads path', () => {
|
||||||
|
it.skip(({ mode }) => mode === 'service', 'download.path() is not available in remote mode');
|
||||||
|
|
||||||
it.beforeEach(async ({ server }) => {
|
it.beforeEach(async ({ server }) => {
|
||||||
server.setRoute('/download', (req, res) => {
|
server.setRoute('/download', (req, res) => {
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ function expectRedFrames(videoFile: string, size: { width: number, height: numbe
|
||||||
|
|
||||||
it.describe('screencast', () => {
|
it.describe('screencast', () => {
|
||||||
it.slow();
|
it.slow();
|
||||||
|
it.skip(({ mode }) => mode === 'service', 'video.path() is not avaialble in remote mode');
|
||||||
|
|
||||||
it('videoSize should require videosPath', async ({ browser }) => {
|
it('videoSize should require videosPath', async ({ browser }) => {
|
||||||
const error = await browser.newContext({ videoSize: { width: 100, height: 100 } }).catch(e => e);
|
const error = await browser.newContext({ videoSize: { width: 100, height: 100 } }).catch(e => e);
|
||||||
|
|
@ -218,26 +219,6 @@ it.describe('screencast', () => {
|
||||||
expect(fs.existsSync(path)).toBeTruthy();
|
expect(fs.existsSync(path)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should saveAs video', async ({ browser }, testInfo) => {
|
|
||||||
const videosPath = testInfo.outputPath('');
|
|
||||||
const size = { width: 320, height: 240 };
|
|
||||||
const context = await browser.newContext({
|
|
||||||
recordVideo: {
|
|
||||||
dir: videosPath,
|
|
||||||
size
|
|
||||||
},
|
|
||||||
viewport: size,
|
|
||||||
});
|
|
||||||
const page = await context.newPage();
|
|
||||||
await page.evaluate(() => document.body.style.backgroundColor = 'red');
|
|
||||||
await page.waitForTimeout(1000);
|
|
||||||
await context.close();
|
|
||||||
|
|
||||||
const saveAsPath = testInfo.outputPath('my-video.webm');
|
|
||||||
await page.video().saveAs(saveAsPath);
|
|
||||||
expect(fs.existsSync(saveAsPath)).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('saveAs should throw when no video frames', async ({ browser, browserName }, testInfo) => {
|
it('saveAs should throw when no video frames', async ({ browser, browserName }, testInfo) => {
|
||||||
const videosPath = testInfo.outputPath('');
|
const videosPath = testInfo.outputPath('');
|
||||||
const size = { width: 320, height: 240 };
|
const size = { width: 320, height: 240 };
|
||||||
|
|
@ -668,5 +649,26 @@ it.describe('screencast', () => {
|
||||||
const files = fs.readdirSync(videoDir);
|
const files = fs.readdirSync(videoDir);
|
||||||
expect(files.length).toBe(1);
|
expect(files.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should saveAs video', async ({ browser }, testInfo) => {
|
||||||
|
it.slow();
|
||||||
|
|
||||||
|
const videosPath = testInfo.outputPath('');
|
||||||
|
const size = { width: 320, height: 240 };
|
||||||
|
const context = await browser.newContext({
|
||||||
|
recordVideo: {
|
||||||
|
dir: videosPath,
|
||||||
|
size
|
||||||
|
},
|
||||||
|
viewport: size,
|
||||||
|
});
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.evaluate(() => document.body.style.backgroundColor = 'red');
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await context.close();
|
||||||
|
|
||||||
|
const saveAsPath = testInfo.outputPath('my-video.webm');
|
||||||
|
await page.video().saveAs(saveAsPath);
|
||||||
|
expect(fs.existsSync(saveAsPath)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue