import { mkdtempSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { _electron as electron, expect, test, type ElectronApplication, type Page, } from '@playwright/test'; /** * Rauchtest: Startet die gebaute Anwendung wirklich? * * Die Modultests pruefen Fachlogik, Ausdruck und Oberflaechenfunktionen - * aber keiner von ihnen startet das Programm. Genau dort liegen die Fehler, die * erst beim Anwender auffallen: ein Preload-Bundle, das nach dem Bauen nicht * mehr passt; ein Pfad, der nur im Paket anders liegt; eine Sicherheits- * richtlinie, die das Einsetzen beim Bauen nicht ueberlebt hat und die eigenen * Skripte blockt. * * Dieser Test laeuft deshalb gegen `build/` - gegen das Erzeugnis, nicht gegen * den Quelltext. Er braucht `npm run build` davor; `npm run e2e` erledigt das. * * Er prueft bewusst wenig, aber das Richtige: Startet sie, ist die Bruecke * vollstaendig, haelt die Abschottung, und kam die Sicherheitsrichtlinie an? */ const WURZEL = join(__dirname, '..'); /** Die Methoden, die `electron/preload.ts` ueber contextBridge freigibt. */ const BRUECKE = [ 'platform', 'saveFile', 'openFile', 'writeFile', 'readFile', 'setDirty', 'reportError', 'logPath', 'fetchMapImage', 'onMenuCommand', 'onBeforeClose', ] as const; let anwendung: ElectronApplication; let fenster: Page; const fehlerBeimStart: string[] = []; test.beforeAll(async () => { /* * Eigenes, leeres Anwenderverzeichnis je Lauf. * * Ohne das laeuft der Rauchtest gegen den Sitzungsspeicher des Rechners, auf * dem er startet - und beurteilt damit nicht das Erzeugnis, sondern was * jemand zuletzt darin bearbeitet hat. Gemessen: Beim ersten Lauf gruen, beim * zweiten rot, weil der erste beim Schliessen seinen Stand hinterlassen * hatte. Ein Rauchtest, dessen Ergebnis vom vorigen Lauf abhaengt, taugt als * Wache nichts. * * Geprueft wird damit der Start, den ein Anwender nach der Installation * vorfindet. */ const anwenderverzeichnis = mkdtempSync(join(tmpdir(), 'lsa-planer-rauchtest-')); anwendung = await electron.launch({ args: [WURZEL, `--user-data-dir=${anwenderverzeichnis}`], cwd: WURZEL, }); fenster = await anwendung.firstWindow(); // Alles einsammeln, was der Renderer beim Hochfahren an Fehlern wirft. // Ein Programm, das startet und dabei Ausnahmen schluckt, ist nicht heil. fenster.on('pageerror', (fehler) => fehlerBeimStart.push(String(fehler))); fenster.on('console', (zeile) => { if (zeile.type() === 'error') { fehlerBeimStart.push(zeile.text()); } }); await fenster.waitForLoadState('domcontentloaded'); }); test.afterAll(async () => { // Das Fragezeichen sieht nach dem Typ ueberfluessig aus (der Linter meldet // es auch), ist es aber nicht: `anwendung` wird erst in beforeAll gesetzt. // Schlaegt der Start fehl, laeuft afterAll trotzdem - und ohne die Abfrage // ueberdeckte hier ein TypeError den eigentlichen Startfehler, also genau // die Meldung, wegen der es diesen Test gibt. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- anwendung ist vor beforeAll undefined; siehe darueber await anwendung?.close(); }); test('das Fenster oeffnet sich und der Renderer setzt den Titel', async () => { // Nicht auf den festen Titel aus index.html pruefen: Den traegt das Fenster // schon, bevor irgendetwas laeuft. Die Anwendung stellt ihm zur Laufzeit den // Projektnamen voran - genau daran zeigt sich, dass der Renderer uebernommen // hat und nicht bloss die HTML-Huelle dasteht. await expect(fenster).toHaveTitle(/^Projekt .+ – LSA-Planer Professional$/u); }); test('die Oberflaeche baut sich auf, nicht nur die leere Huelle', async () => { const wurzel = fenster.locator('#anwendung'); await expect(wurzel).toBeAttached(); // Ohne diese Pruefung wuerde auch eine Anwendung bestehen, die zwar ein // Fenster oeffnet, in dem aber nur das