@rettangoli/vt 0.0.10 → 0.0.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rettangoli/vt",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Rettangoli Visual Testing",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -9,9 +9,8 @@ import {
9
9
  readYaml,
10
10
  } from "../common.js";
11
11
 
12
- const libraryTemplatesPath = new URL('./templates', import.meta.url).pathname;
13
- const libraryStaticPath = new URL('./static', import.meta.url).pathname;
14
-
12
+ const libraryTemplatesPath = new URL("./templates", import.meta.url).pathname;
13
+ const libraryStaticPath = new URL("./static", import.meta.url).pathname;
15
14
 
16
15
  /**
17
16
  * Main function that orchestrates the entire process
@@ -21,14 +20,14 @@ async function main(options) {
21
20
  skipScreenshots = false,
22
21
  vtPath = "./vt",
23
22
  screenshotWaitTime = 0,
24
- port = 3001
23
+ port = 3001,
25
24
  } = options;
26
25
 
27
26
  const specsPath = join(vtPath, "specs");
28
27
  const mainConfigPath = "rettangoli.config.yaml";
29
28
  const siteOutputPath = join(".rettangoli", "vt", "_site");
30
29
  const candidatePath = join(siteOutputPath, "candidate");
31
-
30
+
32
31
  // Read VT config from main rettangoli.config.yaml
33
32
  let configData = {};
34
33
  try {
@@ -38,32 +37,30 @@ async function main(options) {
38
37
  console.log("Main config file not found, using defaults");
39
38
  }
40
39
 
40
+ const configUrl = configData.url;
41
+
41
42
  // Clear candidate directory
42
43
  await rm(candidatePath, { recursive: true, force: true });
43
44
  await new Promise((resolve) => setTimeout(resolve, 100));
44
45
 
45
46
  // Copy static files from library to site directory
46
47
  await cp(libraryStaticPath, siteOutputPath, { recursive: true });
47
-
48
+
48
49
  // Copy user's static files if they exist
49
50
  const userStaticPath = join(vtPath, "static");
50
51
  if (existsSync(userStaticPath)) {
51
52
  await cp(userStaticPath, siteOutputPath, { recursive: true });
52
53
  }
53
54
 
54
- // Check for local templates first, fallback to library templates
55
+ // Resolve template paths
55
56
  const localTemplatesPath = join(vtPath, "templates");
56
-
57
57
  const defaultTemplatePath = existsSync(join(localTemplatesPath, "default.html"))
58
- ? join(localTemplatesPath, "default.html")
59
- : join(libraryTemplatesPath, "default.html");
60
-
61
- // Resolve index template path
58
+ ? join(localTemplatesPath, "default.html")
59
+ : join(libraryTemplatesPath, "default.html");
62
60
  const indexTemplatePath = existsSync(join(localTemplatesPath, "index.html"))
63
- ? join(localTemplatesPath, "index.html")
64
- : join(libraryTemplatesPath, "index.html");
61
+ ? join(localTemplatesPath, "index.html")
62
+ : join(libraryTemplatesPath, "index.html");
65
63
 
66
- // Build template configuration for per-file/section templates
67
64
  const templateConfig = {
68
65
  defaultTemplate: defaultTemplatePath,
69
66
  vtPath: vtPath,
@@ -74,40 +71,46 @@ async function main(options) {
74
71
  specsPath,
75
72
  defaultTemplatePath,
76
73
  candidatePath,
77
- templateConfig
74
+ templateConfig,
78
75
  );
79
76
 
80
- // Generate overview page with all files
77
+ // Generate overview page (includes all files, skipped or not)
81
78
  generateOverview(
82
79
  generatedFiles,
83
80
  indexTemplatePath,
84
81
  join(siteOutputPath, "index.html"),
85
- configData
82
+ configData,
86
83
  );
87
84
 
85
+ // Take screenshots (only for non-skipped files)
88
86
  if (!skipScreenshots) {
89
- // Start web server from site output path to serve both /public and /candidate
90
- const server = startWebServer(
91
- siteOutputPath,
92
- vtPath,
93
- port
87
+ // Filter out files with skipScreenshot: true in frontmatter
88
+ const filesToScreenshot = generatedFiles.filter(
89
+ (file) => !file.frontMatter?.skipScreenshot
94
90
  );
91
+
92
+ const skippedCount = generatedFiles.length - filesToScreenshot.length;
93
+ if (skippedCount > 0) {
94
+ console.log(`Skipping screenshots for ${skippedCount} files`);
95
+ }
96
+
97
+ const server = configUrl ? null : startWebServer(siteOutputPath, vtPath, port);
95
98
  try {
96
- // Take screenshots with specified concurrency
97
99
  await takeScreenshots(
98
- generatedFiles,
100
+ filesToScreenshot,
99
101
  `http://localhost:${port}`,
100
102
  candidatePath,
101
103
  24,
102
- screenshotWaitTime
104
+ screenshotWaitTime,
105
+ configUrl,
103
106
  );
104
107
  } finally {
105
- // Stop server
106
- server.close();
107
- console.log("Server stopped");
108
+ if (server) {
109
+ server.close();
110
+ console.log("Server stopped");
111
+ }
108
112
  }
109
113
  }
110
-
111
114
  }
112
115
 
113
116
  export default main;
package/src/cli/report.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import crypto from "crypto";
4
- import sharp from "sharp";
5
4
  import { Liquid } from "liquidjs";
6
5
  import { cp } from "node:fs/promises";
7
6
 
@@ -227,19 +226,19 @@ async function main(options = {}) {
227
226
  };
228
227
  console.log(JSON.stringify(logData, null, 2));
229
228
  });
230
-
229
+
231
230
  // Summary at the end
232
231
  console.log(`\nSummary:`);
233
232
  console.log(`Total images: ${results.length}`);
234
233
  console.log(`Mismatched images: ${mismatchingItems.length}`);
235
-
234
+
236
235
  // Generate HTML report
237
236
  await generateReport({
238
237
  results: mismatchingItems,
239
238
  templatePath,
240
239
  outputPath,
241
240
  });
242
- if(mismatchingItems.length > 0){
241
+ if(mismatchingItems.length > 0){
243
242
  console.error("Error: there are more than 0 mismatching item.")
244
243
  process.exit(1);
245
244
  }
package/src/common.js CHANGED
@@ -83,9 +83,8 @@ function getAllFiles(dirPath, arrayOfFiles = []) {
83
83
  * Extract frontmatter from content
84
84
  */
85
85
  function extractFrontMatter(content) {
86
- const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
86
+ const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---\s*(\r?\n|$)/;
87
87
  const match = content.match(frontMatterRegex);
88
-
89
88
  if (!match) {
90
89
  return {
91
90
  content: content,
@@ -180,7 +179,6 @@ function startWebServer(artifactsDir, staticDir, port) {
180
179
  const server = http.createServer((req, res) => {
181
180
  const url = new URL(req.url, `http://localhost:${port}`);
182
181
  let path = url.pathname;
183
-
184
182
  // Default to index.html for root path
185
183
  if (path === "/") {
186
184
  path = "/index.html";
@@ -249,7 +247,8 @@ async function takeScreenshots(
249
247
  serverUrl,
250
248
  screenshotsDir,
251
249
  concurrency = 8,
252
- waitTime = 0
250
+ waitTime = 0,
251
+ configUrl = undefined,
253
252
  ) {
254
253
  // Ensure screenshots directory exists
255
254
  ensureDirectoryExists(screenshotsDir);
@@ -258,13 +257,13 @@ async function takeScreenshots(
258
257
  console.log("Launching browser to take screenshots...");
259
258
  const browser = await chromium.launch();
260
259
 
261
- const takeAndSaveScreenshot = async (page, basePath, suffix = '') => {
260
+ const takeAndSaveScreenshot = async (page, basePath, suffix = "") => {
262
261
  const finalPath = suffix ? `${basePath}-${suffix}` : basePath;
263
262
  const tempPngPath = join(screenshotsDir, `${finalPath}.png`);
264
263
  const screenshotPath = join(screenshotsDir, `${finalPath}.webp`);
265
264
  ensureDirectoryExists(dirname(screenshotPath));
266
265
 
267
- await page.screenshot({ path: tempPngPath, fullPage: true});
266
+ await page.screenshot({ path: tempPngPath, fullPage: true });
268
267
  await sharp(tempPngPath).webp({ quality: 85 }).toFile(screenshotPath);
269
268
 
270
269
  if (existsSync(tempPngPath)) {
@@ -285,13 +284,26 @@ async function takeScreenshots(
285
284
  // Create a new context and page for each file (for parallelism)
286
285
  const context = await browser.newContext();
287
286
  const page = await context.newPage();
288
- let screenshotIndex = 0;
289
287
 
290
288
  try {
291
- // Construct URL from file path (add /candidate prefix since server serves from parent)
292
- const fileUrl = convertToHtmlExtension(
293
- `${serverUrl}/candidate/${file.path.replace(/\\/g, '/')}`
294
- );
289
+ const envVars = {};
290
+ for (const [key, value] of Object.entries(process.env)) {
291
+ if (key.startsWith('RTGL_VT_')) {
292
+ envVars[key] = value;
293
+ }
294
+ }
295
+
296
+ if (Object.keys(envVars).length > 0) {
297
+ await page.addInitScript((vars) => {
298
+ Object.assign(window, vars);
299
+ }, envVars);
300
+ }
301
+
302
+ const frontMatterUrl = file.frontMatter?.url;
303
+ const constructedUrl = convertToHtmlExtension(`${serverUrl}/candidate/${file.path.replace(/\\/g, "/")}`);
304
+ const url = frontMatterUrl ?? configUrl ?? constructedUrl;
305
+ const fileUrl = url.startsWith("http") ? url : new URL(url, serverUrl).href;
306
+
295
307
  console.log(`Navigating to ${fileUrl}`);
296
308
  await page.goto(fileUrl, { waitUntil: "networkidle" });
297
309
  if (waitTime > 0) {
@@ -300,7 +312,7 @@ async function takeScreenshots(
300
312
  const baseName = removeExtension(file.path);
301
313
 
302
314
  const initialScreenshotPath = await takeAndSaveScreenshot(page, baseName);
303
- console.log(`Initial screenshot saved: ${initialScreenshotPath}`);
315
+ console.log(`Screenshot saved: ${initialScreenshotPath}`);
304
316
 
305
317
  const stepContext = {
306
318
  baseName,