fix: prove resilient keyboard and no-script paths

This commit is contained in:
2026-09-02 03:25:22 +03:00
parent 091ff70deb
commit 829fed53aa
4 changed files with 244 additions and 2 deletions

View File

@@ -24,9 +24,99 @@ const sectionHeadings = [
'CONTACT',
] as const;
test('keeps the complete hiring-manager path readable without JavaScript', async ({
const visibleTextLeafSelector = 'h1,h2,h3,p,dt,dd,li,figcaption,a';
const visibleTextSelector = [
`.site-header :is(${visibleTextLeafSelector})`,
`.portfolio-main :is(${visibleTextLeafSelector})`,
].join(',');
const normalizedText = (value: string): string =>
value.replace(/\s+/gu, ' ').trim();
const rawVisibleTextSegments = async (
page: import('@playwright/test').Page,
rawHtml: string,
): Promise<string[]> =>
page.evaluate(
({ html, leafSelector, rootSelector }) => {
const document = new DOMParser().parseFromString(html, 'text/html');
return [...document.querySelectorAll<HTMLElement>(rootSelector)]
.filter(
(element) =>
element.closest('[aria-hidden="true"]') === null &&
element.querySelector(leafSelector) === null,
)
.map((element) =>
(element.textContent ?? '').replace(/\s+/gu, ' ').trim(),
)
.filter((text) => text.length > 0);
},
{
html: rawHtml,
leafSelector: visibleTextLeafSelector,
rootSelector: visibleTextSelector,
},
);
const browserVisibleTextSegments = async (
page: import('@playwright/test').Page,
): Promise<string[]> =>
page.locator(visibleTextSelector).evaluateAll(
(elements, leafSelector) =>
elements
.filter((element) => {
const htmlElement = element as HTMLElement;
const style = getComputedStyle(htmlElement);
return (
htmlElement.closest('[aria-hidden="true"]') === null &&
htmlElement.querySelector(leafSelector) === null &&
htmlElement.getClientRects().length > 0 &&
style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0'
);
})
.map((element) =>
(element as HTMLElement).innerText.replace(/\s+/gu, ' ').trim(),
)
.filter((text) => text.length > 0),
visibleTextLeafSelector,
);
const assertVisibleUnobscuredFocus = async (
page: import('@playwright/test').Page,
): Promise<void> => {
await expect
.poll(() =>
page.evaluate(() => {
const active = document.activeElement;
if (!(active instanceof HTMLElement)) return false;
const rect = active.getBoundingClientRect();
const left = Math.max(0, rect.left);
const right = Math.min(window.innerWidth, rect.right);
const top = Math.max(0, rect.top);
const bottom = Math.min(window.innerHeight, rect.bottom);
if (right <= left || bottom <= top) return false;
const topElement = document.elementFromPoint(
left + (right - left) / 2,
top + Math.min(bottom - top, window.innerHeight) / 2,
);
const style = getComputedStyle(active);
return (
topElement !== null &&
(active === topElement || active.contains(topElement)) &&
style.outlineStyle !== 'none' &&
Number.parseFloat(style.outlineWidth) >= 2
);
}),
)
.toBe(true);
};
test('keeps every raw SSR reading-path segment visible without JavaScript', async ({
page,
distUrl,
request,
}) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
@@ -43,7 +133,19 @@ test('keeps the complete hiring-manager path readable without JavaScript', async
}
});
const rawResponse = await request.get(`${distUrl}/`);
expect(rawResponse.status()).toBe(200);
const rawHtml = await rawResponse.text();
const expectedVisibleText = await rawVisibleTextSegments(page, rawHtml);
await page.goto(distUrl, { waitUntil: 'load' });
const actualVisibleText = await browserVisibleTextSegments(page);
expect(expectedVisibleText.length).toBeGreaterThan(80);
expect(actualVisibleText).toEqual(expectedVisibleText);
expect(normalizedText(actualVisibleText.join(' '))).not.toContain(
'preview-semantic-v1',
);
await expect(page.locator('h1:visible')).toHaveCount(1);
await expect(page.locator('h1')).toHaveText('Luca Pîslaru');
@@ -104,6 +206,25 @@ test('keeps the complete hiring-manager path readable without JavaScript', async
expect(failedRequests).toEqual([]);
});
test('activating the skip link focuses visible portfolio content without JavaScript', async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: 'load' });
await page.keyboard.press('Tab');
await expect(page.locator('.skip-link')).toBeFocused();
await page.keyboard.press('Enter');
await expect(page).toHaveURL(`${distUrl}/#portfolio-content`);
await expect(page.locator('#portfolio-content')).toBeFocused();
await expect(page.locator('#portfolio-content')).toHaveAttribute(
'tabindex',
'-1',
);
await assertVisibleUnobscuredFocus(page);
});
test('retains native section navigation when scripts are disabled', async ({
page,
distUrl,