import { _electron as electron, ElectronApplication, Page } from '@playwright/test'; import * as path from 'path'; /** * ElectronTestHarness - Manages Electron app lifecycle for smoke tests * * Responsibilities: * - Launch actual compiled Electron app * - Wait for renderer window to open * - Provide access to main process and renderer page * - Clean shutdown */ export class ElectronTestHarness { private app: ElectronApplication | null = null; private mainWindow: Page | null = null; /** * Launch Electron app and wait for main window * * @throws Error if app fails to launch or window doesn't open */ async launch(): Promise { // Path to the built Electron app entry point const electronEntryPath = path.join(__dirname, '../../../apps/companion/dist/main/main.cjs'); // Launch Electron app with the compiled entry file // Note: Playwright may have compatibility issues with certain Electron versions // regarding --remote-debugging-port flag const launchOptions: any = { args: [electronEntryPath], env: { ...Object.fromEntries(Object.entries(process.env).filter(([_, v]) => v !== undefined)), NODE_ENV: 'test', }, }; if (process.env.ELECTRON_EXECUTABLE_PATH) { launchOptions.executablePath = process.env.ELECTRON_EXECUTABLE_PATH; } this.app = await electron.launch(launchOptions); // Wait for first window (renderer process) this.mainWindow = await this.app.firstWindow({ timeout: 10_000, }); // Wait for React to render await this.mainWindow.waitForLoadState('domcontentloaded'); } /** * Get the main renderer window */ getMainWindow(): Page { if (!this.mainWindow) { throw new Error('Main window not available. Did you call launch()?'); } return this.mainWindow; } /** * Get the Electron app instance for IPC testing */ getApp(): ElectronApplication { if (!this.app) { throw new Error('Electron app not available. Did you call launch()?'); } return this.app; } /** * Clean shutdown of Electron app */ async close(): Promise { if (this.app) { await this.app.close(); this.app = null; this.mainWindow = null; } } }