-
Notifications
You must be signed in to change notification settings - Fork 42
feat: add automated axe-core accessibility scan in CI (Closes #175) #260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| /** | ||
| * Automated accessibility scan (axe-core) for the InvoFi frontend. | ||
| * | ||
| * Scans every page listed in issue #175 using @axe-core/playwright and fails CI | ||
| * on serious/critical violations. Known, unavoidable violations are documented | ||
| * in a waiver list (see waivers below). | ||
| * | ||
| * The scan runs alongside the Playwright smoke suite as a separate CI job | ||
| * (see .github/workflows/ci.yml) so it does not block the fast-feedback unit | ||
| * test / lint pass. | ||
| */ | ||
| import { test, expect } from '@playwright/test'; | ||
| import AxeBuilder from '@axe-core/playwright'; | ||
| import { | ||
| authenticate, | ||
| SMOKE_INVOICE, | ||
| SMOKE_INVOICES, | ||
| SMOKE_LISTINGS, | ||
| mockPositionListings, | ||
| } from './fixtures'; | ||
|
|
||
| /** | ||
| * Known-violation waiver list. | ||
| * | ||
| * Every entry documents a specific rule + CSS selector that we accept as a | ||
| * known limitation. Add entries here only when: | ||
| * 1. The violation is a false positive (axe-core heuristic limitations). | ||
| * 2. The element is from a third-party library we cannot patch. | ||
| * 3. The fix would require a cross-cutting refactor tracked in a separate issue. | ||
| * | ||
| * Format: { ruleId: string, selector: string, reason: string } | ||
| */ | ||
| const WAIVERS = [ | ||
| // The Stellar Wallet Kit dialog injects a <style> block with no text | ||
| // contrast requirements — it's a third-party overlay, not our code. | ||
| // Same for the SEP-10 wallet popup buttons. | ||
| { | ||
| ruleId: 'color-contrast', | ||
| selector: '.wallet-kit-dialog, [data-walletkit]', | ||
| reason: 'Third-party wallet kit dialog — upstream fix tracked in #176', | ||
| }, | ||
| // The wallet sign-in buttons are rendered by the Stellar Wallets Kit | ||
| // library and use its own styling. | ||
| { | ||
| ruleId: 'button-name', | ||
| selector: '[data-walletkit] button', | ||
| reason: 'Third-party wallet kit buttons — labelled by the library, not our DOM', | ||
| }, | ||
| // The Freighter auth redirect may produce a page with duplicate IDs | ||
| // from the extension's injected content script. | ||
| { | ||
| ruleId: 'duplicate-id', | ||
| selector: '#freighter-*', | ||
| reason: 'Freighter extension injects its own elements — not our DOM', | ||
|
Comment on lines
+49
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: printf '%s\n' '--- target file ---'
sed -n '1,130p' invofi/apps/frontend/e2e/a11y.spec.ts
printf '%s\n' '--- waiver and scan references ---'
rg -n -C 5 'WAIVERS|assertNoAccessibilityViolations|duplicate-id|freighter-\*|Freighter' invofi/apps/frontend/e2e invofi/apps/frontend package.json invofi/package.json 2>/dev/null || true
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 '`@axe-core/playwright`|axe-core|playwright' invofi/apps/frontend/package.json invofi/package.json invofi/pnpm-lock.yaml invofi/package-lock.json invofi/yarn.lock 2>/dev/null || trueRepository: Stellar-VaultLink/invofi Length of output: 45310 🌐 Web query:
💡 Result: The.exclude method in Citations:
🏁 Script executed: python3 - <<'PY'
import json, urllib.request
urls = {
"playwright_builder": "https://raw.githubusercontent.com/dequelabs/axe-core-npm/develop/packages/playwright/src/index.ts",
"axe_context": "https://raw.githubusercontent.com/dequelabs/axe-core/develop/lib/core/utils/ environment.js",
}
for name, url in urls.items():
print(f"--- {name}: {url} ---")
try:
with urllib.request.urlopen(url, timeout=10) as response:
text = response.read().decode()
for i, line in enumerate(text.splitlines(), 1):
if any(term in line for term in ("exclude", "selector", "querySelector", "Context")):
print(f"{i}: {line}")
except Exception as exc:
print(f"ERROR: {exc}")
PY
printf '%s\n' '--- local lockfile and package files ---'
git ls-files | rg '(^|/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|package\.json)$' | head -80Repository: Stellar-VaultLink/invofi Length of output: 979 🏁 Script executed: set -eu
printf '%s\n' '--- AxeBuilder implementation ---'
curl -kLs https://raw.githubusercontent.com/dequelabs/axe-core-npm/develop/packages/playwright/src/index.ts |
nl -ba | rg -C 8 'exclude|buildConfiguration|include'
printf '%s\n' '--- axe-core context documentation ---'
curl -kLs https://raw.githubusercontent.com/dequelabs/axe-core/develop/doc/context.md |
nl -ba | rg -C 8 'exclude|CSS selector|selector'
printf '%s\n' '--- relevant package-lock entries ---'
rg -n -C 3 '"`@axe-core/playwright`"|"axe-core"' invofi/apps/frontend/package-lock.jsonRepository: Stellar-VaultLink/invofi Length of output: 232 🏁 Script executed: printf '%s\n' '--- AxeBuilder implementation ---'
curl -kLs https://raw.githubusercontent.com/dequelabs/axe-core-npm/develop/packages/playwright/src/index.ts |
awk '/exclude|buildConfiguration|include/ { for (i=NR-8; i<=NR+8; i++) wanted[i]=1 } { lines[NR]=$0 } END { for (i=1; i<=NR; i++) if (wanted[i]) printf "%d: %s\n", i, lines[i] }'
printf '%s\n' '--- axe-core context documentation ---'
curl -kLs https://raw.githubusercontent.com/dequelabs/axe-core/develop/doc/context.md |
awk '/exclude|CSS selector|selector/ { for (i=NR-8; i<=NR+8; i++) wanted[i]=1 } { lines[NR]=$0 } END { for (i=1; i<=NR; i++) if (wanted[i]) printf "%d: %s\n", i, lines[i] }'
printf '%s\n' '--- relevant package-lock entries ---'
rg -n -C 3 '"`@axe-core/playwright`"|"axe-core"' invofi/apps/frontend/package-lock.json || trueRepository: Stellar-VaultLink/invofi Length of output: 16551 🌐 Web query:
💡 Result: The CSS selector Citations:
🏁 Script executed: printf '%s\n' '--- axe-core 4.13 context normalization and selector handling ---'
for url in \
https://raw.githubusercontent.com/dequelabs/axe-core/v4.13.0/lib/core/utils/commons.js \
https://raw.githubusercontent.com/dequelabs/axe-core/v4.13.0/lib/core/utils/selector.js \
https://raw.githubusercontent.com/dequelabs/axe-core/v4.13.0/lib/core/utils/normalize.js \
https://raw.githubusercontent.com/dequelabs/axe-core/v4.13.0/lib/core/base/context.js
do
echo "--- $url ---"
curl -kLs "$url" | rg -n -C 5 'querySelectorAll|selector|exclude|normalizeContext' || true
done
printf '%s\n' '--- available browser/runtime probes ---'
command -v chromium || true
command -v chromium-browser || true
command -v google-chrome || true
command -v firefox || true
node -e "for (const p of ['playwright','`@playwright/test`','jsdom','css-tree','postcss-selector-parser']) { try { console.log(p, require.resolve(p)) } catch { console.log(p, 'unavailable') } }"Repository: Stellar-VaultLink/invofi Length of output: 3519 🏁 Script executed: python3 - <<'PY'
import re
selectors = ['`#freighter-`*', '[id^="freighter-"]']
ids = ['freighter-dialog', 'freighter-', 'other-dialog']
id_selector = re.compile(r'^#[A-Za-z_][A-Za-z0-9_-]*$')
prefix_selector = re.compile(r'^\[id\^="([^"]*)"\]$')
for selector in selectors:
if id_selector.fullmatch(selector):
result = [value for value in ids if value == selector[1:]]
status = 'valid ID selector'
elif (match := prefix_selector.fullmatch(selector)):
result = [value for value in ids if value.startswith(match.group(1))]
status = 'valid prefix attribute selector'
else:
result = 'SyntaxError'
status = 'invalid selector'
print(f'{selector}: {status}; result={result}')
PYRepository: Stellar-VaultLink/invofi Length of output: 308 Replace the invalid Freighter selector.
🤖 Prompt for AI Agents |
||
| }, | ||
| ]; | ||
|
|
||
| /** | ||
| * Runs axe-core on the current page and checks for violations, excluding | ||
| * known waivers. | ||
| */ | ||
| async function assertNoAccessibilityViolations(page: any) { | ||
| const builder = new AxeBuilder({ page }) | ||
| .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) | ||
| // Exclude known waivers | ||
| .options({ | ||
| runOnly: { | ||
| type: 'tag', | ||
| values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'], | ||
| }, | ||
| }); | ||
|
|
||
| // Apply each waiver | ||
| for (const waiver of WAIVERS) { | ||
| builder.exclude(waiver.selector); | ||
| } | ||
|
Comment on lines
+73
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
file="invofi/apps/frontend/e2e/a11y.spec.ts"
printf '%s\n' '--- target file ---'
cat -n "$file" | sed -n '1,130p'
printf '%s\n' '--- related declarations and usages ---'
rg -n -C 8 'WAIVERS|assertNoAccessibilityViolations|ruleId|exclude\(' "$file" invofi/apps/frontend 2>/dev/null || true
printf '%s\n' '--- repository metadata and dependency declarations ---'
rg -n -C 3 '"`@axe-core/playwright`"|"axe-core"|`@axe-core/playwright`' \
invofi/package.json invofi/*lock* invofi/**/package.json 2>/dev/null || trueRepository: Stellar-VaultLink/invofi Length of output: 27486 🏁 Script executed: #!/bin/bash
set -eu
file="invofi/apps/frontend/e2e/a11y.spec.ts"
printf '%s\n' '--- target file ---'
cat -n "$file" | sed -n '1,130p'
printf '%s\n' '--- related declarations and usages ---'
rg -n -C 8 'WAIVERS|assertNoAccessibilityViolations|ruleId|exclude\(' "$file" invofi/apps/frontend 2>/dev/null || true
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 '"`@axe-core/playwright`"|"axe-core"|`@axe-core/playwright`' \
invofi/package.json invofi/*lock* invofi/**/package.json 2>/dev/null || trueRepository: Stellar-VaultLink/invofi Length of output: 27462 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- package and lock files ---'
git ls-files | rg '(^|/)(package\.json|.*lock.*|.*Axe.*|.*axe.*)$' | sed -n '1,120p'
printf '%s\n' '--- AxeBuilder references and local documentation ---'
rg -n -C 5 'class AxeBuilder|exclude\(|include\(|axe-playwright|`@axe-core/playwright`|axe-core' . \
-g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' 2>/dev/null | sed -n '1,260p'
printf '%s\n' '--- selector syntax probe ---'
node - <<'JS'
const selectors = [
'.wallet-kit-dialog, [data-walletkit]',
'[data-walletkit] button',
'`#freighter-`*',
];
for (const selector of selectors) {
try {
new Function(`return document.querySelectorAll(${JSON.stringify(selector)})`);
console.log(`${selector}: parser construction only`);
} catch (error) {
console.log(`${selector}: ${error.name}: ${error.message}`);
}
}
JSRepository: Stellar-VaultLink/invofi Length of output: 9311 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- repository roots and frontend files ---'
git ls-files | sed -n '1,100p'
printf '%s\n' '--- package metadata anywhere in the repository ---'
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' | while IFS= read -r f; do
echo "--- $f"
sed -n '1,180p' "$f"
done
printf '%s\n' '--- `@axe-core/playwright` API source ---'
curl -L --fail --silent --show-error \
https://raw.githubusercontent.com/dequelabs/axe-playwright/develop/packages/playwright/src/builder.ts \
| sed -n '1,240p'
printf '%s\n' '--- axe-core context exclusion implementation ---'
curl -L --fail --silent --show-error \
https://raw.githubusercontent.com/dequelabs/axe-core/develop/lib/core/public/run.js \
| rg -n -C 8 'exclude|include|context' | sed -n '1,220p'Repository: Stellar-VaultLink/invofi Length of output: 31948 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- repository roots and frontend files ---'
git ls-files | sed -n '1,100p'
printf '%s\n' '--- package metadata ---'
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' | while IFS= read -r f; do
echo "--- $f"
sed -n '1,180p' "$f"
done
printf '%s\n' '--- `@axe-core/playwright` API source ---'
curl -L --fail --silent --show-error \
https://raw.githubusercontent.com/dequelabs/axe-playwright/develop/packages/playwright/src/builder.ts \
| sed -n '1,260p'
printf '%s\n' '--- axe-core context exclusion implementation ---'
curl -L --fail --silent --show-error \
https://raw.githubusercontent.com/dequelabs/axe-core/develop/lib/core/public/run.js \
| rg -n -C 8 'exclude|include|context' | sed -n '1,220p'Repository: Stellar-VaultLink/invofi Length of output: 31921 🏁 Script executed: #!/bin/bash
set -eu
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
url='https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.13.0.tgz'
curl -L --fail --silent --show-error "$url" -o "$tmp/playwright.tgz"
printf '%s\n' '--- package contents ---'
tar -tzf "$tmp/playwright.tgz" | sed -n '1,160p'
printf '%s\n' '--- builder implementation and declarations ---'
for f in package/dist/index.js package/dist/index.mjs package/dist/builder.js package/dist/builder.mjs package/dist/index.d.ts package/dist/builder.d.ts; do
if tar -tzf "$tmp/playwright.tgz" | grep -qx "$f"; then
echo "--- $f"
tar -xOzf "$tmp/playwright.tgz" "$f" | rg -n -C 12 'exclude|include|analyze|class AxeBuilder' | sed -n '1,260p'
fi
done
printf '%s\n' '--- axe-core package context implementation ---'
curl -L --fail --silent --show-error \
'https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz' -o "$tmp/axe.tgz"
tar -tzf "$tmp/axe.tgz" | rg 'context|run\.js|utils|commons' | sed -n '1,160p'Repository: Stellar-VaultLink/invofi Length of output: 9716 🏁 Script executed: #!/bin/bash
set -eu
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -L --fail --silent --show-error \
'https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.13.0.tgz' \
-o "$tmp/playwright.tgz"
printf '%s\n' '--- package contents ---'
tar -tzf "$tmp/playwright.tgz" | sed -n '1,160p'
printf '%s\n' '--- builder implementation and declarations ---'
for f in package/dist/index.js package/dist/index.mjs package/dist/builder.js package/dist/builder.mjs package/dist/index.d.ts package/dist/builder.d.ts; do
if tar -tzf "$tmp/playwright.tgz" | grep -qx "$f"; then
echo "--- $f"
tar -xOzf "$tmp/playwright.tgz" "$f" \
| rg -n -C 12 'exclude|include|analyze|class AxeBuilder' \
| sed -n '1,260p'
fi
done
printf '%s\n' '--- axe-core package files ---'
curl -L --fail --silent --show-error \
'https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz' \
-o "$tmp/axe.tgz"
tar -tzf "$tmp/axe.tgz" \
| rg 'context|run\.js|utils|commons' \
| sed -n '1,160p'Repository: Stellar-VaultLink/invofi Length of output: 9699 🏁 Script executed: #!/bin/bash
set -eu
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -L --fail --silent --show-error \
'https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz' \
-o "$tmp/axe.tgz"
printf '%s\n' '--- axe-core package contents ---'
tar -tzf "$tmp/axe.tgz" | sed -n '1,220p'
printf '%s\n' '--- selector/context code in axe-core bundle ---'
for f in package/axe.js package/axe.min.js package/axe.d.ts; do
if tar -tzf "$tmp/axe.tgz" | grep -qx "$f"; then
echo "--- $f"
tar -xOzf "$tmp/axe.tgz" "$f" \
| rg -n -o '.{0,160}(querySelectorAll|matches\\(|exclude|invalid selector|selector).{0,220}' \
| sed -n '1,220p'
fi
done
printf '%s\n' '--- read-only behavioral verifier ---'
python3 - "$tmp" <<'PY'
import json, re, subprocess, sys, tarfile
from pathlib import Path
root = Path("invofi/apps/frontend")
source = Path("invofi/apps/frontend/e2e/a11y.spec.ts").read_text()
lock = json.loads((root / "package-lock.json").read_text())
assert "builder.exclude(waiver.selector)" in source
assert "waiver.ruleId" not in source
assert lock["packages"]["node_modules/@axe-core/playwright"]["version"] == "4.13.0"
tgz = Path(sys.argv[1]) / "playwright.tgz"
with tarfile.open(tgz) as archive:
js = archive.extractfile("package/dist/index.js").read().decode()
assert "this.excludes.push(selector)" in js
assert "base.exclude.push(...excludes)" in js
assert "axeCore.run(context || document, options || {})" in js
print("WAIVERS.ruleId: declared but not consumed")
print("AxeBuilder.exclude: accumulates selectors")
print("analyze(): passes all accumulated selectors as one axe context exclusion")
print("result: exclusions apply at axe context scope, before rule-specific results")
PYRepository: Stellar-VaultLink/invofi Length of output: 2009 🏁 Script executed: #!/bin/bash
set -eu
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -L --fail --silent --show-error \
'https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz' \
-o "$tmp/axe.tgz"
printf '%s\n' '--- axe-core package contents ---'
tar -tzf "$tmp/axe.tgz" | sed -n '1,220p'
printf '%s\n' '--- selector/context code in axe-core bundle ---'
for f in package/axe.js package/axe.min.js package/axe.d.ts; do
if tar -tzf "$tmp/axe.tgz" | grep -qx "$f"; then
echo "--- $f"
tar -xOzf "$tmp/axe.tgz" "$f" \
| rg -n -o '.{0,160}(querySelectorAll|matches\\(|exclude|invalid selector|selector).{0,220}' \
| sed -n '1,220p'
fi
done
printf '%s\n' '--- read-only behavioral verifier ---'
python3 - "$tmp" <<'PY'
import json, sys, tarfile
from pathlib import Path
root = Path("invofi/apps/frontend")
source = Path("invofi/apps/frontend/e2e/a11y.spec.ts").read_text()
lock = json.loads((root / "package-lock.json").read_text())
assert "builder.exclude(waiver.selector)" in source
assert "waiver.ruleId" not in source
assert lock["packages"]["node_modules/@axe-core/playwright"]["version"] == "4.13.0"
with tarfile.open(Path(sys.argv[1]) / "playwright.tgz") as archive:
js = archive.extractfile("package/dist/index.js").read().decode()
assert "this.excludes.push(selector)" in js
assert "base.exclude.push(...excludes)" in js
assert "axeCore.run(context || document, options || {})" in js
print("WAIVERS.ruleId: declared but not consumed")
print("AxeBuilder.exclude: accumulates selectors")
print("analyze(): passes all accumulated selectors as one axe context exclusion")
print("result: exclusions apply at axe context scope, before rule-specific results")
PYRepository: Stellar-VaultLink/invofi Length of output: 2009 🏁 Script executed: #!/bin/bash
set -eu
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -L --fail --silent --show-error \
'https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.13.0.tgz' \
-o "$tmp/playwright.tgz"
curl -L --fail --silent --show-error \
'https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz' \
-o "$tmp/axe.tgz"
printf '%s\n' '--- axe-core selector implementation references ---'
tar -xOzf "$tmp/axe.tgz" package/axe.js > "$tmp/axe.js"
grep -n -E -C 8 'querySelectorAll|querySelector|matches|exclude' "$tmp/axe.js" \
| sed -n '1,260p'
printf '%s\n' '--- available DOM runtimes ---'
for cmd in chromium chromium-browser google-chrome google-chrome-stable firefox; do
command -v "$cmd" 2>/dev/null || true
done
printf '%s\n' '--- read-only behavioral verifier ---'
python3 - "$tmp" <<'PY'
import json, sys, tarfile
from pathlib import Path
source = Path("invofi/apps/frontend/e2e/a11y.spec.ts").read_text()
lock = json.loads(Path("invofi/apps/frontend/package-lock.json").read_text())
assert "builder.exclude(waiver.selector)" in source
assert "waiver.ruleId" not in source
assert lock["packages"]["node_modules/@axe-core/playwright"]["version"] == "4.13.0"
with tarfile.open(Path(sys.argv[1]) / "playwright.tgz") as archive:
js = archive.extractfile("package/dist/index.js").read().decode()
assert "this.excludes.push(selector)" in js
assert "base.exclude.push(...excludes)" in js
assert "axeCore.run(context || document, options || {})" in js
print("WAIVERS.ruleId: declared but not consumed")
print("AxeBuilder.exclude: accumulates selectors")
print("analyze(): passes all accumulated selectors as one axe context exclusion")
print("result: each exclusion applies to the axe analysis context, not to one rule")
PYRepository: Stellar-VaultLink/invofi Length of output: 12227 Apply each waiver only to its declared rule.
🤖 Prompt for AI Agents |
||
|
|
||
| const results = await builder.analyze(); | ||
|
|
||
| // Filter to only serious/critical violations | ||
| const seriousOrCritical = results.violations.filter( | ||
| (v) => v.impact === 'serious' || v.impact === 'critical', | ||
| ); | ||
|
|
||
| // Log all violations for debugging | ||
| if (seriousOrCritical.length > 0) { | ||
| console.log(`\n⚠️ ${seriousOrCritical.length} serious/critical a11y violations found:`); | ||
| for (const v of seriousOrCritical) { | ||
| console.log(` • ${v.id} (${v.impact}) — ${v.help}`); | ||
| console.log(` ${v.helpUrl}`); | ||
| for (const node of v.nodes.slice(0, 3)) { | ||
| console.log(` Target: ${node.target}`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| expect(seriousOrCritical).toHaveLength(0); | ||
| } | ||
|
|
||
| // ── Public pages ──────────────────────────────────────────────────────────── | ||
|
|
||
| test.describe('public page accessibility', () => { | ||
| test('landing page has no serious/critical violations', async ({ page }) => { | ||
| await page.goto('/'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('login page has no serious/critical violations', async ({ page }) => { | ||
| await page.goto('/auth/login'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('register page has no serious/critical violations', async ({ page }) => { | ||
| await page.goto('/auth/register'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('register (lender role) page has no serious/critical violations', async ({ page }) => { | ||
| await page.goto('/auth/register?role=lender'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
| }); | ||
|
|
||
| // ── Authenticated pages ───────────────────────────────────────────────────── | ||
|
|
||
| test.describe('authenticated page accessibility', () => { | ||
| test('dashboard page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page, { invoices: SMOKE_INVOICES }); | ||
| await page.goto('/dashboard'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('marketplace page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page, { invoices: SMOKE_INVOICES }); | ||
| await page.goto('/marketplace'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('marketplace positions page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page); | ||
| await mockPositionListings(page, SMOKE_LISTINGS); | ||
| await page.goto('/marketplace/positions'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('invoice detail page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page, { invoice: SMOKE_INVOICE }); | ||
| await page.goto(`/invoices/${SMOKE_INVOICE.id}`); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('portfolio page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page, { invoices: SMOKE_INVOICES }); | ||
| await page.goto('/portfolio'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('settings page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page, { invoices: SMOKE_INVOICES }); | ||
| await page.goto('/settings'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('transactions page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page, { invoices: SMOKE_INVOICES }); | ||
| await page.goto('/transactions'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
|
|
||
| test('profile page has no serious/critical violations', async ({ page }) => { | ||
| await authenticate(page, { invoices: SMOKE_INVOICES }); | ||
| await page.goto('/profile'); | ||
| await assertNoAccessibilityViolations(page); | ||
| }); | ||
| }); | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Stellar-VaultLink/invofi
Length of output: 16048
🏁 Script executed:
Repository: Stellar-VaultLink/invofi
Length of output: 15774
🌐 Web query:
Official actions/checkout persist-credentials default action.yml documentation💡 Result:
In the actions/checkout GitHub Action, the persist-credentials input defaults to true [1][2][3]. When set to true, the action configures the provided token or SSH key within the local git configuration [1][4][3]. This allows subsequent git commands in your workflow to run authenticated [1][5][4]. The action automatically removes these credentials during the post-job cleanup phase [1][6][4]. While there have been community discussions and pull requests proposing to change this default to false to reduce potential security risks [7], as of August 2026, the official documentation and the action.yml file maintain the default value as true [2][3][8]. If you wish to disable this behavior for security or other reasons, you must explicitly set persist-credentials: false in your workflow step [1][6][4].
Citations:
Disable checkout credential persistence.
actions/checkoutdefaultspersist-credentialstotrueand storesGITHUB_TOKENin local Git configuration. PR-controlled commands can access this token. Setpersist-credentials: false. The workflow already setscontents: readat the workflow level.🧰 Tools
🪛 zizmor (1.29.0)
[warning] 54-54: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Source: Linters/SAST tools