All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 5m15s
159 lines
9.4 KiB
TypeScript
159 lines
9.4 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
|
import AxeBuilder from '@axe-core/playwright'
|
|
|
|
async function login(page: Page, email='super@example.org') {
|
|
await page.goto('http://127.0.0.1:3219/auth/signin')
|
|
await page.getByLabel('E-Mail-Adresse',{exact:true}).fill(email)
|
|
await page.getByLabel('Passwort',{exact:true}).fill('Test-password-only-2026')
|
|
await page.getByRole('button',{name:'Anmelden',exact:true}).click()
|
|
await expect(page).toHaveURL(/\/admin$/)
|
|
}
|
|
test('public search announces zero results and reset restores focus',async({page})=>{
|
|
await page.goto('/')
|
|
const search=page.getByRole('searchbox')
|
|
await search.fill('zzzznonexistent')
|
|
await expect(page.getByRole('status')).toContainText('0 Erklärungen')
|
|
await page.getByRole('button',{name:'Suche zurücksetzen'}).first().click()
|
|
await expect(search).toBeFocused()
|
|
await expect(page.getByRole('link',{name:/Bauantrag/})).toBeVisible()
|
|
for(const width of [320,1280]){
|
|
await page.setViewportSize({width,height:800})
|
|
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
|
const results=await new AxeBuilder({page}).withTags(['wcag2a','wcag2aa','wcag21aa']).analyze()
|
|
expect(results.violations).toEqual([])
|
|
}
|
|
})
|
|
test('login errors are announced and password button changes its name',async({page})=>{
|
|
await page.goto('/auth/signin')
|
|
await page.getByRole('button',{name:'Passwort anzeigen'}).click()
|
|
await expect(page.getByRole('button',{name:'Passwort verbergen'})).toBeVisible()
|
|
await page.getByLabel('E-Mail-Adresse',{exact:true}).fill('missing@example.org')
|
|
await page.getByLabel('Passwort',{exact:true}).fill('wrong-password')
|
|
await page.getByRole('button',{name:'Anmelden',exact:true}).click()
|
|
await expect(page.getByRole('alert').filter({hasText:'Ungültige Anmeldedaten'})).toBeVisible()
|
|
})
|
|
test('dialogs close without blocking interaction; required pages cannot be cleared',async({page})=>{
|
|
await login(page)
|
|
for(const name of ['Rechtliche Seiten','Standardtexte pflegen','Benutzer verwalten']){
|
|
await page.getByRole('button',{name:'Benutzermenü'}).click()
|
|
await page.getByRole('menuitem',{name}).click()
|
|
await expect(page.getByRole('dialog')).toBeVisible()
|
|
await page.keyboard.press('Escape')
|
|
await expect(page.getByRole('dialog')).toHaveCount(0)
|
|
await expect(page.getByRole('button',{name:'Benutzermenü'})).toBeFocused()
|
|
}
|
|
const previous=await (await page.request.get('/api/site-pages')).json()
|
|
expect((await page.request.put('/api/site-pages',{data:{...previous,accessibility:''}})).status()).toBe(400)
|
|
expect((await page.request.get('/api/site-pages')).ok()).toBe(true)
|
|
const users=await (await page.request.get('/api/users')).json()
|
|
const self=users.find((user: {id:number;email:string})=>user.email==='super@example.org')
|
|
expect((await page.request.put('/api/users/'+self.id,{data:{isActive:false}})).status()).toBe(400)
|
|
})
|
|
test('editor cannot promote itself and an existing session is revoked after suspension',async({browser})=>{
|
|
const adminContext=await browser.newContext()
|
|
const editorContext=await browser.newContext()
|
|
const admin=await adminContext.newPage(), editor=await editorContext.newPage()
|
|
await login(admin)
|
|
await login(editor,'editor@example.org')
|
|
expect((await editor.request.post('/api/users',{data:{email:'intruder@example.org',password:'Test-password-only-2026',role:'SUPER_ADMIN'}})).ok()).toBe(false)
|
|
const users=await (await admin.request.get('/api/users')).json()
|
|
const user=users.find((user: {id:number;email:string})=>user.email==='editor@example.org')
|
|
expect((await admin.request.put('/api/users/'+user.id,{data:{isActive:false}})).ok()).toBe(true)
|
|
expect((await editor.request.put('/api/site-pages',{data:{}})).status()).toBe(401)
|
|
await editor.goto('/admin')
|
|
await expect(editor).toHaveURL(/\/auth\/signin/)
|
|
await adminContext.close(); await editorContext.close()
|
|
})
|
|
|
|
test('public information and admin forms remain accessible at narrow widths',async({page})=>{
|
|
await page.setViewportSize({width:320,height:640})
|
|
for(const route of ['/auth/signin','/erklaerung/test-bauantrag','/barrierefreiheit','/leichte-sprache','/gebaerdensprache']){
|
|
await page.goto(route)
|
|
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
|
await page.waitForFunction(() => document.getAnimations().every(a => a.playState !== 'running' || a.effect?.getTiming().iterations === Infinity))
|
|
expect((await new AxeBuilder({page}).withTags(['wcag2a','wcag2aa','wcag21aa']).analyze()).violations).toEqual([])
|
|
}
|
|
await login(page)
|
|
for(const name of ['Rechtliche Seiten','Standardtexte pflegen','Benutzer verwalten']){
|
|
await page.getByRole('button',{name:'Benutzermenü'}).click()
|
|
await page.getByRole('menuitem',{name}).click()
|
|
await expect(page.getByRole('dialog')).toBeVisible()
|
|
await page.waitForFunction(() => document.getAnimations().every(a => a.playState !== 'running' || a.effect?.getTiming().iterations === Infinity))
|
|
expect((await new AxeBuilder({page}).withTags(['wcag2a','wcag2aa','wcag21aa']).analyze()).violations).toEqual([])
|
|
await page.keyboard.press('Escape')
|
|
}
|
|
await page.getByRole('button',{name:'Neu',exact:true}).click()
|
|
await expect(page.getByRole('dialog')).toBeVisible()
|
|
await page.getByRole('button',{name:/Erklärung erstellen|Speichern|Erstellen/}).last().click()
|
|
await expect(page.getByRole('region',{name:'Fehler beim Speichern'})).toBeFocused()
|
|
await page.waitForFunction(() => document.getAnimations().every(a => a.playState !== 'running' || a.effect?.getTiming().iterations === Infinity))
|
|
expect((await new AxeBuilder({page}).withTags(['wcag2a','wcag2aa','wcag21aa']).analyze()).violations).toEqual([])
|
|
await page.getByRole('button',{name:'Zum ersten fehlerhaften Feld'}).click()
|
|
const title=page.getByRole('textbox',{name:/Titel der Datenschutzerklärung/})
|
|
await expect(title).toBeFocused()
|
|
await title.fill('Test der erreichbaren Eingabe')
|
|
expect((await title.boundingBox())!.height).toBeGreaterThan(20)
|
|
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
|
await page.screenshot({path:'test-results/admin-mobile.png',fullPage:true})
|
|
await page.keyboard.press('Escape')
|
|
})
|
|
|
|
test('dark mode and nested user dialogs remain accessible',async({page})=>{
|
|
await page.addInitScript(()=>localStorage.setItem('theme','dark'))
|
|
await page.setViewportSize({width:320,height:640})
|
|
for(const route of ['/','/barrierefreiheit','/auth/signin']){
|
|
await page.goto(route)
|
|
await expect(page.locator('html')).toHaveClass(/dark/)
|
|
await page.waitForFunction(()=>document.getAnimations().every(a=>a.playState!=='running'||a.effect?.getTiming().iterations===Infinity))
|
|
expect((await new AxeBuilder({page}).withTags(['wcag2a','wcag2aa','wcag21aa']).analyze()).violations).toEqual([])
|
|
}
|
|
await login(page)
|
|
await page.getByRole('button',{name:'Benutzermenü'}).click()
|
|
await page.getByRole('menuitem',{name:'Benutzer verwalten'}).click()
|
|
await page.getByRole('button',{name:'Neuer Benutzer',exact:true}).click()
|
|
const dialog=page.getByRole('dialog',{name:'Neuer Benutzer',exact:true})
|
|
await expect(dialog).toBeVisible()
|
|
await page.waitForFunction(()=>document.getAnimations().every(a=>a.playState!=='running'||a.effect?.getTiming().iterations===Infinity))
|
|
expect(await dialog.evaluate(el=>el.scrollWidth<=el.clientWidth)).toBe(true)
|
|
expect((await new AxeBuilder({page}).withTags(['wcag2a','wcag2aa','wcag21aa']).analyze()).violations).toEqual([])
|
|
await page.keyboard.press('Escape')
|
|
await expect(page.getByRole('button',{name:'Neuer Benutzer',exact:true})).toBeFocused()
|
|
await page.keyboard.press('Escape')
|
|
await expect(page.getByRole('button',{name:'Benutzermenü'})).toBeFocused()
|
|
})
|
|
|
|
test('keyboard navigation, text resizing and spacing preserve access', async ({page}) => {
|
|
await page.goto('/')
|
|
await page.keyboard.press('Tab')
|
|
await expect(page.getByRole('link',{name:'Zum Inhalt springen'})).toBeFocused()
|
|
await page.keyboard.press('Enter')
|
|
await expect(page.locator('main')).toBeFocused()
|
|
await page.setViewportSize({width:1280,height:800})
|
|
await page.addStyleTag({content:'html { font-size:200% !important; } * { line-height:1.5 !important; letter-spacing:.12em !important; word-spacing:.16em !important; } p { margin-bottom:2em !important; }'})
|
|
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
|
const search=page.getByRole('searchbox')
|
|
await search.fill('Bauantrag')
|
|
await expect(page.getByRole('link',{name:/Bauantrag/})).toBeVisible()
|
|
await page.getByRole('link',{name:/Bauantrag/}).click()
|
|
await expect(page.getByRole('heading',{name:'Bauantrag',exact:true})).toBeVisible()
|
|
await login(page)
|
|
await page.getByRole('button',{name:'Neu',exact:true}).focus()
|
|
await page.keyboard.press('Enter')
|
|
const dialog=page.getByRole('dialog',{name:'Neue Datenschutzerklärung',exact:true})
|
|
await expect(dialog).toBeVisible()
|
|
for(let step=0;step<16;step++) {
|
|
await page.keyboard.press(step%3===0?'Shift+Tab':'Tab')
|
|
expect(await dialog.evaluate(el=>el.contains(document.activeElement))).toBe(true)
|
|
}
|
|
await page.keyboard.press('Escape')
|
|
await expect(page.getByRole('button',{name:'Neu',exact:true})).toBeFocused()
|
|
})
|
|
|
|
test('policy APIs reject malformed data without server errors',async({page})=>{
|
|
await login(page)
|
|
for(const data of [null,[],{title:123},{recipients:{}},{isPublic:'false'}]){
|
|
expect((await page.request.post('/api/privacy-policies',{data})).status()).toBe(400)
|
|
}
|
|
expect((await page.request.post('/api/privacy-policies',{data:'{invalid',headers:{'Content-Type':'application/json'}})).status()).toBe(400)
|
|
})
|