From 1dadeb2c3f59b6f2732d7478a68e49003dc3b38c Mon Sep 17 00:00:00 2001 From: hanna-skryl Date: Wed, 21 Jan 2026 13:16:19 -0500 Subject: [PATCH] test(plugin-axe): add setupScript auth E2E test --- .../mocks/fixtures/auth/_package.json | 4 + .../mocks/fixtures/auth/axe-setup.ts | 9 ++ .../mocks/fixtures/auth/code-pushup.config.ts | 10 ++ .../mocks/fixtures/auth/server/dashboard.html | 24 +++++ .../mocks/fixtures/auth/server/login.html | 22 ++++ .../mocks/fixtures/auth/server/server.ts | 100 ++++++++++++++++++ .../{ => collect}/code-pushup.config.ts | 0 .../mocks/fixtures/{ => collect}/index.html | 0 e2e/plugin-axe-e2e/tests/auth.e2e.test.ts | 66 ++++++++++++ e2e/plugin-axe-e2e/tests/collect.e2e.test.ts | 8 +- e2e/plugin-axe-e2e/tsconfig.test.json | 3 +- packages/plugin-axe/src/lib/runner/setup.ts | 4 +- 12 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 e2e/plugin-axe-e2e/mocks/fixtures/auth/_package.json create mode 100644 e2e/plugin-axe-e2e/mocks/fixtures/auth/axe-setup.ts create mode 100644 e2e/plugin-axe-e2e/mocks/fixtures/auth/code-pushup.config.ts create mode 100644 e2e/plugin-axe-e2e/mocks/fixtures/auth/server/dashboard.html create mode 100644 e2e/plugin-axe-e2e/mocks/fixtures/auth/server/login.html create mode 100644 e2e/plugin-axe-e2e/mocks/fixtures/auth/server/server.ts rename e2e/plugin-axe-e2e/mocks/fixtures/{ => collect}/code-pushup.config.ts (100%) rename e2e/plugin-axe-e2e/mocks/fixtures/{ => collect}/index.html (100%) create mode 100644 e2e/plugin-axe-e2e/tests/auth.e2e.test.ts diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/auth/_package.json b/e2e/plugin-axe-e2e/mocks/fixtures/auth/_package.json new file mode 100644 index 000000000..0304ceef5 --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/auth/_package.json @@ -0,0 +1,4 @@ +{ + "name": "auth-setup-fixture", + "type": "module" +} diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/auth/axe-setup.ts b/e2e/plugin-axe-e2e/mocks/fixtures/auth/axe-setup.ts new file mode 100644 index 000000000..911179872 --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/auth/axe-setup.ts @@ -0,0 +1,9 @@ +import type { Page } from 'playwright-core'; + +export default async function setup(page: Page): Promise { + await page.goto('http://localhost:8080/login'); + await page.fill('#username', 'testuser'); + await page.fill('#password', 'testpass'); + await page.click('button[type="submit"]'); + await page.waitForURL('**/dashboard'); +} diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/auth/code-pushup.config.ts b/e2e/plugin-axe-e2e/mocks/fixtures/auth/code-pushup.config.ts new file mode 100644 index 000000000..9356555fa --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/auth/code-pushup.config.ts @@ -0,0 +1,10 @@ +import axePlugin from '@code-pushup/axe-plugin'; +import type { CoreConfig } from '@code-pushup/models'; + +export default { + plugins: [ + axePlugin('http://localhost:8080/dashboard', { + setupScript: './axe-setup.ts', + }), + ], +} satisfies CoreConfig; diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/dashboard.html b/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/dashboard.html new file mode 100644 index 000000000..f14ecd79c --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/dashboard.html @@ -0,0 +1,24 @@ + + + + + + Dashboard + + +

Dashboard

+

Welcome to the protected dashboard!

+ + + + + + + + + + + +
NameEmailRole
+ + diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/login.html b/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/login.html new file mode 100644 index 000000000..fb4bf251f --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/login.html @@ -0,0 +1,22 @@ + + + + + + Login + + +

Login

+
+ + + +
+ + diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/server.ts b/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/server.ts new file mode 100644 index 000000000..6d2d22e0c --- /dev/null +++ b/e2e/plugin-axe-e2e/mocks/fixtures/auth/server/server.ts @@ -0,0 +1,100 @@ +import { readFile } from 'node:fs/promises'; +import { + type IncomingMessage, + type Server, + type ServerResponse, + createServer, +} from 'node:http'; +import { join } from 'node:path'; +import { text } from 'node:stream/consumers'; + +const SESSION_COOKIE = 'session=authenticated'; + +function getCookie(req: IncomingMessage, name: string): string | undefined { + const cookies = req.headers.cookie?.split(';').map(c => c.trim()) ?? []; + const cookie = cookies.find(c => c.startsWith(`${name}=`)); + return cookie?.split('=')[1]; +} + +function isAuthenticated(req: IncomingMessage): boolean { + return getCookie(req, 'session') === 'authenticated'; +} + +async function handleRequest( + req: IncomingMessage, + res: ServerResponse, + serverDir: string, +): Promise { + const url = req.url ?? '/'; + const method = req.method ?? 'GET'; + + if (url === '/login' && method === 'GET') { + const html = await readFile(join(serverDir, 'login.html'), 'utf-8'); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(html); + return; + } + + if (url === '/login' && method === 'POST') { + const body = await text(req); + const params = new URLSearchParams(body); + const username = params.get('username'); + const password = params.get('password'); + + if (username === 'testuser' && password === 'testpass') { + res.writeHead(302, { + Location: '/dashboard', + 'Set-Cookie': `${SESSION_COOKIE}; Path=/; HttpOnly`, + }); + res.end(); + } else { + res.writeHead(401, { 'Content-Type': 'text/plain' }); + res.end('Invalid credentials'); + } + return; + } + + if (url === '/dashboard') { + if (!isAuthenticated(req)) { + res.writeHead(302, { Location: '/login' }); + res.end(); + return; + } + const html = await readFile(join(serverDir, 'dashboard.html'), 'utf-8'); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(html); + return; + } + + res.writeHead(302, { Location: '/login' }); + res.end(); +} + +export function createAuthServer(serverDir: string): Server { + return createServer((req, res) => { + handleRequest(req, res, serverDir).catch(error => { + console.error('Server error:', error); + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Internal Server Error'); + }); + }); +} + +export function startServer(server: Server, port: number): Promise { + return new Promise((resolve, reject) => { + server.on('error', reject); + server.listen(port, () => resolve()); + }); +} + +export function stopServer(server: Server): Promise { + return new Promise((resolve, reject) => { + server.close(err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/code-pushup.config.ts b/e2e/plugin-axe-e2e/mocks/fixtures/collect/code-pushup.config.ts similarity index 100% rename from e2e/plugin-axe-e2e/mocks/fixtures/code-pushup.config.ts rename to e2e/plugin-axe-e2e/mocks/fixtures/collect/code-pushup.config.ts diff --git a/e2e/plugin-axe-e2e/mocks/fixtures/index.html b/e2e/plugin-axe-e2e/mocks/fixtures/collect/index.html similarity index 100% rename from e2e/plugin-axe-e2e/mocks/fixtures/index.html rename to e2e/plugin-axe-e2e/mocks/fixtures/collect/index.html diff --git a/e2e/plugin-axe-e2e/tests/auth.e2e.test.ts b/e2e/plugin-axe-e2e/tests/auth.e2e.test.ts new file mode 100644 index 000000000..dec684b0e --- /dev/null +++ b/e2e/plugin-axe-e2e/tests/auth.e2e.test.ts @@ -0,0 +1,66 @@ +import { cp } from 'node:fs/promises'; +import type { Server } from 'node:http'; +import path from 'node:path'; +import type { Report } from '@code-pushup/models'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + restoreNxIgnoredFiles, + teardownTestFolder, +} from '@code-pushup/test-utils'; +import { executeProcess, readJsonFile } from '@code-pushup/utils'; +import { + createAuthServer, + startServer, + stopServer, +} from '../mocks/fixtures/auth/server/server.js'; + +describe('PLUGIN collect with setupScript authentication', () => { + const PORT = 8080; + const fixturesDir = path.join( + 'e2e', + nxTargetProject(), + 'mocks', + 'fixtures', + 'auth', + ); + const testFileDir = path.join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'auth', + ); + + let server: Server; + + beforeAll(async () => { + await cp(fixturesDir, testFileDir, { recursive: true }); + await restoreNxIgnoredFiles(testFileDir); + server = createAuthServer(path.join(testFileDir, 'server')); + await startServer(server, PORT); + }); + + afterAll(async () => { + await stopServer(server); + await teardownTestFolder(testFileDir); + }); + + it('should analyze authenticated page using setupScript', async () => { + const { code } = await executeProcess({ + command: 'npx', + args: ['@code-pushup/cli', 'collect'], + cwd: testFileDir, + }); + + expect(code).toBe(0); + + const report = await readJsonFile( + path.join(testFileDir, '.code-pushup', 'report.json'), + ); + + expect(report.plugins[0]!.audits).toSatisfyAny( + audit => audit.score < 1 && audit.slug === 'th-has-data-cells', + ); + }); +}); diff --git a/e2e/plugin-axe-e2e/tests/collect.e2e.test.ts b/e2e/plugin-axe-e2e/tests/collect.e2e.test.ts index f8ff3e610..b5c325849 100644 --- a/e2e/plugin-axe-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-axe-e2e/tests/collect.e2e.test.ts @@ -13,7 +13,13 @@ import { import { executeProcess, readJsonFile } from '@code-pushup/utils'; describe('PLUGIN collect report with axe-plugin NPM package', () => { - const fixturesDir = path.join('e2e', nxTargetProject(), 'mocks', 'fixtures'); + const fixturesDir = path.join( + 'e2e', + nxTargetProject(), + 'mocks', + 'fixtures', + 'collect', + ); const testFileDir = path.join( E2E_ENVIRONMENTS_DIR, nxTargetProject(), diff --git a/e2e/plugin-axe-e2e/tsconfig.test.json b/e2e/plugin-axe-e2e/tsconfig.test.json index c212b6893..a56a1a31f 100644 --- a/e2e/plugin-axe-e2e/tsconfig.test.json +++ b/e2e/plugin-axe-e2e/tsconfig.test.json @@ -9,6 +9,7 @@ "vitest.e2e.config.ts", "tests/**/*.e2e.test.ts", "tests/**/*.d.ts", - "mocks/**/*.ts" + "mocks/**/*.ts", + "../../testing/test-setup/src/vitest.d.ts" ] } diff --git a/packages/plugin-axe/src/lib/runner/setup.ts b/packages/plugin-axe/src/lib/runner/setup.ts index 16f0416b0..9c98ce839 100644 --- a/packages/plugin-axe/src/lib/runner/setup.ts +++ b/packages/plugin-axe/src/lib/runner/setup.ts @@ -1,4 +1,5 @@ import path from 'node:path'; +import { pathToFileURL } from 'node:url'; import type { Page } from 'playwright-core'; import { z } from 'zod/v4'; import { @@ -41,7 +42,8 @@ export async function loadSetupScript( const validModule = await logger.task( `Loading setup script from ${absolutePath}`, async () => { - const module: unknown = await import(absolutePath); + const url = pathToFileURL(absolutePath).toString(); + const module: unknown = await import(url); const validated = await validateAsync(setupScriptModuleSchema, module, { filePath: absolutePath, });