feat(electron): expose app process(), detach on exit (#13280)

This commit is contained in:
Pavel Feldman 2022-04-04 10:50:46 -08:00 committed by GitHub
parent 3636d8548f
commit 8232497c88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 46 additions and 3 deletions

View file

@ -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
- returns: <[any]>

View file

@ -135,6 +135,10 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
}
}
_toImpl(): any {
return this._connection.toImpl?.(this);
}
private toJSON() {
// Jest's expect library tries to print objects sometimes.
// RPC objects can contain links to lots of other objects,

View file

@ -67,6 +67,8 @@ export class Connection extends EventEmitter {
private _rootObject: Root;
private _closedErrorMessage: string | undefined;
private _isRemote = false;
// Some connections allow resolving in-process dispatchers.
toImpl: ((client: ChannelOwner) => any) | undefined;
constructor() {
super();

View file

@ -15,6 +15,7 @@
*/
import type { BrowserWindow } from 'electron';
import * as childProcess from 'child_process';
import * as structs from '../../types/structs';
import * as api from '../../types/types';
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));
}
process(): childProcess.ChildProcess {
return this._toImpl().process();
}
_onPage(page: Page) {
this._windows.add(page);
this.emit(Events.ElectronApplication.Window, page);

View file

@ -44,6 +44,7 @@ export function createInProcessPlaywright(): PlaywrightAPI {
dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.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;
}

View file

@ -52,9 +52,11 @@ export class ElectronApplication extends SdkObject {
private _nodeExecutionContext: js.ExecutionContext | undefined;
_nodeElectronHandlePromise: Promise<js.JSHandle<any>>;
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');
this._process = process;
this._browserContext = browser._defaultContext as CRBrowserContext;
this._browserContext.on(BrowserContext.Events.Close, () => {
// Emit application closed after context closed.
@ -77,6 +79,10 @@ export class ElectronApplication extends SdkObject {
this._nodeSession.send('Runtime.enable', {}).catch(e => {});
}
process(): childProcess.ChildProcess {
return this._process;
}
context(): BrowserContext {
return this._browserContext;
}
@ -166,6 +172,10 @@ export class Electron extends SdkObject {
const nodeTransport = await WebSocketTransport.connect(progress, nodeMatch[1]);
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([
waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/),
waitForXserverError,
@ -196,7 +206,7 @@ export class Electron extends SdkObject {
};
validateBrowserContextOptions(contextOptions, browserOptions);
const browser = await CRBrowser.connect(chromeTransport, browserOptions);
app = new ElectronApplication(this, browser, nodeConnection);
app = new ElectronApplication(this, browser, nodeConnection, launchedProcess);
return app;
}, TimeoutSettings.timeout(options));
}

View file

@ -10921,6 +10921,11 @@ export interface ElectronApplication {
*/
firstWindow(): Promise<Page>;
/**
* Returns the main process for this Electron Application.
*/
process(): ChildProcess;
/**
* This event is issued when the application closes.
*/

View file

@ -180,3 +180,14 @@ test('should record video', async ({ playwright }, testInfo) => {
const videoPath = await page.video().path();
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;
});