// 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: the ping loses answers, the // list shows the dropped frames in Java's words. import { expect, test, type Locator, type Page } from '@playwright/test'; import { drawing, onDevice, 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(); }); async function tap(page: Page, id: string) { const at = await onDevice(page, id); await page.mouse.click(at.x, at.y); } 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 tap(page, gateway); 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('.desktop-close').click(); // The web server behind it pings out. const web = idOf('Webserver - 192.168.0.10'); await tap(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('.desktop-close').click(); await tap(page, gateway); 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('.desktop-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 tap(page, lanSwitch); 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('.desktop-close').click(); // "Pakete verwerfen" held while a ping runs: answers lost, the frames listed. await tap(page, web); await desktop.locator('[data-program="commandPrompt"]').click(); await input.fill('ping 141.99.4.10'); await input.press('Enter'); const hold = page.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!'); const toggle = page.getByRole('button', { name: /^Verworfen: [1-9]/ }); await toggle.click(); await expect(page.getByLabel('Verworfene Pakete')).toContainText(/\d\d:\d\d:\d\d\.\d\d\d : \[src=[0-9A-F:]+, dest=[0-9A-F:]+, type=0x8/); }); test('keeps “Pakete verwerfen” in reach on a phone', 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'); await expect(page.getByRole('button', { name: 'Pakete verwerfen' })).toBeInViewport({ ratio: 1 }); await expect(page.getByRole('button', { name: /^Verworfen: / })).toBeInViewport({ ratio: 1 }); // 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); });