frontend-go-live-check 1.4.0 → 1.5.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.0
4
+
5
+ - **One folder for everything**: `go-live-check/` holds the settings (`config.json`), the test passwords (`.env`), the API files (`api/`), the design baseline (`baseline/`), the report (`report/`) and a saved sign-in (`session.json`), with its own `.gitignore`. `init` creates it. Projects with the older root files keep working; the README shows how to move.
6
+ - Every section of the HTML report opens and closes (accordion), with **Expand all** and **Collapse all**.
7
+ - The date and time the report was created are at the top of the HTML report, on the PDF and Word cover, and in the page header.
8
+ - Visual changes show the real pages: **Before** (the approved look), **Now** and **Changes** side by side; in each finding, Actual is the page now and Expected is the approved look.
9
+
10
+ ## 1.4.1
11
+
12
+ - Problems are grouped **by type** (Functionality, Design, Accessibility, Performance, Sign-in, APIs, Security, SEO, Code) in the HTML report, the PDF and the Word file, most serious first in each group.
13
+ - Every problem has **Steps to reproduce** and **Test again**: the shortest command that checks only that problem after the fix.
14
+ - README: new *How it works* and *Built with* sections.
15
+
3
16
  ## 1.4.0
4
17
 
5
18
  - New layout checks on every page at every screen size, each with Actual and Expected pictures where possible:
package/README.md CHANGED
@@ -18,11 +18,14 @@
18
18
 
19
19
  <p align="center">
20
20
  <a href="#quick-start">Quick start</a> ·
21
+ <a href="#files-everything-in-one-folder">Files</a> ·
21
22
  <a href="#sign-in-email-google-microsoft-uae-pass">Sign-in</a> ·
22
23
  <a href="#what-it-checks">What it checks</a> ·
24
+ <a href="#how-it-works">How it works</a> ·
23
25
  <a href="#the-report">The report</a> ·
24
26
  <a href="#pipelines-approve-or-reject-the-merge">Pipelines</a> ·
25
27
  <a href="#settings">Settings</a> ·
28
+ <a href="#built-with">Built with</a> ·
26
29
  <a href="#faq">FAQ</a>
27
30
  </p>
28
31
 
@@ -36,11 +39,11 @@ npx frontend-go-live-check init
36
39
  npm run go-live
37
40
  ```
38
41
 
39
- That's it. The result is `go-live-report/index.html`, plus a PDF and a Word file to share with your team.
42
+ That's it. The result is `go-live-check/report/index.html`, plus a PDF and a Word file to share with your team.
40
43
 
41
44
  `init` adds the `go-live` scripts and a settings file, and downloads the test browser. It never overwrites anything.
42
45
 
43
- Have an API? Save your Postman collection as **`api/postman_collection.json`** (or your OpenAPI file as `api/openapi.json`) and the API tests run too.
46
+ Have an API? Save your Postman collection as **`go-live-check/api/postman_collection.json`** (or your OpenAPI file as `go-live-check/api/openapi.json`) and the API tests run too.
44
47
 
45
48
  <p align="center">
46
49
  <img src="https://unpkg.com/frontend-go-live-check@1/docs/terminal.png" alt="Terminal output: NOT READY, 2 blocker, 7 major, 5 minor, with a score per area" width="760">
@@ -48,6 +51,53 @@ Have an API? Save your Postman collection as **`api/postman_collection.json`** (
48
51
 
49
52
  ---
50
53
 
54
+ ## Files: everything in one folder
55
+
56
+ Everything the tool uses or makes is in **one `go-live-check/` folder** at the root of your project. `init` creates it.
57
+
58
+ ```text
59
+ my-project/
60
+ ├── go-live-check/
61
+ │ ├── config.json ← the settings (all optional)
62
+ │ ├── .env ← test passwords (Git ignores it)
63
+ │ ├── .env.example ← the names to fill in (committed)
64
+ │ ├── api/
65
+ │ │ ├── postman_collection.json ← your Postman collection
66
+ │ │ ├── postman_environment.json ← optional: its environment
67
+ │ │ └── openapi.json ← or/and your OpenAPI file
68
+ │ ├── baseline/ ← the approved look of every page (commit it)
69
+ │ ├── report/ ← the results: HTML, PDF, Word, zip (Git ignores it)
70
+ │ ├── session.json ← a saved sign-in, after `login` (Git ignores it)
71
+ │ └── .gitignore ← keeps .env, report/ and session.json out of Git
72
+ ├── src/
73
+ └── package.json
74
+ ```
75
+
76
+ | Commit | Never commit (already ignored) |
77
+ | --- | --- |
78
+ | `config.json`, `.env.example`, `api/`, `baseline/` | `.env`, `report/`, `session.json` |
79
+
80
+ <details>
81
+ <summary><b>Set up with an older version?</b></summary>
82
+
83
+ Projects with `go-live-check.config.json`, `go-live-check.env`, `api/`, `go-live-baseline/` and `go-live-report/` at the root keep working. To move to the folder:
84
+
85
+ ```bash
86
+ mkdir -p go-live-check
87
+ git mv go-live-check.config.json go-live-check/config.json
88
+ git mv go-live-check.env.example go-live-check/.env.example
89
+ git mv api go-live-check/api
90
+ git mv go-live-baseline go-live-check/baseline
91
+ mv go-live-check.env go-live-check/.env
92
+ printf '.env\nreport/\nsession.json\n' > go-live-check/.gitignore
93
+ ```
94
+
95
+ In `go-live-check/config.json`, change `$schema` to `../node_modules/frontend-go-live-check/schema.json`. Remove the old `go-live-report/` folder and its `.gitignore` lines.
96
+
97
+ </details>
98
+
99
+ ---
100
+
51
101
  ## Why
52
102
 
53
103
  | Without it | With it |
@@ -97,7 +147,7 @@ npx frontend-go-live-check login
97
147
  What happens:
98
148
 
99
149
  - A normal browser window opens on your app. Sign in the way your users do (approve UAE PASS on your phone), wait for a signed-in page, then press Enter in the terminal.
100
- - The session (cookies and browser storage) is saved in **`go-live-check.session.json`**. Treat it like a password: Git ignores it, never share it.
150
+ - The session (cookies and browser storage) is saved in **`go-live-check/session.json`**. Treat it like a password: Git ignores it, never share it.
101
151
  - When it expires, the report says ***"The saved sign-in session has expired"*** (also when the app's API starts answering 401), and `doctor` tells you before you run. Run `login` again.
102
152
 
103
153
  **Pipelines (`ciLogin`)**: a pipeline has no one to approve a sign-in, so it uses `ciLogin` instead of `login`. Pick one of these:
@@ -145,7 +195,7 @@ The report lists every sign-in method found (*email and password · Google · UA
145
195
  | 🧭 | **Functionality** | JavaScript errors, console errors, failed requests, broken links, images and anchors, blank pages, buttons covered by something else, unfinished text (`undefined`, `NaN`, `[object Object]`, translation keys, lorem ipsum, TODO), loading spinners that never stop, the production build, the "page not found" page |
146
196
  | 🛟 | **Resilience** | Each page opened again with its API calls failing: does it crash, go blank, or tell the user? |
147
197
  | 📱 | **Design and responsive layout** | Every page at every screen size: sideways scrolling, text that spills out of its box (a button too small for its label), text on top of other text, content cut off at the screen edge, text cut off inside its box, overlapping buttons, stretched or squashed images, tiny text on phones, right-to-left layout (Arabic, Hebrew…), fonts that fail to load, too many fonts and sizes |
148
- | 🖼️ | **Visual changes** | A pixel comparison of every page with the saved baseline, with the changed areas in red |
198
+ | 🖼️ | **Visual changes** | Every page compared with its approved look (the baseline): **Before**, **Now** and the changed areas side by side |
149
199
  | ♿ | **Accessibility** | axe-core against WCAG 2.0 / 2.1 / 2.2 A and AA; keyboard-only use: visible focus, no traps, nothing that only a mouse can click; form field types |
150
200
  | ⚡ | **Performance** | Size, JavaScript, requests, load time, LCP, layout shift and frozen-page time for every page, against budgets; Lighthouse as a phone and as a desktop |
151
201
  | 🔌 | **API calls made by the pages** | Slow, very large, repeated, too many, uncompressed or uncached calls |
@@ -160,6 +210,24 @@ The report's **What was checked** table says exactly what ran on your project, a
160
210
 
161
211
  ---
162
212
 
213
+ ## How it works
214
+
215
+ ```text
216
+ ① Detect ──▶ ② Build & start ──▶ ③ Sign in ──▶ ④ Find pages ──▶ ⑤ Test ──▶ ⑥ Report ──▶ ⑦ Verdict
217
+ ```
218
+
219
+ | Step | What happens |
220
+ | :-: | --- |
221
+ | **1. Detect** | Reads `package.json`: the framework, the package manager, the sign-in libraries, and your files in `go-live-check/` |
222
+ | **2. Build & start** | Builds the app and starts it **the way it runs in production**, plus any services you listed (a backend or a mock API) |
223
+ | **3. Sign in** | With the test account, the saved session (`login`) or a test token, so the signed-in pages are tested too |
224
+ | **4. Find pages** | Follows every link, reads your routes in the source code and your `sitemap.xml` |
225
+ | **5. Test** | Opens every page at every screen size in a real browser and runs every check in [What it checks](#what-it-checks); then calls every API endpoint from your Postman or OpenAPI file |
226
+ | **6. Report** | Writes the HTML report with Actual and Expected pictures, plus the PDF, the Word file and the zip |
227
+ | **7. Verdict** | **READY**, **NEEDS ATTENTION** or **NOT READY**; in a pipeline, the run fails on blockers and the merge is rejected |
228
+
229
+ ---
230
+
163
231
  ## Works with
164
232
 
165
233
  **Next.js** · **React** (Vite, Create React App) · **Vue** · **Nuxt** · **Angular** · **Svelte / SvelteKit** · **Astro** · **Remix / React Router** · **Gatsby** · **Solid** · **Preact** · **Qwik** · **plain HTML**. Anything else, in any language, works with a start command or `--url`.
@@ -194,10 +262,11 @@ The framework and the package manager (npm, pnpm, yarn, bun) are detected. The a
194
262
 
195
263
  The report has:
196
264
 
197
- - the **verdict** and a **score per area** (click an area to filter);
198
- - every problem, with where it is and at which screen sizes;
265
+ - when it was created, the **verdict** and a **score per area** (click an area to jump to it);
266
+ - every section opens and closes, with **Expand all** and **Collapse all**;
267
+ - **problems by type**: Functionality, Design, Accessibility, Performance, Sign-in, APIs, Security, SEO, Code, each with its score, worst problems first;
199
268
  - the API results, the speed of every page, and the load test;
200
- - screenshots of every page at every size.
269
+ - screenshots of every page at every size, for the design review.
201
270
 
202
271
  ### Every problem: Actual next to Expected
203
272
 
@@ -206,7 +275,9 @@ Each problem is shown so a developer understands it at a glance, without knowing
206
275
  - **one sentence** on what is wrong, in plain words;
207
276
  - **Actual**: a close-up of the page as it is now, with the problem outlined in red, at the screen size where it happens;
208
277
  - **Expected**: the same spot as it should be, outlined in green. For visual problems the tool corrects the page in the browser for this one picture: a visible focus ring, readable contrast, text that fits, a page that fits the screen, right-to-left layout. Other problems (APIs, security, sign-in) get one sentence saying what is expected;
209
- - **where** (page, screen size, the element by name and CSS selector), **who it affects**, and **how to see it yourself**.
278
+ - **where** (page, screen size, the element by name and CSS selector) and **who it affects**;
279
+ - **steps to reproduce** it yourself;
280
+ - **test again**: the shortest command that checks only this problem after the fix (copy button included), for example `npx frontend-go-live-check --only pages --pages /login --max-pages 1`.
210
281
 
211
282
  <p align="center">
212
283
  <img src="https://unpkg.com/frontend-go-live-check@1/docs/finding.png" alt="A problem card: Actual (focus not visible, outlined in red) next to Expected (a clear focus ring, outlined in green)" width="860">
@@ -314,18 +385,18 @@ The short name `go-live-check` works too. After `init`, use `npm run go-live`, `
314
385
 
315
386
  ## Settings
316
387
 
317
- Everything is optional. `init` writes `go-live-check.config.json`, and your editor autocompletes it through `$schema`.
388
+ Everything is optional. `init` writes `go-live-check/config.json`, and your editor autocompletes it through `$schema`.
318
389
 
319
390
  ```json
320
391
  {
321
- "$schema": "./node_modules/frontend-go-live-check/schema.json",
392
+ "$schema": "../node_modules/frontend-go-live-check/schema.json",
322
393
  "login": { "page": "/login", "username": "test.user", "password": "$GO_LIVE_PASSWORD" },
323
394
  "services": [{ "command": "npm run api", "url": "http://localhost:4000/health" }],
324
395
  "failOn": "blocker"
325
396
  }
326
397
  ```
327
398
 
328
- API tests need no settings when your files are in `api/` ([see API tests](#settings)). Passwords are written as `"$NAME"`. Put their values in `go-live-check.env` (Git ignores it) or in your pipeline's secrets. They are replaced with `‹secret›` in every report.
399
+ API tests need no settings when your files are in `go-live-check/api/` (see *API tests from Postman or OpenAPI* below). Passwords are written as `"$NAME"`. Put their values in `go-live-check/.env` (Git ignores it) or in your pipeline's secrets. They are replaced with `‹secret›` in every report.
329
400
 
330
401
  <details>
331
402
  <summary><b>🔐 Sign-in forms that need help</b></summary>
@@ -354,19 +425,16 @@ An external single sign-on page usually can't be automated. Point `login` at a t
354
425
  <details>
355
426
  <summary><b>🧪 API tests from Postman or OpenAPI</b></summary>
356
427
 
357
- Put your API description in an **`api/`** folder at the root of your project, with **exactly these names**:
428
+ Put your API description in **`go-live-check/api/`**, with **exactly these names**:
358
429
 
359
430
  ```text
360
- my-project/
361
- ├── api/
362
- │ ├── postman_collection.json ← the Postman collection (v2.1)
363
- │ ├── postman_environment.json ← optional: its environment ({{baseUrl}} and other variables)
364
- │ └── openapi.json ← or/and the OpenAPI / Swagger file (openapi.yaml works too)
365
- ├── go-live-check.config.json
366
- └── package.json
431
+ go-live-check/api/
432
+ ├── postman_collection.json ← the Postman collection (v2.1)
433
+ ├── postman_environment.json ← optional: its environment ({{baseUrl}} and other variables)
434
+ └── openapi.json ← or/and the OpenAPI / Swagger file (openapi.yaml works too)
367
435
  ```
368
436
 
369
- That's all: no settings are needed. Postman export names (`My API.postman_collection.json`) need renaming to `postman_collection.json`. If both a Postman collection and an OpenAPI file are there, they are combined: OpenAPI gives the rules, Postman the examples. `init` creates `api/README.md` with these instructions. `frontend-go-live-check doctor` shows what it found.
437
+ That's all: no settings are needed. Postman export names (`My API.postman_collection.json`) need renaming to `postman_collection.json`. If both a Postman collection and an OpenAPI file are there, they are combined: OpenAPI gives the rules, Postman the examples. `init` creates `go-live-check/api/README.md` with these instructions. `frontend-go-live-check doctor` shows what it found.
370
438
 
371
439
  Never put real passwords or tokens in these files: use `$NAMES` (see below).
372
440
 
@@ -394,7 +462,7 @@ To test roles and ownership, add test users. Include a **second user of the same
394
462
  <details>
395
463
  <summary><b>🖼️ Visual changes</b></summary>
396
464
 
397
- The first run saves a picture of every page at every size in `go-live-baseline/`. Commit that folder. Later runs report pages that look different, with the changed areas in red.
465
+ The first run saves a picture of every page at every size in `go-live-check/baseline/`. Commit that folder. Later runs report pages that look different, with the changed areas in red.
398
466
 
399
467
  After an intended design change, save the new look:
400
468
 
@@ -433,7 +501,7 @@ Hide parts that change on their own (clocks, live data) with `"visualMask": [".c
433
501
  ## Safety and privacy
434
502
 
435
503
  - **Everything runs on your machine** or your own pipeline. Nothing is sent anywhere except to the app being tested.
436
- - **Test passwords** come from `go-live-check.env` or pipeline secrets, and never appear in a report or log.
504
+ - **Test passwords** come from `go-live-check/.env` or pipeline secrets, and never appear in a report or log.
437
505
  - **Use test accounts on a test environment.** Browser checks only read pages and submit forms empty.
438
506
  - **API writes and the load test** run only against this computer or the hosts you list. Every record the tests create is deleted afterwards.
439
507
  - **Closed networks.** Point `PLAYWRIGHT_DOWNLOAD_HOST` at your mirror, or `GO_LIVE_CHROMIUM` at an existing Chromium.
@@ -451,6 +519,28 @@ console.log(summary.verdict); // READY · NEEDS ATTENTION · NOT READY
451
519
 
452
520
  ---
453
521
 
522
+ ## Built with
523
+
524
+ Trusted open-source tools, used by thousands of teams, do the heavy lifting. The checks, the sign-in handling, the layout and content checks and the reports are this package's own code on top of them.
525
+
526
+ | Job | Tool | What it does here |
527
+ | --- | --- | --- |
528
+ | 🌐 **Real browser** | [Playwright](https://playwright.dev) | Opens every page in Chromium (Firefox and WebKit with `--all-browsers`) at every screen size; clicks, types, uses the keyboard, signs in, takes the screenshots and prints the PDF |
529
+ | ♿ **Accessibility** | [axe-core](https://github.com/dequelabs/axe-core) (`@axe-core/playwright`) | The accessibility rules (WCAG 2.0 / 2.1 / 2.2, A and AA) used by most accessibility tools |
530
+ | ⚡ **Speed and quality** | [Lighthouse](https://developer.chrome.com/docs/lighthouse) | Google's performance, best-practice and SEO scores, as a phone and as a desktop |
531
+ | | [chrome-launcher](https://github.com/GoogleChrome/chrome-launcher) · [puppeteer-core](https://pptr.dev) | Start and control Chrome for Lighthouse, including signed-in pages |
532
+ | 🖼️ **Visual changes** | [pixelmatch](https://github.com/mapbox/pixelmatch) · [pngjs](https://github.com/pngjs/pngjs) | Compare every screenshot with the saved baseline, pixel by pixel, and mark the changes in red |
533
+ | 🧪 **API tests** | [Ajv](https://ajv.js.org) · [ajv-formats](https://github.com/ajv-validator/ajv-formats) | Check every API answer against your OpenAPI or Postman schema, including formats (email, date, URL, ID) |
534
+ | | [Faker](https://fakerjs.dev) | Realistic test data (names, emails, numbers) for the requests that create records |
535
+ | | [yaml](https://eemeli.org/yaml) | Reads OpenAPI files written in YAML |
536
+ | 📄 **Documents** | [docx](https://docx.js.org) | Writes the Word report |
537
+ | | [JSZip](https://stuk.github.io/jszip) | Packs the full report, screenshots, PDF and Word file into one zip |
538
+ | 🛡️ **Dependencies** | `npm audit` (comes with npm) | Known security problems in your project's libraries |
539
+
540
+ No other services: nothing is uploaded, and no account is needed.
541
+
542
+ ---
543
+
454
544
  ## FAQ
455
545
 
456
546
  <details>
@@ -490,7 +580,7 @@ Security headers, compression and caching usually come from your web server or C
490
580
  | You see | Fix |
491
581
  | --- | --- |
492
582
  | `Executable doesn't exist` | `npx playwright install chromium` |
493
- | `Not set: GO_LIVE_PASSWORD` | Copy `go-live-check.env.example` to `go-live-check.env` and fill it in |
583
+ | `Not set: GO_LIVE_PASSWORD` | Copy `go-live-check/.env.example` to `go-live-check/.env` and fill it in |
494
584
  | `login-failed` | Check `login`; name the fields (`usernameField`, `passwordField`, `submit`) if the form is unusual |
495
585
  | *Signed-in pages were not tested* | Set `login`, or run `npx frontend-go-live-check login` (Google, UAE PASS…) |
496
586
  | *The saved sign-in session has expired* | Run `npx frontend-go-live-check login` again |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-go-live-check",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Is your frontend ready to go live? One command tests any web frontend (React, Next.js, Vue, Angular, Svelte, plain HTML…): pages, responsive design, accessibility, keyboard, performance and load, API calls, security, SEO, sign-in and APIs from Postman/OpenAPI. Report with evidence, PDF and Word, and a pipeline gate that approves or rejects the merge.",
5
5
  "homepage": "https://www.npmjs.com/package/frontend-go-live-check",
6
6
  "repository": {
package/schema.json CHANGED
@@ -154,7 +154,7 @@
154
154
  },
155
155
  "password": {
156
156
  "type": "string",
157
- "description": "Use \"$NAME\" and put NAME in go-live-check.env."
157
+ "description": "Use \"$NAME\" and put NAME in go-live-check/.env."
158
158
  },
159
159
  "usernameField": {
160
160
  "type": "string",
@@ -207,7 +207,7 @@
207
207
  },
208
208
  "sessionFile": {
209
209
  "type": "string",
210
- "description": "Where the saved session is kept. Default go-live-check.session.json (never commit it)."
210
+ "description": "Where the saved session is kept. Default go-live-check/session.json (never commit it)."
211
211
  }
212
212
  },
213
213
  "description": "How the tool signs in. Email/password: page + username + password. Google, Microsoft, Apple, UAE PASS\u2026: \"method\": \"session\" and run frontend-go-live-check login once. Pipelines: see ciLogin."
package/src/api/model.mjs CHANGED
@@ -242,6 +242,6 @@ export async function loadApiMap(api, dir) {
242
242
  }
243
243
  }
244
244
  const baseUrl = sources.find((s) => s.baseUrl)?.baseUrl;
245
- if (!sources.length) notes.push('No API file given: set "api": { "file": "postman_collection.json" } (or "openapi") in go-live-check.config.json, or --api <file>.');
245
+ if (!sources.length) notes.push('No API file given: set "api": { "file": "postman_collection.json" } (or "openapi") in go-live-check/config.json, or put the file in go-live-check/api/, or --api <file>.');
246
246
  return { endpoints: [...map.values()], baseUrl, basePath: sources.find((s) => s.basePath)?.basePath, title: sources.find((s) => s.title)?.title, sources: sources.map((s) => s.kind), notes };
247
247
  }
@@ -95,7 +95,7 @@ export async function runAuthChecks({ base, config, pages, network, log }) {
95
95
  const landed = new URL(page.url()).pathname;
96
96
  const asksToSignIn = (await page.locator('input[type=password]').count()) > 0;
97
97
  if (landed === new URL(target.url).pathname && !asksToSignIn) {
98
- add({ ruleId: 'page-without-sign-in', page: target.key, detail: `${target.key} opened without signing in. If it is public on purpose, add it to "publicPages" in go-live-check.config.json.` });
98
+ add({ ruleId: 'page-without-sign-in', page: target.key, detail: `${target.key} opened without signing in. If it is public on purpose, add it to "publicPages" in the settings (go-live-check/config.json).` });
99
99
  }
100
100
  }
101
101
  await context.close();
@@ -414,7 +414,7 @@ export async function runBrowserChecks({ base, config, reportDir, log, starts =
414
414
  mkdirSync(join(reportDir, 'visual', 'now'), { recursive: true });
415
415
  const taken = await page.screenshot({ path: shotPath, fullPage: true, type: 'png', animations: 'disabled', caret: 'hide', mask }).then(() => true).catch(() => false);
416
416
  if (taken) {
417
- const result = compareWithBaseline({ shotPath, baselineDir, diffDir: join(reportDir, 'visual', 'diff'), name, update: updateBaseline, threshold: config.visualThreshold ?? 0.001 });
417
+ const result = compareWithBaseline({ shotPath, baselineDir, diffDir: join(reportDir, 'visual', 'diff'), beforeDir: join(reportDir, 'visual', 'before'), name, update: updateBaseline, threshold: config.visualThreshold ?? 0.001 });
418
418
  visualResults.push({ page: landedKey, view: tag, name, ...result });
419
419
  if (result.status === 'changed') {
420
420
  add({
@@ -423,6 +423,10 @@ export async function runBrowserChecks({ base, config, reportDir, log, starts =
423
423
  view: tag,
424
424
  detail: `${(result.ratio * 100).toFixed(1)}% of the page looks different from the saved baseline${result.sizeChanged ? ` (size ${result.before} → ${result.after})` : ''}.`,
425
425
  link: `visual/diff/${name}`,
426
+ // Actual = the page now; Expected = the approved look (the baseline).
427
+ evidence: `visual/now/${name}`,
428
+ expected: `visual/before/${name}`,
429
+ evidenceView: tag,
426
430
  });
427
431
  }
428
432
  }
@@ -10,7 +10,7 @@ import { SECRET_PATTERNS } from './security.mjs';
10
10
  function sourceFiles(dir, out = [], depth = 0) {
11
11
  if (depth > 8 || out.length > 3000) return out;
12
12
  for (const name of readdirSync(dir)) {
13
- if (['node_modules', 'dist', 'build', '.next', 'out', 'coverage', 'reports', 'go-live-report', '.git', 'test-results'].includes(name)) continue;
13
+ if (['node_modules', 'dist', 'build', '.next', 'out', 'coverage', 'reports', 'go-live-report', 'go-live-check', '.git', 'test-results'].includes(name)) continue;
14
14
  const path = join(dir, name);
15
15
  const stat = statSync(path);
16
16
  if (stat.isDirectory()) sourceFiles(path, out, depth + 1);
@@ -124,7 +124,7 @@ export function scanProject(dir) {
124
124
  continue;
125
125
  }
126
126
  for (const name of names) {
127
- if (['node_modules', 'dist', 'build', '.next', '.git', 'go-live-report', 'coverage'].includes(name)) continue;
127
+ if (['node_modules', 'dist', 'build', '.next', '.git', 'go-live-report', 'go-live-check', 'coverage'].includes(name)) continue;
128
128
  const path = join(current, name);
129
129
  const stat = statSync(path);
130
130
  if (stat.isDirectory()) stack.push(path);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Design changes between runs. The first run saves a picture of every page at every screen size
3
- * in go-live-baseline/ (commit it). Later runs compare with it and report pages that look
3
+ * in go-live-check/baseline/ (commit it). Later runs compare with it and report pages that look
4
4
  * different, with a picture that shows the changed area in red.
5
5
  * After an intended design change: frontend-go-live-check --update-baseline.
6
6
  */
@@ -9,7 +9,7 @@ import { join } from 'node:path';
9
9
  import pixelmatch from 'pixelmatch';
10
10
  import { PNG } from 'pngjs';
11
11
 
12
- export function compareWithBaseline({ shotPath, baselineDir, diffDir, name, update, threshold = 0.001 }) {
12
+ export function compareWithBaseline({ shotPath, baselineDir, diffDir, beforeDir, name, update, threshold = 0.001 }) {
13
13
  mkdirSync(baselineDir, { recursive: true });
14
14
  const baselinePath = join(baselineDir, name);
15
15
  if (update || !existsSync(baselinePath)) {
@@ -33,5 +33,10 @@ export function compareWithBaseline({ shotPath, baselineDir, diffDir, name, upda
33
33
  if (ratio <= threshold && !sizeChanged) return { status: 'same', ratio };
34
34
  mkdirSync(diffDir, { recursive: true });
35
35
  writeFileSync(join(diffDir, name), PNG.sync.write(diff));
36
+ // The approved look, next to the report, so Before and Now can be shown side by side.
37
+ if (beforeDir) {
38
+ mkdirSync(beforeDir, { recursive: true });
39
+ copyFileSync(baselinePath, join(beforeDir, name));
40
+ }
36
41
  return { status: 'changed', ratio, sizeChanged, before: `${a.width}×${a.height}`, after: `${b.width}×${b.height}` };
37
42
  }
package/src/cli/main.mjs CHANGED
@@ -29,16 +29,16 @@ What to check
29
29
  --update-baseline save the current look of every page as the new design baseline
30
30
 
31
31
  Output
32
- --out <folder> where the report goes (default go-live-report)
32
+ --out <folder> where the report goes (default go-live-check/report)
33
33
  --open open the report when done
34
34
  --fail-on <level> blocker (default) | major | minor | never: what makes the command fail
35
- --config <file> the settings file (default go-live-check.config.json)
35
+ --config <file> the settings file (default go-live-check/config.json)
36
36
  --quiet print only the result
37
37
  --ci pipeline mode: build summary, no prompts (the pipeline files use it)
38
38
  --format pdf,docx which documents to write (default both)
39
39
  --no-export no Word or PDF document
40
40
 
41
- Settings: go-live-check.config.json (all optional). Test passwords: go-live-check.env (never committed).
41
+ Files: go-live-check/ (config.json settings, .env test passwords never committed, api/ Postman or OpenAPI, report/).
42
42
  Docs: https://www.npmjs.com/package/go-live-check
43
43
  `;
44
44
 
@@ -64,7 +64,7 @@ export function parseArgs(argv) {
64
64
  return { options, positional };
65
65
  }
66
66
 
67
- const isProject = (folder) => ['package.json', 'go-live-check.config.json', 'index.html'].some((f) => existsSync(join(folder, f)));
67
+ const isProject = (folder) => ['package.json', 'go-live-check/config.json', 'go-live-check.config.json', 'index.html'].some((f) => existsSync(join(folder, f)));
68
68
 
69
69
  export async function main(argv) {
70
70
  let parsed;
@@ -148,7 +148,7 @@ export async function main(argv) {
148
148
 
149
149
  const projectFolder = isProject(folder) ? folder : undefined;
150
150
  if (!projectFolder && !options.url) {
151
- console.error(`✖ ${folder} is not a web project (no package.json, index.html or go-live-check.config.json).\n Give a project folder, or --url <address> to check a site that is already running.`);
151
+ console.error(`✖ ${folder} is not a web project (no package.json, index.html or go-live-check/config.json).\n Give a project folder, or --url <address> to check a site that is already running.`);
152
152
  return 2;
153
153
  }
154
154
  const { runCheck } = await import('../commands/run.mjs');
@@ -3,17 +3,17 @@
3
3
  * Writes a pipeline file that runs the full check on every pull / merge request. The run fails
4
4
  * (and the merge is blocked once the check is required) when the verdict reaches "failOn".
5
5
  * The test passwords come from the pipeline's secret variables: the file lists the names found
6
- * in go-live-check.config.json ("$NAME").
6
+ * in the settings ("$NAME").
7
7
  */
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
9
  import { dirname, join } from 'node:path';
10
- import { CONFIG_FILE } from '../core/config.mjs';
10
+ import { layout } from '../core/config.mjs';
11
11
 
12
12
  const PLAYWRIGHT = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).dependencies.playwright;
13
13
  const IMAGE = `mcr.microsoft.com/playwright:v${PLAYWRIGHT}-noble`;
14
14
 
15
15
  function secretNames(dir) {
16
- const path = join(dir, CONFIG_FILE);
16
+ const path = join(dir, layout(dir).config);
17
17
  if (!existsSync(path)) return ['GO_LIVE_PASSWORD'];
18
18
  const names = new Set();
19
19
  const walk = (v) => {
@@ -31,7 +31,7 @@ function secretNames(dir) {
31
31
  const TEMPLATES = {
32
32
  github: {
33
33
  file: '.github/workflows/go-live-check.yml',
34
- text: (secrets) => `# Go-live check: runs on every pull request and blocks the merge when it fails.
34
+ text: (secrets, report) => `# Go-live check: runs on every pull request and blocks the merge when it fails.
35
35
  # 1. Settings > Secrets and variables > Actions: add ${secrets.join(', ') || 'your test passwords'}.
36
36
  # 2. Settings > Branches (or Rules) > protect your main branch > Require status checks: "frontend-go-live-check".
37
37
  name: Go-live check
@@ -74,7 +74,7 @@ ${secrets.length ? ` env:\n${secrets.map((s) => ` ${s}: \${{ sec
74
74
  uses: actions/upload-artifact@v4
75
75
  with:
76
76
  name: go-live-report
77
- path: go-live-report
77
+ path: ${report}
78
78
  retention-days: 30
79
79
 
80
80
  - name: Comment the result on the pull request
@@ -84,7 +84,7 @@ ${secrets.length ? ` env:\n${secrets.map((s) => ` ${s}: \${{ sec
84
84
  script: |
85
85
  const fs = require('fs');
86
86
  const marker = '<!-- frontend-go-live-check -->';
87
- const summary = fs.existsSync('go-live-report/summary.md') ? fs.readFileSync('go-live-report/summary.md', 'utf8') : 'The go-live check did not finish: open the job log.';
87
+ const summary = fs.existsSync('${report}/summary.md') ? fs.readFileSync('${report}/summary.md', 'utf8') : 'The go-live check did not finish: open the job log.';
88
88
  const run = \`\${context.serverUrl}/\${context.repo.owner}/\${context.repo.repo}/actions/runs/\${context.runId}\`;
89
89
  const body = \`\${marker}\\n\${summary}\\n\\nFull report, PDF and Word: the **go-live-report** artifact of [this run](\${run}).\`;
90
90
  const { data: comments } = await github.rest.issues.listComments({ ...context.repo, issue_number: context.issue.number });
@@ -95,7 +95,7 @@ ${secrets.length ? ` env:\n${secrets.map((s) => ` ${s}: \${{ sec
95
95
  },
96
96
  gitlab: {
97
97
  file: '.gitlab/go-live-check.gitlab-ci.yml',
98
- text: (secrets) => `# Go-live check: runs on every merge request and blocks the merge when it fails.
98
+ text: (secrets, report) => `# Go-live check: runs on every merge request and blocks the merge when it fails.
99
99
  # 1. Add to .gitlab-ci.yml: include: '.gitlab/go-live-check.gitlab-ci.yml'
100
100
  # 2. Settings > CI/CD > Variables: add ${secrets.join(', ') || 'your test passwords'} (Masked, Protected as needed).
101
101
  # 3. Settings > Merge requests > "Pipelines must succeed".
@@ -117,14 +117,14 @@ frontend-go-live-check:
117
117
  artifacts:
118
118
  when: always
119
119
  expire_in: 30 days
120
- paths: [go-live-report/]
120
+ paths: [${report}/]
121
121
  reports:
122
- junit: go-live-report/junit.xml
122
+ junit: ${report}/junit.xml
123
123
  `,
124
124
  },
125
125
  azure: {
126
126
  file: 'azure-pipelines.go-live-check.yml',
127
- text: (secrets) => `# Go-live check: runs on every pull request and blocks the merge when it fails.
127
+ text: (secrets, report) => `# Go-live check: runs on every pull request and blocks the merge when it fails.
128
128
  # 1. Pipelines > New pipeline > your repo > Existing YAML file > azure-pipelines.go-live-check.yml
129
129
  # 2. Edit > Variables: add ${secrets.join(', ') || 'your test passwords'}, tick "Keep this value secret".
130
130
  # 3. Project settings > Repositories > the repo > Policies > main > Build validation: add this pipeline, Required.
@@ -156,21 +156,21 @@ ${secrets.length ? ` env:\n${secrets.map((s) => ` ${s}: $(${s})`).join('
156
156
  condition: always()
157
157
  inputs:
158
158
  testResultsFormat: JUnit
159
- testResultsFiles: go-live-report/junit.xml
159
+ testResultsFiles: ${report}/junit.xml
160
160
  testRunTitle: Go-live check
161
161
  displayName: Problems in the Tests tab
162
162
 
163
163
  - task: PublishPipelineArtifact@1
164
164
  condition: always()
165
165
  inputs:
166
- targetPath: go-live-report
166
+ targetPath: ${report}
167
167
  artifact: go-live-report
168
168
  displayName: Keep the report (HTML, PDF, Word)
169
169
  `,
170
170
  },
171
171
  bitbucket: {
172
172
  file: 'bitbucket-pipelines.go-live-check.yml',
173
- text: (secrets) => `# Go-live check for Bitbucket. Copy the "frontend-go-live-check" step into bitbucket-pipelines.yml.
173
+ text: (secrets, report) => `# Go-live check for Bitbucket. Copy the "frontend-go-live-check" step into bitbucket-pipelines.yml.
174
174
  # 1. Repository settings > Repository variables: add ${secrets.join(', ') || 'your test passwords'} (Secured).
175
175
  # 2. Repository settings > Branch restrictions > main > "Minimum successful builds": 1.
176
176
  image: ${IMAGE}
@@ -186,12 +186,12 @@ pipelines:
186
186
  - npm ci
187
187
  - npx frontend-go-live-check --ci
188
188
  artifacts:
189
- - go-live-report/**
189
+ - ${report}/**
190
190
  `,
191
191
  },
192
192
  jenkins: {
193
193
  file: 'Jenkinsfile.go-live-check',
194
- text: (secrets) => `// Go-live check for Jenkins (multibranch pipeline: runs on every pull request).
194
+ text: (secrets, report) => `// Go-live check for Jenkins (multibranch pipeline: runs on every pull request).
195
195
  // 1. Manage Jenkins > Credentials: add ${secrets.map((s) => `"${s.toLowerCase().replace(/_/g, '-')}"`).join(', ') || 'your test passwords'} as "Secret text".
196
196
  // 2. Point the pipeline's Script Path to Jenkinsfile.go-live-check, and require it in your Git server's merge rules.
197
197
  pipeline {
@@ -206,8 +206,8 @@ ${secrets.map((s) => ` ${s} = credentials('${s.toLowerCase().replace(/_/g, '-
206
206
  }
207
207
  post {
208
208
  always {
209
- junit allowEmptyResults: true, testResults: 'go-live-report/junit.xml'
210
- archiveArtifacts artifacts: 'go-live-report/**', allowEmptyArchive: true
209
+ junit allowEmptyResults: true, testResults: '${report}/junit.xml'
210
+ archiveArtifacts artifacts: '${report}/**', allowEmptyArchive: true
211
211
  }
212
212
  }
213
213
  }
@@ -230,7 +230,7 @@ export function addPipeline(dir, provider, log = console.log) {
230
230
  }
231
231
  const secrets = secretNames(dir);
232
232
  mkdirSync(dirname(target), { recursive: true });
233
- writeFileSync(target, template.text(secrets));
233
+ writeFileSync(target, template.text(secrets, layout(dir).report));
234
234
  log(`✔ added ${template.file}`);
235
235
  log(` Read the lines at the top of the file: the secret${secrets.length === 1 ? '' : 's'} to add (${secrets.join(', ') || 'none'}) and how to make the check required for merging.`);
236
236
  return true;
@@ -6,7 +6,7 @@ import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { chromium } from 'playwright';
8
8
  import { loadApiMap } from '../api/model.mjs';
9
- import { CONFIG_FILE, ENV_FILE, loadConfig } from '../core/config.mjs';
9
+ import { loadConfig } from '../core/config.mjs';
10
10
  import { detectProject } from '../core/detect.mjs';
11
11
  import { normaliseLogin } from '../core/login.mjs';
12
12
  import { readSession, sessionExpiry } from '../core/session.mjs';
@@ -41,11 +41,12 @@ export async function doctor(dir, options = {}, log = console.log) {
41
41
  bad(error.message);
42
42
  return problems;
43
43
  }
44
- const { config, configFile, envFile, missingSecrets, hasApi } = loaded;
45
- if (configFile) ok(`Settings: ${CONFIG_FILE}`);
46
- else warn(`No ${CONFIG_FILE}: the defaults are used (run frontend-go-live-check init to create one)`);
47
- if (envFile) ok(`Test passwords file: ${ENV_FILE}`);
48
- if (missingSecrets.length) bad(`Not set: ${missingSecrets.join(', ')} (put them in ${ENV_FILE}, or the environment)`);
44
+ const { config, configFile, envFile, missingSecrets, hasApi, paths } = loaded;
45
+ if (configFile) ok(`Settings: ${paths.config}`);
46
+ else warn(`No ${paths.config}: the defaults are used (run frontend-go-live-check init to create one)`);
47
+ if (envFile) ok(`Test passwords file: ${paths.env}`);
48
+ if (missingSecrets.length) bad(`Not set: ${missingSecrets.join(', ')} (put them in ${paths.env}, or the environment)`);
49
+ if (paths.legacy) warn('Files are at the project root (older layout). It still works; to move them into one go-live-check/ folder, see the README (Files).');
49
50
  else if (configFile) ok('Every $NAME in the settings has a value');
50
51
 
51
52
  if (config.url) ok(`Checks the running site ${config.url}`);
@@ -81,9 +82,9 @@ export async function doctor(dir, options = {}, log = console.log) {
81
82
  } catch (error) {
82
83
  bad(`API file: ${error.message}`);
83
84
  }
84
- } else warn('No API file: put your Postman collection at api/postman_collection.json (or OpenAPI at api/openapi.json) to add the API tests');
85
+ } else warn(`No API file: put your Postman collection at ${paths.api}/postman_collection.json (or OpenAPI at ${paths.api}/openapi.json) to add the API tests`);
85
86
 
86
- if (!existsSync(join(dir, '.gitignore'))) warn('No .gitignore: make sure go-live-check.env is never committed');
87
+ if (!existsSync(join(dir, paths.legacy ? '.gitignore' : 'go-live-check/.gitignore'))) warn(`No .gitignore: make sure ${paths.env} is never committed (run frontend-go-live-check init)`);
87
88
  log(problems ? `\n✖ ${problems} problem(s) to fix first.\n` : '\n✔ Ready: run npm run go-live\n');
88
89
  return problems;
89
90
  }