Skip to content

Commit 0d9fffb

Browse files
committed
feat(plugin-axe): add authentication support via setup script
1 parent 7de25ca commit 0d9fffb

File tree

18 files changed

+405
-113
lines changed

18 files changed

+405
-113
lines changed

code-pushup.preset.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import type {
55
CoreConfig,
66
PluginUrls,
77
} from './packages/models/src/index.js';
8-
import axePlugin, { axeCategories } from './packages/plugin-axe/src/index.js';
8+
import axePlugin, {
9+
type AxePluginOptions,
10+
axeCategories,
11+
} from './packages/plugin-axe/src/index.js';
912
import coveragePlugin, {
1013
type CoveragePluginConfig,
1114
getNxCoveragePaths,
@@ -226,8 +229,11 @@ export async function configureLighthousePlugin(
226229
};
227230
}
228231

229-
export function configureAxePlugin(urls: PluginUrls): CoreConfig {
230-
const axe = axePlugin(urls);
232+
export function configureAxePlugin(
233+
urls: PluginUrls,
234+
options?: AxePluginOptions,
235+
): CoreConfig {
236+
const axe = axePlugin(urls, options);
231237
return {
232238
plugins: [axe],
233239
categories: axeCategories(axe),

e2e/plugin-axe-e2e/mocks/fixtures/default-setup/code-pushup.config.ts renamed to e2e/plugin-axe-e2e/mocks/fixtures/code-pushup.config.ts

File renamed without changes.
File renamed without changes.

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
E2E_ENVIRONMENTS_DIR,
88
TEST_OUTPUT_DIR,
99
omitVariableReportData,
10+
restoreNxIgnoredFiles,
1011
teardownTestFolder,
1112
} from '@code-pushup/test-utils';
1213
import { executeProcess, readJsonFile } from '@code-pushup/utils';
@@ -22,17 +23,17 @@ function sanitizeReportPaths(report: Report): Report {
2223
}
2324

2425
describe('PLUGIN collect report with axe-plugin NPM package', () => {
26+
const fixturesDir = path.join('e2e', nxTargetProject(), 'mocks', 'fixtures');
2527
const testFileDir = path.join(
2628
E2E_ENVIRONMENTS_DIR,
2729
nxTargetProject(),
2830
TEST_OUTPUT_DIR,
2931
'collect',
3032
);
31-
const defaultSetupDir = path.join(testFileDir, 'default-setup');
32-
const fixturesDir = path.join('e2e', nxTargetProject(), 'mocks', 'fixtures');
3333

3434
beforeAll(async () => {
3535
await cp(fixturesDir, testFileDir, { recursive: true });
36+
await restoreNxIgnoredFiles(testFileDir);
3637
});
3738

3839
afterAll(async () => {
@@ -43,13 +44,13 @@ describe('PLUGIN collect report with axe-plugin NPM package', () => {
4344
const { code } = await executeProcess({
4445
command: 'npx',
4546
args: ['@code-pushup/cli', 'collect'],
46-
cwd: defaultSetupDir,
47+
cwd: testFileDir,
4748
});
4849

4950
expect(code).toBe(0);
5051

51-
const report: Report = await readJsonFile(
52-
path.join(defaultSetupDir, '.code-pushup', 'report.json'),
52+
const report = await readJsonFile<Report>(
53+
path.join(testFileDir, '.code-pushup', 'report.json'),
5354
);
5455

5556
expect(() => reportSchema.parse(report)).not.toThrow();

packages/models/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,4 @@ export {
164164
type Tree,
165165
} from './lib/tree.js';
166166
export { uploadConfigSchema, type UploadConfig } from './lib/upload-config.js';
167+
export { convertAsyncZodFunctionToSchema } from './lib/implementation/function.js';

packages/plugin-axe/README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ axePlugin(urls: PluginUrls, options?: AxePluginOptions)
6868
| Property | Type | Default | Description |
6969
| -------------- | ----------- | ------------ | ----------------------------------------- |
7070
| `preset` | `AxePreset` | `'wcag21aa'` | Accessibility ruleset preset |
71+
| `setupScript` | `string` | `undefined` | Path to authentication setup script |
7172
| `scoreTargets` | `object` | `undefined` | Pass/fail thresholds for audits or groups |
7273

73-
See [Presets](#presets) for the list of available presets and [Preset details](#preset-details) for what each preset includes.
74+
See [Presets](#presets) and [Authentication](#authentication) sections below.
7475

7576
## Multiple URLs
7677

@@ -92,6 +93,56 @@ axePlugin({
9293

9394
URLs with higher weights contribute more to overall scores. For example, a URL with weight 3 has three times the influence of a URL with weight 1.
9495

96+
## Authentication
97+
98+
To test login-protected pages, provide a `setupScript` that authenticates before analysis:
99+
100+
```ts
101+
axePlugin('https://example.com/dashboard', {
102+
setupScript: './axe-setup.ts',
103+
});
104+
```
105+
106+
The setup script must export a default async function that receives a Playwright `Page` instance:
107+
108+
```ts
109+
// axe-setup.ts
110+
import type { Page } from 'playwright-core';
111+
112+
export default async function (page: Page): Promise<void> {
113+
await page.goto('https://example.com/login');
114+
await page.fill('#username', process.env.USERNAME);
115+
await page.fill('#password', process.env.PASSWORD);
116+
await page.click('button[type="submit"]');
117+
await page.waitForURL('**/dashboard');
118+
}
119+
```
120+
121+
The script runs once before analyzing URLs. Authentication state (cookies, localStorage) is automatically shared across all URL analyses.
122+
123+
<details>
124+
<summary>Alternative: Cookie-based authentication</summary>
125+
126+
If you have a session token, you can inject it directly via cookies:
127+
128+
```ts
129+
// axe-setup.ts
130+
import type { Page } from 'playwright-core';
131+
132+
export default async function (page: Page): Promise<void> {
133+
await page.context().addCookies([
134+
{
135+
name: 'session_token',
136+
value: process.env.SESSION_TOKEN,
137+
domain: 'example.com',
138+
path: '/',
139+
},
140+
]);
141+
}
142+
```
143+
144+
</details>
145+
95146
## Presets
96147

97148
Choose which accessibility ruleset to test against using the `preset` option:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Page } from 'playwright-core';
2+
3+
export async function setup(page: Page): Promise<void> {
4+
await page.goto('about:blank');
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'not a function';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Page } from 'playwright-core';
2+
3+
export default async function setup(page: Page): Promise<void> {
4+
await page.goto('about:blank');
5+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function axePlugin(
2222
urls: PluginUrls,
2323
options: AxePluginOptions = {},
2424
): PluginConfig {
25-
const { preset, scoreTargets, timeout } = validate(
25+
const { preset, scoreTargets, timeout, setupScript } = validate(
2626
axePluginOptionsSchema,
2727
options,
2828
);
@@ -49,7 +49,7 @@ export function axePlugin(
4949
version: packageJson.version,
5050
audits,
5151
groups,
52-
runner: createRunnerFunction(normalizedUrls, ruleIds, timeout),
52+
runner: createRunnerFunction(normalizedUrls, ruleIds, timeout, setupScript),
5353
context,
5454
...(scoreTargets && { scoreTargets }),
5555
};

0 commit comments

Comments
 (0)