@testspectra/react 1.1.8-rc.9 → 1.1.11-rc.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/dist/cli.js +220 -24
- package/dist/index.d.ts +19 -1
- package/dist/index.js +766 -43
- package/dist/runner.js +1 -0
- package/dist/setup.js +75 -0
- package/dist/vitest_reporter.js +227 -0
- package/package.json +7 -5
package/dist/cli.js
CHANGED
|
@@ -1,33 +1,180 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
3
|
+
import path2 from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import spawn from "cross-spawn";
|
|
6
|
+
|
|
7
|
+
// src/config_generator.ts
|
|
8
|
+
import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { pathToFileURL } from "node:url";
|
|
13
|
+
function resolveEsmUrl(specifier, packageDir) {
|
|
14
|
+
const cjsPath = createRequire(path.join(packageDir, "package.json")).resolve(specifier);
|
|
15
|
+
const esmPath = cjsPath.replace(/\.cjs$/, ".js");
|
|
16
|
+
return pathToFileURL(existsSync(esmPath) ? esmPath : cjsPath).href;
|
|
17
|
+
}
|
|
18
|
+
function buildAutoImportManifest(root) {
|
|
19
|
+
function scanEntityDir(subdir, filePattern) {
|
|
20
|
+
const dir = path.join(root, subdir);
|
|
21
|
+
if (!existsSync(dir)) return [];
|
|
22
|
+
const entries = [];
|
|
23
|
+
for (const name of readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
if (!name.isDirectory()) continue;
|
|
25
|
+
if (name.name.startsWith(".") || name.name === "node_modules" || name.name === "dist" || name.name === "__screenshots__" || name.name === "coverage") {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const platform of ["web", "common"]) {
|
|
29
|
+
for (const ext of ["tsx", "ts"]) {
|
|
30
|
+
const candidate = path.join(dir, name.name, filePattern(platform).replace(/\.tsx?$/, `.${ext}`));
|
|
31
|
+
if (existsSync(candidate)) {
|
|
32
|
+
entries.push({ name: name.name, path: candidate.replace(/\\/g, "/") });
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (entries.at(-1)?.name === name.name) break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return entries;
|
|
40
|
+
}
|
|
41
|
+
function scanFixtures() {
|
|
42
|
+
const dir = path.join(root, "fixtures");
|
|
43
|
+
if (!existsSync(dir)) return [];
|
|
44
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => ({ name: f.replace(/\.json$/, ""), path: path.join(dir, f).replace(/\\/g, "/") }));
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
pageObjects: scanEntityDir("page-objects", (p) => `${p}.tsx`),
|
|
48
|
+
steps: scanEntityDir("support/steps", (p) => `${p}.step.tsx`),
|
|
49
|
+
fixtures: scanFixtures()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function generateVitestConfig(opts) {
|
|
53
|
+
const reactPluginUrl = resolveEsmUrl("@vitejs/plugin-react", opts.packageDir);
|
|
54
|
+
const vitestConfigUrl = resolveEsmUrl("vitest/config", opts.packageDir);
|
|
55
|
+
const manifest = buildAutoImportManifest(opts.testRoot);
|
|
56
|
+
const setupFile = path.join(opts.packageDir, "dist/setup.js").replace(/\\/g, "/");
|
|
57
|
+
const reporterFile = path.join(opts.packageDir, "dist/vitest_reporter.js").replace(/\\/g, "/");
|
|
58
|
+
const reporterUrl = pathToFileURL(reporterFile).href;
|
|
59
|
+
const normalizedPackageDir = opts.packageDir.replace(/\\/g, "/");
|
|
60
|
+
const normalizedTestRoot = opts.testRoot.replace(/\\/g, "/");
|
|
61
|
+
const configContent = `import react from '${reactPluginUrl}';
|
|
62
|
+
import { defineConfig } from '${vitestConfigUrl}';
|
|
63
|
+
${opts.semanticReporter ? `import { SpectraVitestReporter } from '${reporterUrl}';` : ""}
|
|
64
|
+
|
|
65
|
+
export default defineConfig({
|
|
66
|
+
root: ${JSON.stringify(normalizedTestRoot)},
|
|
67
|
+
plugins: [react()],
|
|
68
|
+
define: {
|
|
69
|
+
__TESTSPECTRA_AUTO_IMPORT_MANIFEST__: ${JSON.stringify(JSON.stringify(manifest))},
|
|
70
|
+
__TESTSPECTRA_STEP_DELAY_MS__: ${JSON.stringify(opts.stepDelayMs)},
|
|
71
|
+
__TESTSPECTRA_SEMANTIC_REPORTER__: ${JSON.stringify(opts.semanticReporter)},
|
|
72
|
+
},
|
|
73
|
+
optimizeDeps: {
|
|
74
|
+
include: [
|
|
75
|
+
'react',
|
|
76
|
+
'react-dom',
|
|
77
|
+
'react-dom/client',
|
|
78
|
+
'@testing-library/react',
|
|
79
|
+
'@testing-library/user-event',
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
server: {
|
|
83
|
+
fs: {
|
|
84
|
+
allow: [
|
|
85
|
+
${JSON.stringify(normalizedPackageDir)},
|
|
86
|
+
${JSON.stringify(normalizedTestRoot)},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
test: {
|
|
91
|
+
globals: true,
|
|
92
|
+
setupFiles: [${JSON.stringify(setupFile)}],
|
|
93
|
+
reporters: ${opts.semanticReporter ? `[
|
|
94
|
+
new SpectraVitestReporter({ testRoot: ${JSON.stringify(normalizedTestRoot)} }),
|
|
95
|
+
${opts.outputFile ? `['json', { outputFile: ${JSON.stringify(opts.outputFile)} }],` : ""}
|
|
96
|
+
]` : opts.outputFile ? `['default', ['json', { outputFile: ${JSON.stringify(opts.outputFile)} }]]` : "'default'"},
|
|
97
|
+
dir: ${JSON.stringify(normalizedTestRoot)},
|
|
98
|
+
include: [
|
|
99
|
+
'specs/**/web.test.tsx',
|
|
100
|
+
'specs/**/*.test.tsx',
|
|
101
|
+
'specs/**/web.test.ts',
|
|
102
|
+
'specs/**/*.test.ts',
|
|
103
|
+
],
|
|
104
|
+
exclude: [
|
|
105
|
+
'**/node_modules/**',
|
|
106
|
+
'**/dist/**',
|
|
107
|
+
'**/__screenshots__/**',
|
|
108
|
+
'**/.testspectra/**',
|
|
109
|
+
'**/.git/**',
|
|
110
|
+
'**/coverage/**',
|
|
111
|
+
],
|
|
112
|
+
browser: {
|
|
113
|
+
enabled: true,
|
|
114
|
+
provider: 'playwright',
|
|
115
|
+
name: 'chromium',
|
|
116
|
+
headless: ${opts.headless},
|
|
117
|
+
fileParallelism: ${opts.fileParallelism},
|
|
118
|
+
${opts.browserExecutable ? `providerOptions: { launch: { executablePath: ${JSON.stringify(opts.browserExecutable)} } },` : ""}
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
`;
|
|
123
|
+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "testspectra-react-"));
|
|
124
|
+
const configPath = path.join(tmpDir, "vitest.config.mjs");
|
|
125
|
+
writeFileSync(configPath, configContent, "utf-8");
|
|
126
|
+
return {
|
|
127
|
+
configPath,
|
|
128
|
+
cleanup: () => {
|
|
129
|
+
try {
|
|
130
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/cli.ts
|
|
4
138
|
async function runComponentTests(options = {}) {
|
|
5
|
-
const
|
|
6
|
-
|
|
139
|
+
const runner = options.runner ?? "vitest";
|
|
140
|
+
if (runner === "spectra") {
|
|
141
|
+
console.log("TestSpectra native component testing is orchestrated by the Rust Core Engine.");
|
|
142
|
+
console.log("Use `spectra test` to run tests with full parallel CDP orchestration.");
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
const rawCwd = options.cwd ?? process.cwd();
|
|
146
|
+
const cwd = path2.resolve(rawCwd);
|
|
147
|
+
const packageDir = fileURLToPath(new URL("..", import.meta.url));
|
|
7
148
|
const ext = process.platform === "win32" ? ".cmd" : "";
|
|
8
|
-
const
|
|
149
|
+
const localVitestBin = fileURLToPath(new URL(`../node_modules/.bin/vitest${ext}`, import.meta.url));
|
|
150
|
+
const consumerVitestBin = path2.join(cwd, `node_modules/.bin/vitest${ext}`);
|
|
151
|
+
const vitestBin = existsSync2(localVitestBin) ? localVitestBin : consumerVitestBin;
|
|
152
|
+
const semanticReporter = Boolean(options.onLine);
|
|
153
|
+
const { configPath, cleanup } = generateVitestConfig({
|
|
154
|
+
packageDir,
|
|
155
|
+
testRoot: cwd,
|
|
156
|
+
headless: options.headless !== false,
|
|
157
|
+
stepDelayMs: options.stepDelayMs ?? 0,
|
|
158
|
+
semanticReporter,
|
|
159
|
+
fileParallelism: options.concurrency !== 1,
|
|
160
|
+
browserExecutable: options.browserExecutablePath,
|
|
161
|
+
outputFile: options.outputFile
|
|
162
|
+
});
|
|
9
163
|
const args = ["run", "--config", configPath];
|
|
10
|
-
if (options.outputFile) {
|
|
11
|
-
args.push("--reporter=default", "--reporter=json", `--outputFile.json=${options.outputFile}`);
|
|
12
|
-
}
|
|
13
164
|
if (options.spec) {
|
|
14
|
-
|
|
165
|
+
let specPattern = options.spec.replace(/\\/g, "/");
|
|
166
|
+
if (path2.isAbsolute(options.spec)) {
|
|
167
|
+
specPattern = path2.relative(cwd, options.spec).replace(/\\/g, "/");
|
|
168
|
+
}
|
|
169
|
+
args.push(specPattern);
|
|
15
170
|
}
|
|
16
|
-
const semanticReporter = Boolean(options.onLine);
|
|
17
171
|
return new Promise((resolve) => {
|
|
18
172
|
const child = spawn(vitestBin, args, {
|
|
19
173
|
stdio: semanticReporter ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
20
174
|
cwd,
|
|
21
175
|
env: {
|
|
22
|
-
...process.env
|
|
23
|
-
|
|
24
|
-
TESTSPECTRA_HEADLESS: options.headless === false ? "false" : "true",
|
|
25
|
-
TESTSPECTRA_STEP_DELAY: String(options.stepDelayMs ?? 0),
|
|
26
|
-
TESTSPECTRA_FILE_PARALLELISM: options.concurrency === 1 ? "false" : "true",
|
|
27
|
-
...options.browserExecutablePath ? { TESTSPECTRA_BROWSER_EXECUTABLE: options.browserExecutablePath } : {},
|
|
28
|
-
TESTSPECTRA_SEMANTIC_REPORTER: semanticReporter ? "1" : "0"
|
|
29
|
-
},
|
|
30
|
-
shell: process.platform === "win32"
|
|
176
|
+
...process.env
|
|
177
|
+
}
|
|
31
178
|
});
|
|
32
179
|
if (semanticReporter) {
|
|
33
180
|
const handleData = (buf) => {
|
|
@@ -38,16 +185,30 @@ async function runComponentTests(options = {}) {
|
|
|
38
185
|
child.stdout?.on("data", handleData);
|
|
39
186
|
child.stderr?.on("data", handleData);
|
|
40
187
|
}
|
|
41
|
-
child.on("exit", (code) =>
|
|
188
|
+
child.on("exit", (code) => {
|
|
189
|
+
cleanup();
|
|
190
|
+
resolve(code === 0);
|
|
191
|
+
});
|
|
42
192
|
child.on("error", (err) => {
|
|
193
|
+
cleanup();
|
|
43
194
|
console.error(err);
|
|
44
195
|
resolve(false);
|
|
45
196
|
});
|
|
46
197
|
});
|
|
47
198
|
}
|
|
48
199
|
function getFlagValue(argv, ...names) {
|
|
49
|
-
|
|
50
|
-
|
|
200
|
+
for (let i = 0; i < argv.length; i++) {
|
|
201
|
+
const arg = argv[i];
|
|
202
|
+
for (const name of names) {
|
|
203
|
+
if (arg === name) {
|
|
204
|
+
return argv[i + 1];
|
|
205
|
+
}
|
|
206
|
+
if (arg.startsWith(`${name}=`)) {
|
|
207
|
+
return arg.slice(name.length + 1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return void 0;
|
|
51
212
|
}
|
|
52
213
|
async function runCli(argv = process.argv.slice(2)) {
|
|
53
214
|
const [command] = argv;
|
|
@@ -58,13 +219,40 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
58
219
|
let headless = true;
|
|
59
220
|
if (argv.includes("--headed") || argv.includes("--no-headless")) headless = false;
|
|
60
221
|
else if (argv.includes("--headless")) headless = true;
|
|
61
|
-
const
|
|
222
|
+
const rawRunner = getFlagValue(argv, "--runner", "-r");
|
|
223
|
+
const runner = rawRunner === "vitest" ? "vitest" : "spectra";
|
|
224
|
+
const rawDelay = getFlagValue(argv, "--step-delay", "--delay", "-d");
|
|
62
225
|
let stepDelayMs;
|
|
63
226
|
if (rawDelay !== void 0) {
|
|
64
227
|
const parsed = parseInt(String(rawDelay), 10);
|
|
65
228
|
if (!isNaN(parsed) && parsed >= 0) stepDelayMs = parsed;
|
|
66
229
|
}
|
|
67
|
-
|
|
230
|
+
let spec = getFlagValue(argv, "--spec", "-s");
|
|
231
|
+
if (!spec) {
|
|
232
|
+
const flagsWithValues = /* @__PURE__ */ new Set([
|
|
233
|
+
"--runner",
|
|
234
|
+
"-r",
|
|
235
|
+
"--step-delay",
|
|
236
|
+
"--delay",
|
|
237
|
+
"-d",
|
|
238
|
+
"--spec",
|
|
239
|
+
"-s",
|
|
240
|
+
"--output",
|
|
241
|
+
"-o",
|
|
242
|
+
"--concurrency",
|
|
243
|
+
"-c",
|
|
244
|
+
"--browser-executable"
|
|
245
|
+
]);
|
|
246
|
+
for (let i = 1; i < argv.length; i++) {
|
|
247
|
+
const arg = argv[i];
|
|
248
|
+
if (arg.startsWith("-")) {
|
|
249
|
+
if (flagsWithValues.has(arg)) i++;
|
|
250
|
+
} else {
|
|
251
|
+
spec = arg;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
68
256
|
const outputFile = getFlagValue(argv, "--output", "-o");
|
|
69
257
|
const rawConcurrency = getFlagValue(argv, "--concurrency", "-c");
|
|
70
258
|
let concurrency;
|
|
@@ -73,7 +261,15 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
73
261
|
if (!isNaN(parsed) && parsed >= 1) concurrency = parsed;
|
|
74
262
|
}
|
|
75
263
|
const browserExecutablePath = getFlagValue(argv, "--browser-executable");
|
|
76
|
-
const ok = await runComponentTests({
|
|
264
|
+
const ok = await runComponentTests({
|
|
265
|
+
runner,
|
|
266
|
+
headless,
|
|
267
|
+
stepDelayMs,
|
|
268
|
+
spec,
|
|
269
|
+
outputFile,
|
|
270
|
+
concurrency,
|
|
271
|
+
browserExecutablePath
|
|
272
|
+
});
|
|
77
273
|
process.exit(ok ? 0 : 1);
|
|
78
274
|
}
|
|
79
275
|
export {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare class DomDriverBridge implements PlatformDriverBridge {
|
|
|
7
7
|
export declare function mount<P = any>(element: any): Promise<any>;
|
|
8
8
|
|
|
9
9
|
export interface RunComponentTestsOptions {
|
|
10
|
+
runner?: 'spectra' | 'vitest';
|
|
10
11
|
cwd?: string;
|
|
11
12
|
headless?: boolean;
|
|
12
13
|
stepDelayMs?: number;
|
|
@@ -17,6 +18,23 @@ export interface RunComponentTestsOptions {
|
|
|
17
18
|
onLine?: (line: string) => void;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
export interface BundleServer {
|
|
22
|
+
url: string;
|
|
23
|
+
stop(): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface BundleServerOptions {
|
|
27
|
+
testRoot?: string;
|
|
28
|
+
packageDir?: string;
|
|
29
|
+
manifest?: any;
|
|
30
|
+
stepDelayMs?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare function startBundleServer(opts?: BundleServerOptions): Promise<BundleServer>;
|
|
20
34
|
export declare function runComponentTests(options?: RunComponentTestsOptions): Promise<boolean>;
|
|
21
35
|
export declare function runCli(argv?: string[]): Promise<void>;
|
|
22
|
-
export
|
|
36
|
+
export declare function sanitizeError(err: unknown): string;
|
|
37
|
+
export declare function sanitizeErrorMessage(msg: string): string;
|
|
38
|
+
export declare function sanitizeViteUrl(text: string): string;
|
|
39
|
+
export declare function sanitizeStackTrace(stack: string): string;
|
|
40
|
+
export declare function isVitestNoise(line: string): boolean;
|