import { test, expect, _electron as electron } from '@playwright/test'; import type { ElectronApplication, Page } from '@playwright/test'; import path from 'node:path'; // Live-sandbox verification run (not part of the regular regression suite). // // Unlike e2e/electron-smoke.spec.ts (which deliberately uses a fake // JMAP_SERVER_URL just to skip the /setup wizard, and never expects a real // server on the other end), this spec launches the exact same packaged // artifact against the REAL sandbox JMAP backend at // https://stalwart.sandbox.vnc.de and proves: // 1. the login screen renders with no TLS/network errors reaching that host // 2. submitting an obviously-fake, nonexistent test credential produces a // structured "invalid credentials" style response from the real server // (not a network failure) - proving the renderer -> Next API route -> // real JMAP server round trip works end-to-end, without ever using or // guessing a real account's credentials. const projectRoot = path.resolve(__dirname, '..'); const SANDBOX_URL = 'https://emailcore.src-advisory.com'; test.describe('Electron desktop shell - live sandbox connectivity', () => { let electronApp: ElectronApplication; let appWindow: Page; const pageErrors: Error[] = []; const networkFailures: string[] = []; test.beforeAll(async () => { electronApp = await electron.launch({ args: [projectRoot], env: { ...process.env, JMAP_SERVER_URL: SANDBOX_URL, SESSION_SECRET: process.env.SESSION_SECRET || 'live-sandbox-verification-run', NODE_ENV: 'production', }, }); appWindow = await electronApp.firstWindow(); appWindow.on('pageerror', (error) => { pageErrors.push(error); }); appWindow.on('requestfailed', (request) => { networkFailures.push(`${request.method()} ${request.url()} - ${request.failure()?.errorText}`); }); await appWindow.waitForLoadState('domcontentloaded'); }); test.afterAll(async () => { await electronApp?.close(); }); test('renders the real login screen (not SETUP REQUIRED) with no network/TLS errors', async () => { const bodyText = await appWindow.locator('body').innerText(); expect(bodyText).not.toContain('SETUP REQUIRED'); expect(bodyText).not.toContain('Setup Required'); const emailInput = appWindow.locator('input[type="text"]').first(); const passwordInput = appWindow.locator('input[type="password"]').first(); await expect(emailInput).toBeVisible({ timeout: 20000 }); await expect(passwordInput).toBeVisible(); await appWindow.screenshot({ path: path.join(projectRoot, 'live-sandbox-login-screen.png'), fullPage: true, }); expect(pageErrors.map((e) => e.message).join('\n')).not.toMatch(/ERR_CERT|ERR_CONNECTION|ERR_NAME_NOT_RESOLVED|ECONNREFUSED/i); expect(networkFailures.join('\n')).not.toMatch(/ERR_CERT|ERR_CONNECTION|ERR_NAME_NOT_RESOLVED|ECONNREFUSED/i); }); test('submitting a nonexistent test credential reaches the real JMAP server and returns a structured auth error (no real account used/guessed)', async () => { const emailInput = appWindow.locator('input[type="text"]').first(); const passwordInput = appWindow.locator('input[type="password"]').first(); // Deliberately fake, nonexistent address - not a real account, not a // guess against one. This only proves the pipe to the real server works. await emailInput.fill('electron-live-sandbox-verify-8f2c@invalid-test.example'); await passwordInput.fill('not-a-real-password-8f2c'); const allResponses: { url: string; status: number }[] = []; appWindow.on('response', (res) => { allResponses.push({ url: res.url(), status: res.status() }); }); await appWindow.locator('button[type="submit"]').first().click(); // The important assertion: the app renders a structured "invalid // credentials" style error sourced from the real JMAP server's rejection // (visible in whatever locale the app negotiated), not a network/TLS // failure. A real connectivity break to stalwart.sandbox.vnc.de would // instead surface as a generic network-error message or a stuck spinner. const errorBanner = appWindow.getByText(/invalid|ungültig|incorrect|falsch|unauthorized/i).first(); await expect(errorBanner).toBeVisible({ timeout: 15000 }); const errorText = await errorBanner.innerText(); console.log('[live-sandbox] login error banner text:', errorText); expect(errorText.length).toBeGreaterThan(0); expect(errorText).not.toMatch(/network error|failed to fetch|ERR_CERT|ERR_CONNECTION|ECONNREFUSED/i); await appWindow.screenshot({ path: path.join(projectRoot, 'live-sandbox-after-failed-login-attempt.png'), fullPage: true, }); console.log('[live-sandbox] ALL responses observed after click:', JSON.stringify(allResponses, null, 2)); const authResponses = allResponses.filter((r) => r.url.includes('/api/auth/')); if (authResponses.length > 0) { for (const r of authResponses) { expect(r.status).toBeGreaterThanOrEqual(400); expect(r.status).toBeLessThan(500); } } }); });