@elench/testkit 0.1.83 → 0.1.85

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.
Files changed (87) hide show
  1. package/lib/cli/agents/investigation-interpreter.mjs +320 -0
  2. package/lib/cli/agents/investigation-log.mjs +37 -0
  3. package/lib/cli/agents/providers/codex.mjs +1 -1
  4. package/lib/cli/presentation/tree-reporter.mjs +33 -1
  5. package/lib/cli/tui/run-session-app.mjs +73 -11
  6. package/lib/cli/tui/run-session-state.mjs +29 -5
  7. package/node_modules/@elench/next-analysis/package.json +1 -1
  8. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  9. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  10. package/node_modules/@elench/ts-analysis/package.json +1 -1
  11. package/package.json +7 -6
  12. package/lib/app/configs.test.mjs +0 -34
  13. package/lib/app/typecheck.test.mjs +0 -24
  14. package/lib/bundler/index.test.mjs +0 -164
  15. package/lib/cli/agents/investigation-context.test.mjs +0 -144
  16. package/lib/cli/agents/providers/claude.test.mjs +0 -95
  17. package/lib/cli/agents/providers/codex.test.mjs +0 -93
  18. package/lib/cli/args.test.mjs +0 -110
  19. package/lib/cli/command-helpers.test.mjs +0 -122
  20. package/lib/cli/commands/investigate.test.mjs +0 -83
  21. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  22. package/lib/cli/presentation/events-reporter.test.mjs +0 -73
  23. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  24. package/lib/cli/presentation/summary-box.test.mjs +0 -60
  25. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  26. package/lib/cli/presentation/tree-reporter.test.mjs +0 -166
  27. package/lib/cli/tui/run-session-app.test.mjs +0 -50
  28. package/lib/cli/tui/run-tree-state.test.mjs +0 -324
  29. package/lib/config/database.test.mjs +0 -29
  30. package/lib/config/discovery.test.mjs +0 -276
  31. package/lib/config/env.test.mjs +0 -40
  32. package/lib/config/index.test.mjs +0 -44
  33. package/lib/config/paths.test.mjs +0 -27
  34. package/lib/config/runtime.test.mjs +0 -82
  35. package/lib/config/skip-config.test.mjs +0 -63
  36. package/lib/config-api/index.test.mjs +0 -398
  37. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  38. package/lib/coverage/backend-discovery.test.mjs +0 -61
  39. package/lib/coverage/evidence.test.mjs +0 -87
  40. package/lib/coverage/index.test.mjs +0 -715
  41. package/lib/coverage/routing.test.mjs +0 -36
  42. package/lib/coverage/shared.test.mjs +0 -72
  43. package/lib/database/fingerprint.test.mjs +0 -99
  44. package/lib/database/index.test.mjs +0 -95
  45. package/lib/database/naming.test.mjs +0 -39
  46. package/lib/database/state.test.mjs +0 -66
  47. package/lib/database/template-steps.test.mjs +0 -43
  48. package/lib/discovery/file-metadata.test.mjs +0 -51
  49. package/lib/discovery/index.test.mjs +0 -182
  50. package/lib/discovery/path-policy.test.mjs +0 -65
  51. package/lib/drizzle/index.test.mjs +0 -33
  52. package/lib/env/index.test.mjs +0 -82
  53. package/lib/history/index.test.mjs +0 -115
  54. package/lib/package.test.mjs +0 -59
  55. package/lib/playwright/index.test.mjs +0 -43
  56. package/lib/regressions/github.test.mjs +0 -324
  57. package/lib/regressions/index.test.mjs +0 -187
  58. package/lib/reporters/playwright.test.mjs +0 -167
  59. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  60. package/lib/runner/execution-config.test.mjs +0 -67
  61. package/lib/runner/failure-details.test.mjs +0 -114
  62. package/lib/runner/formatting.test.mjs +0 -205
  63. package/lib/runner/metadata.test.mjs +0 -52
  64. package/lib/runner/planning.test.mjs +0 -371
  65. package/lib/runner/playwright-config.test.mjs +0 -78
  66. package/lib/runner/processes.test.mjs +0 -21
  67. package/lib/runner/regressions.test.mjs +0 -168
  68. package/lib/runner/reporting.test.mjs +0 -310
  69. package/lib/runner/results.test.mjs +0 -376
  70. package/lib/runner/runtime-manager.test.mjs +0 -252
  71. package/lib/runner/runtime-preparation.test.mjs +0 -141
  72. package/lib/runner/selection.test.mjs +0 -24
  73. package/lib/runner/setup-operations.test.mjs +0 -94
  74. package/lib/runner/state.test.mjs +0 -62
  75. package/lib/runner/suite-selection.test.mjs +0 -49
  76. package/lib/runner/template.test.mjs +0 -272
  77. package/lib/runtime-src/k6/http-checks.test.mjs +0 -120
  78. package/lib/runtime-src/k6/http.test.mjs +0 -205
  79. package/lib/runtime-src/shared/http-parsing.test.mjs +0 -69
  80. package/lib/shared/build-config.test.mjs +0 -132
  81. package/lib/shared/configured-steps.test.mjs +0 -102
  82. package/lib/shared/execution-schema.test.mjs +0 -26
  83. package/lib/shared/file-timeout.test.mjs +0 -64
  84. package/lib/shared/test-context.test.mjs +0 -43
  85. package/lib/timing/index.test.mjs +0 -64
  86. package/lib/toolchains/index.test.mjs +0 -168
  87. package/lib/vitest/index.test.mjs +0 -20
@@ -1,398 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- auth,
4
- app,
5
- database,
6
- defineConfig,
7
- defineFile,
8
- toolchain,
9
- } from "./index.mjs";
10
- import { clearRuntimeContext, registerRuntimeContext } from "./runtime.mjs";
11
-
12
- describe("config api", () => {
13
- it("defines repo config plainly", () => {
14
- expect(defineConfig({ execution: { workers: 4 } })).toEqual({
15
- execution: { workers: 4 },
16
- });
17
- });
18
-
19
- it("builds generated auth fixture profiles with actor-aware sessions and headers", () => {
20
- const requests = [];
21
- registerRuntimeContext({
22
- env: {
23
- BASE: "http://api.test",
24
- routeParams: { "x-route": "route-a" },
25
- },
26
- http: {
27
- post(url, body, params) {
28
- const payload = JSON.parse(body);
29
- requests.push({ url, payload, params });
30
- const actor = payload.email.includes(".user-a@")
31
- ? "userA"
32
- : payload.email.includes(".user-b@")
33
- ? "userB"
34
- : "primary";
35
- return {
36
- status: url.endsWith("/signup") ? 201 : 200,
37
- body: JSON.stringify({
38
- data: {
39
- organizations: [{ id: `org-${actor}` }],
40
- },
41
- }),
42
- headers: {
43
- "set-cookie": [
44
- `fixture_session=jwt-${actor}; Path=/`,
45
- `fixture_refresh=refresh-${actor}; Path=/`,
46
- ],
47
- },
48
- };
49
- },
50
- },
51
- });
52
-
53
- const fixture = auth.fixture({
54
- contract: auth.contracts.jsonSession({
55
- authCookie: "fixture_session",
56
- refreshCookie: "fixture_refresh",
57
- organizationIdPath: "data.organizations[0].id",
58
- forwardedFor: "deterministic",
59
- organizationHeader: "X-Organization-Id",
60
- }),
61
- topology: auth.topologies.crossOrg({
62
- namespace: "fixture",
63
- actors: {
64
- primary: { org: "primary" },
65
- userA: { org: "primary" },
66
- userB: { org: "secondary" },
67
- },
68
- }),
69
- });
70
-
71
- const builtProfiles = fixture.profiles({
72
- default: auth.profile.actor("primary", {
73
- headers: {
74
- values: ({ actor }) => ({ "X-Testkit-Actor": actor || "primary" }),
75
- },
76
- }),
77
- dual: auth.profile.actors({
78
- primaryActor: "userA",
79
- actors: ["userA", "userB"],
80
- }),
81
- raw: auth.profile.raw({
82
- headers: {
83
- values: {
84
- "X-Testkit-Mode": "raw",
85
- },
86
- },
87
- }),
88
- });
89
-
90
- expect(fixture.topology.actors.primary.email).toBe("testkit+fixture.primary@example.test");
91
- expect(fixture.topology.actors.userA.name).toBe("Testkit Fixture User A");
92
- expect(fixture.topology.actors.userB.organizationName).toBe("Testkit Fixture Secondary Org");
93
-
94
- const defaultSetup = builtProfiles.default.auth.setup({ env: { BASE: "http://api.test", routeParams: { "x-route": "route-a" } } });
95
- expect(defaultSetup.actors.primary.organizationId).toBe("org-primary");
96
- expect(builtProfiles.default.auth.headers(defaultSetup, { env: { BASE: "http://api.test" } })).toEqual({
97
- Authorization: "Bearer jwt-primary",
98
- });
99
- expect(builtProfiles.default.headers(defaultSetup, { env: { BASE: "http://api.test" } })).toMatchObject({
100
- "Content-Type": "application/json",
101
- "X-Organization-Id": "org-primary",
102
- "X-Testkit-Actor": "primary",
103
- });
104
-
105
- const dualSetup = builtProfiles.dual.auth.setup({ env: { BASE: "http://api.test" } });
106
- expect(dualSetup.actors.userA.organizationId).toBe("org-userA");
107
- expect(dualSetup.actors.userB.organizationId).toBe("org-userB");
108
- expect(builtProfiles.dual.auth.headers(dualSetup)).toEqual({
109
- Authorization: "Bearer jwt-userA",
110
- });
111
-
112
- expect(builtProfiles.raw.rawHeaders(null, { env: { BASE: "http://api.test" } })).toMatchObject({
113
- "Content-Type": "application/json",
114
- "X-Testkit-Mode": "raw",
115
- });
116
-
117
- expect(requests[0].params.headers["x-route"]).toBe("route-a");
118
- expect(requests.map((entry) => entry.url)).toEqual([
119
- "http://api.test/api/v1/auth/signup",
120
- "http://api.test/api/v1/auth/login",
121
- "http://api.test/api/v1/auth/signup",
122
- "http://api.test/api/v1/auth/login",
123
- "http://api.test/api/v1/auth/signup",
124
- "http://api.test/api/v1/auth/login",
125
- ]);
126
- clearRuntimeContext();
127
- });
128
-
129
- it("builds a single-org topology with deterministic defaults", () => {
130
- const topology = auth.topologies.singleOrg({
131
- namespace: "sample-app",
132
- actors: ["primary", "reviewer"],
133
- });
134
-
135
- expect(topology.actors.primary.email).toBe("testkit+sample-app.primary@example.test");
136
- expect(topology.actors.reviewer.name).toBe("Testkit Sample App Reviewer");
137
- expect(topology.actors.primary.organizationName).toBe("Testkit Sample App Primary Org");
138
- });
139
-
140
- it("treats signup as best-effort when login still succeeds", () => {
141
- registerRuntimeContext({
142
- env: {
143
- BASE: "http://api.test",
144
- routeParams: {},
145
- },
146
- http: {
147
- post(url, body) {
148
- const payload = JSON.parse(body);
149
- if (url.endsWith("/signup")) {
150
- return {
151
- status: 500,
152
- body: JSON.stringify({ error: "Failed to create account" }),
153
- headers: {},
154
- };
155
- }
156
-
157
- return {
158
- status: 200,
159
- body: JSON.stringify({
160
- data: {
161
- organizations: [{ id: "org-primary" }],
162
- },
163
- }),
164
- headers: {
165
- "set-cookie": ["fixture_session=jwt-primary; Path=/"],
166
- },
167
- };
168
- },
169
- },
170
- });
171
-
172
- const fixture = auth.fixture({
173
- contract: auth.contracts.jsonSession({
174
- authCookie: "fixture_session",
175
- organizationIdPath: "data.organizations[0].id",
176
- }),
177
- topology: auth.topologies.singleOrg({
178
- namespace: "signup-race",
179
- actors: ["primary"],
180
- }),
181
- });
182
-
183
- const profiles = fixture.profiles({
184
- default: auth.profile.actor("primary"),
185
- });
186
-
187
- const setup = profiles.default.auth.setup({ env: { BASE: "http://api.test", routeParams: {} } });
188
- expect(setup.actors.primary.organizationId).toBe("org-primary");
189
- expect(setup.actors.primary.jwt).toBe("jwt-primary");
190
-
191
- clearRuntimeContext();
192
- });
193
-
194
- it("defines file-local metadata plainly", () => {
195
- expect(defineFile({ skip: "Auth is stubbed", locks: ["background-workers"] })).toEqual({
196
- skip: "Auth is stubbed",
197
- locks: ["background-workers"],
198
- });
199
- });
200
-
201
- it("builds a Next app preset for dev mode", () => {
202
- const config = app.next({ port: 3000 });
203
-
204
- expect(config.local.start).toBe("./node_modules/.bin/next dev -p {port}");
205
- });
206
-
207
- it("builds a Next app preset for start mode with managed runtime env defaults", () => {
208
- const config = app.next({ cwd: "frontend", port: 3000, mode: "start" });
209
-
210
- expect(config.local.start).toBe("./node_modules/.bin/next start --port {port}");
211
- expect(config.local.env).toMatchObject({
212
- NEXT_DIST_DIR: ".next-testkit/{runtimeId}/dist",
213
- NEXT_TSCONFIG_PATH: ".next-testkit/{runtimeId}/tsconfig.json",
214
- });
215
- expect(config.runtime.build).toEqual({
216
- kind: "next",
217
- cwd: "frontend",
218
- distDir: "dist",
219
- tsconfig: "tsconfig.json",
220
- inputs: undefined,
221
- });
222
- });
223
-
224
- it("allows Next start apps to disable managed builds explicitly", () => {
225
- const config = app.next({ cwd: "frontend", port: 3000, mode: "start", build: null });
226
-
227
- expect(config.runtime.build).toBeNull();
228
- expect(config.local.env).toMatchObject({
229
- NEXT_DIST_DIR: ".next-testkit/{runtimeId}/dist",
230
- NEXT_TSCONFIG_PATH: ".next-testkit/{runtimeId}/tsconfig.json",
231
- });
232
- });
233
-
234
- it("builds a Node app preset with tsc build defaults", () => {
235
- const config = app.node({ port: 3000, entry: "src/server.ts" });
236
-
237
- expect(config.local.start).toBe("node {prepareDir}/dist/server.js");
238
- expect(config.runtime.build).toEqual({
239
- kind: "tsc",
240
- cwd: ".",
241
- entry: "src/server.ts",
242
- tsconfig: "tsconfig.json",
243
- outDir: "dist",
244
- inputs: undefined,
245
- });
246
- });
247
-
248
- it("builds node toolchain profiles with a node kind", () => {
249
- expect(toolchain.node({ node: "20.19.5", install: "download" })).toEqual({
250
- kind: "node",
251
- node: "20.19.5",
252
- install: "download",
253
- });
254
- });
255
-
256
- it("builds declarative postgres database templates from plain objects", () => {
257
- expect(
258
- database.postgres({
259
- template: {
260
- inputs: ["db/schema.sql", "scripts/seed.ts"],
261
- schema: "db/schema.sql",
262
- seed: { kind: "command", run: "npm run db:seed" },
263
- verify: { kind: "module", target: "scripts/verify.ts#verifySeed" },
264
- },
265
- })
266
- ).toEqual({
267
- provider: "local",
268
- template: {
269
- inputs: ["db/schema.sql", "scripts/seed.ts"],
270
- migrate: [
271
- {
272
- kind: "sql-file",
273
- path: "db/schema.sql",
274
- },
275
- ],
276
- seed: [
277
- {
278
- kind: "command",
279
- run: "npm run db:seed",
280
- },
281
- ],
282
- verify: [
283
- {
284
- kind: "module",
285
- target: "scripts/verify.ts#verifySeed",
286
- },
287
- ],
288
- },
289
- });
290
- });
291
-
292
- it("prepends schema before explicit migrate steps and normalizes singletons to arrays", () => {
293
- expect(
294
- database.postgres({
295
- template: {
296
- schema: { kind: "sql-file", path: "db/schema.sql", cwd: "db" },
297
- migrate: { kind: "command", run: "echo migrate" },
298
- },
299
- })
300
- ).toEqual({
301
- provider: "local",
302
- template: {
303
- inputs: undefined,
304
- migrate: [
305
- {
306
- kind: "sql-file",
307
- path: "db/schema.sql",
308
- cwd: "db",
309
- },
310
- {
311
- kind: "command",
312
- run: "echo migrate",
313
- },
314
- ],
315
- seed: [],
316
- verify: [],
317
- },
318
- });
319
- });
320
-
321
- it("builds support database presets and expands database env bindings declaratively", () => {
322
- expect(
323
- database.fixture({
324
- reset: true,
325
- })
326
- ).toEqual({
327
- discovery: {
328
- roots: [".testkit-fixture"],
329
- },
330
- envFiles: undefined,
331
- local: false,
332
- database: {
333
- provider: "local",
334
- reset: true,
335
- },
336
- });
337
-
338
- const config = app.node({
339
- port: 3000,
340
- env: {
341
- values: { API_KEY: "test" },
342
- databases: {
343
- onix: { service: "catalog", prefix: "ONIX" },
344
- },
345
- },
346
- });
347
-
348
- expect(config.local.env).toEqual({
349
- API_KEY: "test",
350
- ONIX_DATABASE_HOST: "{dbHost:catalog}",
351
- ONIX_DATABASE_PORT: "{dbPort:catalog}",
352
- ONIX_DATABASE_NAME: "{dbName:catalog}",
353
- ONIX_DATABASE_USER: "{dbUser:catalog}",
354
- ONIX_DATABASE_PASSWORD: "{dbPassword:catalog}",
355
- ONIX_DATABASE_SSL: "0",
356
- });
357
- });
358
-
359
- it("rejects top-level database template lifecycle fields", () => {
360
- expect(() =>
361
- database.postgres({
362
- schema: "db/schema.sql",
363
- })
364
- ).toThrow(/no longer accepts top-level "schema"/);
365
- expect(() =>
366
- database.fixture({
367
- seed: { kind: "command", run: "npm run db:seed" },
368
- })
369
- ).toThrow(/no longer accepts top-level "seed"/);
370
- });
371
-
372
- it("does not leak preset-only helper fields into node app configs", () => {
373
- const config = app.node({
374
- port: 3000,
375
- entry: "src/server.ts",
376
- buildInputs: ["src", "package.json"],
377
- env: { values: { API_KEY: "test" } },
378
- readyPath: "/live",
379
- });
380
-
381
- expect(config).not.toHaveProperty("entry");
382
- expect(config).not.toHaveProperty("buildInputs");
383
- expect(config).not.toHaveProperty("readyPath");
384
- expect(config.local.env).toEqual({ API_KEY: "test" });
385
- expect(config.local.readyUrl).toBe("http://127.0.0.1:{port}/live");
386
- });
387
-
388
- it("rejects flat preset env maps in favor of env.values and env.databases", () => {
389
- expect(() =>
390
- app.node({
391
- port: 3000,
392
- env: {
393
- API_KEY: "test",
394
- },
395
- })
396
- ).toThrow(/Preset env only supports "values" and "databases"/);
397
- });
398
- });
@@ -1,58 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { writeNextRuntimeTsconfig } from "./next-runtime-tsconfig.mjs";
6
-
7
- const cleanups = [];
8
-
9
- afterEach(() => {
10
- while (cleanups.length > 0) {
11
- cleanups.pop()();
12
- }
13
- });
14
-
15
- describe("next runtime tsconfig helper", () => {
16
- it("writes a runtime-specific tsconfig for next builds", async () => {
17
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-next-tsconfig-"));
18
- cleanups.push(() => fs.rmSync(tempRoot, { recursive: true, force: true }));
19
-
20
- const serviceDir = path.join(tempRoot, "frontend");
21
- fs.mkdirSync(path.join(serviceDir, "src"), { recursive: true });
22
- fs.writeFileSync(path.join(serviceDir, "tsconfig.json"), "{}\n");
23
- fs.writeFileSync(path.join(serviceDir, "next-env.d.ts"), "// next\n");
24
-
25
- await writeNextRuntimeTsconfig({
26
- cwd: serviceDir,
27
- productDir: tempRoot,
28
- runtimeId: "runtime-1",
29
- });
30
-
31
- const generated = JSON.parse(
32
- fs.readFileSync(path.join(serviceDir, ".next-testkit", "runtime-1", "tsconfig.json"), "utf8")
33
- );
34
- expect(generated).toEqual({
35
- extends: "../../tsconfig.json",
36
- compilerOptions: {
37
- paths: {
38
- "@/*": ["../../src/*"],
39
- },
40
- },
41
- include: [
42
- "../../next-env.d.ts",
43
- "../../src/**/*.ts",
44
- "../../src/**/*.tsx",
45
- "../../src/**/*.mts",
46
- "dist/types/**/*.ts",
47
- "dist/dev/types/**/*.ts",
48
- ],
49
- exclude: [
50
- "node_modules",
51
- "../../src/**/__testkit__/**",
52
- "../../tests/**",
53
- ".next/cache",
54
- ".next/dev",
55
- ],
56
- });
57
- });
58
- });
@@ -1,61 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { extractBackendImports, extractDataImports } from "./backend-discovery.mjs";
3
-
4
- describe("coverage backend discovery", () => {
5
- it("tracks local import names while anchoring capabilities to exported names", () => {
6
- const content = [
7
- 'import { saveSettings as persistSettings } from "@/backend/server/settings";',
8
- 'import { loadProjects as fetchProjects } from "@/backend/data/projects";',
9
- ].join("\n");
10
-
11
- expect(
12
- extractBackendImports({
13
- serviceName: "web",
14
- serviceRoot: "/repo",
15
- filePath: "src/app/settings/actions.ts",
16
- content,
17
- resolveImport: () => "src/backend/server/settings.ts",
18
- })
19
- ).toEqual([
20
- {
21
- importName: "persistSettings",
22
- node: {
23
- id: "server_capability:src/backend/server/settings#saveSettings",
24
- kind: "server_capability",
25
- service: "web",
26
- label: "saveSettings",
27
- filePath: "src/backend/server/settings.ts",
28
- metadata: {
29
- specifier: "@/backend/server/settings",
30
- localName: "persistSettings",
31
- },
32
- },
33
- },
34
- ]);
35
-
36
- expect(
37
- extractDataImports({
38
- serviceName: "web",
39
- serviceRoot: "/repo",
40
- filePath: "src/backend/server/settings.ts",
41
- content,
42
- resolveImport: () => "src/backend/data/projects.ts",
43
- })
44
- ).toEqual([
45
- {
46
- importName: "fetchProjects",
47
- node: {
48
- id: "data_capability:src/backend/data/projects#loadProjects",
49
- kind: "data_capability",
50
- service: "web",
51
- label: "loadProjects",
52
- filePath: "src/backend/data/projects.ts",
53
- metadata: {
54
- specifier: "@/backend/data/projects",
55
- localName: "fetchProjects",
56
- },
57
- },
58
- },
59
- ]);
60
- });
61
- });
@@ -1,87 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- buildEvidenceDetailsFromTargets,
4
- extractPlaywrightTargetsFromContent,
5
- inferDataCapabilitiesFromOwner,
6
- } from "./evidence.mjs";
7
- import { extractPlaywrightVisitedRoutes } from "@elench/ts-analysis";
8
- import { DYNAMIC_SEGMENT_TOKEN } from "./shared.mjs";
9
-
10
- describe("coverage evidence helpers", () => {
11
- it("extracts and dedupes Playwright targets from source content", () => {
12
- const content = `
13
- await page.getByTestId("save-button").click();
14
- await page.locator('[data-testid="save-button"]').click();
15
- await page.getByTestId("drawer").click();
16
- `;
17
-
18
- expect(extractPlaywrightTargetsFromContent(content)).toEqual([
19
- { kind: "testId", value: "save-button", confidence: "high" },
20
- { kind: "testId", value: "drawer", confidence: "high" },
21
- ]);
22
- });
23
-
24
- it("builds compact evidence details only when meaningful signals exist", () => {
25
- expect(
26
- buildEvidenceDetailsFromTargets({
27
- routes: ["/campaigns"],
28
- requestPaths: ["/api/campaigns"],
29
- targets: [{ kind: "testId", value: "save-button", confidence: "high" }],
30
- })
31
- ).toEqual({
32
- route: "/campaigns",
33
- requestPaths: ["/api/campaigns"],
34
- targets: [{ kind: "testId", value: "save-button", confidence: "high" }],
35
- });
36
-
37
- expect(buildEvidenceDetailsFromTargets({ routes: [], requestPaths: [], targets: [] })).toBeUndefined();
38
- });
39
-
40
- it("infers DAL coverage from owner directories", () => {
41
- const dataCapabilities = [
42
- { id: "data_capability:src/backend/data/campaigns#saveCampaignRow", filePath: "src/backend/data/campaigns/index.ts" },
43
- { id: "data_capability:src/backend/data/users#saveUserRow", filePath: "src/backend/data/users/index.ts" },
44
- ];
45
-
46
- expect(inferDataCapabilitiesFromOwner("src/backend/data/campaigns", dataCapabilities)).toEqual([
47
- "data_capability:src/backend/data/campaigns#saveCampaignRow",
48
- ]);
49
- });
50
- });
51
-
52
- describe("extractPlaywrightVisitedRoutes", () => {
53
- it("extracts routes from page.goto() via AST", () => {
54
- expect(extractPlaywrightVisitedRoutes(`
55
- import { test } from "@playwright/test";
56
- test("nav", async ({ page }) => {
57
- await page.goto("/dashboard");
58
- await page.goto("/settings/profile?tab=general");
59
- await page.goto("/dashboard"); // duplicate
60
- await page.goto("https://external.com/path"); // external
61
- await page.goto("http://localhost:3000/events"); // same-origin absolute
62
- });
63
- `, {
64
- filePath: "nav.pw.testkit.ts",
65
- dynamicSegmentToken: DYNAMIC_SEGMENT_TOKEN,
66
- })).toEqual(["/dashboard", "/settings/profile", "/events"]);
67
- });
68
-
69
- it("handles property chain: foo.page.goto()", () => {
70
- expect(extractPlaywrightVisitedRoutes(`
71
- await this.page.goto("/projects");
72
- `, {
73
- filePath: "test.pw.testkit.ts",
74
- dynamicSegmentToken: DYNAMIC_SEGMENT_TOKEN,
75
- })).toEqual(["/projects"]);
76
- });
77
-
78
- it("normalizes dynamic goto expressions into route patterns", () => {
79
- expect(extractPlaywrightVisitedRoutes(`
80
- await page.goto(\`/projects/\${id}\`);
81
- await page.goto(someVar);
82
- `, {
83
- filePath: "test.pw.testkit.ts",
84
- dynamicSegmentToken: DYNAMIC_SEGMENT_TOKEN,
85
- })).toEqual([`/projects/${DYNAMIC_SEGMENT_TOKEN}`]);
86
- });
87
- });