Skip to content

feat: add accessibility scanner workflow#1636

Draft
onchul wants to merge 6 commits into
mainfrom
a11y-scanner-setup
Draft

feat: add accessibility scanner workflow#1636
onchul wants to merge 6 commits into
mainfrom
a11y-scanner-setup

Conversation

@onchul
Copy link
Copy Markdown
Collaborator

@onchul onchul commented Apr 15, 2026

No description provided.

Copilot AI review requested due to automatic review settings April 15, 2026 12:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a GitHub Actions workflow intended to run an automated accessibility scan against a locally started (Docker-based) deployment of the app, and optionally file grouped issues based on findings.

Changes:

  • Introduces .github/workflows/a11y-scan.yaml to start Postgres + backend + frontend + gateway via Docker on the runner.
  • Adds readiness checks for DB and app health before running github/accessibility-scanner@v2.
  • Captures backend/gateway logs after the scan attempt.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/a11y-scan.yaml Outdated
Comment thread .github/workflows/a11y-scan.yaml Outdated
Comment thread .github/workflows/a11y-scan.yaml
Comment thread .github/workflows/a11y-scan.yaml
Comment thread .github/workflows/a11y-scan.yaml
Comment thread .github/workflows/a11y-scan.yaml
Comment thread .github/workflows/a11y-scan.yaml
Comment thread .github/workflows/a11y-scan.yaml
@onchul onchul force-pushed the a11y-scanner-setup branch from 5ebe28f to 42d5a1e Compare April 15, 2026 12:36
Copilot AI review requested due to automatic review settings April 15, 2026 14:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +23 to +31
- name: Start database
run: |
docker run -d \
--network a11y-network \
--name postgres \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=cccc \
pgvector/pgvector:pg16
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Postgres container password is hardcoded (POSTGRES_PASSWORD=secret) and the same value is embedded later in DB_URL. Please wire the DB password through a secret or a generated runtime value (and reference it consistently) so credentials aren’t committed to the workflow and can be rotated safely.

Copilot uses AI. Check for mistakes.
@onchul onchul force-pushed the a11y-scanner-setup branch from d1b2455 to 945973d Compare April 15, 2026 15:04
@onchul onchul force-pushed the a11y-scanner-setup branch from 945973d to e683c06 Compare April 16, 2026 06:19
Copilot AI review requested due to automatic review settings April 16, 2026 06:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

placeholder={texts.common.email}
autoFocus
key={form.key('email')}
label="username"
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email field is labeled as "username", which doesn’t match the actual value being collected (email) and will be announced incorrectly by screen readers. Please use the same localized label used elsewhere (e.g., texts.common.email) or otherwise ensure the accessible name reflects "Email" rather than "username".

Suggested change
label="username"
label={texts.common.email}

Copilot uses AI. Check for mistakes.
<PasswordInput
placeholder={texts.common.password}
key={form.key('password')}
label="password"
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The password field uses a hard-coded label string ("password") instead of the localized texts.common.password used across the app. This is user-visible and also affects accessibility naming/translation consistency; please switch to the shared texts label (or an equivalent localized string).

Suggested change
label="password"
label={texts.common.password}

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +14
uses: ./.github/workflows/build-container-images.yaml

accessibility_scanner:
Copilot AI review requested due to automatic review settings April 16, 2026 07:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +127 to +136
# Convert Netscape cookie jar to Playwright auth_context JSON
AUTH_CONTEXT=$(awk '!/^#/ && NF {
printf "{\"name\":\"%s\",\"value\":\"%s\",\"domain\":\"%s\",\"path\":\"%s\",\"secure\":%s,\"httpOnly\":%s}\n",
$6, $7, $1, $3, ($4=="TRUE"?"true":"false"), ($2=="TRUE"?"true":"false")
}' /tmp/cookies.txt | jq -sc '{
username: "${{ env.AUTH_INITIAL_ADMIN_USERNAME }}",
password: "${{ env.AUTH_INITIAL_ADMIN_PASSWORD }}",
cookies: .,
localStorage: {}
}')
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auth cookie extraction drops any lines starting with # (awk '!/^#/'). curl writes HttpOnly cookies in the Netscape cookie jar as lines prefixed with #HttpOnly_..., so this will likely discard the session cookie entirely and the scanner will run unauthenticated. Adjust the parsing to retain #HttpOnly_ cookie lines (stripping the prefix) and derive httpOnly correctly (the 2nd Netscape field is the include-subdomains flag, not HttpOnly).

Copilot uses AI. Check for mistakes.
placeholder={texts.common.email}
autoFocus
key={form.key('email')}
label="username"
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TextInput is bound to the email field (getInputProps('email') and placeholder uses texts.common.email), but the accessible label is set to the hard-coded string "username". This will be announced incorrectly by screen readers and is inconsistent with the rest of the UI that uses localized texts.* labels. Use an email-appropriate, localized label (and optionally visually-hide it if you don't want a visible label).

Suggested change
label="username"
label={texts.common.email}

Copilot uses AI. Check for mistakes.
@onchul onchul force-pushed the a11y-scanner-setup branch from d4a2c53 to 7f4b76c Compare April 16, 2026 10:02
Copilot AI review requested due to automatic review settings April 16, 2026 13:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- a11y-scanner-setup

jobs:
build:
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow calls the reusable build-container-images.yaml, which requests packages: write for the job. Since reusable workflow permissions are capped by the caller, add workflow-level (or jobs.build) permissions (e.g., packages: write) to avoid the build job running with default token permissions and failing to authenticate/cache/upload as expected.

Suggested change
build:
build:
permissions:
contents: read
packages: write

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +147
' /tmp/cookies.txt | jq -sc '{
username: "${{ env.AUTH_INITIAL_ADMIN_USERNAME }}",
password: "${{ env.AUTH_INITIAL_ADMIN_PASSWORD }}",
cookies: .,
localStorage: {}
}')

echo "auth_context=$AUTH_CONTEXT" >> "$GITHUB_OUTPUT"
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auth_context is built to include username and password and then written to a step output. Even if the password is not a GitHub secret here, embedding credentials in outputs increases the chance of accidental exposure (logs/action debug output) and isn't necessary if cookie-based auth is sufficient. Consider omitting credentials from auth_context and only passing the minimal cookie/storage state required for Playwright.

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +194
- name: Verify axe timing issue (immediate vs after wait)
run: |
npm install playwright @axe-core/playwright
npx playwright install chromium --with-deps

node << 'SCRIPT'
const { chromium } = require('playwright');
const { default: AxeBuilder } = require('@axe-core/playwright');
const AUTH_CONTEXT = JSON.parse(process.env.AUTH_CONTEXT);

(async () => {
const browser = await chromium.launch();
const context = await browser.newContext({
storageState: {
cookies: AUTH_CONTEXT.cookies.map(c => ({ ...c, sameSite: 'Lax', expires: -1 })),
origins: []
}
});

const urls = [
'http://localhost:3080/admin/theme',
'http://localhost:3080/admin/dashboard',
'http://localhost:3080/chat'
];

for (const url of urls) {
const page = await context.newPage();
await page.goto(url);

const immediate = await new AxeBuilder({ page }).analyze();
console.log(`\n=== ${url} ===`);
console.log('Without wait:', immediate.violations.map(v => v.id));

await page.waitForLoadState('networkidle');
const waited = await new AxeBuilder({ page }).analyze();
console.log('With wait: ', waited.violations.map(v => v.id));

await page.close();
}

await browser.close();
})();
SCRIPT
env:
AUTH_CONTEXT: ${{ steps.auth.outputs.auth_context }}

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Verify axe timing issue" step installs Playwright/Axe at runtime via npm install without pinning versions or using actions/setup-node/npm ci with a lockfile, which can make the workflow slow and non-reproducible. If this is only for debugging, remove it before merging; otherwise, align it with the repo’s E2E workflow pattern (setup-node + cached npm ci + pinned deps).

Suggested change
- name: Verify axe timing issue (immediate vs after wait)
run: |
npm install playwright @axe-core/playwright
npx playwright install chromium --with-deps
node << 'SCRIPT'
const { chromium } = require('playwright');
const { default: AxeBuilder } = require('@axe-core/playwright');
const AUTH_CONTEXT = JSON.parse(process.env.AUTH_CONTEXT);
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext({
storageState: {
cookies: AUTH_CONTEXT.cookies.map(c => ({ ...c, sameSite: 'Lax', expires: -1 })),
origins: []
}
});
const urls = [
'http://localhost:3080/admin/theme',
'http://localhost:3080/admin/dashboard',
'http://localhost:3080/chat'
];
for (const url of urls) {
const page = await context.newPage();
await page.goto(url);
const immediate = await new AxeBuilder({ page }).analyze();
console.log(`\n=== ${url} ===`);
console.log('Without wait:', immediate.violations.map(v => v.id));
await page.waitForLoadState('networkidle');
const waited = await new AxeBuilder({ page }).analyze();
console.log('With wait: ', waited.violations.map(v => v.id));
await page.close();
}
await browser.close();
})();
SCRIPT
env:
AUTH_CONTEXT: ${{ steps.auth.outputs.auth_context }}

Copilot uses AI. Check for mistakes.
@onchul onchul marked this pull request as draft April 16, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants