Measured first: SheetRun drives FILIUS' firewall dialog down to its table editor (Swing's own end of an edit). Java's table writes a cell into the rule before it checks it, so that the firewall holds "abc", a mask of 255.0.255.0 or port 0 while the table shows the old value, and reads a port that is no number as all ports. Here nothing the check refuses reaches the rule. - doc: the three switches, the default action, rules (new as Java's, cells by Java's checks: masks of ones then zeros, the directly connected marker as a source only, an empty port for all ports; protocol, action, moved up or down, removed) - editor: the firewall as a page of the router's and home router's sheet, with Java's explanations; the general tab named so (Java: "network interfaces") Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
438 lines
26 KiB
TypeScript
438 lines
26 KiB
TypeScript
// The device sheet in Chromium (Playwright): it opens with the device that
|
||
// is chosen, shows Java's fields, takes a field when it is left, marks and
|
||
// keeps a text its rule refuses, moves a notebook into a wireless network —
|
||
// beside the surface on a wide screen, below it on a phone.
|
||
|
||
import { expect, test, type Page } from '@playwright/test';
|
||
import type { HomeRouterSheet, HostSheet, RouterSheet } from '@mdg-filius/doc';
|
||
import { drawing, onDevice, openEditor, serveEditor, sheetOf } 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 deviceNamed(page: Page, name: string): Promise<string> {
|
||
return (await drawing(page)).nodes.find((node) => node.name === name)!.id;
|
||
}
|
||
|
||
async function choose(page: Page, id: string): Promise<void> {
|
||
const at = await onDevice(page, id);
|
||
await page.mouse.click(at.x, at.y);
|
||
}
|
||
|
||
const sheet = (page: Page) => page.getByRole('complementary', { name: 'Einstellungen' });
|
||
const field = (page: Page, name: string) => sheet(page).locator(`input[name="${name}"]`);
|
||
|
||
test("opens with the chosen device, shows Java's fields beside the surface, and closes letting go of it", async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||
await expect(sheet(page)).toHaveCount(0);
|
||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||
await choose(page, id);
|
||
await expect(sheet(page)).toBeVisible();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Notebook');
|
||
const values = (await sheetOf(page, id)) as HostSheet;
|
||
await expect(field(page, 'name')).toHaveValue('Notebook 1.10');
|
||
await expect(field(page, 'macAddress')).toHaveValue(values.macAddress);
|
||
await expect(field(page, 'ipAddress')).toHaveValue(values.ipAddress);
|
||
await expect(field(page, 'subnetMask')).toHaveValue(values.subnetMask);
|
||
await expect(field(page, 'defaultGateway')).toHaveValue(values.defaultGateway);
|
||
await expect(sheet(page).getByLabel('Kabelgebunden (LAN)')).toBeChecked();
|
||
await expect(sheet(page).getByLabel('DHCP zur Konfiguration verwenden')).not.toBeChecked();
|
||
const surface = (await page.locator('svg.filius-canvas').boundingBox())!;
|
||
const aside = (await sheet(page).boundingBox())!;
|
||
expect(aside.x).toBeGreaterThanOrEqual(surface.x + surface.width - 1);
|
||
expect(aside.width).toBe(340);
|
||
await sheet(page).getByRole('button', { name: 'Schließen' }).click();
|
||
await expect(sheet(page)).toHaveCount(0);
|
||
await expect(page.locator(`g.device[data-node="${id}"]`)).not.toHaveAttribute('data-selected', 'true');
|
||
});
|
||
|
||
test('marks a refused address while it is typed, keeps it with what still holds, and takes a mended one', async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="pc"]').click();
|
||
await expect(sheet(page)).toBeVisible();
|
||
const id = (await drawing(page)).nodes[0]!.id;
|
||
const ip = field(page, 'ipAddress');
|
||
await ip.fill('10.0.0.256');
|
||
await expect(ip).toHaveAttribute('aria-invalid', 'true');
|
||
await field(page, 'subnetMask').focus();
|
||
await expect(sheet(page).getByRole('status')).toHaveText('Nicht übernommen – es gilt weiterhin 192.168.0.10.');
|
||
await expect(ip).toHaveValue('10.0.0.256');
|
||
expect(((await sheetOf(page, id)) as HostSheet).ipAddress).toBe('192.168.0.10');
|
||
await ip.fill('010.0.0.5');
|
||
await expect(ip).toHaveAttribute('aria-invalid', 'false');
|
||
await expect(sheet(page).getByRole('status')).toHaveCount(0);
|
||
await ip.press('Enter');
|
||
await expect(ip).toHaveValue('10.0.0.5');
|
||
expect(((await sheetOf(page, id)) as HostSheet).ipAddress).toBe('10.0.0.5');
|
||
// Escape gives a text up; the empty gateway is taken, the empty address is not.
|
||
await ip.fill('1.2.3');
|
||
await ip.press('Escape');
|
||
await expect(ip).toHaveValue('10.0.0.5');
|
||
await expect(sheet(page)).toBeVisible();
|
||
await field(page, 'defaultGateway').fill('');
|
||
await field(page, 'defaultGateway').press('Enter');
|
||
await ip.fill('');
|
||
await ip.press('Enter');
|
||
await expect(sheet(page).getByRole('status')).toHaveText('Nicht übernommen – es gilt weiterhin 10.0.0.5.');
|
||
});
|
||
|
||
test('takes a change back as one step, and shows the address as the name', async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="pc"]').click();
|
||
const id = (await drawing(page)).nodes[0]!.id;
|
||
await field(page, 'ipAddress').fill('10.0.0.9');
|
||
await field(page, 'ipAddress').press('Enter');
|
||
await sheet(page).getByLabel('IP-Adresse als Name verwenden').check();
|
||
await expect.poll(async () => (await drawing(page)).nodes[0]!.label).toBe('10.0.0.9');
|
||
await expect(field(page, 'name')).toBeDisabled();
|
||
await expect(field(page, 'name')).toHaveValue('10.0.0.9');
|
||
await page.getByRole('button', { name: 'Rückgängig' }).click();
|
||
await expect.poll(async () => (await drawing(page)).nodes[0]!.label).toBe('Rechner');
|
||
await expect(field(page, 'name')).toBeEnabled();
|
||
await page.getByRole('button', { name: 'Rückgängig' }).click();
|
||
await expect(field(page, 'ipAddress')).toHaveValue('192.168.0.10');
|
||
expect(((await sheetOf(page, id)) as HostSheet).ipAddress).toBe('192.168.0.10');
|
||
});
|
||
|
||
test('takes the text of one device when another is chosen, and shows the other', async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||
const first = await deviceNamed(page, 'Notebook 1.10');
|
||
const second = await deviceNamed(page, 'Notebook 1.11');
|
||
await choose(page, first);
|
||
await field(page, 'dnsServer').fill('192.168.1.53');
|
||
await choose(page, second);
|
||
await expect(sheet(page)).toHaveAttribute('data-device', second);
|
||
expect(((await sheetOf(page, first)) as HostSheet).dnsServer).toBe('192.168.1.53');
|
||
await expect(field(page, 'name')).toHaveValue('Notebook 1.11');
|
||
await expect(field(page, 'dnsServer')).toHaveValue(((await sheetOf(page, second)) as HostSheet).dnsServer);
|
||
});
|
||
|
||
test('offers no text of one device to the next, a refused one neither', async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||
const first = await deviceNamed(page, 'Notebook 1.10');
|
||
const second = await deviceNamed(page, 'Notebook 1.11');
|
||
await choose(page, first);
|
||
await field(page, 'ipAddress').fill('300.0.0.1');
|
||
await choose(page, second);
|
||
await expect(sheet(page)).toHaveAttribute('data-device', second);
|
||
await expect(field(page, 'ipAddress')).toHaveValue(((await sheetOf(page, second)) as HostSheet).ipAddress);
|
||
await expect(field(page, 'ipAddress')).toHaveAttribute('aria-invalid', 'false');
|
||
await expect(sheet(page).getByRole('status')).toHaveCount(0);
|
||
});
|
||
|
||
test('closes the address fields while DHCP configures the card, as Java does', async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="pc"]').click();
|
||
const fields = ['ipAddress', 'subnetMask', 'defaultGateway', 'dnsServer'];
|
||
await sheet(page).getByLabel('DHCP zur Konfiguration verwenden').check();
|
||
for (const name of fields) await expect(field(page, name)).toBeDisabled();
|
||
await expect(field(page, 'name')).toBeEnabled();
|
||
await sheet(page).getByLabel('DHCP zur Konfiguration verwenden').uncheck();
|
||
for (const name of fields) await expect(field(page, name)).toBeEnabled();
|
||
});
|
||
|
||
test("joins a notebook to a switch's wireless network, drawn as wireless", async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="networkSwitch"]').click();
|
||
const hub = (await drawing(page)).nodes[0]!.id;
|
||
await field(page, 'ssid').fill('Schule');
|
||
await field(page, 'ssid').press('Enter');
|
||
await sheet(page).getByLabel('als Wolke darstellen').check();
|
||
await expect.poll(async () => (await drawing(page)).nodes[0]!.cloud).toBe(true);
|
||
await page.locator('[data-palette="notebook"]').click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Notebook');
|
||
await expect(sheet(page).locator('select[name="ssid"]')).toBeDisabled();
|
||
await sheet(page).getByLabel('Drahtlos (WLAN)').check();
|
||
await sheet(page).locator('select[name="ssid"]').selectOption('Schule');
|
||
await expect.poll(async () => (await drawing(page)).links.map((link) => [link.targetNode, link.wireless])).toEqual([[hub, true]]);
|
||
await sheet(page).getByLabel('Kabelgebunden (LAN)').check();
|
||
await expect.poll(async () => (await drawing(page)).links).toEqual([]);
|
||
});
|
||
|
||
test("shows a router's cards in Java's tabs, draws the open card's cable active, and renames a tab by its address", async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||
const id = (await drawing(page)).nodes.find((node) => node.kind === 'router')!.id;
|
||
await choose(page, id);
|
||
const values = (await sheetOf(page, id)) as RouterSheet;
|
||
const tabs = sheet(page).getByRole('tab');
|
||
await expect(tabs).toHaveText(['Allgemein', ...values.interfaces.map((card) => card.ipAddress), 'Weiterleitungstabelle']);
|
||
await expect(field(page, 'name')).toHaveValue(values.name);
|
||
await expect(sheet(page).getByLabel('IP-Weiterleitung aktivieren')).toBeDisabled();
|
||
await expect(sheet(page).getByLabel('IP-Weiterleitung aktivieren')).toBeChecked();
|
||
const card = values.interfaces[0]!;
|
||
await tabs.nth(1).click();
|
||
await expect(sheet(page).locator('.sheet-connected')).toHaveText(`Verbunden mit ${card.connected!.device}`);
|
||
await expect(page.locator(`path[data-link="${card.connected!.link}"]`)).toHaveAttribute('data-active', 'true');
|
||
// Java's active cable is green (JCablePanel.farbeBlinken).
|
||
await expect(page.locator(`path[data-link="${card.connected!.link}"]`)).toHaveCSS('stroke', 'rgb(0, 255, 64)');
|
||
await field(page, 'ipAddress').fill('192.168.0.1');
|
||
await field(page, 'ipAddress').press('Enter');
|
||
await expect(tabs.nth(1)).toHaveText('192.168.0.1');
|
||
await tabs.first().click();
|
||
await expect(page.locator('path[data-active]')).toHaveCount(0);
|
||
await tabs.nth(1).click();
|
||
await sheet(page).getByRole('button', { name: 'Schließen' }).click();
|
||
await expect(page.locator('path[data-active]')).toHaveCount(0);
|
||
});
|
||
|
||
test("edits a router's forwarding table: a new row once its four cells pass, a cell, the row removed; RIP greys it out", async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="router"]').click();
|
||
await page.getByRole('dialog').getByRole('button', { name: '2', exact: true }).click();
|
||
const id = (await drawing(page)).nodes[0]!.id;
|
||
const tabs = sheet(page).getByRole('tab');
|
||
await tabs.last().click();
|
||
const rows = sheet(page).locator('.route-table tbody tr');
|
||
// Java's own rows: two addresses, two networks (the same twice: both cards are new), localhost.
|
||
await expect(rows).toHaveCount(5);
|
||
await sheet(page).getByRole('button', { name: 'Neuer Eintrag' }).click();
|
||
await expect(sheet(page).getByText('Die Zeile wird übernommen, sobald alle Felder gültig sind.')).toBeVisible();
|
||
await expect(field(page, 'route-destinationNetwork')).toHaveAttribute('aria-invalid', 'false');
|
||
for (const [column, text] of [
|
||
['destinationNetwork', '10.0.0.0'],
|
||
['subnetMask', '255.0.0.0'],
|
||
['gateway', '192.168.0.1'],
|
||
] as const) {
|
||
await field(page, `route-${column}`).fill(text);
|
||
await field(page, `route-${column}`).press('Enter');
|
||
}
|
||
expect(((await sheetOf(page, id)) as RouterSheet).routes).toEqual([]);
|
||
await field(page, 'route-interfaceIPAddress').fill('192.168.0.10');
|
||
await field(page, 'route-interfaceIPAddress').press('Enter');
|
||
await expect
|
||
.poll(async () => ((await sheetOf(page, id)) as RouterSheet).routes)
|
||
.toEqual([{ destinationNetwork: '10.0.0.0', subnetMask: '255.0.0.0', gateway: '192.168.0.1', interfaceIPAddress: '192.168.0.10' }]);
|
||
await expect(sheet(page).locator('tr[data-manual="0"]')).toHaveAttribute('aria-selected', 'true');
|
||
await field(page, 'route-gateway').fill('192.168.0.2');
|
||
await field(page, 'route-gateway').press('Enter');
|
||
await expect(sheet(page).locator('tr[data-manual="0"] td').nth(2)).toHaveText('192.168.0.2');
|
||
await sheet(page).getByLabel('Alle Einträge anzeigen').uncheck();
|
||
await expect(rows).toHaveCount(1);
|
||
await sheet(page).getByLabel('Alle Einträge anzeigen').check();
|
||
await sheet(page).locator('tr[data-manual="0"]').click();
|
||
await sheet(page).getByRole('button', { name: 'Eintrag löschen' }).click();
|
||
await expect.poll(async () => ((await sheetOf(page, id)) as RouterSheet).routes).toEqual([]);
|
||
await expect(rows).toHaveCount(5);
|
||
await tabs.first().click();
|
||
await sheet(page).getByLabel('Automatisches Routing').check();
|
||
await expect(tabs.last()).toBeDisabled();
|
||
await sheet(page).getByLabel('Automatisches Routing').uncheck();
|
||
await expect(tabs.last()).toBeEnabled();
|
||
// RIP switched on while the table is open (here: taken back by undo) closes it.
|
||
await tabs.last().click();
|
||
await expect(sheet(page).locator('.route-table')).toBeVisible();
|
||
await page.getByRole('button', { name: 'Rückgängig' }).click();
|
||
await expect(tabs.last()).toBeDisabled();
|
||
await expect(tabs.first()).toHaveAttribute('aria-selected', 'true');
|
||
await expect(sheet(page).getByLabel('Automatisches Routing')).toBeChecked();
|
||
});
|
||
|
||
test("shows a home router's LAN and WAN tabs, DHCP closing the WAN card's address", async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="gateway"]').click();
|
||
const tabs = sheet(page).getByRole('tab');
|
||
await expect(tabs).toHaveText(['Allgemein', 'LAN: 192.168.0.10', 'WAN: 42.0.0.10']);
|
||
await expect(sheet(page).locator('.sheet-connected')).toHaveCount(0);
|
||
await tabs.nth(2).click();
|
||
await expect(sheet(page).locator('.sheet-connected')).toHaveText('Keine Verbindung');
|
||
await sheet(page).getByLabel('DHCP zur Konfiguration verwenden').check();
|
||
await expect(field(page, 'ipAddress')).toBeDisabled();
|
||
await expect(field(page, 'subnetMask')).toBeDisabled();
|
||
await tabs.nth(1).click();
|
||
await expect(field(page, 'ipAddress')).toBeEnabled();
|
||
await field(page, 'ipAddress').fill('192.168.7.1');
|
||
await field(page, 'ipAddress').press('Enter');
|
||
await expect(tabs.nth(1)).toHaveText('LAN: 192.168.7.1');
|
||
});
|
||
|
||
test("sets up a PC's DHCP server on its own page: base settings as Java determines them, static assignments, back to the fields", async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="pc"]').click();
|
||
const id = (await drawing(page)).nodes[0]!.id;
|
||
await sheet(page).getByRole('button', { name: 'DHCP-Server einrichten' }).click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('DHCP-Server einrichten');
|
||
await expect(field(page, 'ipAddress')).toBeHidden();
|
||
await expect(field(page, 'dhcpGateway')).toHaveValue('0.0.0.0');
|
||
await expect(field(page, 'dhcpGateway')).toHaveAttribute('readonly', '');
|
||
await expect(field(page, 'dhcpSubnetMask')).toHaveValue('255.255.255.0');
|
||
await field(page, 'lowerBound').fill('192.168.0.300');
|
||
await field(page, 'lowerBound').press('Enter');
|
||
await expect(sheet(page).getByRole('status')).toHaveText('Nicht übernommen – es gilt weiterhin 0.0.0.0.');
|
||
await field(page, 'lowerBound').fill('192.168.000.100');
|
||
await field(page, 'lowerBound').press('Enter');
|
||
await expect(field(page, 'lowerBound')).toHaveValue('192.168.0.100');
|
||
await sheet(page).getByLabel('Manuelle Einstellungen').check();
|
||
await expect(field(page, 'dhcpGateway')).not.toHaveAttribute('readonly', '');
|
||
await field(page, 'dhcpGateway').fill('192.168.0.1');
|
||
await field(page, 'dhcpGateway').press('Enter');
|
||
await sheet(page).getByLabel('DHCP aktivieren').check();
|
||
await expect
|
||
.poll(async () => ((await sheetOf(page, id)) as HostSheet).dhcpServer)
|
||
.toMatchObject({ isActive: true, useOwnSettings: true, gateway: '192.168.0.1', lowerBoundIPAddress: '192.168.0.100' });
|
||
|
||
await sheet(page).getByRole('tab', { name: 'Statische Adresszuweisung' }).click();
|
||
await sheet(page).getByRole('button', { name: 'Hinzufügen' }).click();
|
||
await field(page, 'static-macAddress').fill('aa:bb:cc:dd:ee:01');
|
||
await field(page, 'static-macAddress').press('Enter');
|
||
await field(page, 'static-ipAddress').fill('192.168.0.50');
|
||
await field(page, 'static-ipAddress').press('Enter');
|
||
await expect(sheet(page).locator('table[data-rows="static"] tr[data-manual="0"] td').first()).toHaveText('AA:BB:CC:DD:EE:01');
|
||
await sheet(page).getByRole('button', { name: 'Hinzufügen' }).click();
|
||
await field(page, 'static-macAddress').fill('AA:BB:CC:DD:EE:01');
|
||
await field(page, 'static-macAddress').press('Enter');
|
||
await field(page, 'static-ipAddress').fill('192.168.0.51');
|
||
await field(page, 'static-ipAddress').press('Enter');
|
||
await expect(sheet(page).getByRole('alert')).toHaveText('Diese MAC-Adresse hat schon eine feste Adresse.');
|
||
expect(((await sheetOf(page, id)) as HostSheet).dhcpServer.staticAssignments).toHaveLength(1);
|
||
await sheet(page).locator('table[data-rows="static"] tr[data-manual="0"]').click();
|
||
await sheet(page).getByRole('button', { name: 'Entfernen' }).click();
|
||
await expect.poll(async () => ((await sheetOf(page, id)) as HostSheet).dhcpServer.staticAssignments).toEqual([]);
|
||
|
||
// Another device starts at its fields; back at the first, its page is closed too.
|
||
await page.locator('[data-palette="pc"]').click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Rechner');
|
||
await expect(field(page, 'ipAddress')).toBeVisible();
|
||
await choose(page, id);
|
||
await expect(sheet(page)).toHaveAttribute('data-device', id);
|
||
await sheet(page).getByRole('button', { name: 'DHCP-Server einrichten' }).click();
|
||
await sheet(page).getByRole('button', { name: 'Zurück' }).click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Rechner');
|
||
await expect(field(page, 'ipAddress')).toBeVisible();
|
||
await sheet(page).getByLabel('DHCP zur Konfiguration verwenden').check();
|
||
await expect(sheet(page).getByRole('button', { name: 'DHCP-Server einrichten' })).toBeDisabled();
|
||
});
|
||
|
||
test("keeps a home router's port forwarding on its own page, and shows its DHCP server with the LAN address as gateway", async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="gateway"]').click();
|
||
const id = (await drawing(page)).nodes[0]!.id;
|
||
await sheet(page).getByRole('button', { name: 'Portfreigaben' }).click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Portfreigaben');
|
||
await sheet(page).getByRole('button', { name: 'Neuer Eintrag' }).click();
|
||
await sheet(page).locator('select[name="forwarding-protocolValue"]').selectOption('UDP');
|
||
for (const [column, text] of [
|
||
['publicPortValue', '53'],
|
||
['lanIPAddress', '192.168.0.20'],
|
||
['lanPortValue', '5353'],
|
||
] as const) {
|
||
await field(page, `forwarding-${column}`).fill(text);
|
||
await field(page, `forwarding-${column}`).press('Enter');
|
||
}
|
||
await expect
|
||
.poll(async () => ((await sheetOf(page, id)) as HomeRouterSheet).portForwarding)
|
||
.toEqual([{ protocolValue: 'UDP', publicPortValue: '53', lanIPAddress: '192.168.0.20', lanPortValue: '5353', forwarded: true }]);
|
||
await field(page, 'forwarding-lanPortValue').fill('05353');
|
||
await field(page, 'forwarding-lanPortValue').press('Enter');
|
||
await expect(sheet(page).getByRole('status')).toHaveText('Nicht übernommen – es gilt weiterhin 5353.');
|
||
await sheet(page).locator('select[name="forwarding-protocolValue"]').selectOption('TCP');
|
||
await expect.poll(async () => ((await sheetOf(page, id)) as HomeRouterSheet).portForwarding[0]!.protocolValue).toBe('TCP');
|
||
await sheet(page).getByRole('button', { name: 'Eintrag löschen' }).click();
|
||
await expect.poll(async () => ((await sheetOf(page, id)) as HomeRouterSheet).portForwarding).toEqual([]);
|
||
await sheet(page).getByRole('button', { name: 'Zurück' }).click();
|
||
|
||
await sheet(page).getByRole('tab', { name: 'LAN: 192.168.0.10' }).click();
|
||
await sheet(page).getByRole('button', { name: 'DHCP-Server einrichten' }).click();
|
||
await expect(field(page, 'dhcpGateway')).toHaveValue('192.168.0.10');
|
||
await expect(field(page, 'dhcpGateway')).toHaveAttribute('readonly', '');
|
||
await expect(field(page, 'dhcpDnsServer')).not.toHaveAttribute('readonly', '');
|
||
await expect(sheet(page).getByLabel('Manuelle Einstellungen')).toHaveCount(0);
|
||
await sheet(page).getByRole('button', { name: 'Zurück' }).click();
|
||
// Back on the tab it was opened from, as Java's panel stays behind its dialog.
|
||
await expect(sheet(page).getByRole('tab', { name: 'LAN: 192.168.0.10' })).toHaveAttribute('aria-selected', 'true');
|
||
});
|
||
|
||
test("sets up a router's firewall on its own page: the switches, the default, rules edited, moved and removed", async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="router"]').click();
|
||
await page.getByRole('dialog').getByRole('button', { name: '2', exact: true }).click();
|
||
const id = (await drawing(page)).nodes[0]!.id;
|
||
const firewall = async () => ((await sheetOf(page, id)) as RouterSheet).firewall;
|
||
await sheet(page).getByRole('button', { name: 'Firewall einrichten' }).click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Firewall einrichten');
|
||
await expect(sheet(page).getByRole('tab')).toHaveText(['Allgemein', 'Firewall-Regeln']);
|
||
await sheet(page).getByLabel('Firewall aktivieren').check();
|
||
await sheet(page).getByLabel('nur SYN-Pakete verwerfen').uncheck();
|
||
await expect.poll(firewall).toMatchObject({ isActive: true, filterSynSegmentsOnly: false });
|
||
|
||
await sheet(page).getByRole('tab', { name: 'Firewall-Regeln' }).click();
|
||
await sheet(page).locator('select[name="defaultPolicy"]').selectOption('accept');
|
||
await sheet(page).getByRole('button', { name: 'Neue Regel' }).click();
|
||
const rows = sheet(page).locator('table[data-rows="firewall"] tbody tr');
|
||
await expect(rows.first().locator('td')).toHaveText(['1', '', '', '', '', 'TCP', '', 'verwerfen']);
|
||
await expect(sheet(page).getByRole('button', { name: 'rauf' })).toBeDisabled();
|
||
await field(page, 'firewall-sourceIPAddress').fill('abc');
|
||
await field(page, 'firewall-sourceIPAddress').press('Enter');
|
||
await expect(sheet(page).getByRole('status')).toHaveText('Nicht übernommen – es gilt weiterhin (leer).');
|
||
await field(page, 'firewall-sourceIPAddress').fill('10.0.0.0');
|
||
await field(page, 'firewall-sourceIPAddress').press('Enter');
|
||
await field(page, 'firewall-sourceSubnetMask').fill('255.0.255.0');
|
||
await field(page, 'firewall-sourceSubnetMask').press('Enter');
|
||
await expect(field(page, 'firewall-sourceSubnetMask')).toHaveAttribute('aria-invalid', 'true');
|
||
await field(page, 'firewall-sourceSubnetMask').fill('255.0.0.0');
|
||
await field(page, 'firewall-sourceSubnetMask').press('Enter');
|
||
await field(page, 'firewall-port').fill('80');
|
||
await field(page, 'firewall-port').press('Enter');
|
||
await sheet(page).locator('select[name="firewall-protocol"]').selectOption('udp');
|
||
await sheet(page).locator('select[name="firewall-action"]').selectOption('accept');
|
||
await expect(rows.first().locator('td')).toHaveText(['1', '10.0.0.0', '255.0.0.0', '', '', 'UDP', '80', 'akzeptieren']);
|
||
|
||
await sheet(page).getByRole('button', { name: 'Neue Regel' }).click();
|
||
await field(page, 'firewall-destinationIPAddress').fill('192.168.0.1');
|
||
await field(page, 'firewall-destinationIPAddress').press('Enter');
|
||
await expect(sheet(page).getByRole('button', { name: 'runter' })).toBeDisabled();
|
||
await sheet(page).getByRole('button', { name: 'rauf' }).click();
|
||
await expect.poll(async () => (await firewall()).rules.map((rule) => rule.destinationIPAddress)).toEqual(['192.168.0.1', '']);
|
||
await expect(rows.first()).toHaveAttribute('aria-selected', 'true');
|
||
await sheet(page).getByRole('button', { name: 'Regel löschen' }).click();
|
||
await expect.poll(async () => (await firewall()).rules.map((rule) => rule.sourceIPAddress)).toEqual(['10.0.0.0']);
|
||
// Java keeps a row chosen: the one that took the removed one's place.
|
||
await expect(rows.first()).toHaveAttribute('aria-selected', 'true');
|
||
await expect(field(page, 'firewall-sourceIPAddress')).toHaveValue('10.0.0.0');
|
||
await expect(await firewall()).toMatchObject({ defaultPolicy: 'accept' });
|
||
await sheet(page).getByRole('button', { name: 'Zurück' }).click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Vermittlungsrechner');
|
||
});
|
||
|
||
test("opens a home router's firewall from its general tab", async ({ page }) => {
|
||
await openEditor(page, server.base, 'new');
|
||
await page.locator('[data-palette="gateway"]').click();
|
||
await sheet(page).getByRole('button', { name: 'Firewall einrichten' }).click();
|
||
await expect(sheet(page).getByRole('heading')).toHaveText('Firewall einrichten');
|
||
await expect(sheet(page).getByLabel('Firewall aktivieren')).not.toBeChecked();
|
||
});
|
||
|
||
test.describe('on a phone', () => {
|
||
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true });
|
||
|
||
test('opens below the surface with a tap, the device still in sight', async ({ page }) => {
|
||
await openEditor(page, server.base, 'zwei_rechnernetze_komplett.fls');
|
||
const id = await deviceNamed(page, 'Notebook 1.10');
|
||
const at = await onDevice(page, id);
|
||
await page.touchscreen.tap(at.x, at.y);
|
||
await expect(sheet(page)).toBeVisible();
|
||
const surface = (await page.locator('svg.filius-canvas').boundingBox())!;
|
||
const aside = (await sheet(page).boundingBox())!;
|
||
expect(aside.y).toBeGreaterThanOrEqual(surface.y + surface.height - 1);
|
||
expect(aside.height).toBeLessThanOrEqual(844 / 2);
|
||
expect(surface.height).toBeGreaterThan(200);
|
||
const fieldBox = (await field(page, 'ipAddress').boundingBox())!;
|
||
expect(fieldBox.height).toBeGreaterThanOrEqual(44);
|
||
});
|
||
});
|
||
|
||
test('speaks English when asked', async ({ page }) => {
|
||
await openEditor(page, server.base, 'new', '&lang=en');
|
||
await page.locator('[data-palette="networkSwitch"]').click();
|
||
const aside = page.getByRole('complementary', { name: 'Settings' });
|
||
await expect(aside.getByText('Wi-Fi Name (SSID)')).toBeVisible();
|
||
await expect(aside.getByText('Retention Time SAT Entries (Seconds)')).toBeVisible();
|
||
await expect(aside.getByLabel('Display as Cloud')).not.toBeChecked();
|
||
});
|