93 lines
7.1 KiB
JavaScript
93 lines
7.1 KiB
JavaScript
import { chromium } from 'playwright';
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync, spawn } from 'node:child_process';
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import { randomBytes } from 'node:crypto';
|
|
import net from 'node:net';
|
|
|
|
const php = process.env.PHP_BINARY || 'php';
|
|
const fixture = JSON.parse(execFileSync(php, ['tests/pilot-fixture.php'], { encoding: 'utf8', windowsHide: true }));
|
|
const env = { ...process.env, APP_ENV: 'prod', APP_DEBUG: '0', APP_SECRET: randomBytes(32).toString('hex'), DATABASE_URL: fixture.databaseUrl, MEDIA_DIRECTORY: fixture.mediaDirectory };
|
|
execFileSync(php, ['bin/console', 'cache:clear', '--env=prod', '--no-debug'], { stdio: 'ignore', windowsHide: true, env });
|
|
const probe = net.createServer(); await new Promise(resolve => probe.listen(0, '127.0.0.1', resolve));
|
|
const port = probe.address().port; await new Promise(resolve => probe.close(resolve));
|
|
const origin = `http://127.0.0.1:${port}`;
|
|
const server = spawn(php, ['-d', 'memory_limit=256M', '-S', `127.0.0.1:${port}`, '-t', 'public', 'bin/dev-router.php'], { windowsHide: true, stdio: 'ignore', env: { ...env, SITE_ORIGIN: origin } });
|
|
const report = 'reports/navigation-ux'; await mkdir(report, { recursive: true });
|
|
let browser, page;
|
|
const checks = [], errors = [];
|
|
|
|
try {
|
|
for (let attempt = 0; attempt < 60; attempt++) {
|
|
try { if ((await fetch(origin)).ok) break; } catch {}
|
|
if (attempt === 59) throw new Error('Pilot test server did not start');
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
}
|
|
browser = await chromium.launch();
|
|
const context = await browser.newContext({ viewport: { width: 1600, height: 1050 } });
|
|
page = await context.newPage(); page.setDefaultTimeout(15000); page.on('pageerror', error => errors.push(error.message));
|
|
await page.goto(origin + '/admin/login');
|
|
await page.getByLabel('Benutzername').fill(fixture.username); await page.getByLabel('Passwort', { exact: true }).fill(fixture.password);
|
|
await page.getByRole('button', { name: 'Anmelden', exact: true }).click(); await page.waitForURL(origin + '/admin');
|
|
const api = `${origin}/admin/api/pages/${fixture.homeId}`;
|
|
const data = await (await context.request.get(api)).json();
|
|
data.document.blocks.push({ id: 'qa-anchor', type: 'section', children: [{ id: 'qa-anchor-title', type: 'heading', props: { text: 'Kontaktbereich', level: 2 } }] });
|
|
assert.equal((await context.request.post(api + '/draft', { headers: { 'X-CSRF-Token': data.csrfToken }, data: { document: data.document, expectedVersion: data.version } })).status(), 200);
|
|
await page.goto(`${origin}/admin/pages/${fixture.homeId}`);
|
|
await page.locator('#structure [data-block-id="qa-anchor"]').click();
|
|
await page.locator('#property-anchor').fill('#kontakt-check'); await page.locator('#property-anchor').blur();
|
|
await page.locator('#save').click(); await page.locator('#save-status').filter({ hasText: 'Entwurf gespeichert' }).waitFor();
|
|
await page.locator('#publish').click(); await page.locator('#confirm-publish').click(); await page.locator('#save-status').filter({ hasText: 'Veröffentlicht' }).waitFor();
|
|
const stored = (await (await context.request.get(api)).json()).document;
|
|
assert.equal(stored.blocks.at(-1).anchor, 'kontakt-check'); assert.equal(stored.schemaVersion, 21);
|
|
checks.push('named jump target created in editor and saved/published with stable block ID');
|
|
|
|
await page.goto(origin + '/admin/menus');
|
|
const rows = page.locator('#menu-items > li');
|
|
const originalFirst = await rows.first().locator('[data-key="label"]').inputValue();
|
|
const handle = rows.first().locator('[data-menu-drag]');
|
|
const start = await handle.boundingBox(), end = await rows.last().boundingBox();
|
|
await page.mouse.move(start.x + start.width / 2, start.y + start.height / 2); await page.mouse.down();
|
|
await page.mouse.move(start.x + start.width / 2, end.y + end.height - 2, { steps: 12 }); await page.mouse.up();
|
|
assert.equal(await rows.last().locator('[data-key="label"]').inputValue(), originalFirst);
|
|
await page.locator('#menu-undo').click(); assert.equal(await rows.first().locator('[data-key="label"]').inputValue(), originalFirst);
|
|
await rows.first().locator('[data-menu-drag]').focus(); await page.keyboard.press('ArrowDown');
|
|
assert.notEqual(await rows.first().locator('[data-key="label"]').inputValue(), originalFirst);
|
|
await page.locator('#menu-undo').click();
|
|
await rows.first().locator('summary').click();
|
|
await rows.first().locator('[data-key="icon"]').selectOption('phone');
|
|
await rows.first().locator('[data-key="iconPosition"]').selectOption('after');
|
|
assert.equal(await rows.first().locator('.menu-entry-title > svg').count(), 1);
|
|
await page.getByText('Eigener Link', { exact: true }).first().click();
|
|
await page.locator('#link-label').fill('Zum Kontaktbereich'); await page.locator('#link-url').fill('#kontakt-check');
|
|
await page.locator('#menu-add-url').click();
|
|
await page.getByRole('button', { name: 'Menü speichern', exact: true }).click();
|
|
await page.locator('#menu-status').filter({ hasText: 'Gespeicherter Stand' }).waitFor();
|
|
await page.reload();
|
|
assert.equal(await rows.first().locator('[data-key="icon"]').inputValue(), 'phone');
|
|
assert.equal(await rows.first().locator('[data-key="iconPosition"]').inputValue(), 'after');
|
|
await rows.first().locator('summary').click();
|
|
await page.screenshot({ path: `${report}/menus.png`, fullPage: true });
|
|
checks.push('pointer drag, keyboard ordering, undo, icon placement, fragment menu link and save/reload');
|
|
const plain = await browser.newContext({ javaScriptEnabled: false }); const publicPage = await plain.newPage();
|
|
await publicPage.goto(origin);
|
|
assert.ok(await publicPage.locator('.nav-label > svg').count() > 0);
|
|
await publicPage.getByRole('link', { name: 'Zum Kontaktbereich', exact: true }).first().click();
|
|
assert.ok(publicPage.url().endsWith('#kontakt-check'));
|
|
assert.equal(await publicPage.locator('#kontakt-check').count(), 1);
|
|
assert.ok(await publicPage.evaluate(() => window.scrollY) > 0);
|
|
await plain.close();
|
|
execFileSync(php, ['-r', 'require "vendor/autoload.php"; $db=App\\Infrastructure\\ConnectionFactory::create($argv[1]); $db->executeStatement("UPDATE scheduled_task SET last_status = ?, last_message = ? WHERE name = ?", ["ok", "Fassung 99.0.0 ist verfügbar; installiert ist 1.8.4.", "update-check"]);', fixture.databaseUrl], { windowsHide: true });
|
|
await page.reload();
|
|
assert.equal(await page.getByRole('link', { name: 'Update 99.0.0 verfügbar. Aktualisierung öffnen.', exact: true }).count(), 1);
|
|
assert.match(await page.locator('.version-update').getAttribute('href'), /system\?pruefen=1$/);
|
|
await page.locator('.shell-foot').screenshot({ path: `${report}/update-hint.png` });
|
|
checks.push('public icon and native anchor navigation without JavaScript; cached update notice by version');
|
|
assert.deepEqual(errors, []);
|
|
await writeFile(`${report}/result.json`, JSON.stringify({ checks, passed: true }, null, 2));
|
|
console.log('Navigation checks passed:', checks.length, 'groups.');
|
|
} catch (error) {
|
|
if (page) await page.screenshot({ path: `${report}/failure.png`, fullPage: true }).catch(() => {});
|
|
throw error;
|
|
} finally { await browser?.close(); server.kill(); }
|