// FILIUS' „Report“ in Chromium (F4.8d), on FILIUS' example // „einfaches_rechnernetz_komplett.fls“, run for a moment (its notebooks ask for // addresses by DHCP at once), then in the documentation mode the report // saved under the project's name — a PDF with Java's title, the overview's // image, every computer's configuration and the data exchange of each card // that saw frames, with the rows' words as the data exchange window has // them. import { readFileSync } from 'node:fs'; import { expect, test } from '@playwright/test'; import { decodePDFRawStream, PDFArray, PDFDocument, PDFName, PDFRawStream } from 'pdf-lib'; import { openEditor, serveEditor } from './editorPage.js'; let server: { readonly base: string; readonly close: () => Promise }; test.beforeAll(async () => { server = await serveEditor(); }); test.afterAll(async () => { await server.close(); }); /** Each page's words, as pdf-lib writes them (hex strings shown with Tj, in Windows-1252). */ async function pageTexts(bytes: Uint8Array): Promise { const pdf = await PDFDocument.load(bytes); return pdf.getPages().map((page) => { const contents = page.node.get(PDFName.of('Contents')); const streams = contents instanceof PDFArray ? contents.asArray() : [contents]; const words: string[] = []; for (const reference of streams) { const stream = pdf.context.lookup(reference); if (!(stream instanceof PDFRawStream)) continue; const operators = new TextDecoder('latin1').decode(decodePDFRawStream(stream).decode()); for (const [, hex] of operators.matchAll(/<([0-9A-Fa-f]*)>\s*Tj/g)) words.push(new TextDecoder('windows-1252').decode(Uint8Array.from(hex!.match(/../g) ?? [], (pair) => Number.parseInt(pair, 16)))); } return words; }); } test("saves Java's report of the project, with the data exchange so far", async ({ page }) => { test.setTimeout(90_000); await openEditor(page, server.base, 'einfaches_rechnernetz_komplett.fls'); await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click(); await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation'); // A cable lights up: frames went over it. await expect(page.locator('path[data-active="true"]').first()).toBeAttached({ timeout: 20_000 }); await page.getByRole('button', { name: 'Wechselt in den Dokumentationsmodus' }).click(); const button = page.getByRole('navigation', { name: 'Dokumentation' }).getByRole('button', { name: 'Report' }); await expect(button).toHaveAttribute('title', 'Report als PDF-Dokument erstellen'); const saved = page.waitForEvent('download'); await button.click(); const download = await saved; expect(download.suggestedFilename()).toBe('einfaches_rechnernetz_komplett.pdf'); await expect(button).toBeEnabled(); const bytes = new Uint8Array(readFileSync((await download.path())!)); const pdf = await PDFDocument.load(bytes); expect(pdf.getTitle()).toBe('Filius-Dokumentation: einfaches_rechnernetz_komplett'); expect(pdf.getPage(0).getSize()).toEqual({ width: 595, height: 842 }); const pages = await pageTexts(bytes); // The first page: the title, the project's name, the overview with the image. expect(pages[0]).toEqual(['Filius-Dokumentation: einfaches_rechnernetz_komplett', 'einfaches_rechnernetz_komplett.fls', '1. ', 'Übersicht']); expect(new TextDecoder('latin1').decode(bytes)).toContain('/Subtype /Image'); // Then the devices: every computer and notebook, none of the switches. const words = pages.flat(); expect(words.filter((word) => /^2\.\d+\. $/.test(word)).length).toBe(9); expect(words).toContain('Server'); expect(words).not.toContain('Switch 1'); expect(words).toEqual(expect.arrayContaining(['Installiert: ', 'Webserver, Text-Editor, Datei-Explorer, Bildbetrachter, DNS-Server, Befehlszeile', 'www.mein-server.de.', 'Nein'])); // Then the data exchange: each card that saw frames, under its device's name and address. const traffic = pages.findIndex((texts) => texts.includes('Datenaustausch')); expect(traffic).toBeGreaterThan(1); const rest = pages.slice(traffic).flat(); expect(rest).toEqual(expect.arrayContaining(['3.1. ', 'Nr.', 'Bemerkungen / Details', 'DHCP', 'Anwendung'])); expect(rest.some((word) => /^Notebook \d - /.test(word) || /^Server - 192\.168\.0\.100$/.test(word))).toBe(true); expect(rest.some((word) => word.startsWith('DHCPDISCOVER'))).toBe(true); });