@elench/testkit 0.1.57 → 0.1.58
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/cli/commands/browser/serve.mjs +112 -0
- package/lib/cli/entrypoint.mjs +4 -0
- package/lib/config/discovery.test.mjs +3 -2
- package/lib/config/index.mjs +29 -0
- package/lib/coverage/index.mjs +774 -0
- package/lib/coverage/index.test.mjs +220 -0
- package/lib/discovery/index.d.ts +3 -0
- package/lib/discovery/index.mjs +14 -2
- package/lib/setup/index.d.ts +5 -0
- package/node_modules/@elench/testkit-bridge/package.json +17 -0
- package/node_modules/@elench/testkit-bridge/src/index.mjs +391 -0
- package/node_modules/@elench/testkit-bridge/src/index.test.mjs +183 -0
- package/node_modules/@elench/testkit-protocol/package.json +18 -0
- package/node_modules/@elench/testkit-protocol/src/index.d.ts +204 -0
- package/node_modules/@elench/testkit-protocol/src/index.mjs +245 -0
- package/node_modules/@elench/testkit-protocol/src/index.test.mjs +154 -0
- package/package.json +11 -2
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
export declare const TESTKIT_BROWSER_PROTOCOL_VERSION = 1;
|
|
2
|
+
export declare const TESTKIT_COVERAGE_GRAPH_VERSION = 1;
|
|
3
|
+
|
|
4
|
+
export type BrowserTargetKind = "testId" | "role" | "text" | "css" | "xpath" | "component";
|
|
5
|
+
export type BrowserConfidence = "low" | "medium" | "high";
|
|
6
|
+
export type BrowserFailureState = "failing" | "healthy" | "unavailable";
|
|
7
|
+
export type BrowserCoverageState = "covered" | "missing" | "unavailable";
|
|
8
|
+
|
|
9
|
+
export type CoverageNodeKind =
|
|
10
|
+
| "page_view"
|
|
11
|
+
| "ui_surface"
|
|
12
|
+
| "ui_action"
|
|
13
|
+
| "client_request"
|
|
14
|
+
| "api_route"
|
|
15
|
+
| "server_action"
|
|
16
|
+
| "server_capability"
|
|
17
|
+
| "data_capability"
|
|
18
|
+
| "test_file";
|
|
19
|
+
|
|
20
|
+
export type CoverageEdgeKind =
|
|
21
|
+
| "contains"
|
|
22
|
+
| "renders"
|
|
23
|
+
| "triggers"
|
|
24
|
+
| "requests"
|
|
25
|
+
| "handles"
|
|
26
|
+
| "delegates_to"
|
|
27
|
+
| "covers";
|
|
28
|
+
|
|
29
|
+
export type CoverageEvidenceSource = "convention" | "static" | "runtime";
|
|
30
|
+
|
|
31
|
+
export interface BrowserTarget {
|
|
32
|
+
kind: BrowserTargetKind;
|
|
33
|
+
value: string;
|
|
34
|
+
label?: string;
|
|
35
|
+
confidence?: BrowserConfidence;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BrowserMetadata {
|
|
39
|
+
label?: string;
|
|
40
|
+
origins?: string[];
|
|
41
|
+
routes?: string[];
|
|
42
|
+
targets?: BrowserTarget[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BrowserMetadataDocument {
|
|
46
|
+
schemaVersion: number;
|
|
47
|
+
browser: BrowserMetadata;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CoverageGraphNode {
|
|
51
|
+
id: string;
|
|
52
|
+
kind: CoverageNodeKind;
|
|
53
|
+
service: string;
|
|
54
|
+
label: string;
|
|
55
|
+
filePath?: string;
|
|
56
|
+
route?: string;
|
|
57
|
+
method?: string;
|
|
58
|
+
path?: string;
|
|
59
|
+
target?: BrowserTarget | null;
|
|
60
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CoverageGraphEdge {
|
|
64
|
+
id: string;
|
|
65
|
+
kind: CoverageEdgeKind;
|
|
66
|
+
from: string;
|
|
67
|
+
to: string;
|
|
68
|
+
confidence?: BrowserConfidence;
|
|
69
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CoverageEvidence {
|
|
73
|
+
id: string;
|
|
74
|
+
source: CoverageEvidenceSource;
|
|
75
|
+
confidence?: BrowserConfidence;
|
|
76
|
+
service: string;
|
|
77
|
+
suiteName: string;
|
|
78
|
+
selectionType: string;
|
|
79
|
+
framework: string;
|
|
80
|
+
testFilePath: string;
|
|
81
|
+
coveredNodeIds: string[];
|
|
82
|
+
details?: {
|
|
83
|
+
requestPaths?: string[];
|
|
84
|
+
route?: string;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CoverageGraph {
|
|
89
|
+
schemaVersion: number;
|
|
90
|
+
nodes: CoverageGraphNode[];
|
|
91
|
+
edges: CoverageGraphEdge[];
|
|
92
|
+
evidence: CoverageEvidence[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface BridgeProductRef {
|
|
96
|
+
name: string;
|
|
97
|
+
directory: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface BridgeServiceRef {
|
|
101
|
+
name: string;
|
|
102
|
+
baseUrl: string;
|
|
103
|
+
origin: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface BrowserMatchResponse {
|
|
107
|
+
protocolVersion: number;
|
|
108
|
+
url: string;
|
|
109
|
+
origin: string;
|
|
110
|
+
route: string;
|
|
111
|
+
matched: boolean;
|
|
112
|
+
product: BridgeProductRef;
|
|
113
|
+
service: BridgeServiceRef | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface BridgeRunSummary {
|
|
117
|
+
artifactAvailable: boolean;
|
|
118
|
+
generatedAt: string | null;
|
|
119
|
+
status: string | null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface BridgeSupportingTestRef {
|
|
123
|
+
service: string;
|
|
124
|
+
suite: string;
|
|
125
|
+
type: string;
|
|
126
|
+
framework: string;
|
|
127
|
+
filePath: string;
|
|
128
|
+
label: string;
|
|
129
|
+
status?: "passed" | "failed" | "skipped" | "not_run";
|
|
130
|
+
error?: string | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface BridgeCoverageViaNodeRef {
|
|
134
|
+
id: string;
|
|
135
|
+
kind: CoverageNodeKind;
|
|
136
|
+
label: string;
|
|
137
|
+
route?: string;
|
|
138
|
+
method?: string;
|
|
139
|
+
path?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface FailureOverlayEntry {
|
|
143
|
+
id: string;
|
|
144
|
+
kind: CoverageNodeKind;
|
|
145
|
+
label: string;
|
|
146
|
+
service: string;
|
|
147
|
+
route?: string | null;
|
|
148
|
+
targets: BrowserTarget[];
|
|
149
|
+
failedTests: BridgeSupportingTestRef[];
|
|
150
|
+
viaNodes: BridgeCoverageViaNodeRef[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface CoverageOverlayEntry {
|
|
154
|
+
id: string;
|
|
155
|
+
kind: CoverageNodeKind;
|
|
156
|
+
label: string;
|
|
157
|
+
service: string;
|
|
158
|
+
route?: string | null;
|
|
159
|
+
targets: BrowserTarget[];
|
|
160
|
+
supportingTests: BridgeSupportingTestRef[];
|
|
161
|
+
viaNodes: BridgeCoverageViaNodeRef[];
|
|
162
|
+
confidence: BrowserConfidence;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface PageOverlayResponse {
|
|
166
|
+
protocolVersion: number;
|
|
167
|
+
page: {
|
|
168
|
+
url: string;
|
|
169
|
+
origin: string;
|
|
170
|
+
route: string;
|
|
171
|
+
};
|
|
172
|
+
match: BrowserMatchResponse;
|
|
173
|
+
run: BridgeRunSummary;
|
|
174
|
+
summary: {
|
|
175
|
+
failureState: BrowserFailureState;
|
|
176
|
+
coverageState: BrowserCoverageState;
|
|
177
|
+
relatedFailureCount: number;
|
|
178
|
+
relatedCoverageCount: number;
|
|
179
|
+
};
|
|
180
|
+
failures: FailureOverlayEntry[];
|
|
181
|
+
coverage: CoverageOverlayEntry[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface BridgeErrorResponse {
|
|
185
|
+
protocolVersion: number;
|
|
186
|
+
error: {
|
|
187
|
+
code: string;
|
|
188
|
+
message: string;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export declare function normalizeBrowserTarget(value: unknown): BrowserTarget | null;
|
|
193
|
+
export declare function normalizeBrowserMetadata(value: unknown): BrowserMetadata | null;
|
|
194
|
+
export declare function normalizeBrowserMetadataDocument(value: unknown): BrowserMetadataDocument | null;
|
|
195
|
+
export declare function normalizeCoverageGraphNode(value: unknown): CoverageGraphNode | null;
|
|
196
|
+
export declare function normalizeCoverageGraphEdge(value: unknown): CoverageGraphEdge | null;
|
|
197
|
+
export declare function normalizeCoverageEvidence(value: unknown): CoverageEvidence | null;
|
|
198
|
+
export declare function normalizeCoverageGraph(value: unknown): CoverageGraph | null;
|
|
199
|
+
export declare function normalizePageOverlayResponse(value: unknown): PageOverlayResponse | null;
|
|
200
|
+
export declare function isPageOverlayResponse(value: unknown): value is PageOverlayResponse;
|
|
201
|
+
export declare function createBridgeErrorResponse(
|
|
202
|
+
code: string,
|
|
203
|
+
message: string
|
|
204
|
+
): BridgeErrorResponse;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
export const TESTKIT_BROWSER_PROTOCOL_VERSION = 1;
|
|
2
|
+
export const TESTKIT_COVERAGE_GRAPH_VERSION = 1;
|
|
3
|
+
|
|
4
|
+
const TARGET_KINDS = new Set(["testId", "role", "text", "css", "xpath", "component"]);
|
|
5
|
+
const CONFIDENCE_LEVELS = new Set(["low", "medium", "high"]);
|
|
6
|
+
const FAILURE_STATES = new Set(["failing", "healthy", "unavailable"]);
|
|
7
|
+
const COVERAGE_STATES = new Set(["covered", "missing", "unavailable"]);
|
|
8
|
+
const NODE_KINDS = new Set([
|
|
9
|
+
"page_view",
|
|
10
|
+
"ui_surface",
|
|
11
|
+
"ui_action",
|
|
12
|
+
"client_request",
|
|
13
|
+
"api_route",
|
|
14
|
+
"server_action",
|
|
15
|
+
"server_capability",
|
|
16
|
+
"data_capability",
|
|
17
|
+
"test_file",
|
|
18
|
+
]);
|
|
19
|
+
const EDGE_KINDS = new Set([
|
|
20
|
+
"contains",
|
|
21
|
+
"renders",
|
|
22
|
+
"triggers",
|
|
23
|
+
"requests",
|
|
24
|
+
"handles",
|
|
25
|
+
"delegates_to",
|
|
26
|
+
"covers",
|
|
27
|
+
]);
|
|
28
|
+
const EVIDENCE_SOURCES = new Set(["convention", "static", "runtime"]);
|
|
29
|
+
|
|
30
|
+
export function normalizeBrowserTarget(value) {
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
32
|
+
const kind = normalizeOptionalString(value.kind);
|
|
33
|
+
const targetValue = normalizeOptionalString(value.value);
|
|
34
|
+
if (!kind || !TARGET_KINDS.has(kind) || !targetValue) return null;
|
|
35
|
+
const label = normalizeOptionalString(value.label);
|
|
36
|
+
const confidence = normalizeOptionalString(value.confidence);
|
|
37
|
+
return {
|
|
38
|
+
kind,
|
|
39
|
+
value: targetValue,
|
|
40
|
+
...(label ? { label } : {}),
|
|
41
|
+
...(confidence && CONFIDENCE_LEVELS.has(confidence) ? { confidence } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function normalizeBrowserMetadata(value) {
|
|
46
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
47
|
+
const label = normalizeOptionalString(value.label);
|
|
48
|
+
const origins = normalizeStringArray(value.origins);
|
|
49
|
+
const routes = normalizeStringArray(value.routes);
|
|
50
|
+
const targets = Array.isArray(value.targets) ? value.targets.map(normalizeBrowserTarget).filter(Boolean) : [];
|
|
51
|
+
if (!label && origins.length === 0 && routes.length === 0 && targets.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...(label ? { label } : {}),
|
|
56
|
+
...(origins.length > 0 ? { origins } : {}),
|
|
57
|
+
...(routes.length > 0 ? { routes } : {}),
|
|
58
|
+
...(targets.length > 0 ? { targets } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function normalizeBrowserMetadataDocument(value) {
|
|
63
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
64
|
+
const schemaVersion =
|
|
65
|
+
Number.isInteger(value.schemaVersion) && value.schemaVersion > 0
|
|
66
|
+
? value.schemaVersion
|
|
67
|
+
: TESTKIT_BROWSER_PROTOCOL_VERSION;
|
|
68
|
+
const browser = normalizeBrowserMetadata(value.browser);
|
|
69
|
+
if (!browser) return null;
|
|
70
|
+
return {
|
|
71
|
+
schemaVersion,
|
|
72
|
+
browser,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function normalizeCoverageGraphNode(value) {
|
|
77
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
78
|
+
const id = normalizeOptionalString(value.id);
|
|
79
|
+
const kind = normalizeOptionalString(value.kind);
|
|
80
|
+
const service = normalizeOptionalString(value.service);
|
|
81
|
+
const label = normalizeOptionalString(value.label);
|
|
82
|
+
if (!id || !kind || !NODE_KINDS.has(kind) || !service || !label) return null;
|
|
83
|
+
const filePath = normalizeOptionalString(value.filePath);
|
|
84
|
+
const route = normalizeOptionalString(value.route);
|
|
85
|
+
const method = normalizeOptionalString(value.method);
|
|
86
|
+
const path = normalizeOptionalString(value.path);
|
|
87
|
+
const target = normalizeBrowserTarget(value.target);
|
|
88
|
+
const metadata = normalizeMetadataRecord(value.metadata);
|
|
89
|
+
return {
|
|
90
|
+
id,
|
|
91
|
+
kind,
|
|
92
|
+
service,
|
|
93
|
+
label,
|
|
94
|
+
...(filePath ? { filePath } : {}),
|
|
95
|
+
...(route ? { route } : {}),
|
|
96
|
+
...(method ? { method } : {}),
|
|
97
|
+
...(path ? { path } : {}),
|
|
98
|
+
...(target ? { target } : {}),
|
|
99
|
+
...(metadata ? { metadata } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function normalizeCoverageGraphEdge(value) {
|
|
104
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
105
|
+
const id = normalizeOptionalString(value.id);
|
|
106
|
+
const kind = normalizeOptionalString(value.kind);
|
|
107
|
+
const from = normalizeOptionalString(value.from);
|
|
108
|
+
const to = normalizeOptionalString(value.to);
|
|
109
|
+
if (!id || !kind || !EDGE_KINDS.has(kind) || !from || !to) return null;
|
|
110
|
+
const confidence = normalizeOptionalString(value.confidence);
|
|
111
|
+
const metadata = normalizeMetadataRecord(value.metadata);
|
|
112
|
+
return {
|
|
113
|
+
id,
|
|
114
|
+
kind,
|
|
115
|
+
from,
|
|
116
|
+
to,
|
|
117
|
+
...(confidence && CONFIDENCE_LEVELS.has(confidence) ? { confidence } : {}),
|
|
118
|
+
...(metadata ? { metadata } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function normalizeCoverageEvidence(value) {
|
|
123
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
124
|
+
const id = normalizeOptionalString(value.id);
|
|
125
|
+
const source = normalizeOptionalString(value.source);
|
|
126
|
+
const service = normalizeOptionalString(value.service);
|
|
127
|
+
const suiteName = normalizeOptionalString(value.suiteName);
|
|
128
|
+
const selectionType = normalizeOptionalString(value.selectionType);
|
|
129
|
+
const framework = normalizeOptionalString(value.framework);
|
|
130
|
+
const testFilePath = normalizeOptionalString(value.testFilePath);
|
|
131
|
+
const coveredNodeIds = normalizeStringArray(value.coveredNodeIds);
|
|
132
|
+
if (
|
|
133
|
+
!id ||
|
|
134
|
+
!source ||
|
|
135
|
+
!EVIDENCE_SOURCES.has(source) ||
|
|
136
|
+
!service ||
|
|
137
|
+
!suiteName ||
|
|
138
|
+
!selectionType ||
|
|
139
|
+
!framework ||
|
|
140
|
+
!testFilePath ||
|
|
141
|
+
coveredNodeIds.length === 0
|
|
142
|
+
) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const confidence = normalizeOptionalString(value.confidence);
|
|
146
|
+
const details = normalizeEvidenceDetails(value.details);
|
|
147
|
+
return {
|
|
148
|
+
id,
|
|
149
|
+
source,
|
|
150
|
+
service,
|
|
151
|
+
suiteName,
|
|
152
|
+
selectionType,
|
|
153
|
+
framework,
|
|
154
|
+
testFilePath,
|
|
155
|
+
coveredNodeIds,
|
|
156
|
+
...(confidence && CONFIDENCE_LEVELS.has(confidence) ? { confidence } : {}),
|
|
157
|
+
...(details ? { details } : {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function normalizeCoverageGraph(value) {
|
|
162
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
163
|
+
const schemaVersion =
|
|
164
|
+
Number.isInteger(value.schemaVersion) && value.schemaVersion > 0
|
|
165
|
+
? value.schemaVersion
|
|
166
|
+
: TESTKIT_COVERAGE_GRAPH_VERSION;
|
|
167
|
+
const nodes = Array.isArray(value.nodes) ? value.nodes.map(normalizeCoverageGraphNode).filter(Boolean) : [];
|
|
168
|
+
const edges = Array.isArray(value.edges) ? value.edges.map(normalizeCoverageGraphEdge).filter(Boolean) : [];
|
|
169
|
+
const evidence = Array.isArray(value.evidence)
|
|
170
|
+
? value.evidence.map(normalizeCoverageEvidence).filter(Boolean)
|
|
171
|
+
: [];
|
|
172
|
+
if (nodes.length === 0) return null;
|
|
173
|
+
return {
|
|
174
|
+
schemaVersion,
|
|
175
|
+
nodes,
|
|
176
|
+
edges,
|
|
177
|
+
evidence,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function isPageOverlayResponse(value) {
|
|
182
|
+
return Boolean(normalizePageOverlayResponse(value));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function normalizePageOverlayResponse(value) {
|
|
186
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
187
|
+
if (value.protocolVersion !== TESTKIT_BROWSER_PROTOCOL_VERSION) return null;
|
|
188
|
+
if (!value.page || typeof value.page !== "object") return null;
|
|
189
|
+
if (!value.match || typeof value.match !== "object") return null;
|
|
190
|
+
if (!value.summary || typeof value.summary !== "object") return null;
|
|
191
|
+
if (!Array.isArray(value.coverage) || !Array.isArray(value.failures)) return null;
|
|
192
|
+
if (!FAILURE_STATES.has(value.summary.failureState)) return null;
|
|
193
|
+
if (!COVERAGE_STATES.has(value.summary.coverageState)) return null;
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function createBridgeErrorResponse(code, message) {
|
|
198
|
+
return {
|
|
199
|
+
protocolVersion: TESTKIT_BROWSER_PROTOCOL_VERSION,
|
|
200
|
+
error: {
|
|
201
|
+
code,
|
|
202
|
+
message,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeEvidenceDetails(value) {
|
|
208
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
209
|
+
const requestPaths = normalizeStringArray(value.requestPaths);
|
|
210
|
+
const route = normalizeOptionalString(value.route);
|
|
211
|
+
if (requestPaths.length === 0 && !route) return null;
|
|
212
|
+
return {
|
|
213
|
+
...(requestPaths.length > 0 ? { requestPaths } : {}),
|
|
214
|
+
...(route ? { route } : {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function normalizeMetadataRecord(value) {
|
|
219
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
220
|
+
const normalized = {};
|
|
221
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
222
|
+
const normalizedKey = normalizeOptionalString(key);
|
|
223
|
+
if (!normalizedKey) continue;
|
|
224
|
+
if (
|
|
225
|
+
entry === null ||
|
|
226
|
+
typeof entry === "string" ||
|
|
227
|
+
typeof entry === "number" ||
|
|
228
|
+
typeof entry === "boolean"
|
|
229
|
+
) {
|
|
230
|
+
normalized[normalizedKey] = entry;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return Object.keys(normalized).length > 0 ? normalized : null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizeStringArray(value) {
|
|
237
|
+
if (!Array.isArray(value)) return [];
|
|
238
|
+
return value.map(normalizeOptionalString).filter(Boolean);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizeOptionalString(value) {
|
|
242
|
+
if (typeof value !== "string") return null;
|
|
243
|
+
const normalized = value.trim();
|
|
244
|
+
return normalized.length > 0 ? normalized : null;
|
|
245
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
TESTKIT_BROWSER_PROTOCOL_VERSION,
|
|
4
|
+
TESTKIT_COVERAGE_GRAPH_VERSION,
|
|
5
|
+
isPageOverlayResponse,
|
|
6
|
+
normalizeBrowserTarget,
|
|
7
|
+
normalizeCoverageEvidence,
|
|
8
|
+
normalizeCoverageGraph,
|
|
9
|
+
normalizeCoverageGraphEdge,
|
|
10
|
+
normalizeCoverageGraphNode,
|
|
11
|
+
} from "./index.mjs";
|
|
12
|
+
|
|
13
|
+
describe("testkit browser protocol", () => {
|
|
14
|
+
it("normalizes browser targets", () => {
|
|
15
|
+
expect(
|
|
16
|
+
normalizeBrowserTarget({
|
|
17
|
+
kind: "testId",
|
|
18
|
+
value: "coverage-refresh-button",
|
|
19
|
+
confidence: "high",
|
|
20
|
+
})
|
|
21
|
+
).toEqual({
|
|
22
|
+
kind: "testId",
|
|
23
|
+
value: "coverage-refresh-button",
|
|
24
|
+
confidence: "high",
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("normalizes coverage graph nodes and edges", () => {
|
|
29
|
+
expect(
|
|
30
|
+
normalizeCoverageGraphNode({
|
|
31
|
+
id: "page_view:web:/coverage",
|
|
32
|
+
kind: "page_view",
|
|
33
|
+
service: "web",
|
|
34
|
+
label: "Coverage",
|
|
35
|
+
route: "/coverage",
|
|
36
|
+
})
|
|
37
|
+
).toEqual({
|
|
38
|
+
id: "page_view:web:/coverage",
|
|
39
|
+
kind: "page_view",
|
|
40
|
+
service: "web",
|
|
41
|
+
label: "Coverage",
|
|
42
|
+
route: "/coverage",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
normalizeCoverageGraphEdge({
|
|
47
|
+
id: "edge-1",
|
|
48
|
+
kind: "requests",
|
|
49
|
+
from: "page_view:web:/coverage",
|
|
50
|
+
to: "client_request:web:/api/coverage",
|
|
51
|
+
confidence: "high",
|
|
52
|
+
})
|
|
53
|
+
).toEqual({
|
|
54
|
+
id: "edge-1",
|
|
55
|
+
kind: "requests",
|
|
56
|
+
from: "page_view:web:/coverage",
|
|
57
|
+
to: "client_request:web:/api/coverage",
|
|
58
|
+
confidence: "high",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("normalizes positive coverage evidence", () => {
|
|
63
|
+
expect(
|
|
64
|
+
normalizeCoverageEvidence({
|
|
65
|
+
id: "evidence-1",
|
|
66
|
+
source: "convention",
|
|
67
|
+
confidence: "high",
|
|
68
|
+
service: "web",
|
|
69
|
+
suiteName: "coverage",
|
|
70
|
+
selectionType: "pw",
|
|
71
|
+
framework: "playwright",
|
|
72
|
+
testFilePath: "app/__testkit__/coverage/home.pw.testkit.ts",
|
|
73
|
+
coveredNodeIds: ["page_view:web:/coverage"],
|
|
74
|
+
details: {
|
|
75
|
+
route: "/coverage",
|
|
76
|
+
requestPaths: ["/api/coverage"],
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
).toEqual({
|
|
80
|
+
id: "evidence-1",
|
|
81
|
+
source: "convention",
|
|
82
|
+
confidence: "high",
|
|
83
|
+
service: "web",
|
|
84
|
+
suiteName: "coverage",
|
|
85
|
+
selectionType: "pw",
|
|
86
|
+
framework: "playwright",
|
|
87
|
+
testFilePath: "app/__testkit__/coverage/home.pw.testkit.ts",
|
|
88
|
+
coveredNodeIds: ["page_view:web:/coverage"],
|
|
89
|
+
details: {
|
|
90
|
+
route: "/coverage",
|
|
91
|
+
requestPaths: ["/api/coverage"],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("normalizes graph payloads", () => {
|
|
97
|
+
expect(
|
|
98
|
+
normalizeCoverageGraph({
|
|
99
|
+
schemaVersion: TESTKIT_COVERAGE_GRAPH_VERSION,
|
|
100
|
+
nodes: [
|
|
101
|
+
{
|
|
102
|
+
id: "page_view:web:/coverage",
|
|
103
|
+
kind: "page_view",
|
|
104
|
+
service: "web",
|
|
105
|
+
label: "Coverage",
|
|
106
|
+
route: "/coverage",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
edges: [],
|
|
110
|
+
evidence: [],
|
|
111
|
+
})
|
|
112
|
+
).toEqual({
|
|
113
|
+
schemaVersion: TESTKIT_COVERAGE_GRAPH_VERSION,
|
|
114
|
+
nodes: [
|
|
115
|
+
{
|
|
116
|
+
id: "page_view:web:/coverage",
|
|
117
|
+
kind: "page_view",
|
|
118
|
+
service: "web",
|
|
119
|
+
label: "Coverage",
|
|
120
|
+
route: "/coverage",
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
edges: [],
|
|
124
|
+
evidence: [],
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("recognizes valid page overlay responses", () => {
|
|
129
|
+
expect(
|
|
130
|
+
isPageOverlayResponse({
|
|
131
|
+
protocolVersion: TESTKIT_BROWSER_PROTOCOL_VERSION,
|
|
132
|
+
page: { url: "http://localhost:3000/coverage", origin: "http://localhost:3000", route: "/coverage" },
|
|
133
|
+
match: {
|
|
134
|
+
protocolVersion: TESTKIT_BROWSER_PROTOCOL_VERSION,
|
|
135
|
+
url: "http://localhost:3000/coverage",
|
|
136
|
+
origin: "http://localhost:3000",
|
|
137
|
+
route: "/coverage",
|
|
138
|
+
matched: true,
|
|
139
|
+
product: { name: "web", directory: "/tmp/web" },
|
|
140
|
+
service: { name: "web", baseUrl: "http://localhost:3000", origin: "http://localhost:3000" },
|
|
141
|
+
},
|
|
142
|
+
run: { artifactAvailable: true, generatedAt: null, status: "failed" },
|
|
143
|
+
summary: {
|
|
144
|
+
failureState: "failing",
|
|
145
|
+
coverageState: "covered",
|
|
146
|
+
relatedFailureCount: 1,
|
|
147
|
+
relatedCoverageCount: 1,
|
|
148
|
+
},
|
|
149
|
+
failures: [],
|
|
150
|
+
coverage: [],
|
|
151
|
+
})
|
|
152
|
+
).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
4
4
|
"description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
6
9
|
"types": "./lib/index.d.ts",
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
@@ -38,7 +41,7 @@
|
|
|
38
41
|
},
|
|
39
42
|
"scripts": {
|
|
40
43
|
"test": "vitest run",
|
|
41
|
-
"test:unit": "vitest run lib",
|
|
44
|
+
"test:unit": "vitest run lib packages",
|
|
42
45
|
"test:integration": "vitest run test/integration",
|
|
43
46
|
"test:system": "vitest run test/system --passWithNoTests"
|
|
44
47
|
},
|
|
@@ -47,11 +50,17 @@
|
|
|
47
50
|
"lib/",
|
|
48
51
|
"vendor/"
|
|
49
52
|
],
|
|
53
|
+
"bundleDependencies": [
|
|
54
|
+
"@elench/testkit-bridge",
|
|
55
|
+
"@elench/testkit-protocol"
|
|
56
|
+
],
|
|
50
57
|
"devDependencies": {
|
|
51
58
|
"@playwright/test": "^1.52.0",
|
|
52
59
|
"vitest": "^3.2.4"
|
|
53
60
|
},
|
|
54
61
|
"dependencies": {
|
|
62
|
+
"@elench/testkit-bridge": "0.1.58",
|
|
63
|
+
"@elench/testkit-protocol": "0.1.58",
|
|
55
64
|
"@babel/code-frame": "^7.29.0",
|
|
56
65
|
"@oclif/core": "^4.10.6",
|
|
57
66
|
"esbuild": "^0.25.11",
|