@@ -285,4 +285,201 @@ test.describe('Newsletter Signup - Logged-Out User Flow', () => {
285285 expect ( isYesVisible ) . toBe ( false ) ;
286286 expect ( isNoVisible ) . toBe ( false ) ;
287287 } ) ;
288+
289+ // ========== MULTI-ERROR VALIDATION TESTS ==========
290+
291+ test ( 'should show all validation errors at once when submitting empty form' , async ( { page } ) => {
292+ // Submit empty form
293+ const submitButton = page . locator ( 'button:has-text("Submit")' ) . first ( ) ;
294+ await submitButton . click ( ) ;
295+
296+ // Wait for validation to complete
297+ await page . waitForTimeout ( 500 ) ;
298+
299+ // Should see error summary at the top
300+ const errorSummary = page . locator ( '.newsletterErrorSummary' ) ;
301+ await expect ( errorSummary ) . toBeVisible ( ) ;
302+
303+ // Should list multiple errors in the summary
304+ const errorItems = page . locator ( '.errorSummaryList li' ) ;
305+ const errorCount = await errorItems . count ( ) ;
306+ expect ( errorCount ) . toBeGreaterThanOrEqual ( 3 ) ; // firstName, email, newsletters at minimum
307+
308+ // Should have inline errors above fields
309+ const inlineErrors = page . locator ( '.inlineFieldError' ) ;
310+ const inlineCount = await inlineErrors . count ( ) ;
311+ expect ( inlineCount ) . toBeGreaterThanOrEqual ( 3 ) ;
312+
313+ // Verify specific errors are shown
314+ const firstNameError = page . locator ( '#firstName-error' ) ;
315+ const emailError = page . locator ( '#email-error' ) ;
316+ const newslettersError = page . locator ( '#newsletters-error' ) ;
317+
318+ await expect ( firstNameError ) . toBeVisible ( ) ;
319+ await expect ( emailError ) . toBeVisible ( ) ;
320+ await expect ( newslettersError ) . toBeVisible ( ) ;
321+ } ) ;
322+
323+ test ( 'should focus error summary on validation failure for accessibility' , async ( { page } ) => {
324+ // Submit empty form
325+ const submitButton = page . locator ( 'button:has-text("Submit")' ) . first ( ) ;
326+ await submitButton . click ( ) ;
327+
328+ // Wait for validation and focus management
329+ await page . waitForTimeout ( 500 ) ;
330+
331+ // Error summary should be focused (for screen reader users)
332+ const errorSummary = page . locator ( '.newsletterErrorSummary' ) ;
333+ await expect ( errorSummary ) . toBeFocused ( ) ;
334+ } ) ;
335+
336+ test ( 'should clear inline error when field is fixed and loses focus' , async ( { page } ) => {
337+ // Submit empty form to trigger errors
338+ await page . locator ( 'button:has-text("Submit")' ) . first ( ) . click ( ) ;
339+ await page . waitForTimeout ( 500 ) ;
340+
341+ // Verify firstName error exists
342+ const firstNameError = page . locator ( '#firstName-error' ) ;
343+ await expect ( firstNameError ) . toBeVisible ( ) ;
344+
345+ // Fill in first name
346+ const firstNameInput = page . locator ( 'input#firstName' ) ;
347+ await firstNameInput . fill ( 'John' ) ;
348+
349+ // Error should still be visible while typing (not cleared on change)
350+ await expect ( firstNameError ) . toBeVisible ( ) ;
351+
352+ // Blur the field (click somewhere else or tab away)
353+ await firstNameInput . blur ( ) ;
354+
355+ // Wait for state update
356+ await page . waitForTimeout ( 100 ) ;
357+
358+ // Now error should be cleared
359+ await expect ( firstNameError ) . not . toBeVisible ( ) ;
360+
361+ // But other errors should still be visible
362+ const emailError = page . locator ( '#email-error' ) ;
363+ await expect ( emailError ) . toBeVisible ( ) ;
364+ } ) ;
365+
366+ test ( 'should update error summary when errors are fixed' , async ( { page } ) => {
367+ // Submit empty form to trigger all errors
368+ await page . locator ( 'button:has-text("Submit")' ) . first ( ) . click ( ) ;
369+ await page . waitForTimeout ( 500 ) ;
370+
371+ // Count initial errors
372+ const initialErrorCount = await page . locator ( '.errorSummaryList li' ) . count ( ) ;
373+ expect ( initialErrorCount ) . toBeGreaterThanOrEqual ( 3 ) ;
374+
375+ // Fix first name
376+ await page . locator ( 'input#firstName' ) . fill ( 'John' ) ;
377+ await page . locator ( 'input#firstName' ) . blur ( ) ;
378+ await page . waitForTimeout ( 100 ) ;
379+
380+ // Error count should decrease
381+ const afterFirstFix = await page . locator ( '.errorSummaryList li' ) . count ( ) ;
382+ expect ( afterFirstFix ) . toBe ( initialErrorCount - 1 ) ;
383+
384+ // Fix email
385+ await page . locator ( 'input#email' ) . fill ( 'john@example.com' ) ;
386+ await page . locator ( 'input#email' ) . blur ( ) ;
387+ await page . waitForTimeout ( 100 ) ;
388+
389+ const afterEmailFix = await page . locator ( '.errorSummaryList li' ) . count ( ) ;
390+ expect ( afterEmailFix ) . toBe ( afterFirstFix - 1 ) ;
391+ } ) ;
392+
393+ test ( 'should show error styling on invalid input fields' , async ( { page } ) => {
394+ // Submit empty form
395+ await page . locator ( 'button:has-text("Submit")' ) . first ( ) . click ( ) ;
396+ await page . waitForTimeout ( 500 ) ;
397+
398+ // Input fields should have error styling
399+ const firstNameInput = page . locator ( 'input#firstName' ) ;
400+ await expect ( firstNameInput ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
401+ await expect ( firstNameInput ) . toHaveClass ( / h a s E r r o r / ) ;
402+
403+ const emailInput = page . locator ( 'input#email' ) ;
404+ await expect ( emailInput ) . toHaveAttribute ( 'aria-invalid' , 'true' ) ;
405+ await expect ( emailInput ) . toHaveClass ( / h a s E r r o r / ) ;
406+ } ) ;
407+
408+ test ( 'should allow clicking error summary links to navigate to fields' , async ( { page } ) => {
409+ // Submit empty form
410+ await page . locator ( 'button:has-text("Submit")' ) . first ( ) . click ( ) ;
411+ await page . waitForTimeout ( 500 ) ;
412+
413+ // Click the email error link in the summary
414+ const emailErrorLink = page . locator ( '.errorSummaryLink[href="#email"]' ) ;
415+ await emailErrorLink . click ( ) ;
416+
417+ // Email input should now be focused (due to href="#email" navigation)
418+ // Note: This works because the input has id="email"
419+ await page . waitForTimeout ( 100 ) ;
420+
421+ // The email input should be scrolled into view and potentially focused
422+ const emailInput = page . locator ( 'input#email' ) ;
423+ await expect ( emailInput ) . toBeInViewport ( ) ;
424+ } ) ;
425+
426+ test ( 'should validate email format and show appropriate error' , async ( { page } ) => {
427+ // Fill invalid email
428+ await page . locator ( 'input#firstName' ) . fill ( 'John' ) ;
429+ await page . locator ( 'input#email' ) . fill ( 'not-an-email' ) ;
430+ await page . locator ( 'input#confirmEmail' ) . fill ( 'not-an-email' ) ;
431+
432+ // Select a newsletter
433+ const checkboxLabels = page . locator ( 'label.newsletterCheckboxLabel' ) ;
434+ await checkboxLabels . nth ( 0 ) . click ( ) ;
435+
436+ // Submit
437+ await page . locator ( 'button:has-text("Submit")' ) . first ( ) . click ( ) ;
438+ await page . waitForTimeout ( 500 ) ;
439+
440+ // Should show email format error
441+ const emailError = page . locator ( '#email-error' ) ;
442+ await expect ( emailError ) . toBeVisible ( ) ;
443+ await expect ( emailError ) . toContainText ( 'valid email' ) ;
444+ } ) ;
445+
446+ test ( 'should show mismatched email error when emails do not match' , async ( { page } ) => {
447+ // Fill form with mismatched emails
448+ await page . locator ( 'input#firstName' ) . fill ( 'John' ) ;
449+ await page . locator ( 'input#email' ) . fill ( 'john@example.com' ) ;
450+ await page . locator ( 'input#confirmEmail' ) . fill ( 'jane@example.com' ) ;
451+
452+ // Select a newsletter
453+ const checkboxLabels = page . locator ( 'label.newsletterCheckboxLabel' ) ;
454+ await checkboxLabels . nth ( 0 ) . click ( ) ;
455+
456+ // Submit
457+ await page . locator ( 'button:has-text("Submit")' ) . first ( ) . click ( ) ;
458+ await page . waitForTimeout ( 500 ) ;
459+
460+ // Should show email mismatch error
461+ const confirmEmailError = page . locator ( '#confirmEmail-error' ) ;
462+ await expect ( confirmEmailError ) . toBeVisible ( ) ;
463+ await expect ( confirmEmailError ) . toContainText ( 'do not match' ) ;
464+ } ) ;
465+
466+ test ( 'should clear newsletter error when a newsletter is selected' , async ( { page } ) => {
467+ // Submit empty form to trigger errors
468+ await page . locator ( 'button:has-text("Submit")' ) . first ( ) . click ( ) ;
469+ await page . waitForTimeout ( 500 ) ;
470+
471+ // Verify newsletters error exists
472+ const newslettersError = page . locator ( '#newsletters-error' ) ;
473+ await expect ( newslettersError ) . toBeVisible ( ) ;
474+
475+ // Select a newsletter
476+ const checkboxLabels = page . locator ( 'label.newsletterCheckboxLabel' ) ;
477+ await checkboxLabels . nth ( 0 ) . click ( ) ;
478+
479+ // Wait for state update
480+ await page . waitForTimeout ( 200 ) ;
481+
482+ // Newsletter error should be cleared
483+ await expect ( newslettersError ) . not . toBeVisible ( ) ;
484+ } ) ;
288485} ) ;
0 commit comments