@elench/testkit 0.1.59 → 0.1.60
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/lib/coverage/index.mjs +42 -2
- package/lib/coverage/index.test.mjs +21 -0
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-bridge/src/index.mjs +19 -2
- package/node_modules/@elench/testkit-bridge/src/index.test.mjs +6 -1
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/testkit-protocol/src/index.d.ts +1 -0
- package/node_modules/@elench/testkit-protocol/src/index.mjs +3 -1
- package/node_modules/@elench/testkit-protocol/src/index.test.mjs +14 -0
- package/package.json +3 -3
package/lib/coverage/index.mjs
CHANGED
|
@@ -84,7 +84,7 @@ export function buildCoverageGraph({ productDir, repoDiscovery = {}, services =
|
|
|
84
84
|
framework: entry.framework,
|
|
85
85
|
testFilePath: entry.filePath,
|
|
86
86
|
coveredNodeIds,
|
|
87
|
-
details: buildEvidenceDetails(coveredNodeIds, graph),
|
|
87
|
+
details: buildEvidenceDetails(coveredNodeIds, graph, entry, context),
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -238,7 +238,7 @@ function inferCoveredNodeIdsForTest(entry, context) {
|
|
|
238
238
|
return [...coveredNodeIds].sort();
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
function buildEvidenceDetails(coveredNodeIds, graph) {
|
|
241
|
+
function buildEvidenceDetails(coveredNodeIds, graph, entry, context) {
|
|
242
242
|
const routes = new Set();
|
|
243
243
|
const requestPaths = new Set();
|
|
244
244
|
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
@@ -255,9 +255,39 @@ function buildEvidenceDetails(coveredNodeIds, graph) {
|
|
|
255
255
|
if (requestPaths.size > 0) {
|
|
256
256
|
details.requestPaths = [...requestPaths].sort();
|
|
257
257
|
}
|
|
258
|
+
const targets = extractPlaywrightTargets(entry, context);
|
|
259
|
+
if (targets.length > 0) {
|
|
260
|
+
details.targets = targets;
|
|
261
|
+
}
|
|
258
262
|
return Object.keys(details).length > 0 ? details : undefined;
|
|
259
263
|
}
|
|
260
264
|
|
|
265
|
+
function extractPlaywrightTargets(entry, context) {
|
|
266
|
+
if (!entry || entry.framework !== "playwright" || !context?.serviceRoot) return [];
|
|
267
|
+
const absolutePath = path.join(context.serviceRoot, entry.filePath);
|
|
268
|
+
if (!fs.existsSync(absolutePath)) return [];
|
|
269
|
+
const content = fs.readFileSync(absolutePath, "utf8");
|
|
270
|
+
const targets = [];
|
|
271
|
+
|
|
272
|
+
for (const match of content.matchAll(/\bgetByTestId\(\s*["'`]([^"'`]+)["'`]\s*\)/gu)) {
|
|
273
|
+
targets.push({
|
|
274
|
+
kind: "testId",
|
|
275
|
+
value: match[1],
|
|
276
|
+
confidence: "high",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const match of content.matchAll(/\blocator\(\s*["'`]\[data-testid=(?:"|')([^"'`\]]+)(?:"|')\]["'`]\s*\)/gu)) {
|
|
281
|
+
targets.push({
|
|
282
|
+
kind: "testId",
|
|
283
|
+
value: match[1],
|
|
284
|
+
confidence: "medium",
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return dedupeTargets(targets);
|
|
289
|
+
}
|
|
290
|
+
|
|
261
291
|
function discoverPageViews(serviceName, serviceRoot, nextAppRoot, exclude = []) {
|
|
262
292
|
const pageFiles = walkFiles(nextAppRoot, { baseDir: serviceRoot, exclude }).filter(
|
|
263
293
|
(filePath) => filePath.endsWith("/page.tsx") || filePath.endsWith("/page.ts")
|
|
@@ -765,6 +795,16 @@ function dedupeNodes(nodes) {
|
|
|
765
795
|
});
|
|
766
796
|
}
|
|
767
797
|
|
|
798
|
+
function dedupeTargets(targets) {
|
|
799
|
+
const seen = new Set();
|
|
800
|
+
return targets.filter((target) => {
|
|
801
|
+
const key = `${target.kind}:${target.value}`;
|
|
802
|
+
if (seen.has(key)) return false;
|
|
803
|
+
seen.add(key);
|
|
804
|
+
return true;
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
768
808
|
function dedupeBackendImports(entries) {
|
|
769
809
|
const seen = new Set();
|
|
770
810
|
return entries.filter((entry) => {
|
|
@@ -153,6 +153,18 @@ describe("coverage graph builder", () => {
|
|
|
153
153
|
const productDir = createProduct();
|
|
154
154
|
writeFile(productDir, "src/app/campaigns/page.tsx", `export default function CampaignsPage() { return null; }`);
|
|
155
155
|
writeFile(productDir, "src/app/api/campaigns/route.ts", `export async function GET() { return Response.json({ ok: true }); }`);
|
|
156
|
+
writeFile(
|
|
157
|
+
productDir,
|
|
158
|
+
"src/app/campaigns/__testkit__/campaigns.pw.testkit.ts",
|
|
159
|
+
`
|
|
160
|
+
import { expect, test } from "@playwright/test";
|
|
161
|
+
|
|
162
|
+
test("campaigns route", async ({ page }) => {
|
|
163
|
+
await page.goto("/campaigns");
|
|
164
|
+
await expect(page.getByTestId("campaign-save-button")).toBeVisible();
|
|
165
|
+
});
|
|
166
|
+
`
|
|
167
|
+
);
|
|
156
168
|
|
|
157
169
|
const graph = buildCoverageGraph({
|
|
158
170
|
productDir,
|
|
@@ -184,6 +196,15 @@ describe("coverage graph builder", () => {
|
|
|
184
196
|
expect.objectContaining({
|
|
185
197
|
testFilePath: "src/app/campaigns/__testkit__/campaigns.pw.testkit.ts",
|
|
186
198
|
coveredNodeIds: ["page_view:web:/campaigns"],
|
|
199
|
+
details: expect.objectContaining({
|
|
200
|
+
route: "/campaigns",
|
|
201
|
+
targets: [
|
|
202
|
+
expect.objectContaining({
|
|
203
|
+
kind: "testId",
|
|
204
|
+
value: "campaign-save-button",
|
|
205
|
+
}),
|
|
206
|
+
],
|
|
207
|
+
}),
|
|
187
208
|
}),
|
|
188
209
|
expect.objectContaining({
|
|
189
210
|
testFilePath: "src/app/api/campaigns/__testkit__/campaigns.int.testkit.ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"src/"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@elench/testkit-protocol": "0.1.
|
|
14
|
+
"@elench/testkit-protocol": "0.1.60"
|
|
15
15
|
},
|
|
16
16
|
"private": false
|
|
17
17
|
}
|
|
@@ -277,7 +277,7 @@ function buildGraphProjection(context, page, matchedServiceName) {
|
|
|
277
277
|
label: pageNode.label,
|
|
278
278
|
service: pageNode.service,
|
|
279
279
|
route: pageNode.route || null,
|
|
280
|
-
targets:
|
|
280
|
+
targets: collectTargetsForEvidence(relevantEvidence, pageNode.target),
|
|
281
281
|
supportingTests,
|
|
282
282
|
viaNodes,
|
|
283
283
|
confidence: supportingTests.some((entry) => entry.type === "pw") ? "high" : "medium",
|
|
@@ -298,7 +298,7 @@ function buildGraphProjection(context, page, matchedServiceName) {
|
|
|
298
298
|
label: supporting?.label || pageNode.label,
|
|
299
299
|
service: pageNode.service,
|
|
300
300
|
route: pageNode.route || null,
|
|
301
|
-
targets:
|
|
301
|
+
targets: collectTargetsForEvidence([entry], pageNode.target),
|
|
302
302
|
failedTests: supporting ? [{ ...supporting, error: failed?.error || supporting.error || null, status: "failed" }] : [],
|
|
303
303
|
viaNodes: collectViaNodes([entry], reachableNodeIds, nodeById, pageNode.id),
|
|
304
304
|
};
|
|
@@ -351,6 +351,23 @@ function collectViaNodes(evidenceEntries, reachableNodeIds, nodeById, pageNodeId
|
|
|
351
351
|
return [...nodes.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
function collectTargetsForEvidence(evidenceEntries, pageTarget) {
|
|
355
|
+
const targets = [];
|
|
356
|
+
if (pageTarget) targets.push(pageTarget);
|
|
357
|
+
for (const entry of evidenceEntries || []) {
|
|
358
|
+
for (const target of entry?.details?.targets || []) {
|
|
359
|
+
targets.push(target);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const seen = new Set();
|
|
363
|
+
return targets.filter((target) => {
|
|
364
|
+
const key = `${target.kind}:${target.value}`;
|
|
365
|
+
if (seen.has(key)) return false;
|
|
366
|
+
seen.add(key);
|
|
367
|
+
return true;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
354
371
|
function buildSupportingTestRef(evidence, discoveryByFile, runArtifact) {
|
|
355
372
|
const discovery = discoveryByFile.get(evidence.testFilePath);
|
|
356
373
|
const runFile = findRunFileResult(runArtifact, evidence.testFilePath);
|
|
@@ -90,7 +90,10 @@ const context = {
|
|
|
90
90
|
framework: "playwright",
|
|
91
91
|
testFilePath: "app/coverage/__testkit__/coverage.pw.testkit.ts",
|
|
92
92
|
coveredNodeIds: ["page_view:web:/coverage"],
|
|
93
|
-
details: {
|
|
93
|
+
details: {
|
|
94
|
+
route: "/coverage",
|
|
95
|
+
targets: [{ kind: "testId", value: "coverage-refresh-button", confidence: "high" }],
|
|
96
|
+
},
|
|
94
97
|
},
|
|
95
98
|
{
|
|
96
99
|
id: "evidence:web:app/api/coverage/__testkit__/coverage.int.testkit.ts",
|
|
@@ -157,6 +160,7 @@ describe("testkit bridge", () => {
|
|
|
157
160
|
failures: [
|
|
158
161
|
{
|
|
159
162
|
label: "Coverage",
|
|
163
|
+
targets: [{ kind: "testId", value: "coverage-refresh-button", confidence: "high" }],
|
|
160
164
|
failedTests: [
|
|
161
165
|
{
|
|
162
166
|
filePath: "app/coverage/__testkit__/coverage.pw.testkit.ts",
|
|
@@ -168,6 +172,7 @@ describe("testkit bridge", () => {
|
|
|
168
172
|
coverage: [
|
|
169
173
|
{
|
|
170
174
|
label: "Coverage",
|
|
175
|
+
targets: [{ kind: "testId", value: "coverage-refresh-button", confidence: "high" }],
|
|
171
176
|
supportingTests: [
|
|
172
177
|
{
|
|
173
178
|
filePath: "app/coverage/__testkit__/coverage.pw.testkit.ts",
|
|
@@ -208,10 +208,12 @@ function normalizeEvidenceDetails(value) {
|
|
|
208
208
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
209
209
|
const requestPaths = normalizeStringArray(value.requestPaths);
|
|
210
210
|
const route = normalizeOptionalString(value.route);
|
|
211
|
-
|
|
211
|
+
const targets = Array.isArray(value.targets) ? value.targets.map(normalizeBrowserTarget).filter(Boolean) : [];
|
|
212
|
+
if (requestPaths.length === 0 && !route && targets.length === 0) return null;
|
|
212
213
|
return {
|
|
213
214
|
...(requestPaths.length > 0 ? { requestPaths } : {}),
|
|
214
215
|
...(route ? { route } : {}),
|
|
216
|
+
...(targets.length > 0 ? { targets } : {}),
|
|
215
217
|
};
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -74,6 +74,13 @@ describe("testkit browser protocol", () => {
|
|
|
74
74
|
details: {
|
|
75
75
|
route: "/coverage",
|
|
76
76
|
requestPaths: ["/api/coverage"],
|
|
77
|
+
targets: [
|
|
78
|
+
{
|
|
79
|
+
kind: "testId",
|
|
80
|
+
value: "coverage-refresh-button",
|
|
81
|
+
confidence: "high",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
77
84
|
},
|
|
78
85
|
})
|
|
79
86
|
).toEqual({
|
|
@@ -89,6 +96,13 @@ describe("testkit browser protocol", () => {
|
|
|
89
96
|
details: {
|
|
90
97
|
route: "/coverage",
|
|
91
98
|
requestPaths: ["/api/coverage"],
|
|
99
|
+
targets: [
|
|
100
|
+
{
|
|
101
|
+
kind: "testId",
|
|
102
|
+
value: "coverage-refresh-button",
|
|
103
|
+
confidence: "high",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
92
106
|
},
|
|
93
107
|
});
|
|
94
108
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"vitest": "^3.2.4"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@elench/testkit-bridge": "0.1.
|
|
63
|
-
"@elench/testkit-protocol": "0.1.
|
|
62
|
+
"@elench/testkit-bridge": "0.1.60",
|
|
63
|
+
"@elench/testkit-protocol": "0.1.60",
|
|
64
64
|
"@babel/code-frame": "^7.29.0",
|
|
65
65
|
"@oclif/core": "^4.10.6",
|
|
66
66
|
"esbuild": "^0.25.11",
|