Files
mdg-filius/packages/editor/browser/viewers.spec.ts
T
julian.kiedaischandClaude Opus 5.5 12b26f4abe feat(editor): the simulation's properties in a sheet of their own, with a restart for everyone
Who started the simulation, "Pakete verwerfen" and the dropped packets move
from the toolbar into a properties sheet beside the surface. New in it: restart
the simulation, after asking. The room learns a restart message: stopped as by
stop, every capture, dropped frame and program window emptied, started anew
and named after who restarted; refused for readers and frozen projects, logged,
and waiting for asked blobs like a start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:01:41 +00:00

136 lines
8.5 KiB
TypeScript

// The viewers of a running simulation and "Pakete verwerfen" in Chromium
// (F4.6g5), on FILIUS' example nat_with_mapped_port: a tap on a home router
// shows its NAT table (the port forwarding's entry, then a ping's), a tap on
// a switch its table (the addresses a ping taught it), each reset; and
// "Pakete verwerfen" held while a ping runs, in the simulation's
// „Eigenschaften“: the ping loses answers, the list shows the dropped frames
// in Java's words; „Simulation neu starten“ empties the command line and the
// list and keeps the server's file.
import { expect, test, type Locator, type Page } from '@playwright/test';
import { drawing, openEditor, serveEditor, showDesktop, showFromMenu } from './editorPage.js';
let server: { readonly base: string; readonly close: () => Promise<void> };
test.beforeAll(async () => {
server = await serveEditor();
});
test.afterAll(async () => {
await server.close();
});
async function install(desktop: Locator, program: string) {
await desktop.locator('[data-program="installation"]').click();
const installation = desktop.locator('[data-program-window="installation"]');
await installation.locator('[data-list="available"] [role="option"]').filter({ hasText: program }).click();
await installation.getByRole('button', { name: 'Installieren' }).click();
await installation.getByRole('button', { name: 'Änderungen annehmen' }).click();
}
test('shows the tables of a home router and a switch, and drops while “Pakete verwerfen” is held', async ({ page }) => {
test.setTimeout(120_000);
await openEditor(page, server.base, 'nat_with_mapped_port.fls');
const nodes = (await drawing(page)).nodes;
const idOf = (name: string) => nodes.find((node) => node.name === name)!.id;
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
// The home router with the port forwarding: its entry, with Java's columns.
const gateway = idOf('Heimrouter Vincent');
await showFromMenu(page, gateway, 'NAT-Tabelle anzeigen');
const nat = page.locator(`section[data-viewer="${gateway}"]`);
await expect(nat.locator('h2')).toHaveText('NAT-Tabelle Heimrouter Vincent');
await expect(nat.locator('th')).toHaveText(['Protokoll', 'Portfreigabe', 'WAN-Adresse', 'LAN-Adresse', 'LAN-Port', 'Letzte Aktualisierung']);
await expect(nat.locator('tbody tr')).toHaveCount(1);
await expect(nat.locator('tbody tr').first().locator('td')).toHaveText(['TCP', '80', '*', '192.168.0.10', '80', '-']);
await nat.locator('.simulation-sheet__close').click();
// The web server behind it pings out.
const web = idOf('Webserver - 192.168.0.10');
await showDesktop(page, web);
const desktop = page.locator(`section[data-desktop="${web}"]`);
await install(desktop, 'Befehlszeile');
await desktop.locator('[data-program="commandPrompt"]').click();
const input = desktop.locator('input[name="terminalInput"]');
await input.fill('ping 141.99.4.10');
await input.press('Enter');
await expect(desktop.locator('.terminal-text')).toContainText('4 Paket(e) empfangen', { timeout: 30_000 });
await desktop.locator('.simulation-sheet__close').click();
await showFromMenu(page, gateway, 'NAT-Tabelle anzeigen');
await expect(nat.locator('tbody tr')).toHaveCount(2);
await expect(nat.locator('tbody tr').nth(1).locator('td').nth(0)).toHaveText('ICMP');
await expect(nat.locator('tbody tr').nth(1).locator('td').nth(5)).toHaveText(/^\d\d:\d\d:\d\d$/);
await nat.getByRole('button', { name: 'Tabelle zurücksetzen' }).click();
await expect(nat.locator('tbody tr')).toHaveCount(1);
await nat.locator('.simulation-sheet__close').click();
// The switch the web server hangs on: the addresses the ping taught it.
const link = (await drawing(page)).links.find((each) => each.sourceNode === web || each.targetNode === web)!;
const lanSwitch = link.sourceNode === web ? link.targetNode : link.sourceNode;
await showFromMenu(page, lanSwitch, 'SAT Tabelle anzeigen');
const sat = page.locator(`section[data-viewer="${lanSwitch}"]`);
await expect(sat.locator('th')).toHaveText(['MAC', 'Port', 'Letzte Aktualisierung']);
await expect(sat.locator('tbody tr').first().locator('td')).toHaveText([/^[0-9A-F:]{17}$/, /^Port \d+$/, /^\d\d:\d\d:\d\d$/]);
await sat.getByRole('button', { name: 'Tabelle löschen' }).click();
await expect(sat.locator('tbody tr')).toHaveCount(0);
await sat.locator('.simulation-sheet__close').click();
// "Pakete verwerfen" held while a ping runs: answers lost, the frames listed.
await showDesktop(page, web);
await desktop.locator('[data-program="commandPrompt"]').click();
await input.fill('ping 141.99.4.10');
await input.press('Enter');
await page.getByRole('button', { name: 'Eigenschaften' }).click();
const properties = page.getByRole('complementary', { name: 'Eigenschaften' });
await expect(properties.getByText('Noch keine verworfenen Pakete.')).toBeVisible();
const hold = properties.getByRole('button', { name: 'Pakete verwerfen' });
const box = (await hold.boundingBox())!;
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await expect(hold).toHaveAttribute('aria-pressed', 'true');
await page.waitForTimeout(2_500);
await page.mouse.up();
await expect(hold).toHaveAttribute('aria-pressed', 'false');
// The second ping's statistics: an answer lost.
await expect(desktop.locator('.terminal-text')).toContainText(/Paketstatistik ---[^]*Paketstatistik ---\n4 Paket\(e\) gesendet, [0-3] Paket\(e\) empfangen/, { timeout: 30_000 });
await expect(desktop.locator('.terminal-text')).toContainText('-- Timeout!');
await expect(properties.getByRole('heading', { name: /^Verworfene Pakete \([1-9]\d*\)$/ })).toBeVisible();
await expect(properties.getByLabel('Verworfene Pakete', { exact: true })).toContainText(/\d\d:\d\d:\d\d\.\d\d\d : \[src=[0-9A-F:]+, dest=[0-9A-F:]+, type=0x8/);
// „Simulation neu starten“: asked first — cancelled, nothing happens; confirmed, the command line and the
// dropped frames begin anew, the simulation runs, and what the server saved is still there.
await input.fill('touch bleibt.txt');
await input.press('Enter');
await properties.getByRole('button', { name: 'Simulation neu starten' }).click();
await expect(properties.getByText('Die Simulation für alle neu starten? Alle Protokolle und Datenpakete gehen verloren.')).toBeVisible();
await properties.getByRole('button', { name: 'Abbrechen' }).click();
await expect(properties.getByLabel('Verworfene Pakete', { exact: true })).toBeVisible();
await expect(desktop.locator('.terminal-text')).toContainText('ping 141.99.4.10');
await properties.getByRole('button', { name: 'Simulation neu starten' }).click();
await properties.getByRole('button', { name: 'Neu starten' }).click();
await expect(desktop.locator('.terminal-text')).not.toContainText('ping 141.99.4.10');
await expect(properties.getByText('Noch keine verworfenen Pakete.')).toBeVisible();
await expect(properties.getByRole('heading', { name: 'Verworfene Pakete (0)' })).toBeVisible();
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
await input.fill('ls');
await input.press('Enter');
await expect(desktop.locator('.terminal-text')).toContainText('bleibt.txt');
});
test('keeps “Pakete verwerfen” in reach on a phone, in „Eigenschaften“ over the whole editor', async ({ page }) => {
await page.setViewportSize({ width: 360, height: 740 });
await openEditor(page, server.base, 'nat_with_mapped_port.fls');
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
// Every part of the toolbar within the screen (the dev page's own fields above the editor are wider; they are not the editor's).
const rights = await page.locator('.editor-toolbar > *').evaluateAll((parts) => parts.map((part) => part.getBoundingClientRect().right));
expect(Math.max(...rights)).toBeLessThanOrEqual(360);
await page.getByRole('button', { name: 'Eigenschaften' }).click();
const properties = page.getByRole('complementary', { name: 'Eigenschaften' });
expect(await properties.boundingBox()).toEqual(await page.locator('.editor-root').boundingBox());
await expect(properties.getByRole('button', { name: 'Pakete verwerfen' })).toBeInViewport({ ratio: 1 });
await expect(properties.getByRole('button', { name: 'Simulation neu starten' })).toBeInViewport({ ratio: 1 });
});