2026-10-03 14:29:28 +02:00
// Automated a11y scan (axe-core, WCAG 2.2 AA rules) of the main views. Run against `pnpm preview`.
import { readFile } from 'node:fs/promises' ;
import { chromium } from 'playwright-core' ;
const axeSrc = await readFile ( 'node_modules/axe-core/axe.min.js' , 'utf8' ) ;
const URL = process . env . URL ? ? 'http://localhost:4173/' ;
const browser = await chromium . launch ( ) ;
2026-10-03 17:08:38 +02:00
// VIEWPORT=390x844 runs the same scan with a phone-sized (touch) viewport
const [ vw , vh ] = ( process . env . VIEWPORT ? ? '1280x900' ) . split ( 'x' ) . map ( Number ) ;
const page = await browser . newPage ( { viewport : { width : vw , height : vh } , isMobile : vw < 700 , hasTouch : vw < 700 , locale : 'pl-PL' } ) ;
2026-10-03 14:29:28 +02:00
let bad = 0 ;
async function scan ( name ) {
await page . addScriptTag ( { content : axeSrc } ) ;
const r = await page . evaluate ( ( ) => axe . run ( document , { runOnly : { type : 'tag' , values : [ 'wcag2a' , 'wcag2aa' , 'wcag21aa' , 'wcag22aa' , 'best-practice' ] } } ) ) ;
console . log ( ` ${ name } : ${ r . violations . length } violation(s) ` ) ;
for ( const v of r . violations ) { bad ++ ; console . log ( ` [ ${ v . impact } ] ${ v . id } : ${ v . help } — ${ v . nodes . length } node(s), e.g. ${ v . nodes [ 0 ] . target . join ( ' ' ) } ` ) ; }
}
await page . goto ( URL , { waitUntil : 'networkidle' } ) ;
await scan ( 'login' ) ;
await page . click ( 'text=Zaloguj jako Maria Wiśniewska' ) ;
2026-10-03 17:08:38 +02:00
await page . waitForSelector ( '[role=tablist]' ) ;
for ( const [ tab , name ] of [ [ 'Zgłoszenia' , 'dashboard: reports' ] , [ 'Przejścia' , 'dashboard: crossings' ] , [ 'Jakość' , 'dashboard: quality' ] , [ 'Eksport' , 'dashboard: export' ] ] ) {
await page . click ( ` [role=tab]:has-text(" ${ tab } ") ` ) ;
await scan ( name ) ;
}
2026-10-03 14:29:28 +02:00
await page . click ( 'nav >> text=Miejsca' ) ;
await page . waitForSelector ( '#q' ) ;
await scan ( 'places' ) ;
await page . fill ( '#q' , 'Sukiennice' ) ;
await page . click ( 'button[id^=place-]' ) ;
await page . waitForSelector ( 'text=Szczegóły dostępności' ) ;
await scan ( 'place detail' ) ;
await page . click ( 'nav >> text=Trasa' ) ;
2026-10-03 19:14:41 +02:00
await page . fill ( '#route-from' , 'Sukiennice' ) ;
await page . fill ( '#route-to' , 'Zamek Królewski na Wawelu' ) ;
await page . click ( 'button[type=submit]:has-text("Wyznacz")' ) ;
2026-10-03 14:29:28 +02:00
await page . waitForSelector ( 'text=Opis trasy tekstem' ) ;
2026-10-03 19:14:41 +02:00
await page . waitForTimeout ( 1500 ) ; // let the map settle: pins that are still moving overlap and trip the target-size rule
await page . evaluate ( ( ) => document . querySelector ( '.kbb-panel' ) ? . scrollTo ( 0 , 0 ) ) ; // the result scrolls under the phone sheet's sticky handle; scan from the top
2026-10-03 14:29:28 +02:00
await scan ( 'route' ) ;
await page . click ( 'nav >> text=Profil' ) ;
await page . waitForSelector ( '#st1' ) ;
await scan ( 'profile + settings' ) ;
await page . click ( 'nav >> text=Dane' ) ;
await scan ( 'about' ) ;
await browser . close ( ) ;
process . exitCode = bad ? 1 : 0 ;