Kundenportal Aufträge: Playwright-Prüfung mit Bildschirmfotos (Handy 390 px, Desktop 1440 px, dunkel)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
60a51c0f68
commit
d990155df9
2 files changed
+288
-2
No files matched your search
+2
-2
@@ -43,7 +43,7 @@ body.ab-seite {
|
||||
}
|
||||
.ab-seite .ab-fuss { flex-shrink: 0; padding: 8px 0 max(24px, env(safe-area-inset-bottom)); font-size: 13px; color: var(--ab-weich); }
|
||||
.ab-seite .ab-fuss .ab-innen { display: flex; gap: 16px; flex-wrap: wrap; }
|
||||
.ab-seite .ab-fuss a { color: var(--ab-weich); display: inline-block; min-height: 44px; line-height: 44px; }
|
||||
.ab-seite .ab-fuss a { color: var(--ab-weich); display: inline-flex; align-items: center; min-height: 44px; }
|
||||
|
||||
/* ------------------------------------------------------------------ Inhalt --- */
|
||||
.ab-seite .ab-pille, .ab-portal .ab-pille {
|
||||
@@ -71,7 +71,7 @@ body.ab-seite {
|
||||
.ab-seite .ab-gesamt strong, .ab-portal .ab-gesamt strong { font-weight: 800; }
|
||||
.ab-seite .ab-hinweis, .ab-portal .ab-hinweis { margin: 4px 0 0; font-size: 0.85rem; color: var(--ab-weich); }
|
||||
.ab-seite .ab-links, .ab-portal .ab-links { list-style: none; margin: 0 0 16px; padding: 0; }
|
||||
.ab-seite .ab-links a, .ab-portal .ab-links a { display: inline-block; min-height: 44px; line-height: 44px; font-weight: 600; }
|
||||
.ab-seite .ab-links a, .ab-portal .ab-links a { display: flex; align-items: center; min-height: 44px; line-height: 1.35; padding: 4px 0; font-weight: 600; }
|
||||
.ab-seite .ab-bestaetigt, .ab-portal .ab-bestaetigt {
|
||||
background: var(--ab-ok-bg); color: var(--ab-ok-text); border-radius: 12px; padding: 12px 16px; margin: 0 0 16px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
// Playwright-Prüfung der Kundenseiten zu Aufträgen (Strang L6): auftrag.html, Abschnitt „Aufträge“ im Dashboard, widerrufen.html.
|
||||
// Statischer Server für public/ + page.route-Nachbau der /api-Antworten; nichts verlässt den Rechner. Nicht Teil von npm test.
|
||||
// Aufruf: free -m; flock /tmp/claude-0/-root/dc97e1da-8b3d-40c0-9943-f8c37f910877/scratchpad/auftraege-schwer.lock nice node test/ui/auftraege-kundenportal.ui.mjs
|
||||
// Bildschirmfotos: $SHOTS (Standard: Scratchpad auftraege-l6/). Exit-Code 1, wenn eine Prüfung scheitert.
|
||||
import http from 'node:http'
|
||||
import { readFile, mkdir } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from '/root/.npm/_npx/db89d7302a373f10/node_modules/playwright-core/index.mjs'
|
||||
import { TOKEN, ab, privat, bestaetigt, eintrag } from '../auftraegeKundenportalDaten.js'
|
||||
|
||||
const pub = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../public')
|
||||
const SHOTS = process.env.SHOTS || '/tmp/claude-0/-root/dc97e1da-8b3d-40c0-9943-f8c37f910877/scratchpad/auftraege-l6'
|
||||
await mkdir(SHOTS, { recursive: true })
|
||||
const TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.webmanifest': 'application/manifest+json', '.woff2': 'font/woff2' }
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
try {
|
||||
const datei = path.join(pub, new URL(req.url, 'http://x').pathname)
|
||||
if (!datei.startsWith(pub + path.sep)) throw new Error('außerhalb von public/')
|
||||
res.writeHead(200, { 'content-type': TYPES[path.extname(datei)] || 'application/octet-stream' })
|
||||
res.end(await readFile(datei))
|
||||
} catch {
|
||||
res.writeHead(404)
|
||||
res.end('nicht gefunden')
|
||||
}
|
||||
})
|
||||
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
|
||||
const base = `http://127.0.0.1:${server.address().port}`
|
||||
|
||||
let failed = 0
|
||||
const check = (name, ok, detail = '') => {
|
||||
console.log(`${ok ? 'OK ' : 'FAIL'} ${name}${detail ? ` (${detail})` : ''}`)
|
||||
if (!ok) failed += 1
|
||||
}
|
||||
const norm = (text) => String(text).replace(/\s+/g, ' ').trim() // auch das geschützte Leerzeichen vor „€“
|
||||
const laufe = async (name, fn) => {
|
||||
try {
|
||||
await fn()
|
||||
} catch (err) {
|
||||
check(name, false, String(err.message).split('\n')[0])
|
||||
}
|
||||
}
|
||||
|
||||
let aufrufe = [] // alle /api-Anfragen der laufenden Prüfung
|
||||
async function mocke(page, handler) {
|
||||
aufrufe = []
|
||||
await page.unroute('**/api/**')
|
||||
await page.route('**/api/**', async (route) => {
|
||||
const req = route.request()
|
||||
const roh = req.postData()
|
||||
aufrufe.push({ pfad: new URL(req.url()).pathname, methode: req.method(), body: roh ? JSON.parse(roh) : undefined })
|
||||
const antwort = await handler(aufrufe.at(-1))
|
||||
if (antwort === 'abbrechen') return route.abort()
|
||||
const { status = 200, body = {} } = antwort || {}
|
||||
return route.fulfill({ status, contentType: 'application/json', body: JSON.stringify(body) })
|
||||
})
|
||||
}
|
||||
|
||||
const GET_AB = `/api/auftrag/${TOKEN}`
|
||||
const POST_AB = `/api/auftrag/${TOKEN}/bestaetigen`
|
||||
const KUNDE = { id: 'c1', code: '1001', name: 'Berta Berg', email: 'b@example.test' }
|
||||
const RECHNUNGEN = [
|
||||
{ id: 'in_1', source: 'stripe', number: 'WEBKLAR-0009', date: '2026-10-06', dueDate: '2026-10-20', amount: 500, balance: 500, status: 'Offen', paid: false, cancelled: false, overdue: false, portalUrl: 'https://invoice.stripe.test/i/in_1', hasPdf: true, projekt: 'Website Anelia Wolf' },
|
||||
{ id: 'in_2', source: 'stripe', number: 'H96T-0004', date: '2026-10-01', dueDate: '', amount: 25, balance: 0, status: 'Bezahlt', paid: true, cancelled: false, overdue: false, portalUrl: '', hasPdf: true, projekt: '' },
|
||||
]
|
||||
const AUFTRAEGE = [
|
||||
eintrag({ id: 'ab3', nummer: 'AB-2026-0003' }),
|
||||
eintrag({ id: 'ab2', ...bestaetigt(privat()), nummer: 'AB-2026-0002', titel: 'Logo und Visitenkarten', kannBestaetigen: false }),
|
||||
eintrag({ id: 'ab1', nummer: 'AB-2026-0001', titel: 'Wartung Oktober', status: 'storniert', kannBestaetigen: false, art: 'aufwand', positionen: [], summeCents: null, aufwandText: '100,00 € pro Stunde in Schritten von 15 Minuten.' }),
|
||||
]
|
||||
const dashboardApi = (me) => ({ pfad, methode }) => {
|
||||
if (pfad === '/api/auth/me') return { body: me }
|
||||
if (pfad === '/api/invoices') return { body: { invoices: RECHNUNGEN, incomplete: false } }
|
||||
// wie L5: in der Kundenansicht ist kannBestaetigen immer false
|
||||
if (pfad === '/api/auftraege') return { body: { aktiv: true, auftraege: AUFTRAEGE.map((a) => (me.viewAs ? { ...a, kannBestaetigen: false } : a)) } }
|
||||
if (methode === 'POST' && pfad === '/api/auftraege/ab3/bestaetigen') return { body: { auftrag: bestaetigt(ab({ nummer: 'AB-2026-0003' })) } }
|
||||
if (pfad === '/api/projects') return { body: { projects: [] } }
|
||||
if (pfad === '/api/features') return { body: { features: [] } }
|
||||
if (pfad === '/api/activity') return { body: { tickets: [] } }
|
||||
if (pfad === '/api/contact') return { body: { name: 'WEBklar-Team', responseTime: 'Antwort meist < 1 Werktag' } }
|
||||
if (pfad === '/api/chat/unread-count') return { body: { unread: 0 } }
|
||||
if (pfad === '/api/chat/messages') return { body: { messages: [], unread: 0 } }
|
||||
return { body: {} }
|
||||
}
|
||||
const postBody = () => JSON.stringify(aufrufe.filter((a) => a.methode === 'POST').at(-1)?.body)
|
||||
|
||||
const browser = await chromium.launch().catch(() => chromium.launch({ executablePath: '/root/.cache/ms-playwright/chromium-1234/chrome-linux64/chrome' }))
|
||||
try {
|
||||
for (const [label, viewport, mobil] of [['handy', { width: 390, height: 844 }, true], ['desktop', { width: 1440, height: 900 }, false]]) {
|
||||
const ctx = await browser.newContext({ viewport, deviceScaleFactor: mobil ? 2 : 1, isMobile: mobil, hasTouch: mobil, locale: 'de-DE', timezoneId: 'Europe/Berlin' })
|
||||
const page = await ctx.newPage()
|
||||
page.setDefaultTimeout(30000)
|
||||
const jsFehler = []
|
||||
page.on('pageerror', (e) => jsFehler.push(e.message))
|
||||
const foto = async (name) => {
|
||||
// Einblend-Animationen des Dashboards abwarten; das Portal scrollt im Dokument, fullPage zeigt dort nur die Kopfzeile:
|
||||
// darum das Fenster kurz auf die Seitenhöhe ziehen (höchstens 3200 px)
|
||||
await page.evaluate(() => Promise.all(document.getAnimations().filter((a) => a.effect.getComputedTiming().iterations !== Infinity).map((a) => a.finished.catch(() => {}))))
|
||||
const fenster = page.viewportSize()
|
||||
const hoehe = await page.evaluate(() => Math.min(Math.max(document.documentElement.scrollHeight, window.innerHeight), 3200))
|
||||
await page.setViewportSize({ width: fenster.width, height: hoehe })
|
||||
await page.screenshot({ path: `${SHOTS}/${name}-${label}.png`, fullPage: true })
|
||||
await page.setViewportSize(fenster)
|
||||
}
|
||||
const zustand = (z) => page.waitForSelector(`#ab-wurzel[data-zustand="${z}"]`)
|
||||
const ohneUeberlauf = (selektor) => page.evaluate((s) => { const el = document.querySelector(s); return el.scrollWidth <= el.clientWidth + 1 }, selektor)
|
||||
const oeffne = async (pfad, handler) => {
|
||||
await mocke(page, handler)
|
||||
await page.goto('about:blank') // frisch laden, auch wenn sich nur das Fragment unterscheidet
|
||||
await page.goto(`${base}${pfad}`)
|
||||
}
|
||||
const oeffneAuftrag = (handler, adresse = `#t=${TOKEN}`) => oeffne(`/auftrag.html${adresse}`, handler) // K31 verlinkt #t=
|
||||
const alsAb = (a, neu) => ({ pfad, methode }) => {
|
||||
if (methode === 'GET' && pfad === GET_AB) return { body: { auftrag: a } }
|
||||
if (methode === 'POST' && pfad === POST_AB) return { body: { auftrag: neu } }
|
||||
}
|
||||
|
||||
await laufe(`${label} unternehmen`, async () => {
|
||||
await oeffneAuftrag(alsAb(ab(), bestaetigt(ab())))
|
||||
await zustand('offen')
|
||||
check(`${label} offen_titel`, norm(await page.textContent('h1')) === 'Auftragsbestätigung AB-2026-0002')
|
||||
check(`${label} formular_sie_form`, norm(await page.textContent('legend')) === 'Ihre Bestätigung')
|
||||
check(`${label} paragraf_19_aus_der_api`, norm(await page.textContent('[data-hinweis]')) === 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.')
|
||||
check(`${label} offen_positionen_und_summe`, (await page.locator('[data-position]').count()) === 2 && norm(await page.innerText('[data-gesamt]')) === 'Gesamtpreis 800,00 €')
|
||||
check(`${label} offen_links`, (await page.getAttribute('[data-link="pdf"]', 'href')) === `/api/auftrag/${TOKEN}/pdf` && (await page.getAttribute('[data-link="agb"]', 'href')) === '/rechtliches/agb-2026-09-30.pdf')
|
||||
check(`${label} offen_kein_zweites_haekchen`, (await page.locator('[data-feld="fruehbeginn"]').count()) === 0)
|
||||
check(`${label} fusszeile`, (await page.locator('footer a').allInnerTexts()).join('|') === 'Datenschutz|Impressum')
|
||||
check(`${label} kein_seitlicher_ueberlauf`, await ohneUeberlauf('html'))
|
||||
const knopf = page.locator('[data-aktion="bestaetigen"]')
|
||||
check(`${label} knopf_gesperrt_ohne_haken_und_gross_genug`, (await knopf.isDisabled()) && (await knopf.boundingBox()).height >= 44)
|
||||
await foto('auftrag-offen-unternehmen')
|
||||
await page.check('[data-feld="bestaetigung"]')
|
||||
check(`${label} knopf_frei_mit_haken`, await knopf.isEnabled())
|
||||
await knopf.click()
|
||||
await zustand('bestaetigt')
|
||||
check(`${label} post_koerper`, postBody() === JSON.stringify({ bestaetigt: true, fruehbeginn: false }))
|
||||
check(`${label} bestaetigt_zeit`, norm(await page.innerText('[data-bestaetigt]')) === 'Bestätigt am 07.10.2026, 10:15 Uhr')
|
||||
check(`${label} token_nicht_im_text`, !(await page.innerText('body')).includes(TOKEN))
|
||||
})
|
||||
|
||||
await laufe(`${label} rueckfall`, async () => {
|
||||
await oeffneAuftrag(alsAb(ab()), `?t=${TOKEN}`)
|
||||
await zustand('offen')
|
||||
check(`${label} rueckfall_t_in_der_suche`, aufrufe.some((a) => a.methode === 'GET' && a.pfad === GET_AB))
|
||||
})
|
||||
|
||||
await laufe(`${label} privat`, async () => {
|
||||
await oeffneAuftrag(alsAb(privat(), bestaetigt(privat())))
|
||||
await zustand('offen')
|
||||
const frueh = page.locator('[data-feld="fruehbeginn"]')
|
||||
check(`${label} privat_zweites_haekchen_frei_waehlbar`, (await frueh.isVisible()) && !(await frueh.isChecked()))
|
||||
check(`${label} privat_kein_seitlicher_ueberlauf`, await ohneUeberlauf('html'))
|
||||
await foto('auftrag-offen-privat')
|
||||
await page.check('[data-feld="bestaetigung"]')
|
||||
await page.click('[data-aktion="bestaetigen"]')
|
||||
await zustand('bestaetigt')
|
||||
check(`${label} privat_ohne_fruehbeginn`, postBody() === JSON.stringify({ bestaetigt: true, fruehbeginn: false }))
|
||||
check(`${label} privat_fruehester_beginn`, norm(await page.innerText('[data-bestaetigt]')) === 'Bestätigt am 07.10.2026, 10:15 Uhr Wir beginnen frühestens am 21.10.2026.')
|
||||
await foto('auftrag-bestaetigt-privat')
|
||||
})
|
||||
|
||||
await laufe(`${label} aufwand`, async () => {
|
||||
await oeffneAuftrag(alsAb(ab({ art: 'aufwand', positionen: [], summeCents: null, aufwandText: '100,00 € pro Stunde in Schritten von 15 Minuten. Geschätzter Umfang: etwa 10 Stunden (unverbindlich).', zahlungsplanText: 'Monatlich nach Aufwand.' })))
|
||||
await zustand('offen')
|
||||
check(`${label} aufwand_text_statt_tabelle`, (await page.locator('[data-aufwand]').count()) === 1 && (await page.locator('table').count()) === 0)
|
||||
await foto('auftrag-aufwand')
|
||||
})
|
||||
|
||||
await laufe(`${label} fehlerzustaende`, async () => {
|
||||
await oeffneAuftrag(() => ({ status: 404, body: { error: 'Dieser Link ist ungültig oder abgelaufen.' } }))
|
||||
await zustand('ungueltig')
|
||||
check(`${label} ungueltig_kontaktweg`, (await page.locator('a[href="mailto:support@webklar.com"]').count()) === 1)
|
||||
await foto('auftrag-ungueltig')
|
||||
await oeffneAuftrag(() => ({ status: 410, body: { error: 'Diese Auftragsbestätigung wurde ersetzt.' } }))
|
||||
await zustand('beendet')
|
||||
check(`${label} beendet_titel`, norm(await page.textContent('h1')) === 'Diese Auftragsbestätigung wurde ersetzt.')
|
||||
await foto('auftrag-beendet')
|
||||
await oeffneAuftrag(() => ({ status: 429, body: {} }))
|
||||
await zustand('limit')
|
||||
let versuche = 0
|
||||
await oeffneAuftrag(({ pfad }) => {
|
||||
if (pfad !== GET_AB) return undefined
|
||||
versuche += 1
|
||||
return versuche === 1 ? { status: 500, body: { error: 'Interner Fehler.' } } : { body: { auftrag: ab() } }
|
||||
})
|
||||
await zustand('fehler')
|
||||
await foto('auftrag-fehler')
|
||||
await page.click('[data-aktion="erneut"]')
|
||||
await zustand('offen')
|
||||
await oeffneAuftrag(() => 'abbrechen')
|
||||
await zustand('fehler')
|
||||
check(`${label} netzfehler_ohne_browsertext`, !/Failed to fetch|NetworkError|fetch failed/i.test(await page.innerText('body')))
|
||||
await oeffne('/auftrag.html', () => ({}))
|
||||
await zustand('ungueltig')
|
||||
check(`${label} ohne_token_keine_anfrage`, aufrufe.length === 0)
|
||||
})
|
||||
|
||||
await laufe(`${label} portal`, async () => {
|
||||
await oeffne('/dashboard.html#rechnungen', dashboardApi({ authenticated: true, customer: KUNDE }))
|
||||
await page.waitForSelector('#orders-section:not(.hidden) .ab-karte')
|
||||
check(`${label} portal_reihenfolge`, JSON.stringify(await page.$$eval('.ab-karte', (k) => k.map((x) => x.getAttribute('data-auftrag')))) === JSON.stringify(['ab3', 'ab2', 'ab1']))
|
||||
check(`${label} portal_du_form`, norm(await page.textContent('#orders-section > p')) === 'Deine Auftragsbestätigungen. Offene Aufträge bestätigst du hier mit einem Klick.' && norm(await page.textContent('.ab-karte[data-auftrag="ab3"] legend')) === 'Deine Bestätigung')
|
||||
check(`${label} portal_projekt_unter_rechnungsnummer`, norm(await page.textContent('.inv-no .inv-project')) === 'Website Anelia Wolf')
|
||||
check(`${label} portal_offene_ab_aufgeklappt`, (await page.locator('.ab-karte[data-auftrag="ab3"] details').getAttribute('open')) !== null)
|
||||
check(`${label} portal_kein_seitlicher_ueberlauf`, await ohneUeberlauf('#orders-section'))
|
||||
await foto('portal-auftraege')
|
||||
await page.check('.ab-karte[data-auftrag="ab3"] [data-feld="bestaetigung"]')
|
||||
await page.click('.ab-karte[data-auftrag="ab3"] [data-aktion="bestaetigen"]')
|
||||
await page.waitForSelector('.ab-karte[data-auftrag="ab3"] [data-bestaetigt]')
|
||||
check(`${label} portal_post_und_status`, aufrufe.some((a) => a.methode === 'POST' && a.pfad === '/api/auftraege/ab3/bestaetigen') && postBody() === JSON.stringify({ bestaetigt: true, fruehbeginn: false }) && norm(await page.textContent('.ab-karte[data-auftrag="ab3"] .ab-pille')) === 'Bestätigt')
|
||||
})
|
||||
|
||||
await laufe(`${label} ansichtsmodus`, async () => {
|
||||
const me = { authenticated: true, role: 'admin', viewAs: true, viewId: 'a'.repeat(32), viewExpiresAt: Date.now() + 3_600_000, admin: { name: 'Andrej', email: 'andrej@webklar.com', isAdmin: false }, customer: KUNDE }
|
||||
await oeffne('/dashboard.html#rechnungen', dashboardApi(me))
|
||||
await page.waitForSelector('#orders-section:not(.hidden) .ab-karte')
|
||||
const karte = '.ab-karte[data-auftrag="ab3"]'
|
||||
check(`${label} ansicht_gesperrt`, (await page.locator(`${karte} [data-aktion="bestaetigen"]`).isDisabled()) && (await page.locator(`${karte} [data-feld="bestaetigung"]`).isDisabled()))
|
||||
check(`${label} ansicht_hinweis`, norm(await page.textContent(`${karte} [data-gesperrt]`)) === 'Im Ansichtsmodus kann der Auftrag nicht bestätigt werden. Die Bestätigung gibt der Kunde selbst ab.')
|
||||
await foto('portal-auftraege-ansichtsmodus')
|
||||
})
|
||||
|
||||
await laufe(`${label} widerruf`, async () => {
|
||||
const api = ({ pfad, methode }) => {
|
||||
if (pfad === '/api/auth/me') return { body: { authenticated: false } }
|
||||
if (methode === 'POST' && pfad === '/api/abo/public/withdraw') return { body: { receivedAt: '2026-10-07T08:15:00.000Z' } }
|
||||
}
|
||||
const oeffneWiderruf = async () => {
|
||||
await oeffne('/widerrufen.html', api)
|
||||
await page.waitForFunction(() => document.getElementById('ab-nummer-wrap')?.classList.contains('hidden'))
|
||||
}
|
||||
const absenden = async (nummer) => {
|
||||
await page.check('input[name="order"][value="projekt"]')
|
||||
await page.fill('#name', 'Berta Berg')
|
||||
await page.fill('#email', 'b@example.test')
|
||||
if (nummer !== undefined) await page.fill('#abNummer', nummer)
|
||||
await page.click('button[type="submit"]')
|
||||
}
|
||||
await oeffneWiderruf()
|
||||
check(`${label} widerruf_feld_zuerst_versteckt`, !(await page.locator('#ab-nummer-wrap').isVisible()))
|
||||
await absenden('AB-2026-01')
|
||||
check(`${label} widerruf_feld_nur_bei_projektauftrag`, await page.locator('#ab-nummer-wrap').isVisible())
|
||||
check(`${label} widerruf_unpassende_nummer_gestoppt`, aufrufe.every((a) => a.methode !== 'POST') && norm(await page.textContent('#form-error')).startsWith('Die Nummer sieht anders aus'))
|
||||
await foto('widerrufen-projekt')
|
||||
await page.fill('#abNummer', 'ab 2026 0001')
|
||||
await page.click('button[type="submit"]')
|
||||
await page.waitForSelector('#done:not(.hidden)')
|
||||
const mitNummer = aufrufe.find((a) => a.methode === 'POST')?.body
|
||||
check(`${label} widerruf_nummer_vereinheitlicht`, mitNummer?.order === 'projekt' && mitNummer.abNummer === 'AB-2026-0001', JSON.stringify(mitNummer))
|
||||
check(`${label} widerruf_bestaetigung_nennt_nummer`, norm(await page.textContent('#done-list')).includes('Nummer der AuftragsbestätigungAB-2026-0001'))
|
||||
await oeffneWiderruf()
|
||||
await absenden() // ohne Nummer: freiwillig
|
||||
await page.waitForSelector('#done:not(.hidden)')
|
||||
const ohneNummer = aufrufe.filter((a) => a.methode === 'POST').at(-1)?.body
|
||||
check(`${label} widerruf_ohne_nummer_geht_durch`, ohneNummer?.order === 'projekt' && ohneNummer.abNummer === '', JSON.stringify(ohneNummer))
|
||||
await oeffneWiderruf()
|
||||
await page.fill('#name', 'Berta Berg')
|
||||
await page.fill('#email', 'b@example.test')
|
||||
await page.click('button[type="submit"]')
|
||||
await page.waitForSelector('#done:not(.hidden)')
|
||||
const andere = aufrufe.filter((a) => a.methode === 'POST').at(-1)?.body
|
||||
check(`${label} widerruf_andere_art_ohne_nummer`, andere?.order === 'abschluss' && andere.abNummer === '', JSON.stringify(andere))
|
||||
})
|
||||
|
||||
check(`${label} keine_js_fehler`, jsFehler.length === 0, jsFehler.join(' | '))
|
||||
await ctx.close()
|
||||
}
|
||||
|
||||
// Dunkler Modus (Systemeinstellung): die Seite folgt den dunklen Farben der Mailvorlagen
|
||||
const dunkel = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true, locale: 'de-DE', colorScheme: 'dark' })
|
||||
const seite = await dunkel.newPage()
|
||||
await laufe('dunkel', async () => {
|
||||
await mocke(seite, ({ pfad }) => (pfad === GET_AB ? { body: { auftrag: privat() } } : undefined))
|
||||
await seite.goto(`${base}/auftrag.html#t=${TOKEN}`)
|
||||
await seite.waitForSelector('#ab-wurzel[data-zustand="offen"]')
|
||||
check('dunkel hintergrund', (await seite.evaluate(() => getComputedStyle(document.body).backgroundColor)) === 'rgb(22, 22, 21)')
|
||||
await seite.screenshot({ path: `${SHOTS}/auftrag-offen-privat-dunkel-handy.png`, fullPage: true })
|
||||
})
|
||||
await dunkel.close()
|
||||
} finally {
|
||||
await browser.close()
|
||||
server.close()
|
||||
}
|
||||
console.log(failed ? `${failed} Check(s) fehlgeschlagen` : 'alle Checks ok')
|
||||
process.exit(failed ? 1 : 0)
|
||||
Reference in new issue
Block a user