* style(workspace): align approved shell and visual fixtures * test(workflows): preserve evidence across browser projects * style(workspace): match approved image browsing geometry * style(workspace): match natural library card layout * fix(workspace): retain wheel motion during row correction * fix(workspace): contain zoomed controls and image metadata (#215)
71 lines
4.1 KiB
JavaScript
71 lines
4.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtemp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { tmpdir } from 'node:os';
|
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import workflowConfig from '../../tools/vendor/playwright.workflows.config.mjs';
|
|
|
|
const configUrl = new URL('../../tools/vendor/playwright.workflows.config.mjs', import.meta.url);
|
|
const runner = fileURLToPath(new URL('../../tools/vendor/node_modules/@playwright/test/cli.js', import.meta.url));
|
|
const testModule = new URL('../../tools/vendor/node_modules/@playwright/test/index.mjs', import.meta.url).href;
|
|
|
|
async function evidenceFiles(directory) {
|
|
const files = [];
|
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
const path = join(directory, entry.name);
|
|
if (entry.isDirectory()) files.push(...await evidenceFiles(path));
|
|
else if (entry.name === 'evidence.json') files.push(path);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
test('sequential workflow project runs retain all four project evidence sets', async t => {
|
|
const root = await mkdtemp(join(tmpdir(), 'pm-workflow-artifacts-'));
|
|
t.after(() => rm(root, { recursive: true, force: true }));
|
|
const uploadedAncestor = resolve(root, 'test-results/workflows');
|
|
const isolateOutput = value => {
|
|
assert.equal(typeof value, 'string');
|
|
assert.equal(isAbsolute(value), false, 'configured output must remain relative');
|
|
const output = resolve(root, value);
|
|
assert.ok(output.startsWith(root + sep), 'runner output must stay inside its owned temporary root');
|
|
const uploadedRelative = relative(uploadedAncestor, output);
|
|
assert.ok(uploadedRelative === '' || (!uploadedRelative.startsWith('..') && !isAbsolute(uploadedRelative)),
|
|
'project evidence must remain beneath the uploaded workflow ancestor');
|
|
return output;
|
|
};
|
|
// Validate every output before launching a runner that removes output directories.
|
|
const outputDir = isolateOutput(workflowConfig.outputDir);
|
|
const projects = workflowConfig.projects.map(project => ({
|
|
...project, outputDir: isolateOutput(project.outputDir ?? workflowConfig.outputDir),
|
|
}));
|
|
const testDir = join(root, 'specs');
|
|
await mkdir(testDir);
|
|
await writeFile(join(testDir, 'evidence-retention.spec.mjs'), `
|
|
import { test } from ${JSON.stringify(testModule)};
|
|
import { writeFile } from 'node:fs/promises';
|
|
test('writes project evidence without a browser or host', async ({}, testInfo) => {
|
|
await writeFile(testInfo.outputPath('evidence.json'), JSON.stringify({ project: testInfo.project.name }));
|
|
});
|
|
`);
|
|
const configPath = join(root, 'playwright.config.mjs');
|
|
await writeFile(configPath, `
|
|
import config from ${JSON.stringify(configUrl.href)};
|
|
export default { ...config, testDir: ${JSON.stringify(testDir)}, webServer: undefined,
|
|
outputDir: ${JSON.stringify(outputDir)}, projects: ${JSON.stringify(projects)} };
|
|
`);
|
|
const run = names => execFileSync(process.execPath, [runner, 'test', '--config', configPath,
|
|
...names.flatMap(name => ['--project', name])], {
|
|
cwd: root, encoding: 'utf8', timeout: 30_000,
|
|
env: { ...process.env, PM_JS_COVERAGE: '', NODE_V8_COVERAGE: '' },
|
|
});
|
|
assert.match(run(['chromium', 'touch']), /2 passed/);
|
|
const first = new Map(await Promise.all((await evidenceFiles(uploadedAncestor)).map(async path => [path, await readFile(path, 'utf8')])));
|
|
assert.deepEqual([...first.values()].map(value => JSON.parse(value).project).sort(), ['chromium', 'touch']);
|
|
assert.match(run(['firefox', 'webkit']), /2 passed/);
|
|
const retained = new Map(await Promise.all((await evidenceFiles(uploadedAncestor)).map(async path => [path, await readFile(path, 'utf8')])));
|
|
assert.deepEqual([...retained.values()].map(value => JSON.parse(value).project).sort(), ['chromium', 'firefox', 'touch', 'webkit']);
|
|
for (const [path, contents] of first) assert.equal(retained.get(path), contents, 'later runs preserve earlier evidence bytes and paths');
|
|
});
|