From e3b9d5ae3659ce95a218f07d16ce003b5dcf49d3 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 12 May 2026 15:36:21 +0100 Subject: [PATCH] chore: simplify daemon/client handshake --- .../src/tools/cli-client/program.ts | 33 +++++-------------- .../src/tools/cli-client/session.ts | 8 +---- .../src/tools/cli-daemon/program.ts | 7 ++-- .../src/tools/dashboard/dashboardApp.ts | 21 +++++------- 4 files changed, 19 insertions(+), 50 deletions(-) diff --git a/packages/playwright-core/src/tools/cli-client/program.ts b/packages/playwright-core/src/tools/cli-client/program.ts index c01d0aaf70337..8c4de36b1bbd2 100644 --- a/packages/playwright-core/src/tools/cli-client/program.ts +++ b/packages/playwright-core/src/tools/cli-client/program.ts @@ -235,41 +235,24 @@ export async function program(options?: { embedderVersion?: string}) { await new Promise(resolve => child.on('exit', () => resolve())); return; } + const timer = setTimeout(() => child.stdin!.destroy(), 60_000); + child.unref(); try { await new Promise((resolve, reject) => { let outLog = ''; - const settle = (err?: Error) => { - clearTimeout(timer); - child.stdout!.removeAllListeners(); - child.removeAllListeners('exit'); - if (err) - reject(err); - else - resolve(); - }; - const timer = setTimeout(() => settle(new Error('Dashboard daemon did not spin up within 60s')), 60_000); child.stdout!.on('data', data => { outLog += data.toString(); - if (!outLog.includes('')) - return; - if (outLog.match(/### Success\n[\s\S]*/)) - settle(); - else - settle(new Error(outLog.trim())); + if (outLog.includes('Dashboard is running')) + resolve(); }); - child.stdout!.once('error', err => settle(err)); - child.once('exit', (code, signal) => settle(new Error(`Dashboard daemon exited (code=${code}, signal=${signal}) before signaling READY${outLog ? '\n' + outLog : ''}`))); + child.once('exit', (code, signal) => reject(new Error(`Dashboard daemon exited (code=${code}, signal=${signal}) before signaling READY${outLog ? '\n' + outLog : ''}`))); }); - } catch (err) { + } finally { + clearTimeout(timer); + child.removeAllListeners('exit'); child.stdin!.destroy(); child.stdout!.destroy(); - if (child.exitCode === null && child.signalCode === null) - await new Promise(resolve => child.once('exit', () => resolve())); - throw err; } - child.stdin!.destroy(); - child.stdout!.destroy(); - child.unref(); output.show(sessionName, child.pid); return; } diff --git a/packages/playwright-core/src/tools/cli-client/session.ts b/packages/playwright-core/src/tools/cli-client/session.ts index 86329934b2404..3547230b56b35 100644 --- a/packages/playwright-core/src/tools/cli-client/session.ts +++ b/packages/playwright-core/src/tools/cli-client/session.ts @@ -166,14 +166,8 @@ export class Session { await new Promise((resolve, reject) => { child.stdout!.on('data', data => { outLog += data.toString(); - if (!outLog.includes('')) - return; - if (outLog.match(/### Success\nDaemon listening on (.*)\n/)) { + if (outLog.includes('Daemon listening on')) resolve(); - return; - } - const errLogContent = fs.readFileSync(errLog, 'utf-8'); - rejectWithPid(reject, outLog.trim() + (errLogContent ? '\n' + errLogContent : '')); }); child.on('close', code => { if (!signalled) { diff --git a/packages/playwright-core/src/tools/cli-daemon/program.ts b/packages/playwright-core/src/tools/cli-daemon/program.ts index 56ef9351690e8..1d8877330b8ad 100644 --- a/packages/playwright-core/src/tools/cli-daemon/program.ts +++ b/packages/playwright-core/src/tools/cli-daemon/program.ts @@ -67,12 +67,9 @@ export function decorateProgram(program: Command) { throw new Error('Error: unable to connect to a browser that does not have any contexts'); const persistent = options.persistent || options.profile || mcpConfig.browser.userDataDir ? true : undefined; const socketPath = await startCliDaemonServer(sessionName, browserContext, browserInfo, mcpConfig, clientInfo, mcpClientInfo, { persistent, exitOnClose: true, ownership }); - console.log(`### Success\nDaemon listening on ${socketPath}`); - console.log(''); + console.log(`Daemon listening on ${socketPath}\n`); } catch (error) { - const message = process.env.PWDEBUGIMPL ? (error as Error).stack || (error as Error).message : (error as Error).message; - console.log(`### Error\n${message}`); - console.log(''); + console.log(error); gracefullyProcessExitDoNotHang(1); } }); diff --git a/packages/playwright-core/src/tools/dashboard/dashboardApp.ts b/packages/playwright-core/src/tools/dashboard/dashboardApp.ts index ffa1a183e92b6..26afa59aae034 100644 --- a/packages/playwright-core/src/tools/dashboard/dashboardApp.ts +++ b/packages/playwright-core/src/tools/dashboard/dashboardApp.ts @@ -308,6 +308,8 @@ export async function openDashboardApp() { const { url } = await startDashboardServer(new RegistrySessionProvider(), options); // eslint-disable-next-line no-console console.log(`Listening on ${url}`); + // eslint-disable-next-line no-restricted-properties + await new Promise(f => process.stdout.write('', f)); // Make sure stdout is flushed. selfDestructOnParentGone(); return; } @@ -318,14 +320,12 @@ export async function openDashboardApp() { try { server = await acquireSingleton(options); } catch { - // Another daemon is already running; acquireSingleton forwarded our - // options to it. Signal success so the parent doesn't treat our clean - // exit as a startup failure. + // Another daemon is already running, signal success. stopSelfDestruct(); // eslint-disable-next-line no-console - console.log('### Success\nDashboard already running'); - // eslint-disable-next-line no-console - console.log(''); + console.log('Dashboard is running'); + // eslint-disable-next-line no-restricted-properties + await new Promise(f => process.stdout.write('', f)); // Make sure stdout is flushed. return; } process.on('exit', () => server.close()); @@ -333,15 +333,10 @@ export async function openDashboardApp() { await startApp(server, options); stopSelfDestruct(); // eslint-disable-next-line no-console - console.log('### Success\nDashboard ready'); - // eslint-disable-next-line no-console - console.log(''); + console.log('Dashboard is running'); } catch (error) { - const message = (error as Error).stack || (error as Error).message; - // eslint-disable-next-line no-console - console.log(`### Error\n${message}`); // eslint-disable-next-line no-console - console.log(''); + console.log(error); gracefullyProcessExitDoNotHang(1); } }