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
|
||||
- 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() {
|
||||
// Jest's expect library tries to print objects sometimes.
|
||||
// RPC objects can contain links to lots of other objects,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
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>;
|
||||
|
||||
/**
|
||||
* Returns the main process for this Electron Application.
|
||||
*/
|
||||
process(): ChildProcess;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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