@elench/testkit 0.1.108 → 0.1.109
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 +9 -9
- package/lib/app/doctor.mjs +5 -5
- package/lib/app/typecheck.mjs +6 -5
- package/lib/bundler/index.mjs +134 -7
- package/lib/cli/args.mjs +3 -2
- package/lib/cli/assistant/command-observer.mjs +2 -1
- package/lib/cli/assistant/command-results.mjs +2 -1
- package/lib/cli/assistant/context-pack.mjs +2 -2
- package/lib/cli/assistant/prompt-builder.mjs +2 -2
- package/lib/cli/command-flags.mjs +2 -1
- package/lib/cli/commands/cleanup.mjs +13 -2
- package/lib/cli/commands/discover.mjs +2 -1
- package/lib/cli/commands/run.mjs +3 -2
- package/lib/cli/entrypoint.mjs +3 -1
- package/lib/cli/operations/cleanup/operation.mjs +6 -1
- package/lib/cli/operations/status/operation.mjs +2 -2
- package/lib/cli/renderers/discover/report.mjs +6 -8
- package/lib/cli/renderers/run/failure.mjs +1 -1
- package/lib/cli/renderers/run/text-reporter.mjs +1 -1
- package/lib/cli/renderers/status/text.mjs +101 -1
- package/lib/config/discovery.mjs +10 -1
- package/lib/config-api/index.mjs +2 -2
- package/lib/config-api/next-runtime-tsconfig.mjs +2 -1
- package/lib/coverage/graph-builder.mjs +2 -4
- package/lib/coverage/routing.mjs +1 -1
- package/lib/coverage/shared.mjs +1 -2
- package/lib/discovery/index.d.ts +5 -8
- package/lib/discovery/index.mjs +15 -24
- package/lib/domain/test-types.mjs +44 -0
- package/lib/history/index.d.ts +3 -4
- package/lib/history/index.mjs +6 -14
- package/lib/runner/formatting.mjs +2 -3
- package/lib/runner/maintenance.mjs +136 -35
- package/lib/runner/planning.mjs +1 -1
- package/lib/runner/results.mjs +0 -6
- package/lib/runner/status-model.mjs +520 -0
- package/lib/runner/suite-selection.mjs +20 -11
- package/lib/runner/template-steps.mjs +2 -2
- package/lib/runner/template.mjs +4 -0
- package/lib/ui/index.d.ts +1 -0
- package/lib/ui/index.mjs +1 -0
- package/lib/vitest/index.mjs +2 -1
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/dist/index.js +9 -11
- package/node_modules/@elench/testkit-bridge/dist/index.js.map +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/dist/index.d.ts +1 -3
- package/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@elench/testkit-protocol/dist/index.js +3 -6
- package/node_modules/@elench/testkit-protocol/dist/index.js.map +1 -1
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/dist/requests.js +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ npx @elench/testkit --type int
|
|
|
35
35
|
npx @elench/testkit --type dal
|
|
36
36
|
npx @elench/testkit --type e2e
|
|
37
37
|
npx @elench/testkit --type int,e2e,dal
|
|
38
|
-
npx @elench/testkit --type
|
|
38
|
+
npx @elench/testkit --type ui
|
|
39
39
|
|
|
40
40
|
# Parallel file execution
|
|
41
41
|
npx @elench/testkit --workers 8
|
|
@@ -44,7 +44,7 @@ npx @elench/testkit --workers 8
|
|
|
44
44
|
npx @elench/testkit --file-timeout-seconds 60
|
|
45
45
|
|
|
46
46
|
# Specific service / suite
|
|
47
|
-
npx @elench/testkit --service frontend --type
|
|
47
|
+
npx @elench/testkit --service frontend --type ui -s navigation
|
|
48
48
|
npx @elench/testkit --service api --type int -s health
|
|
49
49
|
npx @elench/testkit --type int,e2e,dal -s dal:queries
|
|
50
50
|
|
|
@@ -183,9 +183,8 @@ export default defineConfig({
|
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
```ts
|
|
186
|
-
//
|
|
187
|
-
import { defineConfig } from "@elench/testkit/
|
|
188
|
-
import { devices } from "@playwright/test";
|
|
186
|
+
// ui.config.ts
|
|
187
|
+
import { defineConfig, devices } from "@elench/testkit/ui";
|
|
189
188
|
|
|
190
189
|
export default defineConfig({
|
|
191
190
|
testDir: "./__testkit__",
|
|
@@ -589,7 +588,7 @@ Example layouts:
|
|
|
589
588
|
|
|
590
589
|
- `src/api/routes/__testkit__/auth/me.int.testkit.ts`
|
|
591
590
|
- `src/db/__testkit__/sessions/count-type.dal.testkit.ts`
|
|
592
|
-
- `frontend/__testkit__/navigation/navigation.
|
|
591
|
+
- `frontend/__testkit__/navigation/navigation.ui.testkit.ts`
|
|
593
592
|
- `src/internal/handler/__testkit__/repos/crud.int.testkit.ts`
|
|
594
593
|
|
|
595
594
|
`testkit` uses these suffixes automatically:
|
|
@@ -599,7 +598,9 @@ Example layouts:
|
|
|
599
598
|
- `*.scenario.testkit.ts`
|
|
600
599
|
- `*.dal.testkit.ts`
|
|
601
600
|
- `*.load.testkit.ts`
|
|
602
|
-
- `*.
|
|
601
|
+
- `*.ui.testkit.ts`
|
|
602
|
+
|
|
603
|
+
See [docs/test-types.md](docs/test-types.md) for the canonical type model.
|
|
603
604
|
|
|
604
605
|
Ownership is inferred from:
|
|
605
606
|
|
|
@@ -645,9 +646,8 @@ identifier plus canonical metadata such as:
|
|
|
645
646
|
- `path`
|
|
646
647
|
- `service`
|
|
647
648
|
- `suiteName`
|
|
648
|
-
- `
|
|
649
|
+
- `type`
|
|
649
650
|
- `internalType`
|
|
650
|
-
- `framework`
|
|
651
651
|
- `skipped`
|
|
652
652
|
- `skipReason`
|
|
653
653
|
- `locks`
|
package/lib/app/doctor.mjs
CHANGED
|
@@ -31,12 +31,12 @@ export async function runDoctor(options = {}) {
|
|
|
31
31
|
|
|
32
32
|
const playwrightViolations = findPlaywrightRuntimeImportViolations(productDir);
|
|
33
33
|
checks.push({
|
|
34
|
-
code: "
|
|
34
|
+
code: "ui-runtime-imports",
|
|
35
35
|
level: playwrightViolations.length === 0 ? "pass" : "fail",
|
|
36
36
|
message:
|
|
37
37
|
playwrightViolations.length === 0
|
|
38
|
-
? "No runtime @playwright/test imports found in testkit
|
|
39
|
-
: `Found ${playwrightViolations.length}
|
|
38
|
+
? "No runtime @playwright/test imports found in testkit UI suites"
|
|
39
|
+
: `Found ${playwrightViolations.length} UI runtime import violation(s); import from @elench/testkit/ui instead`,
|
|
40
40
|
details: playwrightViolations,
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -51,7 +51,7 @@ export async function runDoctor(options = {}) {
|
|
|
51
51
|
details: configImportViolations,
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
const hasBrowserOrNextWork = discovery.files.some((entry) => entry.
|
|
54
|
+
const hasBrowserOrNextWork = discovery.files.some((entry) => entry.type === "ui");
|
|
55
55
|
if (hasBrowserOrNextWork) {
|
|
56
56
|
const nodeCount = discovery.coverageGraph?.nodes?.length || 0;
|
|
57
57
|
checks.push({
|
|
@@ -159,7 +159,7 @@ function collectFiles(rootDir, out = []) {
|
|
|
159
159
|
collectFiles(absolutePath, out);
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
|
-
if (entry.isFile() && entry.name.endsWith(".
|
|
162
|
+
if (entry.isFile() && (entry.name.endsWith(".ui.testkit.ts") || entry.name.endsWith(".ui.testkit.ts"))) {
|
|
163
163
|
out.push(absolutePath);
|
|
164
164
|
}
|
|
165
165
|
}
|
package/lib/app/typecheck.mjs
CHANGED
|
@@ -83,7 +83,7 @@ function writeRootTypecheckConfig({ productDir, configFile, outputDir }) {
|
|
|
83
83
|
"**/*.load.testkit.ts",
|
|
84
84
|
"**/__testkit__/helpers/**/*.ts",
|
|
85
85
|
],
|
|
86
|
-
exclude: ["node_modules", "dist", ".testkit", "**/*.
|
|
86
|
+
exclude: ["node_modules", "dist", ".testkit", "**/*.ui.testkit.ts", "**/*.ui.testkit.ts"],
|
|
87
87
|
};
|
|
88
88
|
fs.writeFileSync(tsconfigPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
89
89
|
return tsconfigPath;
|
|
@@ -104,14 +104,15 @@ function writeNextServiceTypecheckConfig({ productDir, cwd, outputDir, serviceNa
|
|
|
104
104
|
}),
|
|
105
105
|
include: [
|
|
106
106
|
serviceExists(serviceDir, "next-env.d.ts") ? relativeJsonPath(tsconfigPath, path.join(serviceDir, "next-env.d.ts")) : undefined,
|
|
107
|
-
relativeGlob(tsconfigPath, serviceDir, "src/**/*.
|
|
107
|
+
relativeGlob(tsconfigPath, serviceDir, "src/**/*.ui.testkit.ts"),
|
|
108
|
+
relativeGlob(tsconfigPath, serviceDir, "src/**/*.ui.testkit.ts"),
|
|
108
109
|
relativeGlob(tsconfigPath, serviceDir, "tests/playwright-fixtures/**/*.ts"),
|
|
109
110
|
relativeGlob(tsconfigPath, serviceDir, "tests/playwright-fixtures/**/*.tsx"),
|
|
110
111
|
relativeGlob(tsconfigPath, serviceDir, "tests/playwright-fixtures/**/*.mts"),
|
|
111
112
|
relativeGlob(tsconfigPath, serviceDir, ".next/types/**/*.ts"),
|
|
112
113
|
relativeGlob(tsconfigPath, serviceDir, ".next/dev/types/**/*.ts"),
|
|
113
|
-
relativeGlob(tsconfigPath,
|
|
114
|
-
relativeGlob(tsconfigPath,
|
|
114
|
+
relativeGlob(tsconfigPath, productDir, ".next-testkit/**/dist/types/**/*.ts"),
|
|
115
|
+
relativeGlob(tsconfigPath, productDir, ".next-testkit/**/dist/dev/types/**/*.ts"),
|
|
115
116
|
].filter(Boolean),
|
|
116
117
|
exclude: ["node_modules", ".next/cache", ".next/dev"],
|
|
117
118
|
};
|
|
@@ -195,7 +196,7 @@ function packageTypePaths(tsconfigPath) {
|
|
|
195
196
|
"@elench/testkit/config": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "config-api", "index.d.ts"))],
|
|
196
197
|
"@elench/testkit/drizzle": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "drizzle", "index.d.ts"))],
|
|
197
198
|
"@elench/testkit/env": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "env", "index.d.ts"))],
|
|
198
|
-
"@elench/testkit/
|
|
199
|
+
"@elench/testkit/ui": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "ui", "index.d.ts"))],
|
|
199
200
|
"@elench/testkit/runtime": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "runtime", "index.d.ts"))],
|
|
200
201
|
"@elench/testkit/vitest": [relativeJsonPath(tsconfigPath, path.join(PACKAGE_ROOT, "lib", "vitest", "index.d.ts"))],
|
|
201
202
|
};
|
package/lib/bundler/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)),
|
|
|
10
10
|
const ROOT_ENTRY = path.join(PACKAGE_ROOT, "lib", "index.mjs");
|
|
11
11
|
const CONFIG_ENTRY = path.join(PACKAGE_ROOT, "lib", "config-api", "index.mjs");
|
|
12
12
|
const RUNTIME_ENTRY = path.join(PACKAGE_ROOT, "lib", "runtime", "index.mjs");
|
|
13
|
+
const MANIFEST_FILE = "manifest.json";
|
|
13
14
|
const bundleCache = new Map();
|
|
14
15
|
|
|
15
16
|
export async function bundleK6File({
|
|
@@ -22,9 +23,18 @@ export async function bundleK6File({
|
|
|
22
23
|
fs.mkdirSync(bundleDir, { recursive: true });
|
|
23
24
|
|
|
24
25
|
const configFile = findConfigFile(productDir);
|
|
25
|
-
const
|
|
26
|
+
const metadata = await buildCacheMetadata(absoluteSource, configFile);
|
|
27
|
+
const { cacheKey } = metadata;
|
|
26
28
|
const cached = bundleCache.get(cacheKey);
|
|
27
29
|
if (cached && fs.existsSync(cached)) {
|
|
30
|
+
updateBundleManifest(bundleDir, {
|
|
31
|
+
...metadata,
|
|
32
|
+
serviceName: serviceName || "shared",
|
|
33
|
+
sourceFile: absoluteSource,
|
|
34
|
+
outputFile: cached,
|
|
35
|
+
entryFile: inferEntryFile(cached),
|
|
36
|
+
hasSourcemap: fileHasInlineSourcemap(cached),
|
|
37
|
+
});
|
|
28
38
|
return cached;
|
|
29
39
|
}
|
|
30
40
|
|
|
@@ -36,6 +46,20 @@ export async function bundleK6File({
|
|
|
36
46
|
bundleDir,
|
|
37
47
|
`${path.basename(sourceFile, path.extname(sourceFile))}-${cacheKey.slice(0, 12)}.entry.mjs`
|
|
38
48
|
);
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(outputFile) && fs.existsSync(entryFile)) {
|
|
51
|
+
bundleCache.set(cacheKey, outputFile);
|
|
52
|
+
updateBundleManifest(bundleDir, {
|
|
53
|
+
...metadata,
|
|
54
|
+
serviceName: serviceName || "shared",
|
|
55
|
+
sourceFile: absoluteSource,
|
|
56
|
+
outputFile,
|
|
57
|
+
entryFile,
|
|
58
|
+
hasSourcemap: fileHasInlineSourcemap(outputFile),
|
|
59
|
+
});
|
|
60
|
+
return outputFile;
|
|
61
|
+
}
|
|
62
|
+
|
|
39
63
|
fs.writeFileSync(entryFile, buildBundleEntryModule({
|
|
40
64
|
sourceFile: absoluteSource,
|
|
41
65
|
configFile,
|
|
@@ -49,7 +73,7 @@ export async function bundleK6File({
|
|
|
49
73
|
legalComments: "none",
|
|
50
74
|
outfile: outputFile,
|
|
51
75
|
platform: "neutral",
|
|
52
|
-
sourcemap: "inline",
|
|
76
|
+
sourcemap: bundleSourcemapEnabled() ? "inline" : false,
|
|
53
77
|
target: "es2020",
|
|
54
78
|
plugins: [testkitPackageAliasPlugin()],
|
|
55
79
|
external: [
|
|
@@ -59,26 +83,44 @@ export async function bundleK6File({
|
|
|
59
83
|
});
|
|
60
84
|
|
|
61
85
|
bundleCache.set(cacheKey, outputFile);
|
|
86
|
+
updateBundleManifest(bundleDir, {
|
|
87
|
+
...metadata,
|
|
88
|
+
serviceName: serviceName || "shared",
|
|
89
|
+
sourceFile: absoluteSource,
|
|
90
|
+
outputFile,
|
|
91
|
+
entryFile,
|
|
92
|
+
hasSourcemap: bundleSourcemapEnabled(),
|
|
93
|
+
});
|
|
62
94
|
return outputFile;
|
|
63
95
|
}
|
|
64
96
|
|
|
65
|
-
async function
|
|
97
|
+
async function buildCacheMetadata(sourceFile, configFile = null) {
|
|
66
98
|
const source = await fs.promises.readFile(sourceFile, "utf8");
|
|
67
|
-
const
|
|
99
|
+
const packageJsonText = await fs.promises.readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8");
|
|
100
|
+
const packageJson = JSON.parse(packageJsonText);
|
|
101
|
+
const sourceHash = crypto.createHash("sha256").update(source).digest("hex");
|
|
68
102
|
const hash = crypto
|
|
69
103
|
.createHash("sha256")
|
|
70
104
|
.update(sourceFile)
|
|
71
105
|
.update("\0")
|
|
72
106
|
.update(source)
|
|
73
107
|
.update("\0")
|
|
74
|
-
.update(
|
|
108
|
+
.update(packageJsonText);
|
|
109
|
+
let configHash = null;
|
|
75
110
|
|
|
76
111
|
if (configFile && fs.existsSync(configFile)) {
|
|
112
|
+
const configText = await fs.promises.readFile(configFile, "utf8");
|
|
113
|
+
configHash = crypto.createHash("sha256").update(configFile).update("\0").update(configText).digest("hex");
|
|
77
114
|
hash.update("\0").update(configFile).update("\0");
|
|
78
|
-
hash.update(
|
|
115
|
+
hash.update(configText);
|
|
79
116
|
}
|
|
80
117
|
|
|
81
|
-
return
|
|
118
|
+
return {
|
|
119
|
+
cacheKey: hash.digest("hex"),
|
|
120
|
+
sourceHash,
|
|
121
|
+
configHash,
|
|
122
|
+
testkitVersion: packageJson.version || null,
|
|
123
|
+
};
|
|
82
124
|
}
|
|
83
125
|
|
|
84
126
|
function testkitPackageAliasPlugin() {
|
|
@@ -153,3 +195,88 @@ function normalizeTestkitSuite(module) {
|
|
|
153
195
|
}
|
|
154
196
|
`;
|
|
155
197
|
}
|
|
198
|
+
|
|
199
|
+
function updateBundleManifest(bundleDir, entry) {
|
|
200
|
+
const manifestPath = path.join(bundleDir, MANIFEST_FILE);
|
|
201
|
+
const manifest = readBundleManifest(manifestPath);
|
|
202
|
+
const now = new Date().toISOString();
|
|
203
|
+
const existingIndex = manifest.entries.findIndex((candidate) => candidate.cacheKey === entry.cacheKey);
|
|
204
|
+
const existing = existingIndex >= 0 ? manifest.entries[existingIndex] : null;
|
|
205
|
+
const document = {
|
|
206
|
+
sourceFile: entry.sourceFile,
|
|
207
|
+
cacheKey: entry.cacheKey,
|
|
208
|
+
outputFile: entry.outputFile,
|
|
209
|
+
entryFile: entry.entryFile,
|
|
210
|
+
createdAt: existing?.createdAt || now,
|
|
211
|
+
lastUsedAt: now,
|
|
212
|
+
sizeBytes: safeSize(entry.outputFile) + safeSize(entry.entryFile),
|
|
213
|
+
testkitVersion: entry.testkitVersion,
|
|
214
|
+
configHash: entry.configHash,
|
|
215
|
+
sourceHash: entry.sourceHash,
|
|
216
|
+
hasSourcemap: Boolean(entry.hasSourcemap),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (existingIndex >= 0) {
|
|
220
|
+
manifest.entries[existingIndex] = document;
|
|
221
|
+
} else {
|
|
222
|
+
manifest.entries.push(document);
|
|
223
|
+
}
|
|
224
|
+
manifest.entries.sort((left, right) => String(left.sourceFile).localeCompare(String(right.sourceFile)) || String(left.cacheKey).localeCompare(String(right.cacheKey)));
|
|
225
|
+
writeBundleManifest(manifestPath, manifest);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function readBundleManifest(manifestPath) {
|
|
229
|
+
if (!fs.existsSync(manifestPath)) {
|
|
230
|
+
return {
|
|
231
|
+
schemaVersion: 1,
|
|
232
|
+
source: "testkit-bundle-cache",
|
|
233
|
+
entries: [],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
238
|
+
return {
|
|
239
|
+
schemaVersion: 1,
|
|
240
|
+
source: "testkit-bundle-cache",
|
|
241
|
+
...parsed,
|
|
242
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : [],
|
|
243
|
+
};
|
|
244
|
+
} catch {
|
|
245
|
+
return {
|
|
246
|
+
schemaVersion: 1,
|
|
247
|
+
source: "testkit-bundle-cache",
|
|
248
|
+
entries: [],
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function writeBundleManifest(manifestPath, manifest) {
|
|
254
|
+
const tempPath = `${manifestPath}.${process.pid}.${Date.now()}.tmp`;
|
|
255
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
256
|
+
fs.renameSync(tempPath, manifestPath);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function bundleSourcemapEnabled() {
|
|
260
|
+
return process.env.TESTKIT_BUNDLE_SOURCEMAP === "1";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function inferEntryFile(outputFile) {
|
|
264
|
+
return outputFile.replace(/\.js$/, ".entry.mjs");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function safeSize(filePath) {
|
|
268
|
+
try {
|
|
269
|
+
return fs.statSync(filePath).size;
|
|
270
|
+
} catch {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function fileHasInlineSourcemap(filePath) {
|
|
276
|
+
try {
|
|
277
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
278
|
+
return /sourceMappingURL=data:application\/json/.test(text);
|
|
279
|
+
} catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
package/lib/cli/args.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { normalizeTypeValues, parseSuiteSelectors } from "../runner/suite-selection.mjs";
|
|
3
|
+
import { publicTestTypeList, publicTestTypeListText } from "../domain/test-types.mjs";
|
|
3
4
|
import {
|
|
4
5
|
parseFileTimeoutOption,
|
|
5
6
|
parseWorkersOption,
|
|
6
7
|
} from "../runner/execution-config.mjs";
|
|
7
8
|
|
|
8
|
-
export const POSITIONAL_TYPES = new Set(
|
|
9
|
+
export const POSITIONAL_TYPES = new Set(publicTestTypeList({ includeAll: true, includeLegacy: true }));
|
|
9
10
|
export const LIFECYCLE = new Set(["status", "destroy", "cleanup"]);
|
|
10
11
|
export const RESERVED = new Set([...POSITIONAL_TYPES, ...LIFECYCLE]);
|
|
11
12
|
|
|
@@ -33,7 +34,7 @@ export function resolveCliSelection({ first, second, third }) {
|
|
|
33
34
|
} else if (first) {
|
|
34
35
|
throw new Error(
|
|
35
36
|
`Unknown argument "${first}". Expected a lifecycle command (status, destroy, cleanup) ` +
|
|
36
|
-
`or suite type (
|
|
37
|
+
`or suite type (${publicTestTypeListText({ includeAll: true })}).`
|
|
37
38
|
);
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { publicTestTypeList } from "../../domain/test-types.mjs";
|
|
3
4
|
|
|
4
5
|
const POLL_INTERVAL_MS = 150;
|
|
5
6
|
const OBSERVED_KINDS = new Set(["run", "discover", "status", "doctor", "typecheck"]);
|
|
6
|
-
const RUN_KINDS = new Set(["run",
|
|
7
|
+
const RUN_KINDS = new Set(["run", ...publicTestTypeList({ includeAll: true, includeLegacy: true })]);
|
|
7
8
|
|
|
8
9
|
export function createAssistantCommandObserver({
|
|
9
10
|
productDir,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { publicTestTypeList } from "../../domain/test-types.mjs";
|
|
3
4
|
|
|
4
5
|
export const ASSISTANT_SESSION_ENV = "TESTKIT_ASSISTANT_SESSION_ID";
|
|
5
6
|
export const ASSISTANT_RESULT_DIR_ENV = "TESTKIT_ASSISTANT_RESULT_DIR";
|
|
@@ -7,7 +8,7 @@ export const ASSISTANT_COMMAND_LOG_ENV = "TESTKIT_ASSISTANT_COMMAND_LOG";
|
|
|
7
8
|
export const ASSISTANT_COMMAND_ID_ENV = "TESTKIT_ASSISTANT_COMMAND_ID";
|
|
8
9
|
export const ASSISTANT_WRAPPER_LOGGED_ENV = "TESTKIT_ASSISTANT_WRAPPER_LOGGED";
|
|
9
10
|
|
|
10
|
-
const RUN_SHORTCUTS = new Set(
|
|
11
|
+
const RUN_SHORTCUTS = new Set(publicTestTypeList({ includeAll: true, includeLegacy: true }));
|
|
11
12
|
const FLAGS_WITH_VALUES = new Set([
|
|
12
13
|
"--dir",
|
|
13
14
|
"--service",
|
|
@@ -178,7 +178,7 @@ function appendCommandLog(event) {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
function inferKind(args) {
|
|
181
|
-
const runShortcuts = new Set(["
|
|
181
|
+
const runShortcuts = new Set(["ui", "e2e", "scenario", "int", "dal", "load", "all"]);
|
|
182
182
|
const flagsWithValues = new Set([
|
|
183
183
|
"--dir",
|
|
184
184
|
"--service",
|
|
@@ -248,7 +248,7 @@ function buildContextMarkdown(productDir, snapshot, paths) {
|
|
|
248
248
|
"## Guidance",
|
|
249
249
|
"- Work normally in the repository and run real commands. Testkit observes recognized Testkit commands and renders them in the assistant UI.",
|
|
250
250
|
"- Prefer `testkit ...` commands when using Testkit directly; `npx testkit ...` and project scripts are also valid.",
|
|
251
|
-
"- `testkit run
|
|
251
|
+
"- `testkit run ui` selects UI suites. `testkit run e2e` selects e2e suites.",
|
|
252
252
|
"- Do not reinterpret CLI syntax after an execution failure unless `testkit run --help` confirms a syntax problem.",
|
|
253
253
|
"- Use the command log and focused context files before rereading artifacts manually.",
|
|
254
254
|
"- Prefer repo-local commands over guessing project-specific wrappers.",
|
|
@@ -15,8 +15,8 @@ export function buildAssistantPrompt({
|
|
|
15
15
|
"You are Testkit Assistant.",
|
|
16
16
|
"You are running as a coding agent inside the user's repository.",
|
|
17
17
|
"Work normally: inspect files, edit files, run real shell commands, and iterate until the user's request is handled.",
|
|
18
|
-
"When using Testkit, run real commands such as `testkit discover`, `testkit run
|
|
19
|
-
"`testkit run
|
|
18
|
+
"When using Testkit, run real commands such as `testkit discover`, `testkit run ui`, `testkit run e2e`, `npx testkit run int`, or the repository's package scripts.",
|
|
19
|
+
"`testkit run ui` selects UI suites. `testkit run e2e` selects e2e suites. Choose commands from the user's request and the command reference, not from Testkit prompt routing.",
|
|
20
20
|
"Testkit observes recognized Testkit commands and renders rich assistant UI from the real command output, sidecars, and artifacts.",
|
|
21
21
|
"Do not respond with a JSON tool envelope. Give the user a normal final answer when you are done.",
|
|
22
22
|
"",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
|
+
import { publicTestTypeListText } from "../domain/test-types.mjs";
|
|
2
3
|
|
|
3
4
|
export const sharedFlags = {
|
|
4
5
|
dir: Flags.string({
|
|
@@ -14,7 +15,7 @@ export const runFlags = {
|
|
|
14
15
|
type: Flags.string({
|
|
15
16
|
char: "t",
|
|
16
17
|
multiple: true,
|
|
17
|
-
description:
|
|
18
|
+
description: `Run specific suite type(s): ${publicTestTypeListText({ includeAll: true })}`,
|
|
18
19
|
}),
|
|
19
20
|
suite: Flags.string({
|
|
20
21
|
char: "s",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from "@oclif/core";
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
2
|
import { sharedFlags } from "../command-flags.mjs";
|
|
3
3
|
import { executeCleanupOperation } from "../operations/cleanup/operation.mjs";
|
|
4
4
|
import { renderCleanupResult } from "../renderers/cleanup/text.mjs";
|
|
@@ -8,7 +8,18 @@ export default class CleanupCommand extends Command {
|
|
|
8
8
|
|
|
9
9
|
static enableJsonFlag = true;
|
|
10
10
|
|
|
11
|
-
static flags =
|
|
11
|
+
static flags = {
|
|
12
|
+
...sharedFlags,
|
|
13
|
+
"dry-run": Flags.boolean({
|
|
14
|
+
description: "Show cleanup actions without deleting state",
|
|
15
|
+
default: false,
|
|
16
|
+
}),
|
|
17
|
+
cache: Flags.string({
|
|
18
|
+
description: "Clean cache state: runtime, bundles, assistant, or all",
|
|
19
|
+
multiple: true,
|
|
20
|
+
options: ["runtime", "bundles", "assistant", "all"],
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
12
23
|
|
|
13
24
|
async run() {
|
|
14
25
|
const { flags } = await this.parse(CleanupCommand);
|
|
@@ -3,6 +3,7 @@ import { executeDiscoverOperation } from "../operations/discover/operation.mjs";
|
|
|
3
3
|
import { renderDiscoverResult } from "../renderers/discover/text.mjs";
|
|
4
4
|
import { sharedFlags } from "../command-flags.mjs";
|
|
5
5
|
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
6
|
+
import { publicTestTypeListText } from "../../domain/test-types.mjs";
|
|
6
7
|
|
|
7
8
|
export default class DiscoverCommand extends Command {
|
|
8
9
|
static summary = "Discover managed tests and report their metadata";
|
|
@@ -14,7 +15,7 @@ export default class DiscoverCommand extends Command {
|
|
|
14
15
|
type: Flags.string({
|
|
15
16
|
char: "t",
|
|
16
17
|
multiple: true,
|
|
17
|
-
description:
|
|
18
|
+
description: `Filter by suite type(s): ${publicTestTypeListText({ includeAll: true })}`,
|
|
18
19
|
}),
|
|
19
20
|
suite: Flags.string({
|
|
20
21
|
char: "s",
|
package/lib/cli/commands/run.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { runFlags } from "../command-flags.mjs";
|
|
|
3
3
|
import { buildRunRequest, executeRunRequest } from "../operations/run/operation.mjs";
|
|
4
4
|
import { resolveTerminalCapabilities } from "../terminal/capabilities.mjs";
|
|
5
5
|
import { withAssistantCommandResult } from "../assistant/command-results.mjs";
|
|
6
|
+
import { publicTestTypeList, publicTestTypeListText } from "../../domain/test-types.mjs";
|
|
6
7
|
|
|
7
8
|
export default class RunCommand extends Command {
|
|
8
9
|
static summary = "Run test suites";
|
|
@@ -11,9 +12,9 @@ export default class RunCommand extends Command {
|
|
|
11
12
|
|
|
12
13
|
static args = {
|
|
13
14
|
type: Args.string({
|
|
14
|
-
description:
|
|
15
|
+
description: `Optional suite type shortcut: ${publicTestTypeListText({ includeAll: true })}`,
|
|
15
16
|
required: false,
|
|
16
|
-
options:
|
|
17
|
+
options: publicTestTypeList({ includeAll: true, includeLegacy: true }),
|
|
17
18
|
}),
|
|
18
19
|
};
|
|
19
20
|
|
package/lib/cli/entrypoint.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { publicTestTypeList } from "../domain/test-types.mjs";
|
|
2
|
+
|
|
1
3
|
export function normalizeCliArgs(argv) {
|
|
2
4
|
if (argv[0] === "help") return normalizeHelpInvocation(argv);
|
|
3
5
|
if (isOclifBuiltinInvocation(argv)) return argv;
|
|
@@ -14,7 +16,7 @@ export function normalizeCliArgs(argv) {
|
|
|
14
16
|
"browser",
|
|
15
17
|
"db",
|
|
16
18
|
]);
|
|
17
|
-
const runTypeShortcuts = new Set(
|
|
19
|
+
const runTypeShortcuts = new Set(publicTestTypeList({ includeAll: true, includeLegacy: true }));
|
|
18
20
|
const valueFlags = new Set([
|
|
19
21
|
"--dir",
|
|
20
22
|
"--service",
|
|
@@ -4,5 +4,10 @@ import { loadManagedConfigs } from "../../../app/configs.mjs";
|
|
|
4
4
|
export async function executeCleanupOperation(flags = {}) {
|
|
5
5
|
const { allConfigs } = await loadManagedConfigs({ dir: flags.dir, service: flags.service });
|
|
6
6
|
const productDir = allConfigs[0]?.productDir || process.cwd();
|
|
7
|
-
return runner.cleanup(productDir
|
|
7
|
+
return runner.cleanup(productDir, {
|
|
8
|
+
allConfigs,
|
|
9
|
+
serviceName: flags.service || null,
|
|
10
|
+
dryRun: flags["dry-run"],
|
|
11
|
+
cache: flags.cache || [],
|
|
12
|
+
});
|
|
8
13
|
}
|
|
@@ -2,6 +2,6 @@ import * as runner from "../../../runner/index.mjs";
|
|
|
2
2
|
import { loadManagedConfigs } from "../../../app/configs.mjs";
|
|
3
3
|
|
|
4
4
|
export async function executeStatusOperation(flags = {}) {
|
|
5
|
-
const { configs } = await loadManagedConfigs({ dir: flags.dir, service: flags.service });
|
|
6
|
-
return configs.map((config) => runner.showStatus(config));
|
|
5
|
+
const { allConfigs, configs } = await loadManagedConfigs({ dir: flags.dir, service: flags.service });
|
|
6
|
+
return configs.map((config) => runner.showStatus(config, { allConfigs }));
|
|
7
7
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
statusLabel,
|
|
11
11
|
} from "../../terminal/colors.mjs";
|
|
12
12
|
|
|
13
|
-
const TYPE_ORDER = ["
|
|
13
|
+
const TYPE_ORDER = ["ui", "e2e", "scenario", "int", "dal", "load"];
|
|
14
14
|
|
|
15
15
|
const TREE_BRANCH = "\u251C\u2500\u2500 ";
|
|
16
16
|
const TREE_LAST = "\u2514\u2500\u2500 ";
|
|
@@ -98,13 +98,11 @@ function buildVerboseLines(result) {
|
|
|
98
98
|
|
|
99
99
|
const suites = result.suites.filter((suite) => suite.service === service.name).sort(compareSuites);
|
|
100
100
|
for (const suite of suites) {
|
|
101
|
-
lines.push(
|
|
102
|
-
` suite ${suite.selectionType}:${suite.name} ${muted(`[${suite.framework}]`)} ${muted(`${suite.fileCount} files`)}`
|
|
103
|
-
);
|
|
101
|
+
lines.push(` suite ${suite.type}:${suite.name} ${muted(`${suite.fileCount} files`)}`);
|
|
104
102
|
if (suite.locks.length > 0) {
|
|
105
103
|
lines.push(` locks ${suite.locks.join(", ")}`);
|
|
106
104
|
}
|
|
107
|
-
for (const file of result.files.filter((entry) => entry.service === service.name && entry.suiteName === suite.name && entry.
|
|
105
|
+
for (const file of result.files.filter((entry) => entry.service === service.name && entry.suiteName === suite.name && entry.type === suite.type)) {
|
|
108
106
|
lines.push(` ${file.displayName}`);
|
|
109
107
|
lines.push(` path ${file.path}`);
|
|
110
108
|
lines.push(` id ${file.id}`);
|
|
@@ -142,9 +140,9 @@ function groupSuitesByServiceAndType(suites) {
|
|
|
142
140
|
byType = new Map();
|
|
143
141
|
grouped.set(suite.service, byType);
|
|
144
142
|
}
|
|
145
|
-
const list = byType.get(suite.
|
|
143
|
+
const list = byType.get(suite.type) || [];
|
|
146
144
|
list.push(suite);
|
|
147
|
-
byType.set(suite.
|
|
145
|
+
byType.set(suite.type, list.sort(compareSuites));
|
|
148
146
|
}
|
|
149
147
|
return grouped;
|
|
150
148
|
}
|
|
@@ -152,7 +150,7 @@ function groupSuitesByServiceAndType(suites) {
|
|
|
152
150
|
function groupFilesBySuite(files) {
|
|
153
151
|
const bySuite = new Map();
|
|
154
152
|
for (const file of files) {
|
|
155
|
-
const suiteId = [file.service, file.
|
|
153
|
+
const suiteId = [file.service, file.type, file.suiteName].join("|");
|
|
156
154
|
const list = bySuite.get(suiteId) || [];
|
|
157
155
|
list.push(file);
|
|
158
156
|
bySuite.set(
|
|
@@ -25,7 +25,7 @@ export function renderFailureBlock(task, outcome, { width, regressionCatalog } =
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function normalizeRegressionType(task) {
|
|
28
|
-
if (task.framework === "playwright") return "
|
|
28
|
+
if (task.framework === "playwright" || task.type === "ui") return "ui";
|
|
29
29
|
if (task.type === "integration") return "int";
|
|
30
30
|
return task.type;
|
|
31
31
|
}
|
|
@@ -157,7 +157,7 @@ export function createRunReporter({ outputMode = "compact", stdout = process.std
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
function displayTaskType(task) {
|
|
160
|
-
if (task.framework === "playwright") return "
|
|
160
|
+
if (task.framework === "playwright" || task.type === "ui") return "ui";
|
|
161
161
|
if (task.type === "integration") return "int";
|
|
162
162
|
return task.type;
|
|
163
163
|
}
|