@elench/testkit 0.1.82 → 0.1.84

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 (100) hide show
  1. package/README.md +37 -7
  2. package/lib/cli/agents/index.mjs +64 -0
  3. package/lib/cli/agents/investigate.mjs +75 -0
  4. package/lib/cli/agents/investigation-context.mjs +102 -0
  5. package/lib/cli/agents/prompt-builder.mjs +25 -0
  6. package/lib/cli/agents/providers/claude.mjs +74 -0
  7. package/lib/cli/agents/providers/codex.mjs +83 -0
  8. package/lib/cli/agents/providers/shared.mjs +134 -0
  9. package/lib/cli/command-helpers.mjs +53 -25
  10. package/lib/cli/commands/investigate.mjs +87 -0
  11. package/lib/cli/entrypoint.mjs +3 -0
  12. package/lib/cli/presentation/colors.mjs +12 -0
  13. package/lib/cli/presentation/events-reporter.mjs +135 -0
  14. package/lib/cli/presentation/summary-box.mjs +11 -11
  15. package/lib/cli/presentation/tree-reporter.mjs +159 -0
  16. package/lib/cli/tui/run-app.mjs +1 -0
  17. package/lib/cli/tui/run-session-app.mjs +370 -0
  18. package/lib/cli/tui/run-session-state.mjs +481 -0
  19. package/lib/cli/tui/run-tree-state.mjs +1 -0
  20. package/lib/config-api/auth-fixtures.mjs +15 -10
  21. package/lib/discovery/index.mjs +1 -1
  22. package/lib/index.d.ts +5 -1
  23. package/lib/runner/orchestrator.mjs +1 -0
  24. package/lib/runtime/index.d.ts +138 -5
  25. package/lib/runtime/index.mjs +68 -2
  26. package/lib/runtime-src/k6/http-assertions.js +31 -1
  27. package/lib/runtime-src/k6/http-checks.js +120 -0
  28. package/lib/runtime-src/k6/http-suite-runtime.js +5 -1
  29. package/lib/runtime-src/k6/http.js +213 -23
  30. package/lib/runtime-src/shared/error-body.mjs +42 -0
  31. package/lib/runtime-src/shared/http-parsing.mjs +68 -0
  32. package/node_modules/@elench/next-analysis/package.json +1 -1
  33. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  34. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  35. package/node_modules/@elench/ts-analysis/package.json +1 -1
  36. package/package.json +7 -6
  37. package/lib/app/configs.test.mjs +0 -34
  38. package/lib/app/typecheck.test.mjs +0 -24
  39. package/lib/bundler/index.test.mjs +0 -164
  40. package/lib/cli/args.test.mjs +0 -110
  41. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  42. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  43. package/lib/cli/presentation/summary-box.test.mjs +0 -43
  44. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  45. package/lib/config/database.test.mjs +0 -29
  46. package/lib/config/discovery.test.mjs +0 -276
  47. package/lib/config/env.test.mjs +0 -40
  48. package/lib/config/index.test.mjs +0 -44
  49. package/lib/config/paths.test.mjs +0 -27
  50. package/lib/config/runtime.test.mjs +0 -82
  51. package/lib/config/skip-config.test.mjs +0 -63
  52. package/lib/config-api/index.test.mjs +0 -344
  53. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  54. package/lib/coverage/backend-discovery.test.mjs +0 -61
  55. package/lib/coverage/evidence.test.mjs +0 -87
  56. package/lib/coverage/index.test.mjs +0 -715
  57. package/lib/coverage/routing.test.mjs +0 -36
  58. package/lib/coverage/shared.test.mjs +0 -72
  59. package/lib/database/fingerprint.test.mjs +0 -99
  60. package/lib/database/index.test.mjs +0 -95
  61. package/lib/database/naming.test.mjs +0 -39
  62. package/lib/database/state.test.mjs +0 -66
  63. package/lib/database/template-steps.test.mjs +0 -43
  64. package/lib/discovery/file-metadata.test.mjs +0 -51
  65. package/lib/discovery/index.test.mjs +0 -182
  66. package/lib/discovery/path-policy.test.mjs +0 -65
  67. package/lib/drizzle/index.test.mjs +0 -33
  68. package/lib/env/index.test.mjs +0 -82
  69. package/lib/history/index.test.mjs +0 -115
  70. package/lib/package.test.mjs +0 -59
  71. package/lib/playwright/index.test.mjs +0 -43
  72. package/lib/regressions/github.test.mjs +0 -324
  73. package/lib/regressions/index.test.mjs +0 -187
  74. package/lib/reporters/playwright.test.mjs +0 -167
  75. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  76. package/lib/runner/execution-config.test.mjs +0 -67
  77. package/lib/runner/failure-details.test.mjs +0 -114
  78. package/lib/runner/formatting.test.mjs +0 -205
  79. package/lib/runner/metadata.test.mjs +0 -52
  80. package/lib/runner/planning.test.mjs +0 -371
  81. package/lib/runner/playwright-config.test.mjs +0 -78
  82. package/lib/runner/processes.test.mjs +0 -21
  83. package/lib/runner/regressions.test.mjs +0 -168
  84. package/lib/runner/reporting.test.mjs +0 -310
  85. package/lib/runner/results.test.mjs +0 -376
  86. package/lib/runner/runtime-manager.test.mjs +0 -252
  87. package/lib/runner/runtime-preparation.test.mjs +0 -141
  88. package/lib/runner/selection.test.mjs +0 -24
  89. package/lib/runner/setup-operations.test.mjs +0 -94
  90. package/lib/runner/state.test.mjs +0 -62
  91. package/lib/runner/suite-selection.test.mjs +0 -49
  92. package/lib/runner/template.test.mjs +0 -272
  93. package/lib/shared/build-config.test.mjs +0 -132
  94. package/lib/shared/configured-steps.test.mjs +0 -102
  95. package/lib/shared/execution-schema.test.mjs +0 -26
  96. package/lib/shared/file-timeout.test.mjs +0 -64
  97. package/lib/shared/test-context.test.mjs +0 -43
  98. package/lib/timing/index.test.mjs +0 -64
  99. package/lib/toolchains/index.test.mjs +0 -168
  100. package/lib/vitest/index.test.mjs +0 -20
@@ -1,276 +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 { discoverProject, discoverSuites } from "./discovery.mjs";
6
-
7
- const cleanups = [];
8
-
9
- afterEach(() => {
10
- while (cleanups.length > 0) {
11
- cleanups.pop()();
12
- }
13
- });
14
-
15
- describe("filesystem-discovery", () => {
16
- it("discovers colocated __testkit__ suites by service root", () => {
17
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
18
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
19
-
20
- writeFile(productDir, "src/api/routes/__testkit__/auth/me.int.testkit.ts");
21
- writeFile(productDir, "src/api/routes/__testkit__/health/ready.int.testkit.ts");
22
- writeFile(productDir, "src/api/routes/__testkit__/journeys/smoke.scenario.testkit.ts");
23
- writeFile(productDir, "frontend/app/__testkit__/homepage/homepage.pw.testkit.ts");
24
-
25
- const project = discoverProject(productDir, {
26
- api: {
27
- local: {
28
- cwd: ".",
29
- },
30
- },
31
- frontend: {
32
- local: {
33
- cwd: "frontend",
34
- },
35
- },
36
- });
37
-
38
- const suites = project.suitesByService;
39
- expect(suites.api.integration).toEqual([
40
- {
41
- name: "auth",
42
- files: ["src/api/routes/__testkit__/auth/me.int.testkit.ts"],
43
- framework: "k6",
44
- },
45
- {
46
- name: "health",
47
- files: ["src/api/routes/__testkit__/health/ready.int.testkit.ts"],
48
- framework: "k6",
49
- },
50
- ]);
51
- expect(suites.api.scenario).toEqual([
52
- {
53
- name: "journeys",
54
- files: ["src/api/routes/__testkit__/journeys/smoke.scenario.testkit.ts"],
55
- framework: "k6",
56
- },
57
- ]);
58
- expect(suites.frontend.e2e).toEqual([
59
- {
60
- name: "homepage",
61
- files: ["frontend/app/__testkit__/homepage/homepage.pw.testkit.ts"],
62
- framework: "playwright",
63
- },
64
- ]);
65
- expect(project.files).toEqual([
66
- {
67
- serviceName: "api",
68
- type: "integration",
69
- framework: "k6",
70
- suiteName: "auth",
71
- suitePath: ["src", "api", "routes", "auth"],
72
- filePath: "src/api/routes/__testkit__/auth/me.int.testkit.ts",
73
- },
74
- {
75
- serviceName: "api",
76
- type: "integration",
77
- framework: "k6",
78
- suiteName: "health",
79
- suitePath: ["src", "api", "routes", "health"],
80
- filePath: "src/api/routes/__testkit__/health/ready.int.testkit.ts",
81
- },
82
- {
83
- serviceName: "api",
84
- type: "scenario",
85
- framework: "k6",
86
- suiteName: "journeys",
87
- suitePath: ["src", "api", "routes", "journeys"],
88
- filePath: "src/api/routes/__testkit__/journeys/smoke.scenario.testkit.ts",
89
- },
90
- {
91
- serviceName: "frontend",
92
- type: "e2e",
93
- framework: "playwright",
94
- suiteName: "homepage",
95
- suitePath: ["app", "homepage"],
96
- filePath: "frontend/app/__testkit__/homepage/homepage.pw.testkit.ts",
97
- },
98
- ]);
99
- expect(project.diagnostics).toEqual([]);
100
- });
101
-
102
- it("infers the suite from the directory that owns __testkit__", () => {
103
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
104
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
105
-
106
- writeFile(productDir, "src/services/search/__testkit__/query/validation.int.testkit.ts");
107
-
108
- const project = discoverProject(productDir, {
109
- api: {
110
- local: {
111
- cwd: ".",
112
- },
113
- },
114
- });
115
- expect(project.services.api).toMatchObject({
116
- name: "api",
117
- inferredLocalCwd: ".",
118
- });
119
- expect(project.suitesByService.api.integration[0]).toMatchObject({
120
- name: "query",
121
- files: ["src/services/search/__testkit__/query/validation.int.testkit.ts"],
122
- framework: "k6",
123
- });
124
- });
125
-
126
- it("prefers the deepest matching service root", () => {
127
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
128
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
129
-
130
- writeFile(productDir, "frontend/app/__testkit__/billing/lifecycle.pw.testkit.ts");
131
-
132
- const project = discoverProject(productDir, {
133
- app: {
134
- local: {
135
- cwd: ".",
136
- },
137
- },
138
- frontend: {
139
- local: {
140
- cwd: "frontend",
141
- },
142
- },
143
- });
144
-
145
- expect(project.suitesByService.app).toBeUndefined();
146
- expect(project.suitesByService.frontend.e2e[0]).toMatchObject({
147
- name: "billing",
148
- files: ["frontend/app/__testkit__/billing/lifecycle.pw.testkit.ts"],
149
- framework: "playwright",
150
- });
151
- });
152
-
153
- it("fails on legacy files outside __testkit__", () => {
154
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
155
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
156
-
157
- writeFile(productDir, "tests/api/integration/health.int.testkit.ts");
158
-
159
- expect(() =>
160
- discoverSuites(productDir, {
161
- api: {
162
- local: {
163
- cwd: ".",
164
- },
165
- },
166
- })
167
- ).toThrow("Legacy test files outside __testkit__");
168
- });
169
-
170
- it("reports legacy files in non-strict mode", () => {
171
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
172
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
173
-
174
- writeFile(productDir, "src/api/routes/__testkit__/health/ready.int.testkit.ts");
175
- writeFile(productDir, "tests/api/integration/health.int.testkit.ts");
176
-
177
- const project = discoverProject(
178
- productDir,
179
- {
180
- api: {
181
- local: {
182
- cwd: ".",
183
- },
184
- },
185
- },
186
- {
187
- strict: false,
188
- }
189
- );
190
-
191
- expect(project.suitesByService.api.integration).toEqual([
192
- {
193
- name: "health",
194
- files: ["src/api/routes/__testkit__/health/ready.int.testkit.ts"],
195
- framework: "k6",
196
- },
197
- ]);
198
- expect(project.diagnostics).toEqual([
199
- {
200
- code: "legacy_path",
201
- severity: "error",
202
- message: "Legacy test file outside __testkit__: tests/api/integration/health.int.testkit.ts",
203
- path: "tests/api/integration/health.int.testkit.ts",
204
- },
205
- ]);
206
- });
207
-
208
- it("does not treat nested coverage source directories as generated output", () => {
209
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
210
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
211
-
212
- writeFile(productDir, "app/coverage/__testkit__/coverage-map.pw.testkit.ts");
213
-
214
- const project = discoverProject(productDir, {
215
- web: {
216
- local: {
217
- cwd: ".",
218
- },
219
- discovery: {
220
- roots: ["app"],
221
- },
222
- },
223
- });
224
-
225
- expect(project.files).toEqual([
226
- {
227
- serviceName: "web",
228
- type: "e2e",
229
- framework: "playwright",
230
- suiteName: "coverage",
231
- suitePath: ["coverage"],
232
- filePath: "app/coverage/__testkit__/coverage-map.pw.testkit.ts",
233
- },
234
- ]);
235
- });
236
-
237
- it("supports repo-level and service-level discovery excludes", () => {
238
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-discovery-"));
239
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
240
-
241
- writeFile(productDir, "coverage/__testkit__/noise/noise.pw.testkit.ts");
242
- writeFile(productDir, "app/generated/__testkit__/noise/noise.pw.testkit.ts");
243
- writeFile(productDir, "app/coverage/__testkit__/coverage-map.pw.testkit.ts");
244
-
245
- const project = discoverProject(
246
- productDir,
247
- {
248
- web: {
249
- local: {
250
- cwd: ".",
251
- },
252
- discovery: {
253
- roots: ["app"],
254
- exclude: ["app/generated"],
255
- },
256
- },
257
- },
258
- {
259
- discovery: {
260
- exclude: ["coverage"],
261
- },
262
- }
263
- );
264
-
265
- expect(project.files.map((entry) => entry.filePath)).toEqual([
266
- "app/coverage/__testkit__/coverage-map.pw.testkit.ts",
267
- ]);
268
- });
269
-
270
- });
271
-
272
- function writeFile(productDir, relativePath, content = "export {};\n") {
273
- const absolutePath = path.join(productDir, relativePath);
274
- fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
275
- fs.writeFileSync(absolutePath, content);
276
- }
@@ -1,40 +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 { inferEnvFiles, parseDotenvString } from "./env.mjs";
6
-
7
- const cleanups = [];
8
-
9
- afterEach(() => {
10
- while (cleanups.length > 0) {
11
- cleanups.pop()();
12
- }
13
- });
14
-
15
- describe("config env helpers", () => {
16
- it("parses dotenv content with comments and quoted values", () => {
17
- expect(
18
- parseDotenvString(`
19
- # comment
20
- FOO=bar
21
- BAR="quoted value"
22
- BAZ='single quoted'
23
- `)
24
- ).toEqual({
25
- FOO: "bar",
26
- BAR: "quoted value",
27
- BAZ: "single quoted",
28
- });
29
- });
30
-
31
- it("infers env files from service cwd and root", () => {
32
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-env-"));
33
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
34
- fs.mkdirSync(path.join(productDir, "apps", "web"), { recursive: true });
35
- fs.writeFileSync(path.join(productDir, ".env"), "ROOT=1\n");
36
- fs.writeFileSync(path.join(productDir, "apps", "web", ".env.testkit"), "WEB=1\n");
37
-
38
- expect(inferEnvFiles(productDir, {}, { cwd: "apps/web" })).toEqual(["apps/web/.env.testkit", ".env"]);
39
- });
40
- });
@@ -1,44 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import os from "os";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { resolveDalBinary, resolveK6Binary } from "./index.mjs";
6
-
7
- const originalK6Bin = process.env.TESTKIT_K6_BIN;
8
-
9
- afterEach(() => {
10
- if (originalK6Bin === undefined) {
11
- delete process.env.TESTKIT_K6_BIN;
12
- } else {
13
- process.env.TESTKIT_K6_BIN = originalK6Bin;
14
- }
15
- });
16
-
17
- describe("config-index", () => {
18
- it("resolves the bundled k6 binary from the package root", () => {
19
- const binaryPath = resolveK6Binary();
20
- expect(path.basename(binaryPath)).toBe("k6");
21
- expect(fs.existsSync(binaryPath)).toBe(true);
22
- });
23
-
24
- it("keeps resolveDalBinary aligned with the shared k6 resolver", () => {
25
- expect(resolveDalBinary()).toBe(resolveK6Binary());
26
- });
27
-
28
- it("uses TESTKIT_K6_BIN when provided with a relative path", () => {
29
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-k6-"));
30
- const binPath = path.join(tempDir, "k6-custom");
31
- fs.writeFileSync(binPath, "#!/usr/bin/env bash\nexit 0\n");
32
- process.env.TESTKIT_K6_BIN = path.relative(process.cwd(), binPath);
33
-
34
- expect(resolveK6Binary()).toBe(binPath);
35
- });
36
-
37
- it("throws a clear error when TESTKIT_K6_BIN points at a missing path", () => {
38
- process.env.TESTKIT_K6_BIN = "./definitely-missing-k6-bin";
39
-
40
- expect(() => resolveK6Binary()).toThrow(
41
- /TESTKIT_K6_BIN points to a missing file/
42
- );
43
- });
44
- });
@@ -1,27 +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 { normalizePath, resolveProductDir, resolveServiceCwd } from "./paths.mjs";
6
-
7
- const cleanups = [];
8
-
9
- afterEach(() => {
10
- while (cleanups.length > 0) {
11
- cleanups.pop()();
12
- }
13
- });
14
-
15
- describe("config path helpers", () => {
16
- it("normalizes repo-relative paths consistently", () => {
17
- expect(normalizePath("./src/app/page.tsx")).toBe("src/app/page.tsx");
18
- });
19
-
20
- it("resolves product and service directories", () => {
21
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-paths-"));
22
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
23
-
24
- expect(resolveProductDir(productDir, ".")).toBe(productDir);
25
- expect(resolveServiceCwd(productDir, "apps/web")).toBe(path.join(productDir, "apps", "web"));
26
- });
27
- });
@@ -1,82 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- normalizeBrowserServiceConfig,
4
- normalizeRuntimeConfig,
5
- normalizeRuntimePrepareConfig,
6
- normalizeTemplateLifecycleStep,
7
- normalizeTemplateStepInputs,
8
- normalizeTemplateInputs,
9
- parseModuleSpecifier,
10
- } from "./runtime.mjs";
11
-
12
- describe("config runtime helpers", () => {
13
- it("normalizes template inputs and steps", () => {
14
- expect(normalizeTemplateInputs(["foo.sql", "bar.sql"], "Service \"web\" database.template")).toEqual([
15
- "foo.sql",
16
- "bar.sql",
17
- ]);
18
- expect(
19
- normalizeRuntimePrepareConfig(
20
- {
21
- inputs: ["fixtures/users.json"],
22
- steps: [{ kind: "module", target: "./scripts/setup.mjs#run", inputs: ["fixtures/users.json"] }],
23
- },
24
- "web"
25
- )
26
- ).toEqual({
27
- inputs: ["fixtures/users.json"],
28
- steps: [
29
- {
30
- kind: "module",
31
- specifier: "./scripts/setup.mjs#run",
32
- cwd: null,
33
- inputs: ["fixtures/users.json"],
34
- },
35
- ],
36
- });
37
- });
38
-
39
- it("validates browser origins and module specifiers", () => {
40
- expect(normalizeBrowserServiceConfig({ origins: ["http://localhost:3000"] }, "web")).toEqual({
41
- origins: ["http://localhost:3000"],
42
- });
43
- expect(parseModuleSpecifier("./script.mjs#seed")).toEqual({
44
- modulePath: "./script.mjs",
45
- exportName: "seed",
46
- });
47
- });
48
-
49
- it("folds declarative runtime.build into runtime.prepare", () => {
50
- expect(
51
- normalizeRuntimeConfig(
52
- {
53
- build: {
54
- kind: "tsc",
55
- entry: "src/server.ts",
56
- },
57
- },
58
- "api",
59
- {}
60
- )
61
- ).toMatchObject({
62
- build: {
63
- kind: "tsc",
64
- entry: "src/server.ts",
65
- tsconfig: "tsconfig.json",
66
- outDir: "dist",
67
- },
68
- prepare: {
69
- inputs: ["tsconfig.json", "package.json", "src"],
70
- },
71
- });
72
- });
73
-
74
- it("rejects malformed lifecycle steps and empty step inputs", () => {
75
- expect(() => normalizeTemplateLifecycleStep({ kind: "module" }, "runtime.prepare.steps[0]")).toThrow(
76
- /target must be a non-empty string/
77
- );
78
- expect(() => normalizeTemplateStepInputs([""], "runtime.prepare.steps[0]")).toThrow(
79
- /must be a non-empty string/
80
- );
81
- });
82
- });
@@ -1,63 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- normalizeRequirementLocks,
4
- normalizeServiceRequirements,
5
- normalizeSkipConfig,
6
- normalizeSkipReason,
7
- } from "./skip-config.mjs";
8
-
9
- const suites = {
10
- integration: [
11
- {
12
- name: "health",
13
- framework: "k6",
14
- files: ["app/api/health/__testkit__/health.int.testkit.ts"],
15
- },
16
- ],
17
- playwright: [
18
- {
19
- name: "campaigns",
20
- framework: "playwright",
21
- files: ["app/campaigns/__testkit__/campaigns.pw.testkit.ts"],
22
- },
23
- ],
24
- };
25
-
26
- describe("config skip helpers", () => {
27
- it("normalizes skip reasons and requirement locks", () => {
28
- expect(normalizeSkipReason("flaky on CI", "skip.files[0]")).toBe("flaky on CI");
29
- expect(normalizeRequirementLocks(["db", "db", "redis"], "locks")).toEqual(["db", "redis"]);
30
- });
31
-
32
- it("normalizes skip config against discovered suites and files", () => {
33
- const result = normalizeSkipConfig(
34
- {
35
- files: [{ path: "app/campaigns/__testkit__/campaigns.pw.testkit.ts", reason: "manual only" }],
36
- suites: [{ selector: "pw:campaigns", reason: "broken locally" }],
37
- },
38
- { name: "web", suites }
39
- );
40
-
41
- expect(result.files).toHaveLength(1);
42
- expect(result.suites).toHaveLength(1);
43
- });
44
-
45
- it("normalizes typed service requirements", () => {
46
- const result = normalizeServiceRequirements(
47
- {
48
- suites: [{ selector: "int:health", locks: ["db"] }],
49
- files: [{ path: "app/campaigns/__testkit__/campaigns.pw.testkit.ts", locks: ["browser", "browser"] }],
50
- },
51
- { name: "web", suites }
52
- );
53
-
54
- expect(result.suites[0]).toMatchObject({
55
- selector: expect.objectContaining({ raw: "int:health" }),
56
- locks: ["db"],
57
- });
58
- expect(result.files[0]).toMatchObject({
59
- path: "app/campaigns/__testkit__/campaigns.pw.testkit.ts",
60
- locks: ["browser"],
61
- });
62
- });
63
- });