import { test, expect, _electron as electron } from '@playwright/test'; import type { ElectronApplication, Page } from '@playwright/test'; import path from 'node:path'; // Regression gate for the Electron desktop shell (electron/main.ts + // electron/preload.ts). Launches the real skeleton - the same standalone // Next.js server artifact the Dockerfile produces, booted as a child // process by main.ts, with a real BrowserWindow on top - and asserts the // login screen renders with zero uncaught page errors. Every later step in // the Electron rollout (notification bridge, realtime sync, packaging) must // keep this green; run it before touching anything else. // // Requires `npm run build:standalone && npm run build:electron` to have run // first (see package.json's `electron:dev`/`test:electron` scripts, which // this suite assumes but does not itself trigger, matching how // playwright.config.ts's browser suite assumes `npm run build` for its own // prod-mode runs). const projectRoot = path.resolve(__dirname, '..'); test.describe('Electron desktop shell', () => { let electronApp: ElectronApplication; let window: Page; const pageErrors: Error[] = []; test.beforeAll(async () => { electronApp = await electron.launch({ args: [projectRoot], env: { ...process.env, // Bypass the first-run setup wizard (lib/setup/state.ts's // "bootstrap" state, which 302s everything to /setup) without // needing a reachable JMAP server just to prove the login screen // renders - any non-empty JMAP_SERVER_URL is enough to reach // "env-managed" state and serve the normal app shell. JMAP_SERVER_URL: 'https://stalwart.sandbox.vnc.de', SESSION_SECRET: 'electron-smoke-test-not-for-production', NODE_ENV: 'production', }, }); window = await electronApp.firstWindow(); window.on('pageerror', (error) => { pageErrors.push(error); }); await window.waitForLoadState('domcontentloaded'); }); test.afterAll(async () => { await electronApp?.close(); }); test('boots the standalone server and renders the login screen', async () => { // Same selectors as e2e/login.spec.ts's browser-based check - the // shell should render the identical login form, not a different view. await expect(window.locator('input[type="text"]')).toBeVisible({ timeout: 20000 }); await expect(window.locator('input[type="password"]')).toBeVisible(); }); test('produces zero uncaught page errors', () => { expect(pageErrors).toEqual([]); }); });