Skip to content

Commit 2acf72a

Browse files
committed
feat(plugin-axe): make timeout configurable
1 parent db19831 commit 2acf72a

File tree

8 files changed

+72
-23
lines changed

8 files changed

+72
-23
lines changed

packages/models/docs/models-reference.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,14 @@ Uncovered line of code, optionally referring to a named function/class/etc.
254254

255255
_Object containing the following properties:_
256256

257-
| Property | Description | Type |
258-
| :------------------- | :-------------------------------- | :------------------- |
259-
| **`startLine`** (\*) | Start line | `number` (_int, >0_) |
260-
| `startColumn` | Start column | `number` (_int, >0_) |
261-
| `endLine` | End line | `number` (_int, >0_) |
262-
| `endColumn` | End column | `number` (_int, >0_) |
263-
| `name` | Identifier of function/class/etc. | `string` |
264-
| `kind` | E.g. "function", "class" | `string` |
257+
| Property | Description | Type |
258+
| :------------------- | :-------------------------------- | :-------------------------- |
259+
| **`startLine`** (\*) | Start line | [PositiveInt](#positiveint) |
260+
| `startColumn` | Start column | [PositiveInt](#positiveint) |
261+
| `endLine` | End line | [PositiveInt](#positiveint) |
262+
| `endColumn` | End column | [PositiveInt](#positiveint) |
263+
| `name` | Identifier of function/class/etc. | `string` |
264+
| `kind` | E.g. "function", "class" | `string` |
265265

266266
_(\*) Required._
267267

@@ -1376,6 +1376,10 @@ _Union of the following possible types:_
13761376
- `Array<string (_url_)>`
13771377
- _Object with dynamic keys of type_ `string` (_url_) _and values of type_ `number` (_≥0_)
13781378

1379+
## PositiveInt
1380+
1381+
_Number which is a safe integer (i.e. between `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER`) and is greater than 0._
1382+
13791383
## Report
13801384

13811385
Collect output data
@@ -1485,10 +1489,10 @@ Source file location
14851489

14861490
_Object containing the following properties:_
14871491

1488-
| Property | Description | Type |
1489-
| :-------------- | :--------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1490-
| **`file`** (\*) | Relative path to source file in Git repo | [FilePath](#filepath) |
1491-
| `position` | Location in file | _Object with properties:_<ul><li>**`startLine`** (\*): `number` (_int, >0_) - Start line</li><li>`startColumn`: `number` (_int, >0_) - Start column</li><li>`endLine`: `number` (_int, >0_) - End line</li><li>`endColumn`: `number` (_int, >0_) - End column</li></ul> |
1492+
| Property | Description | Type |
1493+
| :-------------- | :--------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1494+
| **`file`** (\*) | Relative path to source file in Git repo | [FilePath](#filepath) |
1495+
| `position` | Location in file | _Object with properties:_<ul><li>**`startLine`** (\*): [PositiveInt](#positiveint) - Start line</li><li>`startColumn`: [PositiveInt](#positiveint) - Start column</li><li>`endLine`: [PositiveInt](#positiveint) - End line</li><li>`endColumn`: [PositiveInt](#positiveint) - End column</li></ul> |
14921496

14931497
_(\*) Required._
14941498

packages/models/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export {
6666
filePathSchema,
6767
globPathSchema,
6868
materialIconSchema,
69+
positiveIntSchema,
6970
scoreSchema,
7071
slugSchema,
7172
type MaterialIcon,

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export function axePlugin(
2222
urls: PluginUrls,
2323
options: AxePluginOptions = {},
2424
): PluginConfig {
25-
const { preset, scoreTargets } = validate(axePluginOptionsSchema, options);
25+
const { preset, scoreTargets, timeout } = validate(
26+
axePluginOptionsSchema,
27+
options,
28+
);
2629

2730
const { urls: normalizedUrls, context } = normalizeUrlInput(urls);
2831

@@ -46,7 +49,7 @@ export function axePlugin(
4649
docsUrl: 'https://www.npmjs.com/package/@code-pushup/axe-plugin',
4750
audits,
4851
groups,
49-
runner: createRunnerFunction(normalizedUrls, ruleIds),
52+
runner: createRunnerFunction(normalizedUrls, ruleIds, timeout),
5053
context,
5154
...(scoreTargets && { scoreTargets }),
5255
};

packages/plugin-axe/src/lib/config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { z } from 'zod';
2-
import { pluginScoreTargetsSchema } from '@code-pushup/models';
2+
import {
3+
pluginScoreTargetsSchema,
4+
positiveIntSchema,
5+
} from '@code-pushup/models';
36
import { AXE_DEFAULT_PRESET, AXE_PRESETS } from './constants.js';
47

58
export const axePluginOptionsSchema = z
@@ -9,6 +12,10 @@ export const axePluginOptionsSchema = z
912
'Accessibility ruleset preset (default: wcag21aa for WCAG 2.1 Level AA compliance)',
1013
}),
1114
scoreTargets: pluginScoreTargetsSchema.optional(),
15+
timeout: positiveIntSchema.default(30_000).meta({
16+
description:
17+
'Page navigation timeout in milliseconds (default: 30000ms / 30s)',
18+
}),
1219
})
1320
.meta({
1421
title: 'AxePluginOptions',

packages/plugin-axe/src/lib/config.unit.test.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ import { describe, expect, it } from 'vitest';
22
import { axePluginOptionsSchema } from './config.js';
33

44
describe('axePluginOptionsSchema', () => {
5-
it('should accept empty options object with default preset', () => {
6-
expect(axePluginOptionsSchema.parse({}).preset).toBe('wcag21aa');
5+
it('should accept empty options object with default preset and timeout', () => {
6+
const parsed = axePluginOptionsSchema.parse({});
7+
8+
expect(parsed.preset).toBe('wcag21aa');
9+
expect(parsed.timeout).toBe(30_000);
710
});
811

912
it.each(['wcag21aa', 'wcag22aa', 'best-practice', 'all'])(
@@ -40,4 +43,19 @@ describe('axePluginOptionsSchema', () => {
4043
axePluginOptionsSchema.parse({ scoreTargets: -0.1 }),
4144
).toThrow();
4245
});
46+
47+
it('should accept custom timeout value', () => {
48+
expect(axePluginOptionsSchema.parse({ timeout: 60_000 }).timeout).toBe(
49+
60_000,
50+
);
51+
});
52+
53+
it('should reject non-positive timeout values', () => {
54+
expect(() => axePluginOptionsSchema.parse({ timeout: 0 })).toThrow();
55+
expect(() => axePluginOptionsSchema.parse({ timeout: -1000 })).toThrow();
56+
});
57+
58+
it('should reject non-integer timeout values', () => {
59+
expect(() => axePluginOptionsSchema.parse({ timeout: 1000.5 })).toThrow();
60+
});
4361
});

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ let browser: Browser | undefined;
99
export async function runAxeForUrl(
1010
url: string,
1111
ruleIds: string[],
12+
timeout: number,
1213
): Promise<AuditOutputs> {
1314
try {
1415
if (!browser) {
@@ -23,7 +24,7 @@ export async function runAxeForUrl(
2324
try {
2425
await page.goto(url, {
2526
waitUntil: 'networkidle',
26-
timeout: 30_000,
27+
timeout,
2728
});
2829

2930
const axeBuilder = new AxeBuilder({ page });

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { closeBrowser, runAxeForUrl } from './run-axe.js';
1515
export function createRunnerFunction(
1616
urls: string[],
1717
ruleIds: string[],
18+
timeout: number,
1819
): RunnerFunction {
1920
return async (_runnerArgs?: RunnerArgs): Promise<AuditOutputs> => {
2021
const urlCount = urls.length;
@@ -31,7 +32,7 @@ export function createRunnerFunction(
3132
logger.debug(`Testing URL ${index + 1}/${urlCount}: ${url}`);
3233

3334
try {
34-
const auditOutputs = await runAxeForUrl(url, ruleIds);
35+
const auditOutputs = await runAxeForUrl(url, ruleIds, timeout);
3536

3637
const processedOutputs = isSingleUrl
3738
? auditOutputs

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@ describe('createRunnerFunction', () => {
3030
];
3131
mockRunAxeForUrl.mockResolvedValue(mockResults);
3232

33-
const runnerFn = createRunnerFunction(['https://example.com'], []);
33+
const runnerFn = createRunnerFunction(['https://example.com'], [], 30_000);
3434
const results = await runnerFn({ persist: DEFAULT_PERSIST_CONFIG });
3535

36-
expect(mockRunAxeForUrl).toHaveBeenCalledWith('https://example.com', []);
36+
expect(mockRunAxeForUrl).toHaveBeenCalledWith(
37+
'https://example.com',
38+
[],
39+
30_000,
40+
);
3741
expect(mockCloseBrowser).toHaveBeenCalled();
3842
expect(results).toEqual(mockResults);
3943
});
@@ -55,6 +59,7 @@ describe('createRunnerFunction', () => {
5559
const runnerFn = createRunnerFunction(
5660
['https://example.com', 'https://another-example.org'],
5761
[],
62+
30_000,
5863
);
5964
const results = await runnerFn({ persist: DEFAULT_PERSIST_CONFIG });
6065

@@ -63,11 +68,13 @@ describe('createRunnerFunction', () => {
6368
1,
6469
'https://example.com',
6570
[],
71+
30_000,
6672
);
6773
expect(mockRunAxeForUrl).toHaveBeenNthCalledWith(
6874
2,
6975
'https://another-example.org',
7076
[],
77+
30_000,
7178
);
7279
expect(mockCloseBrowser).toHaveBeenCalled();
7380

@@ -88,12 +95,17 @@ describe('createRunnerFunction', () => {
8895
mockRunAxeForUrl.mockResolvedValue(mockResults);
8996

9097
const ruleIds = ['image-alt', 'html-has-lang'];
91-
const runnerFn = createRunnerFunction(['https://example.com'], ruleIds);
98+
const runnerFn = createRunnerFunction(
99+
['https://example.com'],
100+
ruleIds,
101+
30_000,
102+
);
92103
const results = await runnerFn({ persist: DEFAULT_PERSIST_CONFIG });
93104

94105
expect(mockRunAxeForUrl).toHaveBeenCalledWith(
95106
'https://example.com',
96107
ruleIds,
108+
30_000,
97109
);
98110
expect(results).toEqual(mockResults);
99111
});
@@ -108,6 +120,7 @@ describe('createRunnerFunction', () => {
108120
const runnerFn = createRunnerFunction(
109121
['https://broken.com', 'https://working.com'],
110122
[],
123+
30_000,
111124
);
112125
const results = await runnerFn({ persist: DEFAULT_PERSIST_CONFIG });
113126

@@ -123,6 +136,7 @@ describe('createRunnerFunction', () => {
123136
const runnerFn = createRunnerFunction(
124137
['https://example.com', 'https://another-example.com'],
125138
[],
139+
30_000,
126140
);
127141

128142
await expect(runnerFn({ persist: DEFAULT_PERSIST_CONFIG })).rejects.toThrow(
@@ -133,7 +147,7 @@ describe('createRunnerFunction', () => {
133147
it('should throw error when single URL fails', async () => {
134148
mockRunAxeForUrl.mockRejectedValue(new Error('Failed to load page'));
135149

136-
const runnerFn = createRunnerFunction(['https://example.com'], []);
150+
const runnerFn = createRunnerFunction(['https://example.com'], [], 30_000);
137151

138152
await expect(runnerFn({ persist: DEFAULT_PERSIST_CONFIG })).rejects.toThrow(
139153
'Axe did not produce any results.',

0 commit comments

Comments
 (0)