fix(electron): make recordVideo work (#10810)
This commit is contained in:
parent
a4e68dbac1
commit
4996e184bf
|
|
@ -51,12 +51,14 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
|
||||||
extraHTTPHeaders: options.extraHTTPHeaders && headersObjectToArray(options.extraHTTPHeaders),
|
extraHTTPHeaders: options.extraHTTPHeaders && headersObjectToArray(options.extraHTTPHeaders),
|
||||||
env: envObjectToArray(options.env ? options.env : process.env),
|
env: envObjectToArray(options.env ? options.env : process.env),
|
||||||
};
|
};
|
||||||
return ElectronApplication.from((await this._channel.launch(params)).electronApplication);
|
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
|
||||||
|
app._context._options = params;
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElectronApplication extends ChannelOwner<channels.ElectronApplicationChannel> implements api.ElectronApplication {
|
export class ElectronApplication extends ChannelOwner<channels.ElectronApplicationChannel> implements api.ElectronApplication {
|
||||||
private _context: BrowserContext;
|
readonly _context: BrowserContext;
|
||||||
private _windows = new Set<Page>();
|
private _windows = new Set<Page>();
|
||||||
private _timeoutSettings = new TimeoutSettings();
|
private _timeoutSettings = new TimeoutSettings();
|
||||||
|
|
||||||
|
|
@ -91,7 +93,7 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
||||||
}
|
}
|
||||||
|
|
||||||
context(): BrowserContext {
|
context(): BrowserContext {
|
||||||
return this._context! as BrowserContext;
|
return this._context;
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
readonly _harRecorder: HarRecorder | undefined;
|
readonly _harRecorder: HarRecorder | undefined;
|
||||||
readonly tracing: Tracing;
|
readonly tracing: Tracing;
|
||||||
readonly fetchRequest: BrowserContextAPIRequestContext;
|
readonly fetchRequest: BrowserContextAPIRequestContext;
|
||||||
|
private _customCloseHandler?: () => Promise<any>;
|
||||||
|
|
||||||
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
||||||
super(browser, 'browser-context');
|
super(browser, 'browser-context');
|
||||||
|
|
@ -275,6 +276,10 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
await Promise.all(Array.from(this._downloads).map(download => download.artifact.deleteOnContextClose()));
|
await Promise.all(Array.from(this._downloads).map(download => download.artifact.deleteOnContextClose()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCustomCloseHandler(handler: (() => Promise<any>) | undefined) {
|
||||||
|
this._customCloseHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
async close(metadata: CallMetadata) {
|
async close(metadata: CallMetadata) {
|
||||||
if (this._closedStatus === 'open') {
|
if (this._closedStatus === 'open') {
|
||||||
this.emit(BrowserContext.Events.BeforeClose);
|
this.emit(BrowserContext.Events.BeforeClose);
|
||||||
|
|
@ -291,7 +296,9 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
promises.push(artifact.finishedPromise());
|
promises.push(artifact.finishedPromise());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._isPersistentContext) {
|
if (this._customCloseHandler) {
|
||||||
|
await this._customCloseHandler();
|
||||||
|
} else if (this._isPersistentContext) {
|
||||||
// Close all the pages instead of the context,
|
// Close all the pages instead of the context,
|
||||||
// because we cannot close the default context.
|
// because we cannot close the default context.
|
||||||
await Promise.all(this.pages().map(page => page.close(metadata)));
|
await Promise.all(this.pages().map(page => page.close(metadata)));
|
||||||
|
|
@ -305,6 +312,10 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
promises.push(this._deleteAllDownloads());
|
promises.push(this._deleteAllDownloads());
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
// Custom handler should trigger didCloseInternal itself.
|
||||||
|
if (this._customCloseHandler)
|
||||||
|
return;
|
||||||
|
|
||||||
// Persistent context should also close the browser.
|
// Persistent context should also close the browser.
|
||||||
if (this._isPersistentContext)
|
if (this._isPersistentContext)
|
||||||
await this._browser.close();
|
await this._browser.close();
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import { Page } from '../page';
|
||||||
import { TimeoutSettings } from '../../utils/timeoutSettings';
|
import { TimeoutSettings } from '../../utils/timeoutSettings';
|
||||||
import { WebSocketTransport } from '../transport';
|
import { WebSocketTransport } from '../transport';
|
||||||
import { launchProcess, envArrayToObject } from '../../utils/processLauncher';
|
import { launchProcess, envArrayToObject } from '../../utils/processLauncher';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext, validateBrowserContextOptions } from '../browserContext';
|
||||||
import type { BrowserWindow } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
import { Progress, ProgressController } from '../progress';
|
import { Progress, ProgressController } from '../progress';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
|
|
@ -37,6 +37,7 @@ import * as readline from 'readline';
|
||||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||||
import { internalCallMetadata, SdkObject } from '../instrumentation';
|
import { internalCallMetadata, SdkObject } from '../instrumentation';
|
||||||
import * as channels from '../../protocol/channels';
|
import * as channels from '../../protocol/channels';
|
||||||
|
import { BrowserContextOptions } from '../types';
|
||||||
|
|
||||||
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
|
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
|
||||||
|
|
||||||
|
|
@ -69,6 +70,10 @@ export class ElectronApplication extends SdkObject {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
this._browserContext.setCustomCloseHandler(async () => {
|
||||||
|
const electronHandle = await this._nodeElectronHandlePromise;
|
||||||
|
await electronHandle.evaluate(({ app }) => app.quit());
|
||||||
|
});
|
||||||
this._nodeSession.send('Runtime.enable', {}).catch(e => {});
|
this._nodeSession.send('Runtime.enable', {}).catch(e => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,8 +84,7 @@ export class ElectronApplication extends SdkObject {
|
||||||
async close() {
|
async close() {
|
||||||
const progressController = new ProgressController(internalCallMetadata(), this);
|
const progressController = new ProgressController(internalCallMetadata(), this);
|
||||||
const closed = progressController.run(progress => helper.waitForEvent(progress, this, ElectronApplication.Events.Close).promise, this._timeoutSettings.timeout({}));
|
const closed = progressController.run(progress => helper.waitForEvent(progress, this, ElectronApplication.Events.Close).promise, this._timeoutSettings.timeout({}));
|
||||||
const electronHandle = await this._nodeElectronHandlePromise;
|
await this._browserContext.close(internalCallMetadata());
|
||||||
await electronHandle.evaluate(({ app }) => app.quit());
|
|
||||||
this._nodeConnection.close();
|
this._nodeConnection.close();
|
||||||
await closed;
|
await closed;
|
||||||
}
|
}
|
||||||
|
|
@ -168,26 +172,16 @@ export class Electron extends SdkObject {
|
||||||
close: gracefullyClose,
|
close: gracefullyClose,
|
||||||
kill
|
kill
|
||||||
};
|
};
|
||||||
|
const contextOptions: BrowserContextOptions = {
|
||||||
|
...options,
|
||||||
|
noDefaultViewport: true,
|
||||||
|
};
|
||||||
const browserOptions: BrowserOptions = {
|
const browserOptions: BrowserOptions = {
|
||||||
...this._playwrightOptions,
|
...this._playwrightOptions,
|
||||||
name: 'electron',
|
name: 'electron',
|
||||||
isChromium: true,
|
isChromium: true,
|
||||||
headful: true,
|
headful: true,
|
||||||
persistent: {
|
persistent: contextOptions,
|
||||||
noDefaultViewport: true,
|
|
||||||
acceptDownloads: options.acceptDownloads,
|
|
||||||
bypassCSP: options.bypassCSP,
|
|
||||||
colorScheme: options.colorScheme,
|
|
||||||
extraHTTPHeaders: options.extraHTTPHeaders,
|
|
||||||
geolocation: options.geolocation,
|
|
||||||
httpCredentials: options.httpCredentials,
|
|
||||||
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
|
|
||||||
locale: options.locale,
|
|
||||||
offline: options.offline,
|
|
||||||
recordHar: options.recordHar,
|
|
||||||
recordVideo: options.recordVideo,
|
|
||||||
timezoneId: options.timezoneId,
|
|
||||||
},
|
|
||||||
browserProcess,
|
browserProcess,
|
||||||
protocolLogger: helper.debugProtocolLogger(),
|
protocolLogger: helper.debugProtocolLogger(),
|
||||||
browserLogsCollector,
|
browserLogsCollector,
|
||||||
|
|
@ -195,6 +189,7 @@ export class Electron extends SdkObject {
|
||||||
downloadsPath: artifactsDir,
|
downloadsPath: artifactsDir,
|
||||||
tracesDir: artifactsDir,
|
tracesDir: artifactsDir,
|
||||||
};
|
};
|
||||||
|
validateBrowserContextOptions(contextOptions, browserOptions);
|
||||||
const browser = await CRBrowser.connect(chromeTransport, browserOptions);
|
const browser = await CRBrowser.connect(chromeTransport, browserOptions);
|
||||||
app = new ElectronApplication(this, browser, nodeConnection);
|
app = new ElectronApplication(this, browser, nodeConnection);
|
||||||
return app;
|
return app;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import type { BrowserWindow } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
import { electronTest as test, expect } from './electronTest';
|
import { electronTest as test, expect } from './electronTest';
|
||||||
|
|
||||||
test('should fire close event', async ({ playwright }) => {
|
test('should fire close event', async ({ playwright }) => {
|
||||||
|
|
@ -166,3 +167,16 @@ test('should return same browser window for browser view pages', async ({ playwr
|
||||||
expect(firstWindowId).toEqual(secondWindowId);
|
expect(firstWindowId).toEqual(secondWindowId);
|
||||||
await app.close();
|
await app.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should record video', async ({ playwright }, testInfo) => {
|
||||||
|
const app = await playwright._electron.launch({
|
||||||
|
args: [path.join(__dirname, 'electron-window-app.js')],
|
||||||
|
recordVideo: { dir: testInfo.outputPath('video') }
|
||||||
|
});
|
||||||
|
const page = await app.firstWindow();
|
||||||
|
await page.setContent(`<style>body {background:red}</style>`);
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await app.close();
|
||||||
|
const videoPath = await page.video().path();
|
||||||
|
expect(fs.statSync(videoPath).size).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,9 @@ export const electronTest = baseTest.extend<ElectronTestFixtures, PageWorkerFixt
|
||||||
await run(async () => {
|
await run(async () => {
|
||||||
const [ window ] = await Promise.all([
|
const [ window ] = await Promise.all([
|
||||||
electronApp.waitForEvent('window'),
|
electronApp.waitForEvent('window'),
|
||||||
electronApp.evaluate(electron => {
|
electronApp.evaluate(async electron => {
|
||||||
|
// Avoid "Error: Cannot create BrowserWindow before app is ready".
|
||||||
|
await electron.app.whenReady();
|
||||||
const window = new electron.BrowserWindow({
|
const window = new electron.BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue