Files
kantarciandClaude Opus 5 ace6c50743 Raise loaded documents to the schema their blocks need, and bring the old browser checks back
The kant-it.de header used the hamburger switch at schema 19, so the server refused its
preview and save; the editor now raises a loaded document to the version its blocks and
properties require. The admin, button and navigation checks select blocks through the
structure tab, find the motion button by id and expect what the site's motion setting says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:21:35 +02:00

147 lines
11 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/buttons'; await mkdir(report, { recursive: true });
let browser, page;
const checks = [], errors = [];
const buttonStyle = node => {
const style = getComputedStyle(node);
return Object.fromEntries(['backgroundColor', 'color', 'borderRadius', 'borderColor', 'borderWidth', 'borderStyle', 'paddingTop', 'paddingRight', 'fontFamily', 'fontSize', 'fontWeight', 'letterSpacing', 'textTransform', 'lineHeight', 'textAlign'].map(key => [key, style[key]]));
};
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));
// Ein Baustein wird im Reiter „Struktur“ gewählt und mit „Bearbeiten“ geöffnet - die
// Seitenleiste zeigt seit 1.11 nur eine Ansicht auf einmal.
const selectBlock = async id => {
await page.getByRole('tab', { name: 'Struktur', exact: true }).click();
await page.locator(`#structure [data-block-id="${id}"]`).click();
await page.locator(`#structure [data-edit-block="${id}"]`).click();
};
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.unshift({ id: 'qa-buttons-section', type: 'section', children: [
{ id: 'qa-button-default', type: 'button', props: { text: 'Kontakt aufnehmen', href: '#kontakt' } },
{ id: 'qa-button', type: 'button', props: { text: 'Mehr erfahren', href: '/kontakt' }, style: { desktop: { margin: { left: '12px' }, fontSize: '17px' } } }
] });
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/theme');
await page.locator('#buttons-fontSize').fill('18');
await page.getByRole('button', { name: 'Standardbutton Rahmen · Pille', exact: true }).click();
assert.equal(await page.locator('#buttons-shape').inputValue(), 'pill');
assert.equal(await page.locator('#buttons-appearance').inputValue(), 'outline');
assert.equal(await page.locator('#button-defaults .button-state .cms-button').first().evaluate(el => getComputedStyle(el).borderRadius), '999px');
await page.locator('#button-defaults').screenshot({ path: `${report}/theme.png` });
await page.getByRole('button', { name: 'Design speichern', exact: true }).click();
assert.equal(await page.locator('#buttons-shape').inputValue(), 'pill');
assert.equal(await page.locator('#buttons-fontSize').inputValue(), '18');
checks.push('global typography, visual presets, normal/hover preview and actual form save including empty palette rows');
await page.goto(`${origin}/admin/pages/${fixture.homeId}`);
await selectBlock('qa-button');
await page.getByRole('button', { name: 'Button-Vorlage Abgerundet', exact: true }).click();
const waitVariant = async (id, radius, background) => page.waitForFunction(({id,radius,background}) => {
const doc = document.querySelector('#preview')?.contentDocument, el = doc?.getElementById(id);
if (!el) return false;
const s = doc.defaultView.getComputedStyle(el);
return s.borderRadius === radius && (background === undefined || (s.backgroundColor === 'rgba(0, 0, 0, 0)') === background);
}, { id, radius, background });
await waitVariant('qa-button', '8px', false);
await page.locator('#undo').click(); await waitVariant('qa-button', '999px', true);
await page.locator('#redo').click(); await waitVariant('qa-button', '8px', false);
await page.locator('[data-viewport="tablet"]').click();
await page.getByRole('button', { name: 'Button-Vorlage Rahmen · eckig', exact: true }).click();
await waitVariant('qa-button', '0px', true);
await page.locator('[data-viewport="mobile"]').click();
await page.getByRole('button', { name: 'Button-Vorlage Pille', exact: true }).click();
await waitVariant('qa-button', '999px', false);
await page.locator('#button-reset').click(); await waitVariant('qa-button', '0px', true);
await page.locator('#undo').click(); await waitVariant('qa-button', '999px', false);
await page.locator('[data-viewport="desktop"]').click(); await waitVariant('qa-button', '8px', false);
await page.locator('.button-designer').screenshot({ path: `${report}/editor-controls.png` });
await page.locator('#save').click(); await page.locator('#save-status').filter({ hasText: 'Entwurf gespeichert' }).waitFor();
await page.reload(); await selectBlock('qa-button'); await waitVariant('qa-button', '8px', false);
const stored = (await (await context.request.get(api)).json()).document;
assert.equal(stored.schemaVersion, 21);
assert.deepEqual(stored.blocks[0].children[1].props, { text: 'Mehr erfahren', href: '/kontakt' });
assert.equal(stored.blocks[0].children[1].style.desktop.margin.left, '12px');
assert.equal(stored.blocks[0].children[1].style.desktop.fontSize, '17px');
checks.push('preset selection, device inheritance/reset, undo/redo, save/reload, preserved content and unrelated styling');
await page.locator('#publish').click(); await page.locator('#confirm-publish').click();
await page.locator('#save-status').filter({ hasText: 'Veröffentlicht' }).waitFor();
const plain = await browser.newContext({ javaScriptEnabled: false }); const publicPage = await plain.newPage();
for (const [width, radius, transparent] of [[1600, '8px', false], [900, '0px', true], [390, '999px', false]]) {
await publicPage.setViewportSize({ width, height: 900 }); await publicPage.goto(origin);
const necessaryCookies = publicPage.getByRole('button', { name: 'Nur notwendige', exact: true });
if (await necessaryCookies.count()) await necessaryCookies.click();
const values = await publicPage.locator('#qa-button').evaluate(el => { const s=getComputedStyle(el);return { radius:s.borderRadius, bg:s.backgroundColor, font:s.fontSize }; });
assert.equal(values.radius, radius); assert.equal(values.bg === 'rgba(0, 0, 0, 0)', transparent); assert.equal(values.font, '17px');
assert.equal(await publicPage.locator('#qa-button-default').evaluate(el => getComputedStyle(el).fontSize), '18px');
const submit = publicPage.locator('.block-form button[type=submit]').first();
const standard = publicPage.locator('#qa-button-default');
assert.deepEqual(await submit.evaluate(buttonStyle), await standard.evaluate(buttonStyle), 'Form submit uses global shape, appearance, size, typography and colors');
assert.ok(await submit.evaluate(node => node.getBoundingClientRect().width < node.form.getBoundingClientRect().width), 'Submit keeps natural button width');
await submit.hover(); await publicPage.waitForTimeout(350);
const hoverStyle = await submit.evaluate(buttonStyle);
await standard.hover(); await publicPage.waitForTimeout(350);
assert.deepEqual(hoverStyle, await standard.evaluate(buttonStyle), 'Submit inherits the global hover colors');
await submit.focus();
assert.equal(await submit.evaluate(node => getComputedStyle(node).outlineStyle), 'solid', 'Submit has a keyboard focus indicator');
await submit.screenshot({ path: `${report}/submit-${width}.png` });
await publicPage.locator('#qa-buttons-section').screenshot({ path: `${report}/public-${width}.png` });
}
await page.goto(origin + '/admin/theme');
await page.locator('#buttons-shape').selectOption('rounded');
await page.locator('#buttons-appearance').selectOption('solid');
await page.locator('#buttons-size').selectOption('large');
await page.locator('#buttons-fontSize').fill('20');
for (const [key, hex] of [['backgroundColor', '#124b36'], ['color', '#ffffff'], ['hoverBackgroundColor', '#092c20'], ['hoverColor', '#fff4cc']]) {
await page.locator(`#buttons-${key}`).click();
await page.locator(`#buttons-${key}-hex`).fill(hex);
await page.locator(`#buttons-${key}-hex`).press('Enter');
await page.keyboard.press('Escape');
}
await page.getByRole('button', { name: 'Design speichern', exact: true }).click();
await publicPage.goto(origin);
const updatedSubmit = publicPage.locator('.block-form button[type=submit]').first();
const updatedDefault = publicPage.locator('#qa-button-default');
assert.deepEqual(await updatedSubmit.evaluate(buttonStyle), await updatedDefault.evaluate(buttonStyle), 'A later change to solid global defaults updates existing forms');
assert.equal(await updatedSubmit.evaluate(el => getComputedStyle(el).backgroundColor), 'rgb(18, 75, 54)');
await updatedSubmit.hover(); await publicPage.waitForTimeout(350);
const updatedHover = await updatedSubmit.evaluate(buttonStyle);
await updatedDefault.hover(); await publicPage.waitForTimeout(350);
assert.deepEqual(updatedHover, await updatedDefault.evaluate(buttonStyle), 'Custom global hover colors update existing forms');
await publicPage.setViewportSize({ width: 390, height: 1600 });
await updatedSubmit.scrollIntoViewIfNeeded();
await publicPage.locator('.block-form').first().screenshot({ path: `${report}/form-standard-button.png` });
await plain.close(); checks.push('published global defaults, form submit including hover/focus/natural width, solid and outline theme changes, and local responsive variants without JavaScript');
assert.deepEqual(errors, []);
await writeFile(`${report}/result.json`, JSON.stringify({ checks, passed: true }, null, 2));
console.log('Button 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(); }