microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/1873-devcontainer

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

docs/docusaurus/e2e/_helpers/focus.ts

86lines · modecode

1import type { Page } from '@playwright/test';
2
3// Shared behavioral focus helpers for keyboard/focus-management specs. These
4// drive real keyboard interaction against the rendered Docusaurus DOM, covering
5// WCAG criteria (2.1.1, 2.1.2, 2.4.3) that static axe-based scans cannot reach.
6
7/** A snapshot of the active element captured at one step of a focus traversal. */
8export interface FocusSnapshot {
9 tag: string | undefined;
10 text: string | undefined;
11 ariaLabel: string | null | undefined;
12 id: string | undefined;
13}
14
15/**
16 * Drive Tab / Shift+Tab and snapshot the active element at each step.
17 *
18 * @param page - The Playwright page under test.
19 * @param direction - Traversal direction; 'forward' presses Tab, 'backward' presses Shift+Tab.
20 * @param count - Number of steps (snapshots) to collect.
21 * @returns The ordered sequence of active-element snapshots.
22 */
23export async function collectFocusOrder(
24 page: Page,
25 direction: 'forward' | 'backward' = 'forward',
26 count = 5,
27): Promise<FocusSnapshot[]> {
28 const sequence: FocusSnapshot[] = [];
29 for (let i = 0; i < count; i++) {
30 sequence.push(
31 await page.evaluate(() => {
32 const el = document.activeElement;
33 return {
34 tag: el?.tagName,
35 text: el?.textContent?.slice(0, 50),
36 ariaLabel: el?.getAttribute('aria-label'),
37 id: el?.id,
38 };
39 }),
40 );
41 await page.keyboard.press(direction === 'forward' ? 'Tab' : 'Shift+Tab');
42 }
43 return sequence;
44}
45
46/**
47 * Focus the first focusable element inside a container, press the escape key,
48 * and report whether focus left the container.
49 *
50 * @param page - The Playwright page under test.
51 * @param containerSelector - CSS selector for the container that should release focus.
52 * @param escapeKey - The key expected to release the trap (default 'Escape').
53 * @returns True when the active element is no longer within the container.
54 */
55export async function testFocusTrapEscape(
56 page: Page,
57 containerSelector: string,
58 escapeKey = 'Escape',
59): Promise<boolean> {
60 const container = page.locator(containerSelector);
61 await container.locator('button, a, input').first().focus();
62 await page.keyboard.press(escapeKey);
63 return await page.evaluate(
64 (sel) => document.activeElement?.closest(sel) === null,
65 containerSelector,
66 );
67}
68
69/**
70 * Validate a roving-tabindex container: exactly one element with tabindex="0"
71 * and every other tabindex element set to "-1".
72 *
73 * @param page - The Playwright page under test.
74 * @param containerSelector - CSS selector for the roving-tabindex container.
75 * @returns True when the container satisfies the roving-tabindex invariant.
76 */
77export async function validateRovingTabindex(
78 page: Page,
79 containerSelector: string,
80): Promise<boolean> {
81 const items = page.locator(`${containerSelector} [tabindex]`);
82 const count = await items.count();
83 const zero = await page.locator(`${containerSelector} [tabindex="0"]`).count();
84 const negOne = await page.locator(`${containerSelector} [tabindex="-1"]`).count();
85 return zero === 1 && negOne === count - 1;
86}
87