@rettangoli/vt 0.0.3 → 0.0.5
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/package.json +1 -1
- package/src/cli/report.js +34 -0
- package/src/common.js +68 -78
package/package.json
CHANGED
package/src/cli/report.js
CHANGED
|
@@ -32,6 +32,34 @@ function getAllFiles(dir, fileList = []) {
|
|
|
32
32
|
return fileList;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function extractParts(p) {
|
|
36
|
+
const dir = path.dirname(p);
|
|
37
|
+
const filename = path.basename(p, '.webp');
|
|
38
|
+
const lastHyphenIndex = filename.lastIndexOf('-');
|
|
39
|
+
|
|
40
|
+
if (lastHyphenIndex > -1) {
|
|
41
|
+
const suffix = filename.substring(lastHyphenIndex + 1);
|
|
42
|
+
const number = parseInt(suffix);
|
|
43
|
+
|
|
44
|
+
if (!isNaN(number) && String(number) === suffix) {
|
|
45
|
+
const name = path.join(dir, filename.substring(0, lastHyphenIndex));
|
|
46
|
+
return { name, number };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// -1 is for the first file (as it will result in the first index when sorting)
|
|
50
|
+
return { name: path.join(dir, filename), number: -1 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sortPaths(a, b) {
|
|
54
|
+
const partsA = extractParts(a);
|
|
55
|
+
const partsB = extractParts(b);
|
|
56
|
+
|
|
57
|
+
if (partsA.name < partsB.name) return -1;
|
|
58
|
+
if (partsA.name > partsB.name) return 1;
|
|
59
|
+
|
|
60
|
+
return partsA.number - partsB.number;
|
|
61
|
+
}
|
|
62
|
+
|
|
35
63
|
async function compareImages(artifactPath, goldPath) {
|
|
36
64
|
try {
|
|
37
65
|
const {equal, diffBounds, diffClusters} = await looksSame(artifactPath, goldPath, {
|
|
@@ -122,6 +150,8 @@ async function main(options = {}) {
|
|
|
122
150
|
...new Set([...candidateRelativePaths, ...referenceRelativePaths]),
|
|
123
151
|
];
|
|
124
152
|
|
|
153
|
+
allPaths.sort(sortPaths);
|
|
154
|
+
|
|
125
155
|
for (const relativePath of allPaths) {
|
|
126
156
|
const candidatePath = path.join(candidateDir, relativePath);
|
|
127
157
|
const referencePath = path.join(originalReferenceDir, relativePath);
|
|
@@ -206,6 +236,10 @@ async function main(options = {}) {
|
|
|
206
236
|
templatePath,
|
|
207
237
|
outputPath,
|
|
208
238
|
});
|
|
239
|
+
if(mismatchingItems.length > 0){
|
|
240
|
+
console.error("Error: there are more than 0 mismatching item.")
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
209
243
|
} catch (error) {
|
|
210
244
|
console.error("Error reading directories:", error);
|
|
211
245
|
}
|
package/src/common.js
CHANGED
|
@@ -16,12 +16,14 @@ import { codeToHtml } from "shiki";
|
|
|
16
16
|
import sharp from "sharp";
|
|
17
17
|
import path from "path";
|
|
18
18
|
|
|
19
|
+
const removeExtension = (filePath) => filePath.replace(/\.[^/.]+$/, "");
|
|
20
|
+
|
|
19
21
|
const convertToHtmlExtension = (filePath) => {
|
|
20
22
|
if (filePath.endsWith(".html")) {
|
|
21
23
|
return filePath;
|
|
22
24
|
}
|
|
23
25
|
// Remove existing extension and add .html
|
|
24
|
-
const baseName = filePath
|
|
26
|
+
const baseName = removeExtension(filePath);
|
|
25
27
|
return baseName + ".html";
|
|
26
28
|
};
|
|
27
29
|
|
|
@@ -50,7 +52,7 @@ engine.registerFilter("slug", (value) => {
|
|
|
50
52
|
// Add custom filter to remove file extension
|
|
51
53
|
engine.registerFilter("remove_ext", (value) => {
|
|
52
54
|
if (typeof value !== "string") return "";
|
|
53
|
-
return value
|
|
55
|
+
return removeExtension(value);
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -136,10 +138,10 @@ async function generateHtml(specsDir, templatePath, outputDir, templateConfig) {
|
|
|
136
138
|
const relativePath = path.relative(specsDir, filePath);
|
|
137
139
|
|
|
138
140
|
let templateToUse = defaultTemplateContent;
|
|
139
|
-
const resolvedTemplatePath = frontMatterObj
|
|
141
|
+
const resolvedTemplatePath = frontMatterObj?.template ?
|
|
140
142
|
join(templateConfig.vtPath, "templates", frontMatterObj.template) :
|
|
141
143
|
templateConfig.defaultTemplate;
|
|
142
|
-
templateToUse = readFileSync(resolvedTemplatePath, "utf8");
|
|
144
|
+
templateToUse = readFileSync(resolvedTemplatePath, "utf8");
|
|
143
145
|
|
|
144
146
|
// Render template
|
|
145
147
|
const renderedContent = engine.parseAndRenderSync(templateToUse, {
|
|
@@ -214,7 +216,7 @@ function startWebServer(artifactsDir, staticDir, port) {
|
|
|
214
216
|
server.listen(port, () => {
|
|
215
217
|
console.log(`Server started at http://localhost:${port}`);
|
|
216
218
|
});
|
|
217
|
-
|
|
219
|
+
|
|
218
220
|
return server;
|
|
219
221
|
}
|
|
220
222
|
|
|
@@ -255,9 +257,23 @@ async function takeScreenshots(
|
|
|
255
257
|
console.log("Launching browser to take screenshots...");
|
|
256
258
|
const browser = await chromium.launch();
|
|
257
259
|
|
|
260
|
+
const takeAndSaveScreenshot = async (page, basePath, suffix = '') => {
|
|
261
|
+
const finalPath = suffix ? `${basePath}-${suffix}` : basePath;
|
|
262
|
+
const tempPngPath = join(screenshotsDir, `${finalPath}.png`);
|
|
263
|
+
const screenshotPath = join(screenshotsDir, `${finalPath}.webp`);
|
|
264
|
+
ensureDirectoryExists(dirname(screenshotPath));
|
|
265
|
+
|
|
266
|
+
await page.screenshot({ path: tempPngPath, fullPage: true});
|
|
267
|
+
await sharp(tempPngPath).webp({ quality: 85 }).toFile(screenshotPath);
|
|
268
|
+
|
|
269
|
+
if (existsSync(tempPngPath)) {
|
|
270
|
+
unlinkSync(tempPngPath);
|
|
271
|
+
}
|
|
272
|
+
return screenshotPath;
|
|
273
|
+
};
|
|
274
|
+
|
|
258
275
|
try {
|
|
259
|
-
|
|
260
|
-
const files = [...generatedFiles]; // Create a copy to work with
|
|
276
|
+
const files = [...generatedFiles];
|
|
261
277
|
const total = files.length;
|
|
262
278
|
let completed = 0;
|
|
263
279
|
|
|
@@ -268,95 +284,69 @@ async function takeScreenshots(
|
|
|
268
284
|
// Create a new context and page for each file (for parallelism)
|
|
269
285
|
const context = await browser.newContext();
|
|
270
286
|
const page = await context.newPage();
|
|
287
|
+
let screenshotIndex = 0;
|
|
271
288
|
|
|
272
289
|
try {
|
|
273
290
|
// Construct URL from file path (add /candidate prefix since server serves from parent)
|
|
274
291
|
const fileUrl = convertToHtmlExtension(
|
|
275
292
|
`${serverUrl}/candidate/${file.path.replace(/\\/g, '/')}`
|
|
276
293
|
);
|
|
277
|
-
console.log(`
|
|
278
|
-
|
|
279
|
-
// Navigate to the page
|
|
294
|
+
console.log(`Navigating to ${fileUrl}`);
|
|
280
295
|
await page.goto(fileUrl, { waitUntil: "networkidle" });
|
|
281
|
-
|
|
282
|
-
// Create screenshot output path (remove extension and add .webp)
|
|
283
|
-
const baseName = file.path.replace(/\.[^/.]+$/, "");
|
|
284
|
-
const tempPngPath = join(screenshotsDir, `${baseName}.png`);
|
|
285
|
-
const screenshotPath = join(screenshotsDir, `${baseName}.webp`);
|
|
286
|
-
ensureDirectoryExists(dirname(screenshotPath));
|
|
287
|
-
|
|
288
296
|
if (waitTime > 0) {
|
|
289
|
-
await
|
|
297
|
+
await page.waitForTimeout(waitTime);
|
|
290
298
|
}
|
|
299
|
+
const baseName = removeExtension(file.path);
|
|
291
300
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
path: tempPngPath,
|
|
295
|
-
fullPage: true
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Convert PNG to WebP using Sharp
|
|
299
|
-
await sharp(tempPngPath)
|
|
300
|
-
.webp({ quality: 85 })
|
|
301
|
-
.toFile(screenshotPath);
|
|
302
|
-
|
|
303
|
-
// Remove temporary PNG file
|
|
304
|
-
if (existsSync(tempPngPath)) {
|
|
305
|
-
unlinkSync(tempPngPath);
|
|
306
|
-
}
|
|
301
|
+
const initialScreenshotPath = await takeAndSaveScreenshot(page, baseName);
|
|
302
|
+
console.log(`Initial screenshot saved: ${initialScreenshotPath}`);
|
|
307
303
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const [command, ...args] = instruction.split(" ");
|
|
304
|
+
for (const step of file.frontMatter?.steps || []) {
|
|
305
|
+
const [command, ...args] = step.split(" ");
|
|
311
306
|
switch (command) {
|
|
312
|
-
case "
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
307
|
+
case "move":
|
|
308
|
+
await page.mouse.move(Number(args[0]), Number(args[1]));
|
|
309
|
+
break;
|
|
310
|
+
case "click":
|
|
311
|
+
await page.mouse.click(Number(args[0]), Number(args[1]), { button: "left" });
|
|
312
|
+
break;
|
|
313
|
+
case "rclick":
|
|
314
|
+
await page.mouse.click(Number(args[0]), Number(args[1]), { button: "right" });
|
|
315
|
+
break;
|
|
316
|
+
case "keypress":
|
|
317
|
+
await page.keyboard.press(args[0]);
|
|
318
|
+
break;
|
|
319
|
+
case "wait":
|
|
320
|
+
await page.waitForTimeout(Number(args[0]));
|
|
321
|
+
break;
|
|
322
|
+
case "screenshot":
|
|
323
|
+
screenshotIndex++;
|
|
324
|
+
const screenshotPath = await takeAndSaveScreenshot(page, `${baseName}-${screenshotIndex}`);
|
|
325
|
+
console.log(`Screenshot saved: ${screenshotPath}`);
|
|
318
326
|
break;
|
|
319
|
-
case "
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// Take screenshot as PNG first
|
|
332
|
-
await page.screenshot({
|
|
333
|
-
path: tempAdditionalPngPath,
|
|
334
|
-
fullPage: true
|
|
327
|
+
case "customEvent":
|
|
328
|
+
//Use to dispatch custom event for the test evironment to listen to
|
|
329
|
+
//customEvent <eventName> <...parameters>
|
|
330
|
+
//To listen to the event use
|
|
331
|
+
//window.addEventListener(<eventName>,(event)=>{
|
|
332
|
+
// console.log("The params that you pass through: ",event.detail)
|
|
333
|
+
//})
|
|
334
|
+
const [eventName, ...params] = args;
|
|
335
|
+
const payload = {};
|
|
336
|
+
params.forEach(param => {
|
|
337
|
+
const [key, value] = param.split('=');
|
|
338
|
+
payload[key] = value;
|
|
335
339
|
});
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
.webp({ quality: 85 })
|
|
340
|
-
.toFile(additonalScreenshotPath);
|
|
341
|
-
|
|
342
|
-
// Remove temporary PNG file
|
|
343
|
-
if (existsSync(tempAdditionalPngPath)) {
|
|
344
|
-
unlinkSync(tempAdditionalPngPath);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
console.log(
|
|
348
|
-
`Additional screenshot taken at ${additonalScreenshotPath}`
|
|
349
|
-
);
|
|
340
|
+
await page.evaluate(({eventName,payload}) => {
|
|
341
|
+
window.dispatchEvent(new CustomEvent(eventName, { detail: payload }));
|
|
342
|
+
},{eventName,payload});
|
|
350
343
|
break;
|
|
351
344
|
}
|
|
352
345
|
}
|
|
353
|
-
|
|
354
346
|
completed++;
|
|
355
|
-
console.log(
|
|
356
|
-
`Screenshot saved: ${screenshotPath} (${completed}/${total})`
|
|
357
|
-
);
|
|
347
|
+
console.log(`Finished processing ${file.path} (${completed}/${total})`);
|
|
358
348
|
} catch (error) {
|
|
359
|
-
console.error(`Error
|
|
349
|
+
console.error(`Error processing instructions for ${file.path}:`, error);
|
|
360
350
|
} finally {
|
|
361
351
|
// Close the context when done
|
|
362
352
|
await context.close();
|
|
@@ -467,4 +457,4 @@ export {
|
|
|
467
457
|
takeScreenshots,
|
|
468
458
|
generateOverview,
|
|
469
459
|
readYaml,
|
|
470
|
-
};
|
|
460
|
+
};
|