// 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 }; 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 { 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 { 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 }); });