Add Axe accessibility E2E tests #2
@@ -68,8 +68,10 @@ nix develop -c pnpm start
|
||||
```
|
||||
|
||||
`test:e2e` is intentionally Nix-only and uses the system Chromium supplied by
|
||||
the dev shell. `test:live` is an explicit, opt-in smoke test that performs
|
||||
exactly one read-only Top search using the configured relay.
|
||||
the dev shell. Browser tests are organized under `e2e/`: page object models
|
||||
and fixtures are shared by integration flows and Axe checks for WCAG 2.0/2.1
|
||||
A and AA. `test:live` is an explicit, opt-in smoke test that performs exactly
|
||||
one read-only Top search using the configured relay.
|
||||
|
||||
Development binds to `127.0.0.1` by default. `dev:tailscale` binds to
|
||||
`0.0.0.0`, so it exposes the app on LAN interfaces as well as Tailscale. Use it
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { expect, test } from '../fixtures'
|
||||
|
||||
test('home page has no accessibility violations', async ({
|
||||
a11y,
|
||||
homePage,
|
||||
}) => {
|
||||
await homePage.goTo()
|
||||
await expect(homePage.articlesLocator).toHaveCount(0)
|
||||
|
||||
const result = await a11y().analyze()
|
||||
expect(result.violations).toEqual([])
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
import { expect, test } from '../fixtures'
|
||||
|
||||
test('list picker has no accessibility violations', async ({
|
||||
a11y,
|
||||
listsPage,
|
||||
}) => {
|
||||
await listsPage.goTo()
|
||||
await expect(listsPage.articlesLocator).toHaveCount(0)
|
||||
|
||||
const result = await a11y().analyze()
|
||||
expect(result.violations).toEqual([])
|
||||
})
|
||||
|
||||
test('list timeline has no accessibility violations', async ({
|
||||
a11y,
|
||||
listsPage,
|
||||
}) => {
|
||||
await listsPage.goTo()
|
||||
await listsPage.openList(/My E2E List.*10 メンバー/)
|
||||
await expect(listsPage.endOfFeedLocator).toBeVisible()
|
||||
|
||||
const result = await a11y().analyze()
|
||||
expect(result.violations).toEqual([])
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
import { expect, test } from '../fixtures'
|
||||
|
||||
test('empty search page has no accessibility violations', async ({
|
||||
a11y,
|
||||
searchPage,
|
||||
}) => {
|
||||
await searchPage.goTo()
|
||||
await expect(searchPage.articlesLocator).toHaveCount(0)
|
||||
|
||||
const result = await a11y().analyze()
|
||||
expect(result.violations).toEqual([])
|
||||
})
|
||||
|
||||
test('search results have no accessibility violations', async ({
|
||||
a11y,
|
||||
searchPage,
|
||||
}) => {
|
||||
await searchPage.goTo()
|
||||
await searchPage.search('AI lang:ja')
|
||||
await expect(searchPage.endOfFeedLocator).toBeVisible()
|
||||
|
||||
const result = await a11y().analyze()
|
||||
expect(result.violations).toEqual([])
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { expect, test } from '../fixtures'
|
||||
|
||||
test('conversation has no accessibility violations', async ({
|
||||
a11y,
|
||||
threadPage,
|
||||
}) => {
|
||||
await threadPage.goTo('yuta', '1001')
|
||||
await expect(threadPage.endOfFeedLocator).toBeVisible()
|
||||
|
||||
const result = await a11y().analyze()
|
||||
expect(result.violations).toEqual([])
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { expect, test } from '../fixtures'
|
||||
|
||||
test('user timeline has no accessibility violations', async ({
|
||||
a11y,
|
||||
userPage,
|
||||
}) => {
|
||||
await userPage.goTo('yuta')
|
||||
await expect(userPage.endOfFeedLocator).toBeVisible()
|
||||
|
||||
const result = await a11y().analyze()
|
||||
expect(result.violations).toEqual([])
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import AxeBuilder from '@axe-core/playwright'
|
||||
import { test as base } from '@playwright/test'
|
||||
import { HomePage } from './models/HomePage'
|
||||
import { ListsPage } from './models/ListsPage'
|
||||
@@ -6,6 +7,7 @@ import { ThreadPage } from './models/ThreadPage'
|
||||
import { UserPage } from './models/UserPage'
|
||||
|
||||
type Fixtures = {
|
||||
a11y: () => AxeBuilder
|
||||
homePage: HomePage
|
||||
listsPage: ListsPage
|
||||
searchPage: SearchPage
|
||||
@@ -14,6 +16,14 @@ type Fixtures = {
|
||||
}
|
||||
|
||||
export const test = base.extend<Fixtures>({
|
||||
a11y: async ({ page }, use) => {
|
||||
const makeAxeBuilder = () =>
|
||||
new AxeBuilder({ page })
|
||||
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
|
||||
.disableRules(['meta-viewport'])
|
||||
|
||||
await use(makeAxeBuilder)
|
||||
},
|
||||
homePage: ({ page }, use) => use(new HomePage(page)),
|
||||
listsPage: ({ page }, use) => use(new ListsPage(page)),
|
||||
searchPage: ({ page }, use) => use(new SearchPage(page)),
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, test } from './fixtures'
|
||||
import { expect, test } from '../fixtures'
|
||||
|
||||
let consoleErrors: string[]
|
||||
let pageErrors: string[]
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
@@ -1,10 +1,6 @@
|
||||
import type { KnipConfig } from 'knip'
|
||||
|
||||
export default {
|
||||
entry: [
|
||||
'playwright.config.ts',
|
||||
'tests/e2e/**/*.spec.ts',
|
||||
'tests/e2e/mock-relay.mjs',
|
||||
],
|
||||
entry: ['playwright.config.ts', 'e2e/**/*.test.ts', 'e2e/mock-relay.mjs'],
|
||||
playwright: false,
|
||||
} satisfies KnipConfig
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"zod": "4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "4.11.1",
|
||||
"@biomejs/biome": "2.5.3",
|
||||
"@playwright/test": "1.61.1",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
|
||||
@@ -11,7 +11,7 @@ if (!chromiumExecutable) {
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
testDir: './e2e',
|
||||
fullyParallel: false,
|
||||
workers: 1,
|
||||
use: {
|
||||
@@ -22,7 +22,7 @@ export default defineConfig({
|
||||
},
|
||||
webServer: [
|
||||
{
|
||||
command: `TWITTER_LITE_MOCK_RELAY_PORT=${relayPort} node tests/e2e/mock-relay.mjs`,
|
||||
command: `TWITTER_LITE_MOCK_RELAY_PORT=${relayPort} node e2e/mock-relay.mjs`,
|
||||
port: relayPort,
|
||||
reuseExistingServer: false,
|
||||
timeout: 120_000,
|
||||
|
||||
@@ -36,6 +36,9 @@ importers:
|
||||
specifier: 4.4.3
|
||||
version: 4.4.3
|
||||
devDependencies:
|
||||
'@axe-core/playwright':
|
||||
specifier: 4.11.1
|
||||
version: 4.11.1([email protected])
|
||||
'@biomejs/biome':
|
||||
specifier: 2.5.3
|
||||
version: 2.5.3
|
||||
@@ -99,6 +102,11 @@ packages:
|
||||
'@asamuzakjp/[email protected]':
|
||||
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
||||
|
||||
'@axe-core/[email protected]':
|
||||
resolution: {integrity: sha512-mKEfoUIB1MkVTht0BGZFXtSAEKXMJoDkyV5YZ9jbBmZCcWDz71tegNsdTkIN8zc/yMi5Gm2kx7Z5YQ9PfWNAWw==}
|
||||
peerDependencies:
|
||||
playwright-core: '>= 1.0.0'
|
||||
|
||||
'@babel/[email protected]':
|
||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1074,6 +1082,10 @@ packages:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==}
|
||||
|
||||
@@ -1982,6 +1994,11 @@ snapshots:
|
||||
|
||||
'@asamuzakjp/[email protected]': {}
|
||||
|
||||
'@axe-core/[email protected]([email protected])':
|
||||
dependencies:
|
||||
axe-core: 4.11.4
|
||||
playwright-core: 1.61.1
|
||||
|
||||
'@babel/[email protected]':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
@@ -2843,6 +2860,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "e2e/**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"jsx": "react-jsx",
|
||||
|
||||
@@ -6,6 +6,6 @@ export default defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./tests/setup.ts'],
|
||||
exclude: [...configDefaults.exclude, 'tests/e2e/**'],
|
||||
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||
},
|
||||
})
|
||||
|
||||