232 lines
6.5 KiB
TypeScript
232 lines
6.5 KiB
TypeScript
import {
|
|
assertContrast,
|
|
checkContrast,
|
|
getComputedColors,
|
|
WCAG_CONTRAST,
|
|
} from "../../fixtures/accessibility";
|
|
import { SELECTORS } from "../../fixtures/selectors";
|
|
import { expect, test } from "../../fixtures/test";
|
|
import { THEMES, Theme } from "../../fixtures/themes";
|
|
|
|
test.describe("Color Contrast Across Themes", () => {
|
|
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
|
|
await gotoApp();
|
|
await waitForAppReady();
|
|
});
|
|
|
|
for (const theme of THEMES) {
|
|
test(`hero text has sufficient contrast in ${theme} theme @smoke`, async ({
|
|
page,
|
|
setTheme,
|
|
waitForHero,
|
|
}) => {
|
|
// Set theme
|
|
await setTheme(theme);
|
|
|
|
const hero = await waitForHero();
|
|
|
|
// Check headline contrast
|
|
const headline = hero.locator(SELECTORS.hero.headline);
|
|
await assertContrast(page, headline, WCAG_CONTRAST.largeText);
|
|
|
|
// Check summary contrast
|
|
const summary = hero.locator(SELECTORS.hero.summary);
|
|
await assertContrast(page, summary, WCAG_CONTRAST.normalText);
|
|
});
|
|
|
|
test(`feed card text has sufficient contrast in ${theme} theme`, async ({
|
|
page,
|
|
setTheme,
|
|
waitForFeed,
|
|
}) => {
|
|
// Set theme
|
|
await setTheme(theme);
|
|
|
|
const feed = await waitForFeed();
|
|
const firstArticle = feed.locator(SELECTORS.feed.articles).first();
|
|
|
|
// Check headline contrast
|
|
const headline = firstArticle.locator("h3");
|
|
await assertContrast(page, headline, WCAG_CONTRAST.largeText);
|
|
|
|
// Check summary contrast
|
|
const summary = firstArticle.locator(SELECTORS.feed.articleSummary);
|
|
await assertContrast(page, summary, WCAG_CONTRAST.normalText);
|
|
});
|
|
|
|
test(`modal text has sufficient contrast in ${theme} theme`, async ({
|
|
page,
|
|
setTheme,
|
|
waitForHero,
|
|
}) => {
|
|
// Set theme
|
|
await setTheme(theme);
|
|
|
|
// Open modal
|
|
const hero = await waitForHero();
|
|
await hero.locator(SELECTORS.hero.readButton).click();
|
|
|
|
const modal = page.locator(SELECTORS.summaryModal.root);
|
|
await expect(modal).toBeVisible();
|
|
|
|
// Check headline contrast
|
|
const headline = modal.locator(SELECTORS.summaryModal.headline);
|
|
await assertContrast(page, headline, WCAG_CONTRAST.largeText);
|
|
|
|
// Check body text contrast
|
|
const bodyText = modal.locator(SELECTORS.summaryModal.summaryBody);
|
|
await assertContrast(page, bodyText, WCAG_CONTRAST.normalText);
|
|
|
|
// Check TL;DR list contrast
|
|
const tldrList = modal.locator(SELECTORS.summaryModal.tldrList);
|
|
await assertContrast(page, tldrList, WCAG_CONTRAST.normalText);
|
|
});
|
|
|
|
test(`link colors have sufficient contrast in ${theme} theme`, async ({
|
|
page,
|
|
setTheme,
|
|
waitForFeed,
|
|
}) => {
|
|
// Set theme
|
|
await setTheme(theme);
|
|
|
|
const feed = await waitForFeed();
|
|
const firstArticle = feed.locator(SELECTORS.feed.articles).first();
|
|
|
|
// Check source link contrast
|
|
const sourceLink = firstArticle.locator(SELECTORS.feed.articleSource);
|
|
const hasSourceLink = (await sourceLink.count()) > 0;
|
|
|
|
if (hasSourceLink) {
|
|
await assertContrast(page, sourceLink, WCAG_CONTRAST.normalText);
|
|
}
|
|
});
|
|
|
|
test(`button text has sufficient contrast in ${theme} theme`, async ({
|
|
page,
|
|
setTheme,
|
|
waitForHero,
|
|
}) => {
|
|
// Set theme
|
|
await setTheme(theme);
|
|
|
|
const hero = await waitForHero();
|
|
|
|
// Check read button contrast
|
|
const readButton = hero.locator(SELECTORS.hero.readButton);
|
|
await assertContrast(page, readButton, WCAG_CONTRAST.normalText);
|
|
});
|
|
}
|
|
});
|
|
|
|
test.describe("Interactive State Contrast", () => {
|
|
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
|
|
await gotoApp();
|
|
await waitForAppReady();
|
|
});
|
|
|
|
test("hover state maintains sufficient contrast", async ({
|
|
page,
|
|
waitForFeed,
|
|
}) => {
|
|
const feed = await waitForFeed();
|
|
const firstArticle = feed.locator(SELECTORS.feed.articles).first();
|
|
const readButton = firstArticle.locator(SELECTORS.feed.articleReadButton);
|
|
|
|
// Get normal state colors
|
|
const normalColors = await getComputedColors(page, readButton);
|
|
|
|
// Hover over button
|
|
await readButton.hover();
|
|
await page.waitForTimeout(300); // Wait for transition
|
|
|
|
// Get hover state colors
|
|
const hoverColors = await getComputedColors(page, readButton);
|
|
|
|
// Both states should have sufficient contrast
|
|
const normalRatio = checkContrast(
|
|
normalColors.color,
|
|
normalColors.backgroundColor,
|
|
);
|
|
const hoverRatio = checkContrast(
|
|
hoverColors.color,
|
|
hoverColors.backgroundColor,
|
|
);
|
|
|
|
expect(normalRatio).toBeGreaterThanOrEqual(WCAG_CONTRAST.normalText);
|
|
expect(hoverRatio).toBeGreaterThanOrEqual(WCAG_CONTRAST.normalText);
|
|
});
|
|
|
|
test("focus state maintains sufficient contrast @smoke", async ({
|
|
page,
|
|
waitForFeed,
|
|
}) => {
|
|
const feed = await waitForFeed();
|
|
const firstArticle = feed.locator(SELECTORS.feed.articles).first();
|
|
const readButton = firstArticle.locator(SELECTORS.feed.articleReadButton);
|
|
|
|
// Get normal state colors
|
|
const normalColors = await getComputedColors(page, readButton);
|
|
|
|
// Focus the button
|
|
await readButton.focus();
|
|
|
|
// Get focus state colors
|
|
const focusColors = await getComputedColors(page, readButton);
|
|
|
|
// Both states should have sufficient contrast
|
|
const normalRatio = checkContrast(
|
|
normalColors.color,
|
|
normalColors.backgroundColor,
|
|
);
|
|
const focusRatio = checkContrast(
|
|
focusColors.color,
|
|
focusColors.backgroundColor,
|
|
);
|
|
|
|
expect(normalRatio).toBeGreaterThanOrEqual(WCAG_CONTRAST.normalText);
|
|
expect(focusRatio).toBeGreaterThanOrEqual(WCAG_CONTRAST.normalText);
|
|
});
|
|
});
|
|
|
|
test.describe("High Contrast Theme", () => {
|
|
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
|
|
await gotoApp();
|
|
await waitForAppReady();
|
|
});
|
|
|
|
test("contrast theme provides enhanced visibility @smoke", async ({
|
|
page,
|
|
setTheme,
|
|
waitForHero,
|
|
waitForFeed,
|
|
}) => {
|
|
// Set high contrast theme
|
|
await setTheme("contrast");
|
|
|
|
// Check hero
|
|
const hero = await waitForHero();
|
|
const headline = hero.locator(SELECTORS.hero.headline);
|
|
const headlineColors = await getComputedColors(page, headline);
|
|
const headlineRatio = checkContrast(
|
|
headlineColors.color,
|
|
headlineColors.backgroundColor,
|
|
);
|
|
|
|
// High contrast should provide very strong contrast (7:1 or better)
|
|
expect(headlineRatio).toBeGreaterThanOrEqual(7);
|
|
|
|
// Check feed
|
|
const feed = await waitForFeed();
|
|
const firstArticle = feed.locator(SELECTORS.feed.articles).first();
|
|
const articleHeadline = firstArticle.locator("h3");
|
|
const articleColors = await getComputedColors(page, articleHeadline);
|
|
const articleRatio = checkContrast(
|
|
articleColors.color,
|
|
articleColors.backgroundColor,
|
|
);
|
|
|
|
expect(articleRatio).toBeGreaterThanOrEqual(7);
|
|
});
|
|
});
|