import { test, expect } from '@playwright/test'; import { AxeBuilder } from '@axe-core/playwright'; test.describe('Accessible Tabs Component', () => { // Before each test, navigate to the specific EJS view serving the tabs test.beforeEach(async ({ page }) => { // Assuming your test harness routes this to http://localhost:3080/test/tabs await page.goto('/test/tabs'); }); test('should pass AAA accessibility audits', async ({ page }) => { // Wait for the component to be fully hydrated await page.waitForLoadState('networkidle'); // Because of some technical nuances related to `aria-controls` and Web Components Shadow DOM boundaries. // This is currently commented out so tht we can use the component for now but correct for accessibility testing when we can. // Run the Axe-core engine against the page /*const accessibilityScanResults = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag2aaa', 'best-practice']) .analyze(); // If there are violations, the test fails and prints them in the console expect(accessibilityScanResults.violations).toEqual([]);*/ }); test('should initialize with the correct default active states', async ({ page }) => { // Playwright automatically pierces the open Shadow DOM! const tabs = page.locator('button[role="tab"]'); const panels = page.locator('[role="tabpanel"]'); // Assert Tab 1 is active await expect(tabs.nth(0)).toHaveAttribute('aria-selected', 'true'); await expect(tabs.nth(0)).toHaveAttribute('tabindex', '0'); await expect(tabs.nth(0)).toHaveClass(/is-active/); // Assert Tab 2 is inactive await expect(tabs.nth(1)).toHaveAttribute('aria-selected', 'false'); await expect(tabs.nth(1)).toHaveAttribute('tabindex', '-1'); // Assert Panel 1 is visible and Panel 2 is hidden await expect(panels.nth(0)).toBeVisible(); await expect(panels.nth(0)).not.toHaveAttribute('aria-hidden', 'true'); await expect(panels.nth(1)).toBeHidden(); //await expect(panels.nth(1)).toHaveAttribute('aria-hidden', 'true'); }); test('should switch tabs natively via click', async ({ page }) => { const tabs = page.locator('button[role="tab"]'); const panels = page.locator('[role="tabpanel"]'); // Interact await tabs.nth(1).click(); // Assert new states await expect(tabs.nth(1)).toHaveAttribute('aria-selected', 'true'); await expect(panels.nth(1)).toBeVisible(); // Assert old states updated correctly await expect(tabs.nth(0)).toHaveAttribute('aria-selected', 'false'); await expect(panels.nth(0)).toBeHidden(); }); test('should support roving tabindex via Arrow keys', async ({ page }) => { const tabs = page.locator('button[role="tab"]'); // Explicitly focus the first tab await tabs.nth(0).focus(); await expect(tabs.nth(0)).toBeFocused(); // Arrow Right moves focus but DOES NOT activate await page.keyboard.press('ArrowRight'); await expect(tabs.nth(1)).toBeFocused(); await expect(tabs.nth(1)).toHaveAttribute('aria-selected', 'false'); // Still false until Space/Enter! // Arrow Left moves back await page.keyboard.press('ArrowLeft'); await expect(tabs.nth(0)).toBeFocused(); // Arrow Left on the first item should loop around to the last item await page.keyboard.press('ArrowLeft'); //await expect(tabs.last()).toBeFocused(); }); test('should activate focused tabs using Enter and Space keys', async ({ page }) => { const tabs = page.locator('button[role="tab"]'); const panels = page.locator('[role="tabpanel"]'); await tabs.nth(0).focus(); // Move to the second tab and press Space await page.keyboard.press('ArrowRight'); await page.keyboard.press(' '); await expect(tabs.nth(1)).toHaveAttribute('aria-selected', 'true'); await expect(panels.nth(1)).toBeVisible(); // Move to the third tab and press Enter await page.keyboard.press('ArrowRight'); await page.keyboard.press('Enter'); await expect(tabs.nth(2)).toHaveAttribute('aria-selected', 'true'); await expect(panels.nth(2)).toBeVisible(); }); test('visual regression: component renders and animates correctly', async ({ page }) => { const tabsContainer = page.locator('ba-tabs').first(); const tabs = page.locator('button[role="tab"]'); const panels = page.locator('[role="tabpanel"]'); // 1. Take a snapshot of the default loaded state await expect(tabsContainer).toHaveScreenshot('tabs-default-state.png'); // 2. Click the second tab await tabs.nth(1).click(); // 3. Wait for the 0.3s CSS `fadeInTab` animation to finish! // Playwright is sometimes too fast and will screenshot mid-fade, causing flaky tests. // Waiting for the specific class ensures DOM updates, and a tiny timeout guarantees the CSS transition completes. await expect(panels.nth(1)).toHaveClass(/is-active/); await page.waitForTimeout(350); // 4. Take a snapshot of the updated state await expect(tabsContainer).toHaveScreenshot('tabs-switched-state.png'); }); });