Skip to content

Commit c0cae0c

Browse files
committed
refactor(plugin-axe): omit URL from issues when analyzing single URL
1 parent fcce8a9 commit c0cae0c

File tree

5 files changed

+66
-55
lines changed

5 files changed

+66
-55
lines changed

e2e/plugin-axe-e2e/tests/__snapshots__/collect.e2e.test.ts.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ exports[`PLUGIN collect report with axe-plugin NPM package > should run plugin o
497497
"details": {
498498
"issues": [
499499
{
500-
"message": "[\`body > button\`] Fix any of the following: Element does not have inner text that is visible to screen readers aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element does not have an implicit (wrapped) <label> Element does not have an explicit <label> Element's default semantics were not overridden with role="none" or role="presentation" ([/<TEST_DIR>/index.html](file:///<TEST_DIR>/index.html))",
500+
"message": "[\`body > button\`] Fix any of the following: Element does not have inner text that is visible to screen readers aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element does not have an implicit (wrapped) <label> Element does not have an explicit <label> Element's default semantics were not overridden with role="none" or role="presentation"",
501501
"severity": "error",
502502
},
503503
],
@@ -523,7 +523,7 @@ exports[`PLUGIN collect report with axe-plugin NPM package > should run plugin o
523523
"details": {
524524
"issues": [
525525
{
526-
"message": "[\`.low-contrast\`] Fix any of the following: Element has insufficient color contrast of 1.57 (foreground color: #777777, background color: #999999, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1 ([/<TEST_DIR>/index.html](file:///<TEST_DIR>/index.html))",
526+
"message": "[\`.low-contrast\`] Fix any of the following: Element has insufficient color contrast of 1.57 (foreground color: #777777, background color: #999999, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1",
527527
"severity": "error",
528528
},
529529
],
@@ -612,7 +612,7 @@ exports[`PLUGIN collect report with axe-plugin NPM package > should run plugin o
612612
"details": {
613613
"issues": [
614614
{
615-
"message": "[\`div[role="button"]\`] Fix any of the following: Invalid ARIA attribute name: aria-invalid-attribute ([/<TEST_DIR>/index.html](file:///<TEST_DIR>/index.html))",
615+
"message": "[\`div[role="button"]\`] Fix any of the following: Invalid ARIA attribute name: aria-invalid-attribute",
616616
"severity": "error",
617617
},
618618
],
@@ -629,7 +629,7 @@ exports[`PLUGIN collect report with axe-plugin NPM package > should run plugin o
629629
"details": {
630630
"issues": [
631631
{
632-
"message": "[\`img\`] Fix any of the following: Element does not have an alt attribute aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element's default semantics were not overridden with role="none" or role="presentation" ([/<TEST_DIR>/index.html](file:///<TEST_DIR>/index.html))",
632+
"message": "[\`img\`] Fix any of the following: Element does not have an alt attribute aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element's default semantics were not overridden with role="none" or role="presentation"",
633633
"severity": "error",
634634
},
635635
],
@@ -646,7 +646,7 @@ exports[`PLUGIN collect report with axe-plugin NPM package > should run plugin o
646646
"details": {
647647
"issues": [
648648
{
649-
"message": "[\`a\`] Fix all of the following: Element is in tab order and does not have accessible text Fix any of the following: Element does not have text that is visible to screen readers aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute ([/<TEST_DIR>/index.html](file:///<TEST_DIR>/index.html))",
649+
"message": "[\`a\`] Fix all of the following: Element is in tab order and does not have accessible text Fix any of the following: Element does not have text that is visible to screen readers aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute",
650650
"severity": "error",
651651
},
652652
],

e2e/plugin-axe-e2e/tests/collect.e2e.test.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,6 @@ import {
1212
} from '@code-pushup/test-utils';
1313
import { executeProcess, readJsonFile } from '@code-pushup/utils';
1414

15-
function sanitizeReportPaths(report: Report): Report {
16-
// Convert to JSON, replace paths, and parse back
17-
const reportJson = JSON.stringify(report);
18-
const sanitized = reportJson.replace(
19-
/\/(?:[^/\s"]+\/)+index\.html/g,
20-
'/<TEST_DIR>/index.html',
21-
);
22-
return JSON.parse(sanitized);
23-
}
24-
2515
describe('PLUGIN collect report with axe-plugin NPM package', () => {
2616
const fixturesDir = path.join('e2e', nxTargetProject(), 'mocks', 'fixtures');
2717
const testFileDir = path.join(
@@ -54,8 +44,6 @@ describe('PLUGIN collect report with axe-plugin NPM package', () => {
5444
);
5545

5646
expect(() => reportSchema.parse(report)).not.toThrow();
57-
expect(
58-
omitVariableReportData(sanitizeReportPaths(report)),
59-
).toMatchSnapshot();
47+
expect(omitVariableReportData(report)).toMatchSnapshot();
6048
});
6149
});

packages/plugin-axe/src/lib/runner/run-axe.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
pluralizeToken,
1919
} from '@code-pushup/utils';
2020
import { type SetupFunction, runSetup } from './setup.js';
21-
import { toAuditOutputs } from './transform.js';
21+
import { createUrlSuffix, toAuditOutputs } from './transform.js';
2222

2323
export type AxeUrlArgs = {
2424
url: string;
@@ -58,7 +58,10 @@ export class AxeRunner {
5858
const page = await context.newPage();
5959
try {
6060
const axeResults = await analyzePage(page, args);
61-
const auditOutputs = toAuditOutputs(axeResults, url);
61+
const auditOutputs = toAuditOutputs(
62+
axeResults,
63+
createUrlSuffix(url, urlsCount),
64+
);
6265
return {
6366
message: `${prefix} Analyzed URL ${url}`,
6467
result: { url, axeResults, auditOutputs },

packages/plugin-axe/src/lib/runner/transform.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,24 @@ import {
1818
*/
1919
export function toAuditOutputs(
2020
{ passes, violations, incomplete, inapplicable }: axe.AxeResults,
21-
url: string,
21+
urlSuffix: string,
2222
): AuditOutputs {
23-
const auditMap = new Map<string, AuditOutput>([
24-
...inapplicable.map(res => [res.id, toAuditOutput(res, url, 1)] as const),
25-
...passes.map(res => [res.id, toAuditOutput(res, url, 1)] as const),
26-
...incomplete.map(res => [res.id, toAuditOutput(res, url, 0)] as const),
27-
...violations.map(res => [res.id, toAuditOutput(res, url, 0)] as const),
28-
]);
23+
const toEntries = (results: axe.Result[], score: number) =>
24+
results.map(res => [res.id, toAuditOutput(res, urlSuffix, score)] as const);
2925

30-
return [...auditMap.values()];
26+
return [
27+
...new Map<string, AuditOutput>([
28+
...toEntries(inapplicable, 1),
29+
...toEntries(passes, 1),
30+
...toEntries(incomplete, 0),
31+
...toEntries(violations, 0),
32+
]).values(),
33+
];
34+
}
35+
36+
/** Creates a URL suffix for issue messages, only included when analyzing multiple URLs. */
37+
export function createUrlSuffix(url: string, urlsCount: number): string {
38+
return urlsCount > 1 ? ` ([${getUrlIdentifier(url)}](${url}))` : '';
3139
}
3240

3341
/**
@@ -36,7 +44,7 @@ export function toAuditOutputs(
3644
*/
3745
function toAuditOutput(
3846
result: axe.Result,
39-
url: string,
47+
urlSuffix: string,
4048
score: number,
4149
): AuditOutput {
4250
const base = {
@@ -46,7 +54,7 @@ function toAuditOutput(
4654
};
4755

4856
if (score === 0 && result.nodes.length > 0) {
49-
const issues = result.nodes.map(node => toIssue(node, result, url));
57+
const issues = result.nodes.map(node => toIssue(node, result, urlSuffix));
5058

5159
return {
5260
...base,
@@ -68,15 +76,19 @@ function formatSelector(selector: axe.CrossTreeSelector): string {
6876
return selector.join(' >> ');
6977
}
7078

71-
function toIssue(node: axe.NodeResult, result: axe.Result, url: string): Issue {
79+
function toIssue(
80+
node: axe.NodeResult,
81+
result: axe.Result,
82+
urlSuffix: string,
83+
): Issue {
7284
const selector = formatSelector(node.target?.[0] || node.html);
7385
const rawMessage = node.failureSummary || result.help;
7486
const cleanedMessage = rawMessage.replace(/\s+/g, ' ').trim();
7587

76-
const message = `[\`${selector}\`] ${cleanedMessage} ([${getUrlIdentifier(url)}](${url}))`;
77-
7888
return {
79-
message: truncateIssueMessage(message),
89+
message: truncateIssueMessage(
90+
`[\`${selector}\`] ${cleanedMessage}${urlSuffix}`,
91+
),
8092
severity: impactToSeverity(node.impact),
8193
};
8294
}

packages/plugin-axe/src/lib/runner/transform.unit.test.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { AxeResults, NodeResult, Result } from 'axe-core';
22
import { describe, expect, it } from 'vitest';
33
import type { AuditOutput } from '@code-pushup/models';
4-
import { toAuditOutputs } from './transform.js';
4+
import { createUrlSuffix, toAuditOutputs } from './transform.js';
55

66
function createMockNode(overrides: Partial<NodeResult> = {}): NodeResult {
77
return {
@@ -33,8 +33,6 @@ function createMockAxeResults(overrides: Partial<AxeResults> = {}): AxeResults {
3333
}
3434

3535
describe('toAuditOutputs', () => {
36-
const testUrl = 'https://example.com';
37-
3836
it('should transform passes with score 1 and no issues', () => {
3937
const results = createMockAxeResults({
4038
passes: [
@@ -46,7 +44,7 @@ describe('toAuditOutputs', () => {
4644
],
4745
});
4846

49-
expect(toAuditOutputs(results, testUrl)).toEqual<AuditOutput[]>([
47+
expect(toAuditOutputs(results, '')).toEqual<AuditOutput[]>([
5048
{
5149
slug: 'color-contrast',
5250
score: 1,
@@ -81,7 +79,7 @@ describe('toAuditOutputs', () => {
8179
],
8280
});
8381

84-
expect(toAuditOutputs(results, testUrl)).toEqual<AuditOutput[]>([
82+
expect(toAuditOutputs(results, '')).toEqual<AuditOutput[]>([
8583
{
8684
slug: 'image-alt',
8785
score: 0,
@@ -91,17 +89,16 @@ describe('toAuditOutputs', () => {
9189
issues: [
9290
{
9391
message:
94-
'[`img`] Fix this: Element does not have an alt attribute ([example.com](https://example.com))',
92+
'[`img`] Fix this: Element does not have an alt attribute',
9593
severity: 'error',
9694
},
9795
{
9896
message:
99-
'[`.header > img:nth-child(2)`] Fix this: Element does not have an alt attribute ([example.com](https://example.com))',
97+
'[`.header > img:nth-child(2)`] Fix this: Element does not have an alt attribute',
10098
severity: 'error',
10199
},
102100
{
103-
message:
104-
'[`#main img`] Mock help for image-alt ([example.com](https://example.com))',
101+
message: '[`#main img`] Mock help for image-alt',
105102
severity: 'error',
106103
},
107104
],
@@ -130,7 +127,7 @@ describe('toAuditOutputs', () => {
130127
],
131128
});
132129

133-
expect(toAuditOutputs(results, testUrl)).toEqual<AuditOutput[]>([
130+
expect(toAuditOutputs(results, '')).toEqual<AuditOutput[]>([
134131
{
135132
slug: 'color-contrast',
136133
score: 0,
@@ -140,12 +137,11 @@ describe('toAuditOutputs', () => {
140137
issues: [
141138
{
142139
message:
143-
'[`button`] Fix this: Element has insufficient color contrast ([example.com](https://example.com))',
140+
'[`button`] Fix this: Element has insufficient color contrast',
144141
severity: 'warning',
145142
},
146143
{
147-
message:
148-
'[`a`] Review: Unable to determine contrast ratio ([example.com](https://example.com))',
144+
message: '[`a`] Review: Unable to determine contrast ratio',
149145
severity: 'warning',
150146
},
151147
],
@@ -159,7 +155,7 @@ describe('toAuditOutputs', () => {
159155
inapplicable: [createMockResult('audio-caption', [])],
160156
});
161157

162-
expect(toAuditOutputs(results, testUrl)).toEqual<AuditOutput[]>([
158+
expect(toAuditOutputs(results, '')).toEqual<AuditOutput[]>([
163159
{
164160
slug: 'audio-caption',
165161
score: 1,
@@ -192,7 +188,7 @@ describe('toAuditOutputs', () => {
192188
],
193189
});
194190

195-
const outputs = toAuditOutputs(results, testUrl);
191+
const outputs = toAuditOutputs(results, '');
196192

197193
expect(outputs).toBeArrayOfSize(1);
198194
expect(outputs[0]).toMatchObject({
@@ -204,7 +200,7 @@ describe('toAuditOutputs', () => {
204200
});
205201

206202
it('should handle empty results', () => {
207-
expect(toAuditOutputs(createMockAxeResults(), testUrl)).toBeEmpty();
203+
expect(toAuditOutputs(createMockAxeResults(), '')).toBeEmpty();
208204
});
209205

210206
it('should format severity counts when multiple impacts exist', () => {
@@ -219,7 +215,7 @@ describe('toAuditOutputs', () => {
219215
],
220216
});
221217

222-
const outputs = toAuditOutputs(results, testUrl);
218+
const outputs = toAuditOutputs(results, '');
223219

224220
expect(outputs[0]).toMatchObject({
225221
slug: 'color-contrast',
@@ -243,7 +239,7 @@ describe('toAuditOutputs', () => {
243239
],
244240
});
245241

246-
expect(toAuditOutputs(results, testUrl)).toEqual<AuditOutput[]>([
242+
expect(toAuditOutputs(results, '')).toEqual<AuditOutput[]>([
247243
{
248244
slug: 'color-contrast',
249245
score: 0,
@@ -253,7 +249,7 @@ describe('toAuditOutputs', () => {
253249
issues: [
254250
{
255251
message:
256-
'[`#app >> my-component >> button`] Fix this: Element has insufficient color contrast ([example.com](https://example.com))',
252+
'[`#app >> my-component >> button`] Fix this: Element has insufficient color contrast',
257253
severity: 'error',
258254
},
259255
],
@@ -277,7 +273,7 @@ describe('toAuditOutputs', () => {
277273
],
278274
});
279275

280-
expect(toAuditOutputs(results, testUrl)).toEqual<AuditOutput[]>([
276+
expect(toAuditOutputs(results, '')).toEqual<AuditOutput[]>([
281277
{
282278
slug: 'aria-roles',
283279
score: 0,
@@ -287,7 +283,7 @@ describe('toAuditOutputs', () => {
287283
issues: [
288284
{
289285
message:
290-
'[`<div role="invalid-role">Content</div>`] Fix this: Ensure all values assigned to role="" correspond to valid ARIA roles ([example.com](https://example.com))',
286+
'[`<div role="invalid-role">Content</div>`] Fix this: Ensure all values assigned to role="" correspond to valid ARIA roles',
291287
severity: 'error',
292288
},
293289
],
@@ -296,3 +292,15 @@ describe('toAuditOutputs', () => {
296292
]);
297293
});
298294
});
295+
296+
describe('createUrlSuffix', () => {
297+
it('should return empty string for single URL', () => {
298+
expect(createUrlSuffix('https://example.com', 1)).toBe('');
299+
});
300+
301+
it('should return formatted suffix for multiple URLs', () => {
302+
expect(createUrlSuffix('https://example.com', 2)).toBe(
303+
' ([example.com](https://example.com))',
304+
);
305+
});
306+
});

0 commit comments

Comments
 (0)