Modern QA2026Testing Interactive States — tiles
Log inJoin
27 / 74 · 10 Visual & Accessibility Testing · axe-core Integration← prev⊞ allnext →☰ Read as one page

5.4Testing Interactive States

Accessibility issues often appear in interactive states that axe-core's default scan misses:

test('modal accessibility after opening', async ({ page }) => {
    await page.goto('/');

    // axe scan of the page BEFORE modal
    const beforeResults = await new AxeBuilder({ page }).analyze();
    expect(beforeResults.violations).toEqual([]);

    // Open modal
    await page.click('[data-testid="open-modal"]');
    await page.waitForSelector('[role="dialog"]');

    // axe scan AFTER modal opens -- catches issues in modal content
    const afterResults = await new AxeBuilder({ page })
        .include('[role="dialog"]')
        .analyze();
    expect(afterResults.violations).toEqual([]);
});

test('form error state accessibility', async ({ page }) => {
    await page.goto('/register');

    // Submit empty form to trigger validation
    await page.click('[data-testid="submit-btn"]');

    // Scan the page in its error state
    const results = await new AxeBuilder({ page }).analyze();
    expect(results.violations).toEqual([]);

    // Verify error messages are linked to inputs via aria-describedby
    const errorInputs = await page.locator('[aria-invalid="true"]').all();
    for (const input of errorInputs) {
        const describedby = await input.getAttribute('aria-describedby');
        expect(describedby).toBeTruthy();

        // Verify the referenced error message exists and is visible
        const errorMsg = page.locator(`#${describedby}`);
        await expect(errorMsg).toBeVisible();
    }
});