@cevek/screentest 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -7,33 +7,45 @@ screenshots. Everything ships in one npm package — no copy-paste.
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- pnpm add -D @cevek/screentest playwright vitest
11
- pnpm exec playwright install firefox
10
+ # pick your package manager — all three are supported:
11
+ npm i -D @cevek/screentest playwright vitest && npx playwright install firefox
12
+ # pnpm add -D @cevek/screentest playwright vitest && pnpm exec playwright install firefox
13
+ # yarn add -D @cevek/screentest playwright vitest && yarn playwright install firefox
12
14
  ```
13
15
 
14
16
  `playwright` and `vitest` are peer-deps — keep them in your devDependencies.
15
17
 
18
+ `screentest init` detects your package manager (from your lockfile or
19
+ `packageManager` field) and writes a matching `Dockerfile.tests`.
20
+
16
21
  ## Quick start
17
22
 
18
23
  ```bash
19
24
  # 1. Scaffold the test harness into your project.
20
- pnpm exec screentest init
25
+ npx screentest init
21
26
 
22
27
  # 2. Build the Docker image once.
23
28
  docker build -f Dockerfile.tests -t screentest-tests .
24
29
 
25
30
  # 3. Run.
26
- APP_URL=http://localhost:5050 pnpm exec screentest test
31
+ APP_URL=http://localhost:5050 npx screentest test
27
32
  ```
28
33
 
29
34
  `screentest init` creates:
30
35
 
31
36
  - `Dockerfile.tests` — playwright-noble + your project deps + vitest CMD
32
- - `vitest.config.ts` pre-wired with the package's globalSetup
37
+ (auto-picked for pnpm / npm / yarn based on your lockfile)
38
+ - `vitest.screentest.config.ts` — separate vitest config wired with the
39
+ package's globalSetup. Kept under its own name so your normal
40
+ `vitest.config.ts` (unit tests) is untouched.
33
41
  - `tests/example.test.ts` — minimal Playwright test using the runner helpers
34
42
 
35
43
  Existing files are not overwritten — re-run `init` safely.
36
44
 
45
+ `screentest test` always runs vitest with
46
+ `--config vitest.screentest.config.ts`, so unit tests via plain `vitest run`
47
+ and screenshot tests via `screentest test` stay completely separate.
48
+
37
49
  ## Commands
38
50
 
39
51
  ```bash
@@ -125,7 +137,7 @@ Or pass `--worker-url` / `--token` to `screentest` directly.
125
137
 
126
138
  ```yaml
127
139
  - run: docker build -f Dockerfile.tests -t screentest-tests .
128
- - run: CI=1 pnpm exec screentest test
140
+ - run: CI=1 npx screentest test
129
141
  ```
130
142
 
131
143
  In `CI=1` the orchestrator exits with vitest's code and never tries to open
package/dist/index.js CHANGED
@@ -15286,7 +15286,7 @@ async function runOrchestrator(opts = {}) {
15286
15286
  await fs4.rm(paths.actualDir, { recursive: true, force: true });
15287
15287
  let testCode;
15288
15288
  if (opts.hostMode) {
15289
- testCode = await run("pnpm", ["exec", "vitest", "run"]);
15289
+ testCode = await run("npx", ["vitest", "--config", "vitest.screentest.config.ts", "run"]);
15290
15290
  } else {
15291
15291
  if (opts.requireDockerImage !== false && !ensureDockerImage()) {
15292
15292
  return 1;
@@ -15320,17 +15320,36 @@ async function runOrchestrator(opts = {}) {
15320
15320
  }
15321
15321
 
15322
15322
  // src/init.ts
15323
- import { copyFile, mkdir } from "fs/promises";
15323
+ import { copyFile, mkdir, readFile } from "fs/promises";
15324
15324
  import { existsSync as existsSync2 } from "fs";
15325
15325
  import { dirname as dirname4, join as join4, resolve as resolve6 } from "path";
15326
15326
  import { fileURLToPath as fileURLToPath2 } from "url";
15327
+ async function detectPackageManager(cwd) {
15328
+ if (existsSync2(join4(cwd, "pnpm-lock.yaml"))) return "pnpm";
15329
+ if (existsSync2(join4(cwd, "yarn.lock"))) return "yarn";
15330
+ if (existsSync2(join4(cwd, "package-lock.json"))) return "npm";
15331
+ try {
15332
+ const pkg = JSON.parse(
15333
+ await readFile(join4(cwd, "package.json"), "utf8")
15334
+ );
15335
+ const pm = pkg.packageManager ?? "";
15336
+ if (pm.startsWith("pnpm")) return "pnpm";
15337
+ if (pm.startsWith("yarn")) return "yarn";
15338
+ } catch {
15339
+ }
15340
+ return "npm";
15341
+ }
15327
15342
  async function runInit() {
15328
15343
  const here = dirname4(fileURLToPath2(import.meta.url));
15329
15344
  const templatesDir = [resolve6(here, "./templates"), resolve6(here, "../templates")].find((p) => existsSync2(p)) ?? resolve6(here, "./templates");
15330
15345
  const cwd = process.cwd();
15346
+ const pm = await detectPackageManager(cwd);
15347
+ process.stdout.write(`Detected package manager: ${pm}
15348
+
15349
+ `);
15331
15350
  const targets = [
15332
- { from: "Dockerfile.tests", to: "Dockerfile.tests" },
15333
- { from: "vitest.config.ts", to: "vitest.config.ts" },
15351
+ { from: `Dockerfile.${pm}.tests`, to: "Dockerfile.tests" },
15352
+ { from: "vitest.screentest.config.ts", to: "vitest.screentest.config.ts" },
15334
15353
  { from: "example.test.ts", to: "tests/example.test.ts" }
15335
15354
  ];
15336
15355
  for (const { from, to } of targets) {
@@ -15355,7 +15374,7 @@ async function runInit() {
15355
15374
  `
15356
15375
  Next steps:
15357
15376
  1. docker build -f Dockerfile.tests -t screentest-tests .
15358
- 2. APP_URL=http://localhost:<your-app-port> pnpm exec screentest test
15377
+ 2. APP_URL=http://localhost:<your-app-port> npx screentest test
15359
15378
  `
15360
15379
  );
15361
15380
  return 0;
package/dist/runner.d.ts CHANGED
@@ -1,21 +1,40 @@
1
- import type { BrowserContext, BrowserContextOptions, Page } from 'playwright';
1
+ import type { BrowserContext, BrowserContextOptions, Page, PageScreenshotOptions } from 'playwright';
2
2
 
3
3
  /**
4
- * Capture a full-page PNG, save under
4
+ * Extra Playwright screenshot options the caller can pass through to
5
+ * `compareSnapshot` (e.g. `clip`, `mask`, `omitBackground`, `fullPage: false`).
6
+ * `type` and `path` are reserved — the runner always shoots PNG and writes
7
+ * into `node_modules/.cache/screentest/actual/...`.
8
+ */
9
+ export type CompareSnapshotOptions = Omit<PageScreenshotOptions, 'type' | 'path'>;
10
+
11
+ /**
12
+ * Capture a PNG, save under
5
13
  * `<project>/node_modules/.cache/screentest/actual/<...path>.png`, and
6
14
  * compare its SHA256 against the hash in `<project>/snapshot.json`.
7
15
  *
8
16
  * The snapshot path is built automatically from the surrounding
9
17
  * `describe(...)` + `it(...)` chain, plus `name` as the leaf. Prefix `name`
10
18
  * with `/` to opt out of auto-grouping and use an absolute path.
19
+ *
20
+ * Pass `opts` to forward extra Playwright screenshot options (clip, mask,
21
+ * etc.). Defaults: `fullPage: true`, `animations: 'disabled'`,
22
+ * `caret: 'hide'`.
11
23
  */
12
- export function compareSnapshot(page: Page, name: string): Promise<void>;
24
+ export function compareSnapshot(
25
+ page: Page,
26
+ name: string,
27
+ opts?: CompareSnapshotOptions,
28
+ ): Promise<void>;
13
29
 
14
30
  /**
15
31
  * Freeze `Date` inside `ctx`. `when` may be an ISO string, unix-ms number,
16
32
  * or `Date`. Must be called BEFORE the first navigation in the context.
17
33
  */
18
- export function freezeDate(ctx: BrowserContext, when: string | number | Date): Promise<void>;
34
+ export function freezeDate(
35
+ ctx: BrowserContext,
36
+ when: string | number | Date,
37
+ ): Promise<void>;
19
38
 
20
39
  /**
21
40
  * Wait for `document.fonts.ready` and every `<img>` to reach `complete`.
package/dist/runner.js CHANGED
@@ -96,7 +96,7 @@ function currentTestChain() {
96
96
  if (!full) return [];
97
97
  return full.split(" > ");
98
98
  }
99
- async function compareSnapshot(page, name) {
99
+ async function compareSnapshot(page, name, opts = {}) {
100
100
  const explicit = name.split("/").filter(Boolean);
101
101
  const path = name.startsWith("/") ? explicit : [...currentTestChain(), ...explicit];
102
102
  if (path.length === 0) throw new Error("compareSnapshot: empty name");
@@ -107,9 +107,11 @@ async function compareSnapshot(page, name) {
107
107
  await waitForVisualStable(page);
108
108
  const actualBuf = await page.screenshot({
109
109
  fullPage: true,
110
- type: "png",
111
110
  animations: "disabled",
112
- caret: "hide"
111
+ caret: "hide",
112
+ ...opts,
113
+ type: "png"
114
+ // always — we hash PNG bytes
113
115
  });
114
116
  await writeFile(absFile, actualBuf);
115
117
  const actualHash = createHash("sha256").update(actualBuf).digest("hex");
@@ -0,0 +1,17 @@
1
+ FROM mcr.microsoft.com/playwright:v1.60.0-noble
2
+
3
+ WORKDIR /work
4
+
5
+ # Install dependencies. --ignore-scripts skips your project's postinstall
6
+ # hooks (which usually scan src/ — not needed for the test image).
7
+ COPY package.json package-lock.json ./
8
+ RUN npm ci --ignore-scripts
9
+
10
+ # Only the bits needed to run vitest.
11
+ COPY vitest.screentest.config.ts ./
12
+ COPY tsconfig*.json ./
13
+ COPY tests ./tests
14
+
15
+ # Run vitest directly (skipping `npm exec`) — keeps the test image simple
16
+ # and matches how the host orchestrator invokes it.
17
+ CMD ["./node_modules/.bin/vitest", "--config", "vitest.screentest.config.ts", "run"]
@@ -0,0 +1,28 @@
1
+ FROM mcr.microsoft.com/playwright:v1.60.0-noble
2
+
3
+ WORKDIR /work
4
+
5
+ # corepack lets us use whatever pnpm version your project's `packageManager`
6
+ # field pins (pnpm 11 by default in modern projects). If your project uses
7
+ # npm or yarn instead, swap the install commands accordingly.
8
+ RUN corepack enable
9
+
10
+ # Install dependencies. Flags:
11
+ # --ignore-scripts skips your project's postinstall hooks (which
12
+ # usually scan src/ — not needed for the test image).
13
+ # --config.minimumReleaseAge=0 bypass pnpm 11's supply-chain policy for
14
+ # freshly-published packages (the test image is
15
+ # ephemeral, runs only what's already in lockfile).
16
+ COPY package.json pnpm-lock.yaml ./
17
+ COPY pnpm-workspace.yaml* ./
18
+ RUN pnpm install --frozen-lockfile --ignore-scripts --config.minimumReleaseAge=0
19
+
20
+ # Only the bits needed to run vitest.
21
+ COPY vitest.screentest.config.ts ./
22
+ COPY tsconfig*.json ./
23
+ COPY tests ./tests
24
+
25
+ # Run vitest directly (not via pnpm exec) — pnpm 11 re-runs its supply-chain
26
+ # policy on every exec, which would re-fail freshly-published packages
27
+ # even after install succeeded.
28
+ CMD ["./node_modules/.bin/vitest", "--config", "vitest.screentest.config.ts", "run"]
@@ -0,0 +1,21 @@
1
+ FROM mcr.microsoft.com/playwright:v1.60.0-noble
2
+
3
+ WORKDIR /work
4
+
5
+ # corepack lets yarn 2+/berry self-install from your project's
6
+ # `packageManager` field. For yarn 1 it's a no-op (yarn is bundled in newer
7
+ # Node images anyway).
8
+ RUN corepack enable
9
+
10
+ # Install dependencies. --ignore-scripts skips your project's postinstall
11
+ # hooks (which usually scan src/ — not needed for the test image).
12
+ COPY package.json yarn.lock ./
13
+ COPY .yarnrc.yml* ./
14
+ RUN yarn install --frozen-lockfile --ignore-scripts
15
+
16
+ # Only the bits needed to run vitest.
17
+ COPY vitest.screentest.config.ts ./
18
+ COPY tsconfig*.json ./
19
+ COPY tests ./tests
20
+
21
+ CMD ["./node_modules/.bin/vitest", "--config", "vitest.screentest.config.ts", "run"]
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.1",
6
+ "version": "0.2.3",
7
7
  "description": "Local desktop tool for visual screenshot-test review — CLI, runner helpers, and review UI shipped as a single package.",
8
8
  "license": "MIT",
9
9
  "type": "module",
@@ -1,19 +0,0 @@
1
- FROM mcr.microsoft.com/playwright:v1.60.0-noble
2
-
3
- WORKDIR /work
4
-
5
- # Install pnpm — if your project uses npm/yarn, swap the next two lines.
6
- RUN npm install -g pnpm@9
7
-
8
- # Install dependencies. --ignore-scripts skips your project's postinstall
9
- # hooks (which usually scan src/ — not needed for the test image).
10
- COPY package.json pnpm-lock.yaml ./
11
- COPY pnpm-workspace.yaml* ./
12
- RUN pnpm install --frozen-lockfile --ignore-scripts
13
-
14
- # Only the bits needed to run vitest.
15
- COPY vitest.config.ts ./
16
- COPY tsconfig*.json ./
17
- COPY tests ./tests
18
-
19
- CMD ["pnpm", "exec", "vitest", "run"]