Accessibility Auditing — Manual & Automated Audit Methodology Guide
Accessibility auditing is the systematic evaluation of a website or application against WCAG success criteria using a combination of automated tools, manual keyboard testing, screen reader testing, and assistive technology validation — producing actionable reports with prioritized remediation guidance.
What You'll Learn
By the end of this guide, you'll understand the full Accessibility audit methodology, how to run and interpret automated scans with axe-core and WAVE, how to perform manual keyboard and screen reader tests, how to evaluate against WCAG success criteria, how to prioritize findings by impact and effort, and how to integrate auditing into your CI/CD pipeline.
Why Auditing Matters
Automated tools catch approximately 30% of Accessibility issues. Manual testing catches another 50%. User testing catches the remaining 20%. Without a systematic audit methodology, you cannot know whether your site is accessible or whether you are exposed to legal risk. At DodaTech, Doda Browser includes an Accessibility audit panel, and Durga Antivirus Pro undergoes quarterly third-party Accessibility audits with remediation sprints.
Audit Methodology Flow
flowchart TD
A[Accessibility Audit] --> B[Phase 1: Automated scan]
A --> C[Phase 2: Manual keyboard test]
A --> D[Phase 3: Screen reader test]
A --> E[Phase 4: WCAG evaluation]
A --> F[Phase 5: Reporting]
B --> G[axe-core, WAVE, Lighthouse]
C --> H[Tab navigation, focus visibility]
D --> I[NVDA, VoiceOver, JAWS]
E --> J[Per criterion assessment]
F --> K[Prioritized remediation plan]
G --> K
H --> K
I --> K
J --> K
K --> L[Fix and re-audit]
{{< callout type="info" icon="sparkles" >}} Prerequisites: Understanding of WCAG success criteria and conformance levels. Familiarity with browser DevTools and at least one screen reader. {{< /callout >}}
Phase 1: Automated Scanning
Automated tools catch structural issues quickly. Run them first, then manually verify each finding:
# axe-core CLI — scan a URL
npx axe HTTPS://dodatech.com --save audit-results.JSON
# Pa11y CI — run against multiple URLs
npx pa11y-ci --config .pa11yci.JSON
# Lighthouse — programmatic audit
npx lighthouse HTTPS://dodatech.com --output JSON --output-path lh-report.JSON
// pa11y-ci.config.js
module.exports = {
defaults: {
standard: 'WCAG2AA',
runners: ['axe', 'htmlcs'],
timeout: 30000,
hideElements: '.cookie-banner, .analytics-frame'
},
urls: [
'HTTPS://dodatech.com/',
'HTTPS://dodatech.com/products/doda-browser',
'HTTPS://dodatech.com/products/durga-antivirus',
'HTTPS://dodatech.com/products/dodazip',
'HTTPS://dodatech.com/Accessibility/'
]
};
// Audit script — run automated checks and save results
const { run } = require('axe-core');
async function runAutomatedAudit(url) {
const results = await run(url, {
runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']
});
const violations = results.violations.map(v => ({
id: v.id,
impact: v.impact,
tags: v.tags,
description: v.description,
help: v.help,
helpUrl: v.helpUrl,
elements: v.nodes.map(n => n.target.join(' '))
}));
return {
url,
timestamp: new Date().toISOString(),
totalViolations: violations.length,
byImpact: {
critical: violations.filter(v => v.impact === 'critical').length,
serious: violations.filter(v => v.impact === 'serious').length,
moderate: violations.filter(v => v.impact === 'moderate').length,
minor: violations.filter(v => v.impact === 'minor').length
},
violations
};
}
runAutomatedAudit('https://dodatech.com/products/durga-antivirus')
.then(console.log);
Expected output:
{
"url": "https://dodatech.com/products/durga-antivirus",
"totalViolations": 4,
"byImpact": { "critical": 0, "serious": 2, "moderate": 1, "minor": 1 },
"violations": [
{ "id": "color-contrast", "impact": "serious", "elements": [".secondary-text"] }
]
}
Phase 2: Manual Keyboard Testing
Automated tools cannot test keyboard flow. Perform these checks manually:
### Keyboard Audit Checklist
1. **Tab through all interactive elements** — Start at the top of the page. Press Tab repeatedly. Every link, button, form field, and custom widget must receive focus.
2. **Check focus visibility** — The focus indicator must be visible on every element. WCAG 2.4.11 requires 2px minimum thickness and 3:1 contrast.
3. **Test custom widgets** — Tab into the widget. Use arrow keys, Enter, Space, and Escape. Verify behavior matches the ARIA Authoring Practices.
4. **Test skip links** — Press Tab on page load. The skip link should be the first focusable element and must move focus to the main content.
5. **Check Tab order** — Focus should move in logical DOM order. If the visual order differs from the DOM order, document the discrepancy.
6. **Test dialogs** — Open a modal. Tab should cycle through all elements. Escape should close. Focus must return to the trigger.
7. **Test forms** — Tab through fields. Submit with errors. Verify focus moves to the error summary or first error field.
Phase 3: Screen Reader Testing
Screen reader testing reveals issues that automated tools and keyboard testing miss:
<!-- Screen Reader Test Plan Template -->
<details>
<summary>NVDA Test Plan — DodaTech Homepage</summary>
<table>
<thead>
<tr>
<th>Test</th>
<th>Expected</th>
<th>Actual</th>
<th>Pass/Fail</th>
</tr>
</thead>
<tbody>
<tr>
<td>Page title announced</td>
<td>"DodaTech — Secure Browsing and Antivirus"</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Landmarks navigable</td>
<td>banner, navigation, main, contentinfo</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Heading hierarchy</td>
<td>h1 > h2 > h3, no skipped levels</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Alt text on product images</td>
<td>"Doda Browser logo", "Durga Antivirus Pro icon"</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Link text descriptive</td>
<td>"Download Doda Browser", not "Click here"</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Form labels announced</td>
<td>"Email address" for email field</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Error messages announced</td>
<td>"Email address is required"</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</details>
Phase 4: WCAG Evaluation
Evaluate each WCAG success criterion applicable to your content:
// wcag-evaluator.js — Track evaluation per criterion
const wcagAudit = {
page: 'Durga Antivirus Pro — Dashboard',
date: '2026-06-24',
evaluator: 'DodaTech QA Team',
criteria: [
{
id: '1.1.1',
name: 'Non-text Content',
level: 'A',
result: 'Pass',
notes: 'All icons have aria-label. Charts have data table fallbacks.',
testingMethod: 'Automated + manual review'
},
{
id: '1.4.3',
name: 'Contrast Minimum',
level: 'AA',
result: 'Partial',
notes: 'Status badge colors need adjustment (3.8:1, requires 4.5:1).',
testingMethod: 'Automated color contrast check',
remediation: 'Update badge colors in design tokens. Target: v4.3.'
},
{
id: '2.4.11',
name: 'Focus Appearance',
level: 'AA',
result: 'Pass',
notes: '2px blue outline on all interactive elements.',
testingMethod: 'Manual keyboard test'
}
],
summary() {
const passes = this.criteria.filter(c => c.result === 'Pass').length;
const partials = this.criteria.filter(c => c.result === 'Partial').length;
const fails = this.criteria.filter(c => c.result === 'Fail').length;
return {
total: this.criteria.length,
pass: passes,
partial: partials,
fail: fails,
passRate: `${((passes / this.criteria.length) * 100).toFixed(0)}%`
};
}
};
console.log(wcagAudit.summary());
Expected output:
{ "total": 3, "pass": 2, "partial": 1, "fail": 0, "passRate": "67%" }
Phase 5: Prioritization and Reporting
Not all issues are equal. Prioritize by impact and effort:
### Prioritization Matrix
| Priority | Criteria | Impact | Effort | Action |
|----------|----------|--------|--------|--------|
| P0 | Critical Level A failures | Users completely blocked | Fix immediately |
| P1 | Serious Level AA failures | Users significantly impacted | Fix this sprint |
| P2 | Moderate Level AA issues | Users inconvenienced | Fix next sprint |
| P3 | Minor Level AAA suggestions | Enhancement | Add to backlog |
// Generate remediation report
function generateReport(auditData) {
const priorityOrder = { critical: 'P0', serious: 'P1', moderate: 'P2', minor: 'P3' };
return `
# Accessibility Audit Report
**Page:** ${auditData.url}
**Date:** ${auditData.timestamp}
**Evaluator:** DodaTech QA
## Summary
- Total issues: ${auditData.totalViolations}
- P0 (Critical): ${auditData.byImpact.critical}
- P1 (Serious): ${auditData.byImpact.serious}
- P2 (Moderate): ${auditData.byImpact.moderate}
- P3 (Minor): ${auditData.byImpact.minor}
## Issues by Priority
${Object.entries(auditData.byImpact).map(([impact, count]) => `
### ${priorityOrder[impact]} - ${impact.toUpperCase()} (${count})
${
auditData.violations
.filter(v => v.impact === impact)
.map(v => `- **${v.id}**: ${v.help}\n Elements: ${v.elements.join(', ')}`)
.join('\n')
}
`).join('\n')}
## Remediation Plan
1. Fix all P0 issues before next deployment
2. Fix P1 issues in current sprint
3. Schedule P2 for next sprint
4. Review P3 for backlog
`;
}
CI/CD Integration
Integrate automated checks into your build pipeline to catch regressions:
# .github/workflows/accessibility.yml
name: Accessibility CI
on: [pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run build
- name: Run axe-core
run: |
npx axe http://localhost:3000 \
--exit \
--stdout \
--save axe-results.json
- name: Run Pa11y
run: |
npx pa11y-ci --config .pa11yci.json \
--json > pa11y-results.json
- name: Check for violations
run: |
node -e "
const results = require('./axe-results.json');
const violations = results.violations || [];
const critical = violations.filter(v => v.impact === 'critical');
if (critical.length > 0) {
console.error('FAIL: ' + critical.length + ' critical violations found');
process.exit(1);
}
console.log('PASS: No critical violations');
"
Common Mistakes
1. Only Using Automated Tools
Relying solely on axe-core or WAVE gives a false sense of security. Automated tools catch at most 30% of issues.
2. Not Testing with Real Screen Readers
Simulators and emulators do not replace testing with actual NVDA, JAWS, or VoiceOver. Browser DevTools Accessibility tree is a starting point, not a replacement.
3. Ignoring Mobile Accessibility
Testing only on desktop misses mobile-specific issues: touch targets, viewport zoom, orientation, and mobile screen reader gestures.
4. No Baseline Before Fixing
Without a documented baseline, you cannot measure improvement. Run and save a full audit before starting remediation.
5. Prioritizing Only by WCAG Level
A Level A issue on an unused admin page may be LESS urgent than a Level AA issue on the public checkout flow. Prioritize by user impact.
6. No Remediation Timeline
An audit without a remediation timeline is shelfware. Assign priorities and deadlines for every finding.
7. Not Re-Auditing After Fixes
Fixes can introduce new issues. Always re-audit after remediation to verify fixes and catch regressions.
Practice Questions
1. What percentage of Accessibility issues do automated tools catch?
Approximately 30%. Manual keyboard and screen reader testing are essential for full coverage.
2. What are the five phases of an Accessibility audit?
Automated scan, manual keyboard test, screen reader test, WCAG evaluation, and reporting with prioritized remediation.
3. How should you prioritize Accessibility issues?
By impact (critical, serious, moderate, minor) and user impact. P0 critical level A failures should be fixed immediately.
4. Why is it important to re-audit after fixes?
Fixes can introduce new Accessibility issues. Re-auditing ensures the fix worked and no regressions occurred.
5. Challenge: Run a full Accessibility audit on a page you maintain. Use axe-core for automated scanning, perform a manual keyboard test, test with NVDA or VoiceOver, evaluate each WCAG criterion, and produce a prioritized remediation report.
Real-World Task
Set up an Accessibility CI/CD pipeline for your project. Configure Pa11y CI to run against your staging environment on every Pull Request. Configure the pipeline to fail on critical and serious violations. Run a baseline audit and save the results.
FAQ
Try It Yourself
Run an automated audit on a DodaTech product page and generate a report:
// Run this in browser DevTools console on dodatech.com
(async function auditCurrentPage() {
const { default: axe } = await import('HTTPS://CDN.jsdelivr.net/Npm/axe-core/axe.min.js');
const results = await axe.run(document, {
runOnly: ['wcag2a', 'wcag2aa', 'wcag22aa']
});
console.log(`Violations found: ${results.violations.length}`);
results.violations.forEach(v => {
console.log(`[${v.impact}] ${v.id}: ${v.help}`);
console.log(` Elements: ${v.nodes.length}`);
});
console.log(`Passes: ${results.passes.length}`);
console.log(`Incomplete: ${results.incomplete.length}`);
console.log(`Inapplicable: ${results.inapplicable.length}`);
})();
What's Next
Congratulations on completing this Accessibility Auditing tutorial! Here is where to Go from here:
- Practice daily — Run an automated scan on one page per day
- Build a project — Set up a CI/CD Accessibility pipeline for your team
- Explore related topics — Learn more about Accessibility Testing tools
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro