Skip to content

Commit 23ac2c3

Browse files
committed
test(plugin-axe): add setupScript auth E2E test
1 parent d3230f1 commit 23ac2c3

File tree

11 files changed

+244
-2
lines changed

11 files changed

+244
-2
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "auth-setup-fixture",
3+
"type": "module"
4+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Page } from 'playwright-core';
2+
3+
export default async function setup(page: Page): Promise<void> {
4+
await page.goto('http://localhost:8080/login');
5+
await page.fill('#username', 'testuser');
6+
await page.fill('#password', 'testpass');
7+
await page.click('button[type="submit"]');
8+
await page.waitForURL('**/dashboard');
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import axePlugin from '@code-pushup/axe-plugin';
2+
import type { CoreConfig } from '@code-pushup/models';
3+
4+
export default {
5+
plugins: [
6+
axePlugin('http://localhost:8080/dashboard', {
7+
setupScript: './axe-setup.ts',
8+
}),
9+
],
10+
} satisfies CoreConfig;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Dashboard</title>
7+
</head>
8+
<body>
9+
<h1>Dashboard</h1>
10+
<p>Welcome to the protected dashboard!</p>
11+
12+
<!-- Intentional accessibility issue: table header without data cells -->
13+
<table>
14+
<thead>
15+
<tr>
16+
<th>Name</th>
17+
<th>Email</th>
18+
<th>Role</th>
19+
</tr>
20+
</thead>
21+
<tbody></tbody>
22+
</table>
23+
</body>
24+
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Login</title>
7+
</head>
8+
<body>
9+
<h1>Login</h1>
10+
<form method="POST" action="/login">
11+
<label>
12+
Username
13+
<input type="text" id="username" name="username" required />
14+
</label>
15+
<label>
16+
Password
17+
<input type="password" id="password" name="password" required />
18+
</label>
19+
<button type="submit">Sign In</button>
20+
</form>
21+
</body>
22+
</html>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { readFile } from 'node:fs/promises';
2+
import {
3+
type IncomingMessage,
4+
type Server,
5+
type ServerResponse,
6+
createServer,
7+
} from 'node:http';
8+
import { join } from 'node:path';
9+
import { text } from 'node:stream/consumers';
10+
11+
const SESSION_COOKIE = 'session=authenticated';
12+
13+
function getCookie(req: IncomingMessage, name: string): string | undefined {
14+
const cookies = req.headers.cookie?.split(';').map(c => c.trim()) ?? [];
15+
const cookie = cookies.find(c => c.startsWith(`${name}=`));
16+
return cookie?.split('=')[1];
17+
}
18+
19+
function isAuthenticated(req: IncomingMessage): boolean {
20+
return getCookie(req, 'session') === 'authenticated';
21+
}
22+
23+
async function handleRequest(
24+
req: IncomingMessage,
25+
res: ServerResponse,
26+
serverDir: string,
27+
): Promise<void> {
28+
const url = req.url ?? '/';
29+
const method = req.method ?? 'GET';
30+
31+
if (url === '/login' && method === 'GET') {
32+
const html = await readFile(join(serverDir, 'login.html'), 'utf-8');
33+
res.writeHead(200, { 'Content-Type': 'text/html' });
34+
res.end(html);
35+
return;
36+
}
37+
38+
if (url === '/login' && method === 'POST') {
39+
const body = await text(req);
40+
const params = new URLSearchParams(body);
41+
const username = params.get('username');
42+
const password = params.get('password');
43+
44+
if (username === 'testuser' && password === 'testpass') {
45+
res.writeHead(302, {
46+
Location: '/dashboard',
47+
'Set-Cookie': `${SESSION_COOKIE}; Path=/; HttpOnly`,
48+
});
49+
res.end();
50+
} else {
51+
res.writeHead(401, { 'Content-Type': 'text/plain' });
52+
res.end('Invalid credentials');
53+
}
54+
return;
55+
}
56+
57+
if (url === '/dashboard') {
58+
if (!isAuthenticated(req)) {
59+
res.writeHead(302, { Location: '/login' });
60+
res.end();
61+
return;
62+
}
63+
const html = await readFile(join(serverDir, 'dashboard.html'), 'utf-8');
64+
res.writeHead(200, { 'Content-Type': 'text/html' });
65+
res.end(html);
66+
return;
67+
}
68+
69+
res.writeHead(302, { Location: '/login' });
70+
res.end();
71+
}
72+
73+
export function createAuthServer(serverDir: string): Server {
74+
return createServer((req, res) => {
75+
handleRequest(req, res, serverDir).catch(error => {
76+
console.error('Server error:', error);
77+
res.writeHead(500, { 'Content-Type': 'text/plain' });
78+
res.end('Internal Server Error');
79+
});
80+
});
81+
}
82+
83+
export function startServer(server: Server, port: number): Promise<void> {
84+
return new Promise((resolve, reject) => {
85+
server.on('error', reject);
86+
server.listen(port, () => resolve());
87+
});
88+
}
89+
90+
export function stopServer(server: Server): Promise<void> {
91+
return new Promise((resolve, reject) => {
92+
server.close(err => {
93+
if (err) {
94+
reject(err);
95+
} else {
96+
resolve();
97+
}
98+
});
99+
});
100+
}

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

File renamed without changes.
File renamed without changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { cp } from 'node:fs/promises';
2+
import type { Server } from 'node:http';
3+
import path from 'node:path';
4+
import type { Report } from '@code-pushup/models';
5+
import { nxTargetProject } from '@code-pushup/test-nx-utils';
6+
import {
7+
E2E_ENVIRONMENTS_DIR,
8+
TEST_OUTPUT_DIR,
9+
restoreNxIgnoredFiles,
10+
teardownTestFolder,
11+
} from '@code-pushup/test-utils';
12+
import { executeProcess, readJsonFile } from '@code-pushup/utils';
13+
import {
14+
createAuthServer,
15+
startServer,
16+
stopServer,
17+
} from '../mocks/fixtures/auth/server/server.js';
18+
19+
describe('PLUGIN collect with setupScript authentication', () => {
20+
const PORT = 8080;
21+
const fixturesDir = path.join(
22+
'e2e',
23+
nxTargetProject(),
24+
'mocks',
25+
'fixtures',
26+
'auth',
27+
);
28+
const testFileDir = path.join(
29+
E2E_ENVIRONMENTS_DIR,
30+
nxTargetProject(),
31+
TEST_OUTPUT_DIR,
32+
'auth',
33+
);
34+
35+
let server: Server;
36+
37+
beforeAll(async () => {
38+
await cp(fixturesDir, testFileDir, { recursive: true });
39+
await restoreNxIgnoredFiles(testFileDir);
40+
server = createAuthServer(path.join(testFileDir, 'server'));
41+
await startServer(server, PORT);
42+
});
43+
44+
afterAll(async () => {
45+
await stopServer(server);
46+
await teardownTestFolder(testFileDir);
47+
});
48+
49+
it('should analyze authenticated page using setupScript', async () => {
50+
const { code } = await executeProcess({
51+
command: 'npx',
52+
args: ['@code-pushup/cli', 'collect'],
53+
cwd: testFileDir,
54+
});
55+
56+
expect(code).toBe(0);
57+
58+
const report = await readJsonFile<Report>(
59+
path.join(testFileDir, '.code-pushup', 'report.json'),
60+
);
61+
62+
expect(report.plugins[0]!.audits).toSatisfyAny(
63+
audit => audit.score < 1 && audit.slug === 'th-has-data-cells',
64+
);
65+
});
66+
});

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import {
1313
import { executeProcess, readJsonFile } from '@code-pushup/utils';
1414

1515
describe('PLUGIN collect report with axe-plugin NPM package', () => {
16-
const fixturesDir = path.join('e2e', nxTargetProject(), 'mocks', 'fixtures');
16+
const fixturesDir = path.join(
17+
'e2e',
18+
nxTargetProject(),
19+
'mocks',
20+
'fixtures',
21+
'collect',
22+
);
1723
const testFileDir = path.join(
1824
E2E_ENVIRONMENTS_DIR,
1925
nxTargetProject(),

0 commit comments

Comments
 (0)