@testspectra/react 1.1.8-rc.2 → 1.1.8-rc.20

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/LICENSE.md ADDED
@@ -0,0 +1,48 @@
1
+ # TestSpectra Commercial Software License Agreement
2
+
3
+ Copyright (c) 2026 TestSpectra. All rights reserved.
4
+
5
+ IMPORTANT NOTICE: This software is NOT open-source. Although distribution archives,
6
+ npm packages, and editor extensions are publicly downloadable, installation,
7
+ execution, and use of the Software are strictly conditioned upon obtaining a valid
8
+ Commercial License or an authorized Evaluation/Trial License from TestSpectra.
9
+
10
+ 1. GRANT OF LICENSE
11
+ Subject to payment of applicable license fees or an active authorized evaluation
12
+ period, TestSpectra grants you a non-exclusive, non-transferable, revocable license
13
+ to install and execute the Software solely for authoring, debugging, and executing
14
+ automated software tests.
15
+
16
+ 2. LICENSE ACTIVATION & ENFORCEMENT
17
+ - Use of the Software requires activation via a valid License Key, authorized CI token,
18
+ or active TestSpectra Cloud account.
19
+ - Any attempt to circumvent, disable, modify, reverse engineer, or tamper with the
20
+ license verification mechanisms, cryptographic signatures, or binary checks is
21
+ a material breach of this Agreement and automatically terminates your license.
22
+
23
+ 3. RESTRICTIONS
24
+ You shall not, and shall not permit any third party to:
25
+ - Decompile, disassemble, reverse engineer, or derive source code from the binaries,
26
+ compiled scripts, or libraries of the Software.
27
+ - Sublicense, sell, rent, lease, distribute, or host the Software as a service for third parties.
28
+ - Re-brand, white-label, or package the Software or any of its components as part of a
29
+ competing commercial QA, testing, or developer tooling product.
30
+ - Modify, alter, or remove any proprietary notices, copyright labels, or license headers.
31
+
32
+ 4. INTELLECTUAL PROPERTY
33
+ The Software, including its source code, architecture, Rust core engine, algorithms,
34
+ matchers, extensions, interfaces, and documentation, represents valuable intellectual
35
+ property and trade secrets of TestSpectra.
36
+
37
+ 5. TERMINATION
38
+ This license is effective until terminated. Your rights under this license will terminate
39
+ automatically without notice if you fail to comply with any of its terms, or upon expiration
40
+ of your active subscription or trial term. Upon termination, you must cease all use of
41
+ the Software and delete all copies in your possession.
42
+
43
+ 6. DISCLAIMER OF WARRANTY & LIMITATION OF LIABILITY
44
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
45
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
46
+ PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
47
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
48
+ ARISING FROM THE USE OF OR INABILITY TO USE THE SOFTWARE.
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env node
2
- import { runCli } from '../dist/cli.js';
3
-
4
- runCli().catch((err) => {
5
- console.error(err);
6
- process.exit(1);
7
- });
1
+ #!/usr/bin/env node
2
+ import { runCli } from '../dist/cli.js';
3
+
4
+ runCli().catch((err) => {
5
+ console.error(err);
6
+ process.exit(1);
7
+ });
package/dist/cli.js CHANGED
@@ -1,8 +1,10 @@
1
1
  // src/cli.ts
2
- import { spawn } from "node:child_process";
2
+ import spawn from "cross-spawn";
3
+ import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
  async function runComponentTests(options = {}) {
5
- const cwd = options.cwd ?? process.cwd();
6
+ const rawCwd = options.cwd ?? process.cwd();
7
+ const cwd = path.resolve(rawCwd);
6
8
  const configPath = fileURLToPath(new URL("../vitest.config.ts", import.meta.url));
7
9
  const ext = process.platform === "win32" ? ".cmd" : "";
8
10
  const vitestBin = fileURLToPath(new URL(`../node_modules/.bin/vitest${ext}`, import.meta.url));
@@ -11,7 +13,11 @@ async function runComponentTests(options = {}) {
11
13
  args.push("--reporter=default", "--reporter=json", `--outputFile.json=${options.outputFile}`);
12
14
  }
13
15
  if (options.spec) {
14
- args.push(options.spec);
16
+ let specPattern = options.spec.replace(/\\/g, "/");
17
+ if (path.isAbsolute(options.spec)) {
18
+ specPattern = path.relative(cwd, options.spec).replace(/\\/g, "/");
19
+ }
20
+ args.push(specPattern);
15
21
  }
16
22
  const semanticReporter = Boolean(options.onLine);
17
23
  return new Promise((resolve) => {
@@ -20,14 +26,13 @@ async function runComponentTests(options = {}) {
20
26
  cwd,
21
27
  env: {
22
28
  ...process.env,
23
- TESTSPECTRA_TEST_ROOT: cwd,
29
+ TESTSPECTRA_TEST_ROOT: cwd.replace(/\\/g, "/"),
24
30
  TESTSPECTRA_HEADLESS: options.headless === false ? "false" : "true",
25
31
  TESTSPECTRA_STEP_DELAY: String(options.stepDelayMs ?? 0),
26
32
  TESTSPECTRA_FILE_PARALLELISM: options.concurrency === 1 ? "false" : "true",
27
33
  ...options.browserExecutablePath ? { TESTSPECTRA_BROWSER_EXECUTABLE: options.browserExecutablePath } : {},
28
34
  TESTSPECTRA_SEMANTIC_REPORTER: semanticReporter ? "1" : "0"
29
- },
30
- shell: process.platform === "win32"
35
+ }
31
36
  });
32
37
  if (semanticReporter) {
33
38
  const handleData = (buf) => {
@@ -64,7 +69,29 @@ async function runCli(argv = process.argv.slice(2)) {
64
69
  const parsed = parseInt(String(rawDelay), 10);
65
70
  if (!isNaN(parsed) && parsed >= 0) stepDelayMs = parsed;
66
71
  }
67
- const spec = getFlagValue(argv, "--spec", "-s");
72
+ let spec = getFlagValue(argv, "--spec", "-s");
73
+ if (!spec) {
74
+ const flagsWithValues = /* @__PURE__ */ new Set([
75
+ "--step-delay",
76
+ "--delay",
77
+ "--spec",
78
+ "-s",
79
+ "--output",
80
+ "-o",
81
+ "--concurrency",
82
+ "-c",
83
+ "--browser-executable"
84
+ ]);
85
+ for (let i = 1; i < argv.length; i++) {
86
+ const arg = argv[i];
87
+ if (arg.startsWith("-")) {
88
+ if (flagsWithValues.has(arg)) i++;
89
+ } else {
90
+ spec = arg;
91
+ break;
92
+ }
93
+ }
94
+ }
68
95
  const outputFile = getFlagValue(argv, "--output", "-o");
69
96
  const rawConcurrency = getFlagValue(argv, "--concurrency", "-c");
70
97
  let concurrency;
package/dist/index.js CHANGED
@@ -51,6 +51,14 @@ var DomDriverBridge = class {
51
51
  "Spectra.navigate() is not supported in component testing mode. Use Spectra.mount(<Component />) to render a component tree instead of navigating a URL."
52
52
  );
53
53
  }
54
+ async setCookies(setCookieHeaders, _url) {
55
+ const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
56
+ for (const header of headers) {
57
+ if (typeof document !== "undefined") {
58
+ document.cookie = header;
59
+ }
60
+ }
61
+ }
54
62
  async back() {
55
63
  throw new Error("Spectra.back() is not supported in component testing mode.");
56
64
  }
@@ -284,7 +292,9 @@ var DomDriverBridge = class {
284
292
  }
285
293
  async shouldHaveNoConsoleErrors() {
286
294
  if (this.consoleErrors.length > 0) {
287
- throw new Error(`Expected no console errors, but found ${this.consoleErrors.length}: ${this.consoleErrors.join("; ")}`);
295
+ throw new Error(
296
+ `Expected no console errors, but found ${this.consoleErrors.length}: ${this.consoleErrors.join("; ")}`
297
+ );
288
298
  }
289
299
  }
290
300
  async clearCookies() {
@@ -337,10 +347,12 @@ async function mount(element) {
337
347
  }
338
348
 
339
349
  // src/cli.ts
340
- import { spawn } from "node:child_process";
350
+ import spawn from "cross-spawn";
351
+ import path from "node:path";
341
352
  import { fileURLToPath } from "node:url";
342
353
  async function runComponentTests(options = {}) {
343
- const cwd = options.cwd ?? process.cwd();
354
+ const rawCwd = options.cwd ?? process.cwd();
355
+ const cwd = path.resolve(rawCwd);
344
356
  const configPath = fileURLToPath(new URL("../vitest.config.ts", import.meta.url));
345
357
  const ext = process.platform === "win32" ? ".cmd" : "";
346
358
  const vitestBin = fileURLToPath(new URL(`../node_modules/.bin/vitest${ext}`, import.meta.url));
@@ -349,7 +361,11 @@ async function runComponentTests(options = {}) {
349
361
  args.push("--reporter=default", "--reporter=json", `--outputFile.json=${options.outputFile}`);
350
362
  }
351
363
  if (options.spec) {
352
- args.push(options.spec);
364
+ let specPattern = options.spec.replace(/\\/g, "/");
365
+ if (path.isAbsolute(options.spec)) {
366
+ specPattern = path.relative(cwd, options.spec).replace(/\\/g, "/");
367
+ }
368
+ args.push(specPattern);
353
369
  }
354
370
  const semanticReporter = Boolean(options.onLine);
355
371
  return new Promise((resolve) => {
@@ -358,14 +374,13 @@ async function runComponentTests(options = {}) {
358
374
  cwd,
359
375
  env: {
360
376
  ...process.env,
361
- TESTSPECTRA_TEST_ROOT: cwd,
377
+ TESTSPECTRA_TEST_ROOT: cwd.replace(/\\/g, "/"),
362
378
  TESTSPECTRA_HEADLESS: options.headless === false ? "false" : "true",
363
379
  TESTSPECTRA_STEP_DELAY: String(options.stepDelayMs ?? 0),
364
380
  TESTSPECTRA_FILE_PARALLELISM: options.concurrency === 1 ? "false" : "true",
365
381
  ...options.browserExecutablePath ? { TESTSPECTRA_BROWSER_EXECUTABLE: options.browserExecutablePath } : {},
366
382
  TESTSPECTRA_SEMANTIC_REPORTER: semanticReporter ? "1" : "0"
367
- },
368
- shell: process.platform === "win32"
383
+ }
369
384
  });
370
385
  if (semanticReporter) {
371
386
  const handleData = (buf) => {
@@ -402,7 +417,29 @@ async function runCli(argv = process.argv.slice(2)) {
402
417
  const parsed = parseInt(String(rawDelay), 10);
403
418
  if (!isNaN(parsed) && parsed >= 0) stepDelayMs = parsed;
404
419
  }
405
- const spec = getFlagValue(argv, "--spec", "-s");
420
+ let spec = getFlagValue(argv, "--spec", "-s");
421
+ if (!spec) {
422
+ const flagsWithValues = /* @__PURE__ */ new Set([
423
+ "--step-delay",
424
+ "--delay",
425
+ "--spec",
426
+ "-s",
427
+ "--output",
428
+ "-o",
429
+ "--concurrency",
430
+ "-c",
431
+ "--browser-executable"
432
+ ]);
433
+ for (let i = 1; i < argv.length; i++) {
434
+ const arg = argv[i];
435
+ if (arg.startsWith("-")) {
436
+ if (flagsWithValues.has(arg)) i++;
437
+ } else {
438
+ spec = arg;
439
+ break;
440
+ }
441
+ }
442
+ }
406
443
  const outputFile = getFlagValue(argv, "--output", "-o");
407
444
  const rawConcurrency = getFlagValue(argv, "--concurrency", "-c");
408
445
  let concurrency;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/react",
3
- "version": "1.1.8-rc.2",
3
+ "version": "1.1.8-rc.20",
4
4
  "description": "Bundled Vitest browser-mode runner for React component testing with the TestSpectra Spectra.* DSL",
5
5
  "keywords": [
6
6
  "testspectra",
@@ -31,33 +31,35 @@
31
31
  },
32
32
  "files": [
33
33
  "dist",
34
- "bin"
34
+ "bin",
35
+ "vitest.config.ts"
35
36
  ],
36
- "scripts": {
37
- "build": "node scripts/build.js",
38
- "typecheck": "tsc --noEmit",
39
- "test": "vitest run --config ./vitest.config.ts",
40
- "prepublishOnly": "npm run build"
41
- },
42
37
  "dependencies": {
43
- "@testspectra/matchers": "workspace:*",
44
38
  "@testing-library/react": "^16.0.0",
45
39
  "@testing-library/user-event": "^14.5.2",
46
40
  "@vitejs/plugin-react": "^4.3.1",
47
41
  "@vitest/browser": "^2.1.8",
42
+ "cross-spawn": "^7.0.6",
48
43
  "playwright": "^1.48.0",
49
- "vitest": "catalog:"
44
+ "vitest": "^2.1.8",
45
+ "@testspectra/matchers": "1.1.8-rc.20"
50
46
  },
51
47
  "peerDependencies": {
52
48
  "react": ">=18.0.0",
53
49
  "react-dom": ">=18.0.0"
54
50
  },
55
51
  "devDependencies": {
56
- "@types/node": "catalog:",
52
+ "@types/cross-spawn": "^6.0.6",
53
+ "@types/node": "^20.14.0",
57
54
  "@types/react": "^18.3.3",
58
55
  "@types/react-dom": "^18.3.0",
59
- "esbuild": "catalog:",
60
- "typescript": "catalog:"
56
+ "esbuild": "^0.28.2",
57
+ "typescript": "^5.6.3"
61
58
  },
62
- "license": "SEE LICENSE IN LICENSE.md"
63
- }
59
+ "license": "SEE LICENSE IN LICENSE.md",
60
+ "scripts": {
61
+ "build": "node scripts/build.js",
62
+ "typecheck": "tsc --noEmit",
63
+ "test": "vitest run --config ./vitest.config.ts"
64
+ }
65
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Bundled Vitest browser-mode configuration for @testspectra/react.
3
+ *
4
+ * Consumers never author this file themselves — `spectra test` / `testspectra-react test` loads
5
+ * it programmatically (see src/cli.ts). `root` is deliberately kept fixed at THIS package's own
6
+ * directory (not the consumer's project) — `@vitest/browser`'s transitive dependencies
7
+ * (`@testing-library/dom`, `@testing-library/user-event`) live under this package's own
8
+ * `node_modules` under pnpm's strict per-package isolation, and Vite's dependency optimizer
9
+ * fails to resolve them if `root` points elsewhere. Test discovery and auto-import instead use
10
+ * absolute paths built from `TESTSPECTRA_TEST_ROOT` (see auto_import.ts and cli.ts).
11
+ */
12
+
13
+ import { readdirSync, existsSync } from 'node:fs';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ import react from '@vitejs/plugin-react';
18
+ import { defineConfig } from 'vitest/config';
19
+
20
+ const packageDir = fileURLToPath(new URL('.', import.meta.url));
21
+ const setupFile = path.join(packageDir, 'src/setup.ts');
22
+ const testRoot = (process.env.TESTSPECTRA_TEST_ROOT || process.cwd()).replace(/\\/g, '/');
23
+
24
+ /** Scans <testRoot>/page-objects, /support/steps, /fixtures — mirrors the Rust orchestrator's
25
+ * AutoImportManifest scan for E2E (docs/v2/cli/ambient-types-generator.md), reimplemented here
26
+ * in Node (this config file), since there is no Rust orchestrator in this mode. */
27
+ function buildAutoImportManifest(root: string) {
28
+ function scanEntityDir(subdir: string, filePattern: (platform: string) => string) {
29
+ const dir = path.join(root, subdir);
30
+ if (!existsSync(dir)) return [] as Array<{ name: string; path: string }>;
31
+ const entries: Array<{ name: string; path: string }> = [];
32
+ for (const name of readdirSync(dir, { withFileTypes: true })) {
33
+ if (!name.isDirectory()) continue;
34
+ for (const platform of ['web', 'common']) {
35
+ for (const ext of ['tsx', 'ts']) {
36
+ const candidate = path.join(dir, name.name, filePattern(platform).replace(/\.tsx?$/, `.${ext}`));
37
+ if (existsSync(candidate)) {
38
+ entries.push({ name: name.name, path: candidate.replace(/\\/g, '/') });
39
+ break;
40
+ }
41
+ }
42
+ if (entries.at(-1)?.name === name.name) break;
43
+ }
44
+ }
45
+ return entries;
46
+ }
47
+
48
+ function scanFixtures(): Array<{ name: string; path: string }> {
49
+ const dir = path.join(root, 'fixtures');
50
+ if (!existsSync(dir)) return [];
51
+ return readdirSync(dir)
52
+ .filter((f) => f.endsWith('.json'))
53
+ .map((f) => ({ name: f.replace(/\.json$/, ''), path: path.join(dir, f).replace(/\\/g, '/') }));
54
+ }
55
+
56
+ return {
57
+ pageObjects: scanEntityDir('page-objects', (p) => `${p}.tsx`),
58
+ steps: scanEntityDir('support/steps', (p) => `${p}.step.tsx`),
59
+ fixtures: scanFixtures(),
60
+ };
61
+ }
62
+
63
+ export default defineConfig({
64
+ root: packageDir,
65
+ plugins: [react()],
66
+ define: {
67
+ __TESTSPECTRA_AUTO_IMPORT_MANIFEST__: JSON.stringify(buildAutoImportManifest(testRoot)),
68
+ __TESTSPECTRA_STEP_DELAY_MS__: JSON.stringify(Number(process.env.TESTSPECTRA_STEP_DELAY) || 0),
69
+ __TESTSPECTRA_SEMANTIC_REPORTER__: JSON.stringify(process.env.TESTSPECTRA_SEMANTIC_REPORTER === '1'),
70
+ },
71
+ server: {
72
+ fs: { allow: [packageDir, testRoot] },
73
+ },
74
+ test: {
75
+ globals: true,
76
+ setupFiles: [setupFile],
77
+ // When spectra test drives this run, @testspectra/cli's own Reporter (the same semantic,
78
+ // natural-English reporter E2E uses) owns 100% of the terminal output, fed by the
79
+ // [TESTSPECTRA_*] lines emitEvent() now prints (see prelude.ts) — Vitest's own console
80
+ // reporter would just double/clutter that output, so it's silenced here. Standalone
81
+ // `pnpm test` / `testspectra-react test` (this env var unset) keeps Vitest's normal reporter.
82
+ reporters: process.env.TESTSPECTRA_SEMANTIC_REPORTER === '1' ? [] : 'default',
83
+ // When TESTSPECTRA_TEST_ROOT is unset, this is the package's own `pnpm test` run (no consumer
84
+ // spawning it) — discover its own smoke test instead of a nonexistent specs/ under packageDir.
85
+ // When set (spawned by cli.ts for a real consumer), only that consumer's specs/ are included —
86
+ // deliberately NOT this package's own src/__tests__/**, so a consumer's `spectra test` output
87
+ // never shows this package's internal smoke test mixed in.
88
+ dir: process.env.TESTSPECTRA_TEST_ROOT ? testRoot : undefined,
89
+ include: process.env.TESTSPECTRA_TEST_ROOT
90
+ ? ['specs/**/web.test.tsx', 'specs/**/*.test.tsx']
91
+ : ['src/**/*.test.tsx'],
92
+ browser: {
93
+ enabled: true,
94
+ provider: 'playwright',
95
+ name: 'chromium',
96
+ // Vitest's CLI does NOT reliably override a `browser.headless` value already set here by
97
+ // `--browser.headless=false` (verified empirically: the spawned Chromium process still
98
+ // carried `--headless` even with the CLI flag passed) — so headed/headless is threaded
99
+ // through as an env var (set by cli.ts) that this config file, which we fully control,
100
+ // reads directly, instead of depending on Vitest's CLI/config-file merge semantics.
101
+ headless: process.env.TESTSPECTRA_HEADLESS !== 'false',
102
+ // Vitest 2.x browser mode has no numeric worker-count knob (unlike the E2E Rust orchestrator's
103
+ // WorkerSizingEngine) — only this parallel/sequential switch. `spectra test --concurrency 1`
104
+ // maps to sequential; any other value (or omitted) keeps Vitest's own parallel default.
105
+ fileParallelism: process.env.TESTSPECTRA_FILE_PARALLELISM !== 'false',
106
+ // Zero browser-discovery logic lives in this package (see cli.ts's RunComponentTestsOptions
107
+ // comment) — `spectra test` locates the Chrome-for-Testing binary `spectra doctor` already
108
+ // manages for the E2E CDP driver and passes it in via this env var, so component testing
109
+ // reuses that install instead of requiring a separate `playwright install` download. Omitted
110
+ // when run standalone (e.g. this package's own `pnpm test`), where Playwright falls back to
111
+ // its own bundled/downloaded browser.
112
+ providerOptions: process.env.TESTSPECTRA_BROWSER_EXECUTABLE
113
+ ? { launch: { executablePath: process.env.TESTSPECTRA_BROWSER_EXECUTABLE } }
114
+ : undefined,
115
+ },
116
+ },
117
+ });