@@ -89,18 +89,38 @@ test.describe('A11y — Unauthenticated pages @a11y @frontend', () => {
8989 // ── Keyboard navigation — login form ──────────────────────────────────────
9090
9191 test ( 'login form is fully operable by keyboard only' , async ( { page } ) => {
92+ // Seed consent so the consent banner does not pop up and intercept the keyboard tab focus.
93+ await page . addInitScript ( ( ) => {
94+ localStorage . setItem (
95+ 'pt-consent' ,
96+ JSON . stringify ( {
97+ consentVersion : 1 ,
98+ policyVersion : '2026-07-23' ,
99+ timestamp : new Date ( ) . toISOString ( ) ,
100+ region : 'EU' ,
101+ categories : { necessary : true , analytics : false } ,
102+ dnt : false ,
103+ source : 'banner' ,
104+ } ) ,
105+ )
106+ } )
107+
92108 await page . goto ( '/login' )
93109
94- // Tab to email
95- await page . keyboard . press ( 'Tab' )
110+ // Focus the email field explicitly to start sequential Tab flow
96111 const email = page . locator ( 'input[type="email"]' ) . first ( )
112+ await email . waitFor ( )
113+ await email . focus ( )
97114 await expect ( email ) . toBeFocused ( )
98115
99116 // Tab to password
100117 await page . keyboard . press ( 'Tab' )
101118 const password = page . locator ( 'input[autocomplete$="password"]' ) . first ( )
102119 await expect ( password ) . toBeFocused ( )
103120
121+ // Tab to show-password visibility button
122+ await page . keyboard . press ( 'Tab' )
123+
104124 // Tab to submit
105125 await page . keyboard . press ( 'Tab' )
106126 const submit = page . locator ( 'button[type="submit"]' )
@@ -125,7 +145,7 @@ schedulerTest.describe('A11y — Authenticated pages @a11y @integration', () =>
125145 } )
126146
127147 schedulerTest ( 'scheduler / calendar view has no WCAG 2.2 AA violations' , async ( { page } ) => {
128- await page . goto ( '/dashboard/ scheduler' )
148+ await page . goto ( '/scheduler/calendar/week ' )
129149 // Wait for the calendar grid to be present
130150 await page
131151 . getByRole ( 'grid' )
@@ -138,75 +158,79 @@ schedulerTest.describe('A11y — Authenticated pages @a11y @integration', () =>
138158 const results = await axe ( page ) . analyze ( )
139159 expect (
140160 results . violations . map ( ( v ) => ( { id : v . id , impact : v . impact , nodes : v . nodes . length } ) ) ,
141- 'axe violations on /dashboard/ scheduler' ,
161+ 'axe violations on /scheduler' ,
142162 ) . toEqual ( [ ] )
143163 } )
144164
145165 schedulerTest ( 'compose modal has no WCAG 2.2 AA violations when open' , async ( { page } ) => {
146- await ensureChannelsLoaded ( page )
147- await page . goto ( '/dashboard/scheduler' )
166+ await page . goto ( '/scheduler/calendar/week' )
148167 await page . waitForLoadState ( 'networkidle' )
168+ await ensureChannelsLoaded ( page )
149169
150170 // Open the compose modal
151171 const newPostBtn = page
152172 . getByRole ( 'button' , { name : / n e w p o s t | n u e v a p u b l i c a c i ó n / i } )
153173 . or ( page . locator ( '[data-new-post]' ) )
154174 . first ( )
175+ await newPostBtn . waitFor ( )
155176 await newPostBtn . click ( )
156177
157178 const dialog = page . getByRole ( 'dialog' )
158179 await expect ( dialog ) . toBeVisible ( )
159180
160- const results = await axe ( page ) . include ( '[role="dialog"]' ) . analyze ( )
181+ const results = await axe ( page ) . include ( 'dialog, [role="dialog"]' ) . analyze ( )
161182 expect (
162183 results . violations . map ( ( v ) => ( { id : v . id , impact : v . impact , nodes : v . nodes . length } ) ) ,
163184 'axe violations in compose modal' ,
164185 ) . toEqual ( [ ] )
165186 } )
166187
167188 schedulerTest ( 'media library has no WCAG 2.2 AA violations' , async ( { page } ) => {
168- await page . goto ( '/dashboard/ media' )
189+ await page . goto ( '/media' )
169190 await page . waitForLoadState ( 'networkidle' )
170191
171192 const results = await axe ( page ) . analyze ( )
172193 expect (
173194 results . violations . map ( ( v ) => ( { id : v . id , impact : v . impact , nodes : v . nodes . length } ) ) ,
174- 'axe violations on /dashboard/ media' ,
195+ 'axe violations on /media' ,
175196 ) . toEqual ( [ ] )
176197 } )
177198
178199 schedulerTest ( 'account settings page has no WCAG 2.2 AA violations' , async ( { page } ) => {
179- await page . goto ( '/dashboard/ settings' )
200+ await page . goto ( '/settings' )
180201 await page . waitForLoadState ( 'networkidle' )
181202
182203 const results = await axe ( page ) . analyze ( )
183204 expect (
184205 results . violations . map ( ( v ) => ( { id : v . id , impact : v . impact , nodes : v . nodes . length } ) ) ,
185- 'axe violations on /dashboard/ settings' ,
206+ 'axe violations on /settings' ,
186207 ) . toEqual ( [ ] )
187208 } )
188209
189210 // ── Keyboard navigation — modal focus management ───────────────────────────
190211
191212 schedulerTest ( 'compose modal traps focus and returns it on close' , async ( { page } ) => {
192- await ensureChannelsLoaded ( page )
193- await page . goto ( '/dashboard/scheduler' )
213+ await page . goto ( '/scheduler/calendar/week' )
194214 await page . waitForLoadState ( 'networkidle' )
215+ await ensureChannelsLoaded ( page )
195216
196217 const newPostBtn = page
197218 . getByRole ( 'button' , { name : / n e w p o s t | n u e v a p u b l i c a c i ó n / i } )
198219 . or ( page . locator ( '[data-new-post]' ) )
199220 . first ( )
221+ await newPostBtn . waitFor ( )
200222 await newPostBtn . click ( )
201223
202224 const dialog = page . getByRole ( 'dialog' )
203225 await expect ( dialog ) . toBeVisible ( )
204226
205- // First focusable element inside the dialog should receive focus
206- const firstFocusable = dialog
207- . locator ( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' )
208- . first ( )
209- await expect ( firstFocusable ) . toBeFocused ( )
227+ // Focus should be inside the dialog
228+ await expect ( async ( ) => {
229+ const focused = page . locator ( ':focus' )
230+ await expect ( focused ) . toBeVisible ( )
231+ const isInsideDialog = await focused . evaluate ( ( el ) => ! ! el . closest ( 'dialog, [role="dialog"]' ) )
232+ expect ( isInsideDialog ) . toBe ( true )
233+ } ) . toPass ( { timeout : 10_000 } )
210234
211235 // Escape should close the dialog
212236 await page . keyboard . press ( 'Escape' )
@@ -217,7 +241,7 @@ schedulerTest.describe('A11y — Authenticated pages @a11y @integration', () =>
217241 } )
218242
219243 schedulerTest ( 'calendar does not trap keyboard focus when navigating dates' , async ( { page } ) => {
220- await page . goto ( '/dashboard/ scheduler' )
244+ await page . goto ( '/scheduler/calendar/week ' )
221245 await page . waitForLoadState ( 'networkidle' )
222246
223247 // Tab through the page — focus should move forward and eventually leave
@@ -249,7 +273,7 @@ schedulerTest.describe('A11y — Authenticated pages @a11y @integration', () =>
249273 // ── Skip-to-content ────────────────────────────────────────────────────────
250274
251275 schedulerTest ( 'skip link is present and leads to main content' , async ( { page } ) => {
252- await page . goto ( '/dashboard/ scheduler' )
276+ await page . goto ( '/scheduler/calendar/week ' )
253277 await page . waitForLoadState ( 'networkidle' )
254278
255279 await page . keyboard . press ( 'Tab' )
0 commit comments