feat(electron): expose app process(), detach on exit (#13280)
This commit is contained in:
parent
3636d8548f
commit
8232497c88
|
|
@ -112,6 +112,11 @@ Typically your script will start with:
|
||||||
// ...
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## method: ElectronApplication.process
|
||||||
|
- returns: <[ChildProcess]>
|
||||||
|
|
||||||
|
Returns the main process for this Electron Application.
|
||||||
|
|
||||||
## async method: ElectronApplication.waitForEvent
|
## async method: ElectronApplication.waitForEvent
|
||||||
- returns: <[any]>
|
- returns: <[any]>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,10 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_toImpl(): any {
|
||||||
|
return this._connection.toImpl?.(this);
|
||||||
|
}
|
||||||
|
|
||||||
private toJSON() {
|
private toJSON() {
|
||||||
// Jest's expect library tries to print objects sometimes.
|
// Jest's expect library tries to print objects sometimes.
|
||||||
// RPC objects can contain links to lots of other objects,
|
// RPC objects can contain links to lots of other objects,
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ export class Connection extends EventEmitter {
|
||||||
private _rootObject: Root;
|
private _rootObject: Root;
|
||||||
private _closedErrorMessage: string | undefined;
|
private _closedErrorMessage: string | undefined;
|
||||||
private _isRemote = false;
|
private _isRemote = false;
|
||||||
|
// Some connections allow resolving in-process dispatchers.
|
||||||
|
toImpl: ((client: ChannelOwner) => any) | undefined;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BrowserWindow } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
|
import * as childProcess from 'child_process';
|
||||||
import * as structs from '../../types/structs';
|
import * as structs from '../../types/structs';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
|
|
@ -75,6 +76,10 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
||||||
this._channel.on('close', () => this.emit(Events.ElectronApplication.Close));
|
this._channel.on('close', () => this.emit(Events.ElectronApplication.Close));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process(): childProcess.ChildProcess {
|
||||||
|
return this._toImpl().process();
|
||||||
|
}
|
||||||
|
|
||||||
_onPage(page: Page) {
|
_onPage(page: Page) {
|
||||||
this._windows.add(page);
|
this._windows.add(page);
|
||||||
this.emit(Events.ElectronApplication.Window, page);
|
this.emit(Events.ElectronApplication.Window, page);
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export function createInProcessPlaywright(): PlaywrightAPI {
|
||||||
dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message));
|
dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message));
|
||||||
clientConnection.onmessage = message => setImmediate(() => dispatcherConnection.dispatch(message));
|
clientConnection.onmessage = message => setImmediate(() => dispatcherConnection.dispatch(message));
|
||||||
|
|
||||||
(playwrightAPI as any)._toImpl = (x: any) => dispatcherConnection._dispatchers.get(x._guid)!._object;
|
clientConnection.toImpl = (x: any) => dispatcherConnection._dispatchers.get(x._guid)!._object;
|
||||||
|
(playwrightAPI as any)._toImpl = clientConnection.toImpl;
|
||||||
return playwrightAPI;
|
return playwrightAPI;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,11 @@ export class ElectronApplication extends SdkObject {
|
||||||
private _nodeExecutionContext: js.ExecutionContext | undefined;
|
private _nodeExecutionContext: js.ExecutionContext | undefined;
|
||||||
_nodeElectronHandlePromise: Promise<js.JSHandle<any>>;
|
_nodeElectronHandlePromise: Promise<js.JSHandle<any>>;
|
||||||
readonly _timeoutSettings = new TimeoutSettings();
|
readonly _timeoutSettings = new TimeoutSettings();
|
||||||
|
private _process: childProcess.ChildProcess;
|
||||||
|
|
||||||
constructor(parent: SdkObject, browser: CRBrowser, nodeConnection: CRConnection) {
|
constructor(parent: SdkObject, browser: CRBrowser, nodeConnection: CRConnection, process: childProcess.ChildProcess) {
|
||||||
super(parent, 'electron-app');
|
super(parent, 'electron-app');
|
||||||
|
this._process = process;
|
||||||
this._browserContext = browser._defaultContext as CRBrowserContext;
|
this._browserContext = browser._defaultContext as CRBrowserContext;
|
||||||
this._browserContext.on(BrowserContext.Events.Close, () => {
|
this._browserContext.on(BrowserContext.Events.Close, () => {
|
||||||
// Emit application closed after context closed.
|
// Emit application closed after context closed.
|
||||||
|
|
@ -77,6 +79,10 @@ export class ElectronApplication extends SdkObject {
|
||||||
this._nodeSession.send('Runtime.enable', {}).catch(e => {});
|
this._nodeSession.send('Runtime.enable', {}).catch(e => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process(): childProcess.ChildProcess {
|
||||||
|
return this._process;
|
||||||
|
}
|
||||||
|
|
||||||
context(): BrowserContext {
|
context(): BrowserContext {
|
||||||
return this._browserContext;
|
return this._browserContext;
|
||||||
}
|
}
|
||||||
|
|
@ -166,6 +172,10 @@ export class Electron extends SdkObject {
|
||||||
const nodeTransport = await WebSocketTransport.connect(progress, nodeMatch[1]);
|
const nodeTransport = await WebSocketTransport.connect(progress, nodeMatch[1]);
|
||||||
const nodeConnection = new CRConnection(nodeTransport, helper.debugProtocolLogger(), browserLogsCollector);
|
const nodeConnection = new CRConnection(nodeTransport, helper.debugProtocolLogger(), browserLogsCollector);
|
||||||
|
|
||||||
|
// Immediately release exiting process under debug.
|
||||||
|
waitForLine(progress, launchedProcess, /Waiting for the debugger to disconnect\.\.\./).then(() => {
|
||||||
|
nodeTransport.close();
|
||||||
|
}).catch(() => {});
|
||||||
const chromeMatch = await Promise.race([
|
const chromeMatch = await Promise.race([
|
||||||
waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/),
|
waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/),
|
||||||
waitForXserverError,
|
waitForXserverError,
|
||||||
|
|
@ -196,7 +206,7 @@ export class Electron extends SdkObject {
|
||||||
};
|
};
|
||||||
validateBrowserContextOptions(contextOptions, browserOptions);
|
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, launchedProcess);
|
||||||
return app;
|
return app;
|
||||||
}, TimeoutSettings.timeout(options));
|
}, TimeoutSettings.timeout(options));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
packages/playwright-core/types/types.d.ts
vendored
5
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -10921,6 +10921,11 @@ export interface ElectronApplication {
|
||||||
*/
|
*/
|
||||||
firstWindow(): Promise<Page>;
|
firstWindow(): Promise<Page>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the main process for this Electron Application.
|
||||||
|
*/
|
||||||
|
process(): ChildProcess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This event is issued when the application closes.
|
* This event is issued when the application closes.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -180,3 +180,14 @@ test('should record video', async ({ playwright }, testInfo) => {
|
||||||
const videoPath = await page.video().path();
|
const videoPath = await page.video().path();
|
||||||
expect(fs.statSync(videoPath).size).toBeGreaterThan(0);
|
expect(fs.statSync(videoPath).size).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should detach debugger on app-initiated exit', async ({ playwright }) => {
|
||||||
|
const electronApp = await playwright._electron.launch({
|
||||||
|
args: [path.join(__dirname, 'electron-app.js')],
|
||||||
|
});
|
||||||
|
const closePromise = new Promise(f => electronApp.process().on('close', f));
|
||||||
|
await electronApp.evaluate(({ app }) => {
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
await closePromise;
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue