Skip to content

Commit

Permalink
Merge pull request #315 from GSA/a11y-refactor
Browse files Browse the repository at this point in the history
Refactor a11y scan
  • Loading branch information
akuny authored Mar 27, 2024
2 parents 16e7e7d + d8ab9f3 commit 683a889
Show file tree
Hide file tree
Showing 13 changed files with 539 additions and 129 deletions.
8 changes: 4 additions & 4 deletions entities/core-result.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,12 +368,12 @@ export class CoreResult {
accessibilityScanStatus?: string;

@Column({ nullable: true })
@Expose({ name: 'accessibility_violations' })
@Expose({ name: 'accessibility_results' })
@Exclude()
accessibilityViolations?: string;
accessibilityResults?: string;

@Column({ nullable: true })
@Expose({ name: 'accessibility_violations_list' })
@Expose({ name: 'accessibility_results_list' })
@Exclude()
@Transform((value: string) => {
if (value) {
Expand All @@ -382,7 +382,7 @@ export class CoreResult {
return null;
}
})
accessibilityViolationsList?: string;
accessibilityResultsList?: string;

@Column({ nullable: true })
@Expose({ name: 'viewport_meta_tag' })
Expand Down
4 changes: 2 additions & 2 deletions entities/scan-data.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export type SearchScan = {
};

export type AccessibilityScan = {
accessibilityViolations: string;
accessibilityViolationsList: string;
accessibilityResults: string;
accessibilityResultsList: string;
};

export type MobileScan = {
Expand Down
4 changes: 2 additions & 2 deletions libs/core-scanner/src/core-scanner.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ export class CoreScannerService
status: ScanStatus.Completed,
result: {
accessibilityScan: {
accessibilityViolations: result.accessibilityViolations,
accessibilityViolationsList: result.accessibilityViolationsList,
accessibilityResults: result.accessibilityResults,
accessibilityResultsList: result.accessibilityResultsList,
},
},
error: null,
Expand Down
113 changes: 0 additions & 113 deletions libs/core-scanner/src/pages/accessibility.ts

This file was deleted.

38 changes: 38 additions & 0 deletions libs/core-scanner/src/pages/accessibility/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Logger } from 'pino';
import { getHttpsUrl } from '../../util';
import { CoreInputDto } from '../../core.input.dto';
import { Page } from 'puppeteer';
import { AccessibilityScan } from 'entities/scan-data.entity';
import { AxePuppeteer } from '@axe-core/puppeteer';
import { aggregateResults } from './results-aggregator';

export const createAccessibilityScanner = (
logger: Logger,
input: CoreInputDto,
) => {
logger.info('Starting a11y scan...');

return async (page: Page): Promise<AccessibilityScan> => {
page.on('console', (message) => console.log('PAGE LOG:', message.text()));
page.on('error', (error) => console.log('ERROR LOG:', error));

await page.goto(getHttpsUrl(input.url));

const axeScanResult = await new AxePuppeteer(page).analyze();
const violationResults = axeScanResult.violations;

const { resultsSummary, resultsList } = aggregateResults(violationResults);

const accessibilityResults = Object.keys(resultsSummary).length
? JSON.stringify(resultsSummary)
: null;
const accessibilityResultsList = resultsList.length
? JSON.stringify(resultsList)
: null;

return {
accessibilityResults,
accessibilityResultsList,
};
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { promises as fs } from 'fs';
import { join } from 'path';
import { Result } from 'axe-core';
import { aggregateResults } from './results-aggregator';

async function readJsonFile(filePath) {
try {
const jsonString = await fs.readFile(filePath, 'utf8');
const jsonObject = JSON.parse(jsonString);
return jsonObject;
} catch (error) {
console.error('Error reading the file:', error);
}
}

describe('aggregateResults', () => {
it('should aggregate results from a list of one result', async () => {
const results: Result[] = await readJsonFile(
join(__dirname, './test-fixtures/results1Raw.json'),
);

const result = aggregateResults(results);

const expectedResult = await readJsonFile(
join(__dirname, './test-fixtures/results1Expected.json'),
);

expect(result).toEqual(expectedResult);
});

it('should aggregate results from a list of two results', async () => {
const results: Result[] = await readJsonFile(
join(__dirname, './test-fixtures/results2Raw.json'),
);

const result = aggregateResults(results);

const expectedResult = await readJsonFile(
join(__dirname, './test-fixtures/results2Expected.json'),
);

expect(result).toEqual(expectedResult);
});
});
141 changes: 141 additions & 0 deletions libs/core-scanner/src/pages/accessibility/results-aggregator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
Result,
NodeResult,
CheckResult,
TagValue,
ImpactValue,
UnlabelledFrameSelector,
RelatedNode,
} from 'axe-core';

type ResultSubset = {
description: string;
helpUrl: string;
id: string;
tags: TagValue[];
nodes: NodeResultSubset[];
};

type NodeResultSubset = {
html: string;
impact?: ImpactValue;
xpath?: string[];
ancestry?: UnlabelledFrameSelector;
any: CheckResultSubset[];
all: CheckResultSubset[];
none: CheckResultSubset[];
element?: HTMLElement;
};

type CheckResultSubset = {
id: string;
impact: string;
message: string;
relatedNodes?: RelatedNode[];
};

type AggregatedResults = {
resultsSummary: Record<string, number>;
resultsList: ResultSubset[];
};

export function aggregateResults(results: Result[]): AggregatedResults {
const resultsSummary = {};
const rawResultsList = [];

// Mapping of a11y violation categories to axe-core Result id values
const resultCategoryMapping = {
aria: [
'aria-allowed-attr',
'aria-deprecated-role',
'aria-hidden-body',
'aria-hidden-focus',
'aria-prohibited-attr',
'aria-required-attr',
'aria-required-children',
'aria-required-parent',
'aria-roles',
'aria-tooltip-name',
'aria-valid-attr-value',
'aria-valid-attr',
],
'auto-updating': ['meta-refresh'],
contrast: ['color-contrast'],
flash: ['blink', 'marquee'],
'form-names': ['aria-input-field-name', 'input-field-name', 'select-name'],
'frames-iframes': ['frame-title'],
images: [
'area-alt',
'image-alt',
'input-image-alt',
'object-alt',
'role-img-alt',
'svg-img-alt',
],
'keyboard-access': [
'frame-focusable-content',
'scrollable-region-focusable',
],
language: ['html-lang-valid', 'valid-lang', 'html-has-lang'],
'link-purpose': ['link-name'],
lists: ['definition-list', 'dlitem', 'list', 'listitem'],
'page-titled': ['document-title'],
tables: ['td-headers-attr', 'th-has-data-cells'],
'user-control-name': [
'aria-command-name',
'aria-meter-name',
'aria-progressbar-name',
'aria-toggle-field-name',
'button-name',
],
};

results.forEach((result) => {
for (const categorys in resultCategoryMapping) {
if (resultCategoryMapping[categorys].includes(result.id)) {
resultsSummary[categorys] = resultsSummary[categorys]
? resultsSummary[categorys] + 1
: 1;
rawResultsList.push(result);
break;
}
}
});

return {
resultsSummary,
resultsList: getResultsListSubset(rawResultsList),
};
}

function getResultsListSubset(results: Result[]): ResultSubset[] {
return results.map((result) => ({
description: result.description,
helpUrl: result.helpUrl,
id: result.id,
tags: result.tags,
nodes: getNodeResultsSubset(result.nodes),
}));
}

function getNodeResultsSubset(nodes: NodeResult[]): NodeResultSubset[] {
return nodes.map((node) => ({
html: node.html,
impact: node.impact,
xpath: node.xpath,
ancestry: node.ancestry,
any: getCheckResultSubset(node.any),
all: getCheckResultSubset(node.all),
none: getCheckResultSubset(node.none),
element: node.element,
}));
}

function getCheckResultSubset(checks: CheckResult[]): CheckResultSubset[] {
return checks.map((check) => ({
id: check.id,
impact: check.impact,
message: check.message,
relatedNodes: check.relatedNodes,
}));
}
Loading

0 comments on commit 683a889

Please sign in to comment.