A tap on a device in the simulation now only chooses it; its three-dot button (or a long press) opens what Java's click opened: the desktop, and newly a switch's SAT table and a home router's NAT table, beside each card's data exchange. The desktop, the tables and the data exchange are no windows in the window any more but the simulation's sheet (SimulationSheet), after the dashboard's AppSheet: below the toolbar to the bottom, a head with icon, title, who else has it open, see-through and close. One at a time — it covers the surface. It stays over a return to the design with the program open on a desktop; a sheet whose device was removed closes. See-through fades the body (0.55) and keeps the head; the choice is kept in the browser. Java opens these as movable windows, several at once, with a click; named in CHANGES.md. Tests open through the menu (showFromMenu, showDesktop in editorPage.ts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
204 lines
11 KiB
TypeScript
204 lines
11 KiB
TypeScript
// The web browser and the web server on a computer's desktop in Chromium
|
|
// (F4.6d), on browser/fixtures/web-pages.fls (test/webPagesFixture.test.ts):
|
|
// Java's address line and title, the page in a sandbox that runs no script
|
|
// and loads nothing but the images that came, links and forms handed to the
|
|
// room as Java's window hands them to its program, the server's button,
|
|
// virtual hosts and lines, the routers' pages in the language FILIUS runs in.
|
|
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { expect, test, type Page } from '@playwright/test';
|
|
import { drawing, onDevice, serveEditor, showDesktop } from './editorPage.js';
|
|
|
|
let server: { readonly base: string; readonly close: () => Promise<void> };
|
|
|
|
test.beforeAll(async () => {
|
|
server = await serveEditor();
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
await server.close();
|
|
});
|
|
|
|
const FIXTURE = join(dirname(fileURLToPath(import.meta.url)), 'fixtures/web-pages.fls');
|
|
|
|
async function openPages(page: Page, query = ''): Promise<void> {
|
|
await page.goto(`${server.base}?example=new${query}`);
|
|
await page.waitForSelector('svg.filius-canvas');
|
|
await page.locator('input[type="file"]').setInputFiles(FIXTURE);
|
|
await expect.poll(async () => (await drawing(page).catch(() => ({ nodes: [] }))).nodes.length).toBe(4);
|
|
}
|
|
|
|
async function start(page: Page, name = 'Wechselt in den Aktionsmodus'): Promise<void> {
|
|
await page.getByRole('button', { name }).click();
|
|
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
|
}
|
|
|
|
/** Opens a computer's desktop with a tap and a program's window on it. */
|
|
async function openProgram(page: Page, name: string, program: string) {
|
|
const id = (await drawing(page)).nodes.find((node) => node.name === name)!.id;
|
|
const desktop = await showDesktop(page, id);
|
|
await desktop.locator(`[data-program="${program}"]`).click();
|
|
const window = desktop.locator(`[data-program-window="${program}"]`);
|
|
return { desktop, window, title: window.locator('.program-titlebar h3') };
|
|
}
|
|
|
|
test("browses as Java's browser does: the address line, the page's title, links and forms to the server, the page in a sandbox", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
await openPages(page);
|
|
await start(page);
|
|
const { desktop, window, title } = await openProgram(page, 'Browser', 'webBrowser');
|
|
const address = window.getByRole('textbox', { name: 'Adresse' });
|
|
const frame = window.frameLocator('iframe[title="Seite"]');
|
|
// Java's start: the program's name, "http://", its browser's logo.
|
|
await expect(title).toHaveText('Webbrowser');
|
|
await expect(address).toHaveValue('http://');
|
|
await expect(window.locator('.browser-start img')).toBeVisible();
|
|
|
|
await address.fill('192.168.0.20');
|
|
await address.press('Enter');
|
|
await expect(address).toHaveValue('http://192.168.0.20/');
|
|
await expect(title).toHaveText('Seiten & Formulare', { timeout: 30_000 });
|
|
await expect(frame.locator('h1')).toHaveText('Start');
|
|
// The image that came shows from its data, the missing one does not load; no script ran, nor a handler.
|
|
await expect(frame.locator('img[alt="Bild"]')).toHaveAttribute('src', /^data:image\/png;base64,iVBOR/, { timeout: 30_000 });
|
|
await expect(frame.locator('img[alt="Fehlt"]')).not.toHaveAttribute('src', /./);
|
|
await expect(frame.locator('script')).toHaveCount(0);
|
|
expect(await frame.locator('body').getAttribute('data-ran')).toBeNull();
|
|
expect(await frame.locator('body').getAttribute('onload')).toBeNull();
|
|
await expect(frame.locator('meta[http-equiv="Content-Security-Policy"]')).toHaveAttribute('content', /default-src 'none'; img-src data:/);
|
|
await expect(window.locator('iframe')).toHaveAttribute('sandbox', 'allow-same-origin allow-forms');
|
|
|
|
// A mark in the page stays in it; a link goes to the server.
|
|
await frame.getByRole('link', { name: 'Unten' }).click();
|
|
await expect(frame.locator('#unten')).toBeInViewport();
|
|
await expect(address).toHaveValue('http://192.168.0.20/');
|
|
await frame.getByRole('link', { name: 'Drei' }).click();
|
|
await expect(title).toHaveText('Drei', { timeout: 30_000 });
|
|
await expect(address).toHaveValue('http://192.168.0.20/ordner/drei.html');
|
|
// A link of a page in a folder goes beside it (Java's from the server's root).
|
|
await frame.getByRole('link', { name: 'Vier' }).click();
|
|
await expect(title).toHaveText('Vier im Ordner', { timeout: 30_000 });
|
|
await expect(address).toHaveValue('http://192.168.0.20/ordner/vier.html');
|
|
|
|
// The forms: the POST one sends its fields as Java's window sends them.
|
|
await address.fill('http://192.168.0.20/');
|
|
await window.getByRole('button', { name: 'Start' }).click();
|
|
await expect(title).toHaveText('Seiten & Formulare', { timeout: 30_000 });
|
|
await frame.getByRole('button', { name: 'Gut' }).click();
|
|
await expect(title).toHaveText('Ziel', { timeout: 30_000 });
|
|
await expect(address).toHaveValue('http://192.168.0.20/ziel.html');
|
|
await address.fill('192.168.0.20');
|
|
await address.press('Enter');
|
|
await expect(title).toHaveText('Seiten & Formulare', { timeout: 30_000 });
|
|
// The GET one's data stays in the address, which Java's browser does not send (nor show).
|
|
await frame.getByRole('button', { name: 'Los' }).click();
|
|
await expect(title).toHaveText('Ziel', { timeout: 30_000 });
|
|
await expect(address).toHaveValue('http://192.168.0.20/ziel.html');
|
|
|
|
// An error: the browser's page for its status (Java's never finds its template).
|
|
await address.fill('192.168.0.20/nichts.html');
|
|
await address.press('Enter');
|
|
await expect(title).toHaveText('Fehler Not Found (404)', { timeout: 30_000 });
|
|
await expect(frame.locator('h1')).toHaveText('Not Found');
|
|
await expect(frame.locator('p')).toHaveText('Es ist ein Fehler aufgetreten (HTTP-Status: 404)');
|
|
// No address: the line goes back to "http://", the page stays.
|
|
await address.fill('kein host!');
|
|
await address.press('Enter');
|
|
await expect(address).toHaveValue('http://');
|
|
await expect(title).toHaveText('Fehler Not Found (404)');
|
|
|
|
// The server got the forms as Java's browser sends them.
|
|
await desktop.getByRole('button', { name: 'Schließen' }).click();
|
|
const web = await openProgram(page, 'Server', 'webServer');
|
|
const log = web.window.getByLabel('Meldungen');
|
|
await expect(log).toContainText('>>POST /ziel.html HTTP/1.1');
|
|
await expect(log).toContainText('name=Fil+ius&c=1&s=y&t=%C3%BC%26%3D&ok=Gut');
|
|
await expect(log).toContainText('>>GET /ziel.html HTTP/1.1');
|
|
await expect(log).not.toContainText('q=a+b');
|
|
await expect(log).toContainText('Verbindung zu 192.168.0.10:');
|
|
});
|
|
|
|
test("starts and stops the web server, and edits its virtual hosts, as Java's window does", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
await openPages(page);
|
|
await start(page);
|
|
const { desktop, window, title } = await openProgram(page, 'Server', 'webServer');
|
|
await expect(title).toHaveText('Webserver');
|
|
const button = window.getByRole('button', { name: 'Anhalten' });
|
|
await expect(button).toBeVisible();
|
|
const box = window.getByRole('checkbox', { name: 'Verwende virtuelle Hosts' });
|
|
await expect(box).not.toBeChecked();
|
|
await expect(window.getByRole('table')).toHaveCount(0);
|
|
await button.click();
|
|
await expect(window.getByRole('button', { name: 'Starten' })).toBeVisible();
|
|
await expect(window.getByLabel('Meldungen')).toContainText('Annahme von Verbindungsanfragen beendet');
|
|
await expect(window.getByLabel('Meldungen')).toContainText('Server-Socket geschlossen');
|
|
|
|
// The virtual hosts' table: five rows, a cell written when it is left.
|
|
// The box shows what the server holds: ticked once the room answers.
|
|
await box.click();
|
|
await expect(box).toBeChecked();
|
|
const table = window.getByRole('table', { name: 'Virtuelle Hosts:' });
|
|
await expect(table.getByRole('columnheader')).toHaveText(['Hostname', 'Unterverzeichnis']);
|
|
await expect(table.getByRole('textbox')).toHaveCount(10);
|
|
await table.getByRole('textbox', { name: 'Hostname 1' }).fill('www.filius.de');
|
|
await table.getByRole('textbox', { name: 'Unterverzeichnis 1' }).fill('ordner');
|
|
await table.getByRole('textbox', { name: 'Unterverzeichnis 1' }).press('Enter');
|
|
// Escape goes back to what the server holds: what Enter wrote.
|
|
await table.getByRole('textbox', { name: 'Unterverzeichnis 1' }).press('Escape');
|
|
await expect(table.getByRole('textbox', { name: 'Unterverzeichnis 1' })).toHaveValue('ordner');
|
|
await table.getByRole('textbox', { name: 'Hostname 2' }).fill('weg');
|
|
await table.getByRole('textbox', { name: 'Hostname 2' }).press('Escape');
|
|
await expect(table.getByRole('textbox', { name: 'Hostname 2' })).toHaveValue('');
|
|
// Left and opened again, the window shows what the server holds.
|
|
await desktop.getByRole('button', { name: 'Startbildschirm' }).click();
|
|
await desktop.locator('[data-program="webServer"]').click();
|
|
await expect(table.getByRole('textbox', { name: 'Hostname 1' })).toHaveValue('www.filius.de');
|
|
await expect(table.getByRole('textbox', { name: 'Unterverzeichnis 1' })).toHaveValue('ordner');
|
|
await window.getByRole('button', { name: 'Starten' }).click();
|
|
await expect(window.getByLabel('Meldungen')).toContainText('Annahme von Verbindungsanfragen gestartet');
|
|
|
|
// Asked for by its address, which no row names, the server gives the root's pages.
|
|
await desktop.getByRole('button', { name: 'Schließen' }).click();
|
|
const browser = await openProgram(page, 'Browser', 'webBrowser');
|
|
const address = browser.window.getByRole('textbox', { name: 'Adresse' });
|
|
await address.fill('192.168.0.20');
|
|
await address.press('Enter');
|
|
await expect(browser.title).toHaveText('Seiten & Formulare', { timeout: 30_000 });
|
|
});
|
|
|
|
test("shows the server it cannot reach as Java's browser does", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
await openPages(page);
|
|
await start(page);
|
|
const web = await openProgram(page, 'Server', 'webServer');
|
|
await web.window.getByRole('button', { name: 'Anhalten' }).click();
|
|
await web.desktop.getByRole('button', { name: 'Schließen' }).click();
|
|
const { window, title } = await openProgram(page, 'Browser', 'webBrowser');
|
|
const address = window.getByRole('textbox', { name: 'Adresse' });
|
|
await address.fill('192.168.0.20');
|
|
await address.press('Enter');
|
|
await expect(title).toHaveText('http://192.168.0.20/');
|
|
await expect(title).toHaveText('Fehler', { timeout: 60_000 });
|
|
await expect(window.frameLocator('iframe').locator('h1')).toHaveText('Server konnte nicht erreicht werden!');
|
|
});
|
|
|
|
test("shows a router's pages in English when FILIUS speaks English", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
await openPages(page, '&lang=en');
|
|
await start(page, 'Change to Simulation Mode');
|
|
const { window, title } = await openProgram(page, 'Browser', 'webBrowser');
|
|
await expect(title).toHaveText('Webbrowser');
|
|
const address = window.getByRole('textbox', { name: 'Address' });
|
|
await address.fill('192.168.0.1');
|
|
await address.press('Enter');
|
|
await expect(title).toHaveText('Router Administration', { timeout: 30_000 });
|
|
await window.frameLocator('iframe').getByRole('link', { name: 'Show current routing/forwarding table' }).click();
|
|
await expect(title).toHaveText('Routing/Forwarding Table', { timeout: 30_000 });
|
|
await expect(address).toHaveValue('http://192.168.0.1/routes');
|
|
await address.fill('192.168.0.20/nichts.html');
|
|
await address.press('Enter');
|
|
await expect(title).toHaveText('Error Not Found (404)', { timeout: 30_000 });
|
|
});
|