// The text editor on a computer's desktop in Chromium (F4.6f2), on // browser/fixtures/web-pages.fls (test/webPagesFixture.test.ts): installed // while the simulation runs, a page written and saved through Java's file // dialog into the web server's folder, fetched by the other computer's // browser, changed while it runs and fetched again; the caret where one // types, the window's own undo, what the dialog refuses, a file removed by // the command line while it is open, „Neu“. import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { expect, test, type Page } from '@playwright/test'; import { drawing, onDevice, serveEditor } from './editorPage.js'; let server: { readonly base: string; readonly close: () => Promise }; test.beforeAll(async () => { server = await serveEditor(); }); test.afterAll(async () => { await server.close(); }); /** What the development page lets a test do: write into a file as another would. */ interface DevPage { readonly insertText: (id: string, path: string, index: number, text: string) => void; } const FIXTURE = join(dirname(fileURLToPath(import.meta.url)), 'fixtures/web-pages.fls'); async function openPages(page: Page): Promise { await page.goto(`${server.base}?example=new`); 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); await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click(); await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation'); } /** A computer's desktop, opened with a tap. */ async function desktopOf(page: Page, name: string) { const id = (await drawing(page)).nodes.find((node) => node.name === name)!.id; const at = await onDevice(page, id); await page.mouse.click(at.x, at.y); return page.locator(`section[data-desktop="${id}"]`); } test('writes a page into the web server, which serves it while the simulation runs, and changes it there', async ({ page }) => { test.setTimeout(120_000); await openPages(page); const desktop = await desktopOf(page, 'Server'); // Installed while it runs, with the command line beside it. await desktop.locator('[data-program="installation"]').click(); const installation = desktop.locator('[data-program-window="installation"]'); const available = installation.locator('[data-list="available"] [role="option"]'); await available.filter({ hasText: 'Text-Editor' }).click(); await available.filter({ hasText: 'Befehlszeile' }).click(); await installation.getByRole('button', { name: 'Installieren' }).click(); await installation.getByRole('button', { name: 'Änderungen annehmen' }).click(); await desktop.locator('[data-program="textEditor"]').click(); const window = desktop.locator('[data-program-window="textEditor"]'); const title = window.locator('.program-titlebar h3'); const text = window.getByRole('textbox', { name: 'Text' }); await expect(title).toHaveText('Text-Editor'); await expect(window.locator('.text-editor-bar button')).toHaveText(['Neu', 'Öffnen', 'Speichern', 'Speichern unter']); await expect(text).toHaveCSS('font-size', '16px'); // A new text: „Speichern“ asks for a name in Java's dialog; a tap opens a folder. await text.fill('NeuErster Stand'); await window.getByRole('button', { name: 'Speichern', exact: true }).click(); const dialog = window.getByRole('dialog', { name: 'Datei Speichern' }); await expect(dialog.locator('.file-dialog-folder span')).toHaveText('Ordner: /'); await expect(dialog.getByRole('button', { name: 'Ordner aufwärts' })).toBeDisabled(); await expect(dialog.locator('.file-dialog-entries button')).toHaveText(['webserver']); await dialog.locator('.file-dialog-entries button', { hasText: 'webserver' }).click(); await expect(dialog.locator('.file-dialog-folder span')).toHaveText('Ordner: /webserver'); await expect(dialog.locator('.file-dialog-entries button')).toHaveText(['ordner', 'index.html', 'splashscreen-mini.png', 'ziel.html', 'zwei.html']); // No name: said in the dialog, which stays. await dialog.getByRole('button', { name: 'Speichern' }).click(); await expect(dialog.getByRole('alert')).toHaveText('Geben Sie einen Dateinamen ein.'); await dialog.getByRole('textbox', { name: 'Dateiname:' }).fill('neu.html'); await dialog.getByRole('button', { name: 'Speichern' }).click(); await expect(dialog).toHaveCount(0); await expect(title).toHaveText('Text-Editor - neu.html'); // Saved as it is typed: „Speichern“ has nothing left to do. await expect(window.getByRole('button', { name: 'Speichern', exact: true })).toBeDisabled(); // The other computer's browser fetches it from the running web server (the desktops closed in between, which covered the computers). const fetchPage = async (expected: string) => { await desktop.locator('.desktop-close').click(); const browserDesktop = await desktopOf(page, 'Browser'); await browserDesktop.locator('[data-program="webBrowser"]').click(); const browser = browserDesktop.locator('[data-program-window="webBrowser"]'); const address = browser.getByRole('textbox', { name: 'Adresse' }); await address.fill('192.168.0.20/neu.html'); await address.press('Enter'); await expect(browser.frameLocator('iframe[title="Seite"]').locator('body')).toHaveText(expected, { timeout: 30_000 }); await browserDesktop.locator('.desktop-close').click(); await desktopOf(page, 'Server'); await desktop.locator('[data-program="textEditor"]').click(); }; await fetchPage('Erster Stand'); // The window is kept over the desktop closed, as Java's desktop keeps it. await expect(title).toHaveText('Text-Editor - neu.html'); // Changed in the middle, the caret where one types: the server serves it at once. const at = (await text.inputValue()).indexOf('Erster'); await text.evaluate((area: HTMLTextAreaElement, index) => { area.focus(); area.setSelectionRange(index, index + 'Erster'.length); }, at); await page.keyboard.type('Zweiter'); await expect(text).toHaveValue('NeuZweiter Stand'); await fetchPage('Zweiter Stand'); // Another writes before the caret: the caret moves with its text, and one types on where one was. const id = (await drawing(page)).nodes.find((node) => node.name === 'Server')!.id; await text.evaluate((area: HTMLTextAreaElement, index) => { area.focus(); area.setSelectionRange(index, index); }, at + 'Zweiter'.length); await page.evaluate(([node]) => (window as unknown as { filiusDev: DevPage }).filiusDev.insertText(node!, '/webserver/neu.html', 0, ''), [id]); await expect(text).toHaveValue('NeuZweiter Stand'); await page.keyboard.type('!'); await expect(text).toHaveValue('NeuZweiter! Stand'); await page.keyboard.press('Backspace'); await text.evaluate((area: HTMLTextAreaElement) => area.blur()); await expect(text).toHaveValue('NeuZweiter Stand'); // The window's own undo takes back its typing. await text.press('Control+z'); await expect(text).toHaveValue('NeuErster Stand'); await text.press('Control+y'); await expect(text).toHaveValue('NeuZweiter Stand'); // „Öffnen“: a name that is not there and an image are refused, the open file stays. await window.getByRole('button', { name: 'Öffnen' }).click(); const opening = window.getByRole('dialog', { name: 'Datei Öffnen' }); await opening.getByRole('textbox', { name: 'Dateiname:' }).fill('fehlt.txt'); await opening.getByRole('button', { name: 'Öffnen' }).click(); await expect(opening.getByRole('alert')).toHaveText('Die Datei „fehlt.txt“ gibt es nicht.'); await opening.locator('.file-dialog-entries button', { hasText: 'webserver' }).click(); await opening.locator('.file-dialog-entries button', { hasText: 'splashscreen-mini.png' }).click(); await expect(opening.getByRole('textbox', { name: 'Dateiname:' })).toHaveValue('splashscreen-mini.png'); await opening.getByRole('button', { name: 'Öffnen' }).click(); await expect(opening.getByRole('alert')).toHaveText('„splashscreen-mini.png“ ist keine Textdatei.'); await opening.getByRole('button', { name: 'Abbrechen' }).click(); await expect(title).toHaveText('Text-Editor - neu.html'); // Removed by the command line while open: the text stays as a new text, „Speichern“ asks for a name. await desktop.locator('.desktop-home').click(); await desktop.locator('[data-program="commandPrompt"]').click(); const input = desktop.locator('input[name="terminalInput"]'); await input.fill('rm /webserver/neu.html'); await input.press('Enter'); await desktop.locator('.desktop-home').click(); await desktop.locator('[data-program="textEditor"]').click(); await expect(title).toHaveText('Text-Editor'); await expect(window.getByRole('status')).toHaveText('Die Datei „neu.html“ wurde gelöscht. Der Text ist noch da; „Speichern“ legt ihn als neue Datei ab.'); await expect(text).toHaveValue('NeuZweiter Stand'); await expect(window.getByRole('button', { name: 'Speichern', exact: true })).toBeEnabled(); // „Neu“: empty, no file. await window.getByRole('button', { name: 'Neu' }).click(); await expect(text).toHaveValue(''); await expect(window.getByRole('status')).toHaveCount(0); });