@iqual/playwright-vrt 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{playwright.config.ts → dist/playwright.config.js} +3 -5
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +301 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/collect.d.ts +11 -0
- package/dist/src/collect.d.ts.map +1 -0
- package/dist/src/collect.js +133 -0
- package/dist/src/collect.js.map +1 -0
- package/dist/src/config.d.ts +38 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +79 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/{src/index.ts → dist/src/index.js} +1 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/runner.d.ts +28 -0
- package/dist/src/runner.d.ts.map +1 -0
- package/dist/src/runner.js +182 -0
- package/dist/src/runner.js.map +1 -0
- package/dist/src/workspace.d.ts +18 -0
- package/dist/src/workspace.d.ts.map +1 -0
- package/dist/src/workspace.js +51 -0
- package/dist/src/workspace.js.map +1 -0
- package/package.json +8 -8
- package/src/cli.ts +0 -339
- package/src/collect.ts +0 -171
- package/src/config.ts +0 -119
- package/src/runner.ts +0 -241
- package/src/workspace.ts +0 -64
- package/tsconfig.json +0 -19
- /package/{tests → dist/tests}/vrt.css +0 -0
- /package/{tests → dist/tests}/vrt.spec.ts +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import type { VRTConfig } from './config.js';
|
|
3
|
+
export interface TestResults {
|
|
4
|
+
passed: number;
|
|
5
|
+
failed: number;
|
|
6
|
+
total: number;
|
|
7
|
+
exitCode: number;
|
|
8
|
+
}
|
|
9
|
+
export interface RunnerOptions {
|
|
10
|
+
config: VRTConfig;
|
|
11
|
+
outputDir: string;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
project?: string;
|
|
14
|
+
updateBaseline?: boolean;
|
|
15
|
+
hasExplicitReference?: boolean;
|
|
16
|
+
headed?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if baseline snapshots already exist and are valid
|
|
20
|
+
*/
|
|
21
|
+
export declare function hasExistingSnapshots(snapshotDir: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Run Playwright tests using the shipped config and test files
|
|
24
|
+
* Much simpler than the old approach - just exec playwright
|
|
25
|
+
*/
|
|
26
|
+
export declare function runVisualTests(options: RunnerOptions): Promise<TestResults>;
|
|
27
|
+
export declare function printResults(results: TestResults, config: VRTConfig): void;
|
|
28
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAyBjE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CA0DjF;AA4GD,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAW1E"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
/**
|
|
6
|
+
* Check if baseline snapshots already exist and are valid
|
|
7
|
+
*/
|
|
8
|
+
export function hasExistingSnapshots(snapshotDir) {
|
|
9
|
+
if (!fs.existsSync(snapshotDir)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
// Recursively check for any .png files
|
|
13
|
+
function hasSnapshotFiles(dir) {
|
|
14
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
if (hasSnapshotFiles(fullPath)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (entry.name.endsWith('.png')) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return hasSnapshotFiles(snapshotDir);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run Playwright tests using the shipped config and test files
|
|
32
|
+
* Much simpler than the old approach - just exec playwright
|
|
33
|
+
*/
|
|
34
|
+
export async function runVisualTests(options) {
|
|
35
|
+
const { config, outputDir, verbose, project, updateBaseline, hasExplicitReference, headed } = options;
|
|
36
|
+
// Find the playwright-vrt package directory
|
|
37
|
+
const packageDir = path.join(__dirname, '..');
|
|
38
|
+
const playwrightConfigPath = path.join(packageDir, 'playwright.config.js');
|
|
39
|
+
const snapshotDir = path.join(process.cwd(), 'playwright-snapshots');
|
|
40
|
+
// Check if baseline snapshots already exist (look for any .png files in snapshots)
|
|
41
|
+
const hasBaseline = !updateBaseline && hasExistingSnapshots(snapshotDir);
|
|
42
|
+
if (hasBaseline) {
|
|
43
|
+
console.log('\n📸 Using existing baseline snapshots');
|
|
44
|
+
if (verbose) {
|
|
45
|
+
console.log(' (Use --update-baseline to regenerate from reference URL)');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (updateBaseline) {
|
|
50
|
+
console.log('\n🔄 Updating baseline snapshots...');
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log('\n📸 Creating baseline snapshots (first run)...');
|
|
54
|
+
}
|
|
55
|
+
console.log(` Source: ${config.referenceUrl}`);
|
|
56
|
+
// Step 1: Create baseline screenshots
|
|
57
|
+
await runPlaywright({
|
|
58
|
+
configPath: playwrightConfigPath,
|
|
59
|
+
baseURL: config.referenceUrl,
|
|
60
|
+
vrtConfig: config,
|
|
61
|
+
outputDir,
|
|
62
|
+
updateSnapshots: true,
|
|
63
|
+
verbose: true,
|
|
64
|
+
project,
|
|
65
|
+
headed,
|
|
66
|
+
});
|
|
67
|
+
console.log('✓ Baseline created');
|
|
68
|
+
}
|
|
69
|
+
console.log(`\n🧪 Testing ${config.testUrl}`);
|
|
70
|
+
// Step 2: Run tests against test URL
|
|
71
|
+
const exitCode = await runPlaywright({
|
|
72
|
+
configPath: playwrightConfigPath,
|
|
73
|
+
baseURL: config.testUrl,
|
|
74
|
+
vrtConfig: config,
|
|
75
|
+
outputDir,
|
|
76
|
+
updateSnapshots: false,
|
|
77
|
+
verbose: true,
|
|
78
|
+
project,
|
|
79
|
+
headed,
|
|
80
|
+
});
|
|
81
|
+
// Parse results
|
|
82
|
+
const results = await parseResults(outputDir);
|
|
83
|
+
results.exitCode = exitCode;
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
async function runPlaywright(options) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const args = [
|
|
89
|
+
'playwright',
|
|
90
|
+
'test',
|
|
91
|
+
'--config', options.configPath
|
|
92
|
+
];
|
|
93
|
+
if (options.updateSnapshots) {
|
|
94
|
+
args.push('--update-snapshots');
|
|
95
|
+
}
|
|
96
|
+
if (options.project) {
|
|
97
|
+
args.push('--project', options.project);
|
|
98
|
+
}
|
|
99
|
+
if (options.headed) {
|
|
100
|
+
args.push('--headed');
|
|
101
|
+
}
|
|
102
|
+
const env = {
|
|
103
|
+
...process.env,
|
|
104
|
+
BASE_URL: options.baseURL,
|
|
105
|
+
VRT_CONFIG: JSON.stringify(options.vrtConfig),
|
|
106
|
+
OUTPUT_DIR: options.outputDir,
|
|
107
|
+
};
|
|
108
|
+
const proc = spawn('bunx', args, {
|
|
109
|
+
env,
|
|
110
|
+
stdio: options.verbose ? 'inherit' : 'pipe',
|
|
111
|
+
shell: true,
|
|
112
|
+
cwd: process.cwd(),
|
|
113
|
+
});
|
|
114
|
+
let stdout = '';
|
|
115
|
+
let stderr = '';
|
|
116
|
+
if (!options.verbose) {
|
|
117
|
+
proc.stdout?.on('data', (data) => {
|
|
118
|
+
stdout += data.toString();
|
|
119
|
+
});
|
|
120
|
+
proc.stderr?.on('data', (data) => {
|
|
121
|
+
stderr += data.toString();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
proc.on('close', (code) => {
|
|
125
|
+
const exitCode = code || 0;
|
|
126
|
+
// For baseline creation (update-snapshots), always succeed
|
|
127
|
+
if (options.updateSnapshots) {
|
|
128
|
+
resolve(0);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// For actual tests, return the exit code
|
|
132
|
+
resolve(exitCode);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
proc.on('error', (error) => {
|
|
136
|
+
reject(new Error(`Failed to run Playwright: ${error.message}`));
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function parseResults(outputDir) {
|
|
141
|
+
const resultsPath = path.join(outputDir, 'results.json');
|
|
142
|
+
try {
|
|
143
|
+
const file = Bun.file(resultsPath);
|
|
144
|
+
const results = await file.json();
|
|
145
|
+
let passed = 0;
|
|
146
|
+
let failed = 0;
|
|
147
|
+
let total = 0;
|
|
148
|
+
// Parse Playwright JSON results
|
|
149
|
+
if (results.suites) {
|
|
150
|
+
for (const suite of results.suites) {
|
|
151
|
+
if (suite.specs) {
|
|
152
|
+
for (const spec of suite.specs) {
|
|
153
|
+
total++;
|
|
154
|
+
if (spec.ok) {
|
|
155
|
+
passed++;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
failed++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { passed, failed, total, exitCode: 0 };
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return { passed: 0, failed: 0, total: 0, exitCode: 1 };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export function printResults(results, config) {
|
|
171
|
+
console.log('\n📊 Test Results:');
|
|
172
|
+
console.log(` Total: ${results.total}`);
|
|
173
|
+
console.log(` Passed: ${results.passed}`);
|
|
174
|
+
console.log(` Failed: ${results.failed}`);
|
|
175
|
+
if (results.failed > 0) {
|
|
176
|
+
console.log(`\n❌ ${results.failed} visual difference(s) detected`);
|
|
177
|
+
}
|
|
178
|
+
else if (results.total > 0) {
|
|
179
|
+
console.log('\n✅ All visual tests passed');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Workspace manages the .playwright-vrt/ directory
|
|
4
|
+
* This is persistent between runs for debugging and caching
|
|
5
|
+
*/
|
|
6
|
+
export declare class Workspace {
|
|
7
|
+
private readonly dir;
|
|
8
|
+
constructor(baseDir?: string);
|
|
9
|
+
getPath(file?: string): string;
|
|
10
|
+
writeFile(filename: string, content: string): void;
|
|
11
|
+
readFile(filename: string): string;
|
|
12
|
+
exists(filename: string): boolean;
|
|
13
|
+
writeJSON(filename: string, data: any): void;
|
|
14
|
+
readJSON(filename: string): any;
|
|
15
|
+
clean(): void;
|
|
16
|
+
info(): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/workspace.ts"],"names":[],"mappings":";AAKA;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,OAAO,CAAC,EAAE,MAAM;IAQ5B,OAAO,CAAC,IAAI,GAAE,MAAW,GAAG,MAAM;IAIlC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAWlD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAKlC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIjC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI;IAI5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;IAK/B,KAAK,IAAI,IAAI;IAOb,IAAI,IAAI,IAAI;CAGb"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Workspace manages the .playwright-vrt/ directory
|
|
6
|
+
* This is persistent between runs for debugging and caching
|
|
7
|
+
*/
|
|
8
|
+
export class Workspace {
|
|
9
|
+
dir;
|
|
10
|
+
constructor(baseDir) {
|
|
11
|
+
// Use .playwright-vrt in current directory (or specified base)
|
|
12
|
+
this.dir = path.join(baseDir || process.cwd(), '.playwright-vrt');
|
|
13
|
+
// Create workspace directory
|
|
14
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
getPath(file = '') {
|
|
17
|
+
return file ? path.join(this.dir, file) : this.dir;
|
|
18
|
+
}
|
|
19
|
+
writeFile(filename, content) {
|
|
20
|
+
const filePath = this.getPath(filename);
|
|
21
|
+
const dir = path.dirname(filePath);
|
|
22
|
+
if (!fs.existsSync(dir)) {
|
|
23
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
readFile(filename) {
|
|
28
|
+
const filePath = this.getPath(filename);
|
|
29
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
exists(filename) {
|
|
32
|
+
return fs.existsSync(this.getPath(filename));
|
|
33
|
+
}
|
|
34
|
+
writeJSON(filename, data) {
|
|
35
|
+
this.writeFile(filename, JSON.stringify(data, null, 2));
|
|
36
|
+
}
|
|
37
|
+
readJSON(filename) {
|
|
38
|
+
return JSON.parse(this.readFile(filename));
|
|
39
|
+
}
|
|
40
|
+
// Manual cleanup - workspace is persistent by default
|
|
41
|
+
clean() {
|
|
42
|
+
if (fs.existsSync(this.dir)) {
|
|
43
|
+
fs.rmSync(this.dir, { recursive: true, force: true });
|
|
44
|
+
console.log(`🗑️ Cleaned workspace: ${this.dir}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
info() {
|
|
48
|
+
console.log(`📁 Workspace: ${this.dir}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/workspace.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;GAGG;AACH,MAAM,OAAO,SAAS;IACH,GAAG,CAAS;IAE7B,YAAY,OAAgB;QAC1B,+DAA+D;QAC/D,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,iBAAiB,CAAC,CAAC;QAElE,6BAA6B;QAC7B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,CAAC,OAAe,EAAE;QACvB,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;IACrD,CAAC;IAED,SAAS,CAAC,QAAgB,EAAE,OAAe;QACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;QAED,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,QAAQ,CAAC,QAAgB;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxC,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,CAAC,QAAgB;QACrB,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,SAAS,CAAC,QAAgB,EAAE,IAAS;QACnC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,QAAQ,CAAC,QAAgB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,sDAAsD;IACtD,KAAK;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACtD,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iqual/playwright-vrt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Standalone Visual Regression Testing CLI tool using Playwright",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"playwright-vrt": "./src/cli.
|
|
7
|
+
"playwright-vrt": "./dist/src/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"
|
|
11
|
-
"tests/",
|
|
12
|
-
"playwright.config.ts",
|
|
13
|
-
"tsconfig.json",
|
|
10
|
+
"dist/",
|
|
14
11
|
"README.md"
|
|
15
12
|
],
|
|
16
13
|
"scripts": {
|
|
17
14
|
"dev": "bun run src/cli.ts",
|
|
15
|
+
"build": "tsc && cp -r tests dist/ && cp playwright.config.js dist/",
|
|
16
|
+
"clean": "rm -rf dist",
|
|
18
17
|
"typecheck": "tsc --noEmit",
|
|
19
|
-
"prepublishOnly": "bun run
|
|
20
|
-
"test": "bun run src/cli.ts --help"
|
|
18
|
+
"prepublishOnly": "bun run clean && bun run build",
|
|
19
|
+
"test": "bun run src/cli.ts --help",
|
|
20
|
+
"test:dist": "node dist/src/cli.js --help"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"visual-regression",
|
package/src/cli.ts
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as fs from 'fs';
|
|
5
|
-
import { loadConfig, validateConfig, type CLIOptions } from './config';
|
|
6
|
-
import { collectURLs } from './collect';
|
|
7
|
-
import { runVisualTests, printResults, hasExistingSnapshots } from './runner';
|
|
8
|
-
import { createHash } from 'crypto';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Compute SHA-256 hash of a file
|
|
12
|
-
*/
|
|
13
|
-
function computeFileHash(filePath: string): string {
|
|
14
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
15
|
-
return createHash('sha256').update(content).digest('hex');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Compute SHA-256 hash of a config object
|
|
20
|
-
*/
|
|
21
|
-
function computeConfigHash(config: any): string {
|
|
22
|
-
// Create a stable JSON representation (sorted keys)
|
|
23
|
-
const configStr = JSON.stringify(config, Object.keys(config).sort());
|
|
24
|
-
return createHash('sha256').update(configStr).digest('hex');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Check if cache is valid by comparing stored hashes
|
|
29
|
-
*/
|
|
30
|
-
function isCacheValid(snapshotDir: string, config: any): boolean {
|
|
31
|
-
const hashFile = path.join(snapshotDir, '.cache-hash.json');
|
|
32
|
-
|
|
33
|
-
if (!fs.existsSync(hashFile)) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const stored = JSON.parse(fs.readFileSync(hashFile, 'utf-8'));
|
|
39
|
-
const packageDir = path.join(__dirname, '..');
|
|
40
|
-
const testFilePath = path.join(packageDir, 'tests', 'vrt.spec.ts');
|
|
41
|
-
|
|
42
|
-
const currentHashes = {
|
|
43
|
-
config: computeConfigHash(config),
|
|
44
|
-
testFile: fs.existsSync(testFilePath) ? computeFileHash(testFilePath) : '',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Log cache timestamp
|
|
48
|
-
console.log(` Cache timestamp: ${stored.timestamp}`);
|
|
49
|
-
|
|
50
|
-
return stored.config === currentHashes.config &&
|
|
51
|
-
stored.testFile === currentHashes.testFile;
|
|
52
|
-
} catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Save current config and test file hashes to cache
|
|
59
|
-
*/
|
|
60
|
-
function saveCacheHashes(snapshotDir: string, config: any): void {
|
|
61
|
-
const packageDir = path.join(__dirname, '..');
|
|
62
|
-
const testFilePath = path.join(packageDir, 'tests', 'vrt.spec.ts');
|
|
63
|
-
|
|
64
|
-
const hashes = {
|
|
65
|
-
config: computeConfigHash(config),
|
|
66
|
-
testFile: fs.existsSync(testFilePath) ? computeFileHash(testFilePath) : '',
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const hashFile = path.join(snapshotDir, '.cache-hash.json');
|
|
71
|
-
fs.writeFileSync(hashFile, JSON.stringify(hashes, null, 2), 'utf-8');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function main() {
|
|
75
|
-
const args = parseArgs();
|
|
76
|
-
|
|
77
|
-
// Either --config or --test is required
|
|
78
|
-
if (!args.config && !args.test) {
|
|
79
|
-
console.error('Error: Either --config or --test is required');
|
|
80
|
-
printUsage();
|
|
81
|
-
process.exit(2);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
// Load and validate configuration
|
|
86
|
-
let config;
|
|
87
|
-
let configPath: string | undefined;
|
|
88
|
-
|
|
89
|
-
if (args.config) {
|
|
90
|
-
// Load from config file
|
|
91
|
-
configPath = path.resolve(args.config);
|
|
92
|
-
config = await loadConfig(configPath);
|
|
93
|
-
} else {
|
|
94
|
-
// Use defaults
|
|
95
|
-
const { DEFAULT_CONFIG } = await import('./config');
|
|
96
|
-
config = { ...DEFAULT_CONFIG } as any;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Override with CLI args
|
|
100
|
-
if (args.test) config.testUrl = args.test;
|
|
101
|
-
if (args.reference) config.referenceUrl = args.reference;
|
|
102
|
-
|
|
103
|
-
let hasExplicitReference = true;
|
|
104
|
-
|
|
105
|
-
// If no reference URL set, default to test URL
|
|
106
|
-
if (!config.referenceUrl && config.testUrl) {
|
|
107
|
-
config.referenceUrl = config.testUrl;
|
|
108
|
-
hasExplicitReference = false;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (args.maxUrls) config.maxUrls = args.maxUrls;
|
|
112
|
-
|
|
113
|
-
validateConfig(config);
|
|
114
|
-
|
|
115
|
-
console.log('🚀 Starting Visual Regression Testing');
|
|
116
|
-
console.log(` Reference: ${config.referenceUrl}`);
|
|
117
|
-
console.log(` Test: ${config.testUrl}`);
|
|
118
|
-
|
|
119
|
-
// Create snapshot directory for URLs and snapshots
|
|
120
|
-
const snapshotDir = path.resolve('playwright-snapshots');
|
|
121
|
-
const outputDir = path.resolve(args.output || 'playwright-report');
|
|
122
|
-
|
|
123
|
-
if (!fs.existsSync(snapshotDir)) {
|
|
124
|
-
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (args.verbose) {
|
|
128
|
-
console.log(`📁 Snapshots: ${snapshotDir}`);
|
|
129
|
-
console.log(`📁 Output: ${outputDir}`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check if URLs already exist (unless --update-baseline)
|
|
133
|
-
const urlsPath = path.join(snapshotDir, 'urls.json');
|
|
134
|
-
let urls: string[] = [];
|
|
135
|
-
|
|
136
|
-
// Check cache validity based on config object and test file hashes
|
|
137
|
-
const cacheValid = isCacheValid(snapshotDir, config);
|
|
138
|
-
const shouldRegenerate = args.updateBaseline || !cacheValid;
|
|
139
|
-
|
|
140
|
-
if (!hasExplicitReference && !cacheValid && !args.updateBaseline) {
|
|
141
|
-
console.error('\n❌ Error: No baseline snapshots found and no reference URL provided.');
|
|
142
|
-
console.error(' Either:');
|
|
143
|
-
console.error(' 1. Provide --reference <url> to create baseline from a reference system');
|
|
144
|
-
console.error(' 2. Use --update-baseline to create baseline from test URL');
|
|
145
|
-
console.error(' 3. Add referenceUrl to your config file\n');
|
|
146
|
-
process.exit(2);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (fs.existsSync(urlsPath) && !shouldRegenerate) {
|
|
150
|
-
// Load existing URLs
|
|
151
|
-
console.log('\n📋 Using cached URLs from previous run');
|
|
152
|
-
urls = JSON.parse(fs.readFileSync(urlsPath, 'utf-8'));
|
|
153
|
-
console.log(`✓ Loaded ${urls.length} URLs from cache`);
|
|
154
|
-
|
|
155
|
-
if (args.verbose) {
|
|
156
|
-
console.log(' (Use --update-baseline to regenerate URLs)');
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
// Collect URLs from sitemap/crawler
|
|
160
|
-
if (args.updateBaseline) {
|
|
161
|
-
console.log('\n🔄 Updating baseline (regenerating URLs)...');
|
|
162
|
-
} else if (!cacheValid && fs.existsSync(urlsPath)) {
|
|
163
|
-
console.log('\n🔄 Config or test file changed, regenerating URLs...');
|
|
164
|
-
} else {
|
|
165
|
-
console.log('\n🔍 Collecting URLs (first run)...');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
console.log(` Source: ${config.referenceUrl}${config.sitemapPath || '/sitemap.xml'}`);
|
|
169
|
-
const urlResult = await collectURLs(config);
|
|
170
|
-
|
|
171
|
-
console.log(`✓ Found ${urlResult.total} URLs, filtered to ${urlResult.filtered}, using top ${urlResult.urls.length}`);
|
|
172
|
-
console.log(` Source: ${urlResult.source}`);
|
|
173
|
-
|
|
174
|
-
urls = urlResult.urls;
|
|
175
|
-
|
|
176
|
-
// Save URLs to snapshot directory (co-located with snapshots for easy caching)
|
|
177
|
-
fs.writeFileSync(urlsPath, JSON.stringify(urls, null, 2), 'utf-8');
|
|
178
|
-
|
|
179
|
-
// Save cache hashes based on final config object
|
|
180
|
-
saveCacheHashes(snapshotDir, config);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (urls.length === 0) {
|
|
184
|
-
throw new Error('No URLs found to test');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (args.verbose) {
|
|
188
|
-
console.log('\n📝 URLs to test:');
|
|
189
|
-
urls.forEach((url, i) => console.log(` ${i + 1}. ${url}`));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Run visual regression tests using shipped Playwright config and tests
|
|
193
|
-
const results = await runVisualTests({
|
|
194
|
-
config,
|
|
195
|
-
outputDir,
|
|
196
|
-
verbose: args.verbose,
|
|
197
|
-
project: args.project,
|
|
198
|
-
updateBaseline: shouldRegenerate,
|
|
199
|
-
hasExplicitReference,
|
|
200
|
-
headed: args.headed,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Print results
|
|
204
|
-
printResults(results, config);
|
|
205
|
-
|
|
206
|
-
// Report location
|
|
207
|
-
const reportPath = path.join(outputDir, 'index.html');
|
|
208
|
-
console.log(`\n📊 Report: ${reportPath}`);
|
|
209
|
-
|
|
210
|
-
if (args.verbose) {
|
|
211
|
-
console.log(`📁 Snapshots: ${snapshotDir}`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Exit with appropriate code
|
|
215
|
-
process.exit(results.failed > 0 ? 1 : 0);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
218
|
-
if (args.verbose && error instanceof Error) {
|
|
219
|
-
console.error(error.stack);
|
|
220
|
-
}
|
|
221
|
-
process.exit(2);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function parseArgs(): CLIOptions {
|
|
226
|
-
const args: CLIOptions = {
|
|
227
|
-
config: '',
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
for (let i = 2; i < process.argv.length; i++) {
|
|
231
|
-
const arg = process.argv[i];
|
|
232
|
-
const next = process.argv[i + 1];
|
|
233
|
-
|
|
234
|
-
switch (arg) {
|
|
235
|
-
case '--reference':
|
|
236
|
-
args.reference = next;
|
|
237
|
-
i++;
|
|
238
|
-
break;
|
|
239
|
-
case '--test':
|
|
240
|
-
args.test = next;
|
|
241
|
-
i++;
|
|
242
|
-
break;
|
|
243
|
-
case '--config':
|
|
244
|
-
args.config = next;
|
|
245
|
-
i++;
|
|
246
|
-
break;
|
|
247
|
-
case '--output':
|
|
248
|
-
args.output = next;
|
|
249
|
-
i++;
|
|
250
|
-
break;
|
|
251
|
-
case '--max-urls':
|
|
252
|
-
args.maxUrls = parseInt(next, 10);
|
|
253
|
-
i++;
|
|
254
|
-
break;
|
|
255
|
-
case '--project':
|
|
256
|
-
args.project = next;
|
|
257
|
-
i++;
|
|
258
|
-
break;
|
|
259
|
-
case '--verbose':
|
|
260
|
-
args.verbose = true;
|
|
261
|
-
break;
|
|
262
|
-
case '--headed':
|
|
263
|
-
args.headed = true;
|
|
264
|
-
break;
|
|
265
|
-
case '--update-baseline':
|
|
266
|
-
args.updateBaseline = true;
|
|
267
|
-
break;
|
|
268
|
-
case '--clean':
|
|
269
|
-
// Clean snapshots and reports
|
|
270
|
-
console.log('🗑️ Cleaning...');
|
|
271
|
-
['playwright-snapshots', 'playwright-report', 'playwright-tmp'].forEach(dir => {
|
|
272
|
-
const fullPath = path.resolve(dir);
|
|
273
|
-
if (fs.existsSync(fullPath)) {
|
|
274
|
-
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
275
|
-
console.log(` Removed: ${dir}/`);
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
console.log('✓ Clean complete');
|
|
279
|
-
process.exit(0);
|
|
280
|
-
break;
|
|
281
|
-
case '--help':
|
|
282
|
-
case '-h':
|
|
283
|
-
printUsage();
|
|
284
|
-
process.exit(0);
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return args;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function printUsage(): void {
|
|
293
|
-
console.log(`
|
|
294
|
-
Usage: playwright-vrt run [options]
|
|
295
|
-
|
|
296
|
-
Required (one of):
|
|
297
|
-
--test <url> Test URL
|
|
298
|
-
--config <path> Path to config file with testUrl/referenceUrl
|
|
299
|
-
|
|
300
|
-
Optional:
|
|
301
|
-
--reference <url> Reference URL (defaults to --test URL or config)
|
|
302
|
-
--output <dir> Output directory (default: ./playwright-report)
|
|
303
|
-
--max-urls <number> Override config maxUrls
|
|
304
|
-
--project <name> Playwright project to run (default: all)
|
|
305
|
-
--verbose Detailed logging
|
|
306
|
-
--headed Run browser in headed mode (visible)
|
|
307
|
-
--update-baseline Force regenerate URLs and baseline snapshots
|
|
308
|
-
--clean Clean playwright-snapshots/ and playwright-report/
|
|
309
|
-
--help, -h Show this help message
|
|
310
|
-
|
|
311
|
-
Examples:
|
|
312
|
-
# Minimal - compare staging against itself (first run creates baseline)
|
|
313
|
-
bunx playwright-vrt run --test https://staging.example.com
|
|
314
|
-
|
|
315
|
-
# Compare staging against production
|
|
316
|
-
bunx playwright-vrt run \\
|
|
317
|
-
--reference https://production.com \\
|
|
318
|
-
--test https://staging.com
|
|
319
|
-
|
|
320
|
-
# With config file only (contains testUrl and referenceUrl)
|
|
321
|
-
bunx playwright-vrt run --config ./playwright-vrt.config.json
|
|
322
|
-
|
|
323
|
-
# With config file + URL override
|
|
324
|
-
bunx playwright-vrt run \\
|
|
325
|
-
--test https://preview-123.staging.com \\
|
|
326
|
-
--config ./playwright-vrt.config.json
|
|
327
|
-
|
|
328
|
-
Directories:
|
|
329
|
-
playwright-snapshots/ Baseline snapshots and URLs (cache this!)
|
|
330
|
-
playwright-report/ HTML test report
|
|
331
|
-
playwright-tmp/ Temporary test artifacts (cleared on each run)
|
|
332
|
-
|
|
333
|
-
Clean with: playwright-vrt run --clean
|
|
334
|
-
Or manually: rm -rf playwright-snapshots playwright-report playwright-tmp
|
|
335
|
-
`);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Run the CLI
|
|
339
|
-
main();
|