diff --git a/docs/src/api/class-electronapplication.md b/docs/src/api/class-electronapplication.md index 2079ba10c8..c3df783c9a 100644 --- a/docs/src/api/class-electronapplication.md +++ b/docs/src/api/class-electronapplication.md @@ -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]> diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index 7aebb8e032..d0fc348641 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -135,6 +135,10 @@ export abstract class ChannelOwner any) | undefined; constructor() { super(); diff --git a/packages/playwright-core/src/client/electron.ts b/packages/playwright-core/src/client/electron.ts index 5321a9cf78..709aae9da6 100644 --- a/packages/playwright-core/src/client/electron.ts +++ b/packages/playwright-core/src/client/electron.ts @@ -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 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); diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index f5a50c1759..4ef876fb8d 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -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; } diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index 6b6b02de06..35d4c49ff9 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -52,9 +52,11 @@ export class ElectronApplication extends SdkObject { private _nodeExecutionContext: js.ExecutionContext | undefined; _nodeElectronHandlePromise: Promise>; 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)); } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 4530d7ff57..837b8e15c3 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -10921,6 +10921,11 @@ export interface ElectronApplication { */ firstWindow(): Promise; + /** + * Returns the main process for this Electron Application. + */ + process(): ChildProcess; + /** * This event is issued when the application closes. */ diff --git a/tests/electron/electron-app.spec.ts b/tests/electron/electron-app.spec.ts index 55300f5312..5f6ed031f6 100644 --- a/tests/electron/electron-app.spec.ts +++ b/tests/electron/electron-app.spec.ts @@ -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; +});