@skyramp/mcp 0.3.4 → 0.3.6-rc.1
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/build/playwright/registerPlaywrightTools.js +92 -30
- package/build/playwright/traceRecordingPrompt.d.ts +6 -0
- package/build/playwright/traceRecordingPrompt.js +6 -2
- package/build/prompts/code-reuse.d.ts +1 -2
- package/build/prompts/code-reuse.js +182 -77
- package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
- package/build/prompts/modularization/integration-test-modularization.js +83 -41
- package/build/prompts/modularization/render.d.ts +18 -0
- package/build/prompts/modularization/render.js +12 -0
- package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
- package/build/prompts/modularization/ui-test-modularization.js +89 -47
- package/build/prompts/pom-aware-code-reuse.js +3 -1
- package/build/prompts/shared-helper-policy.d.ts +57 -0
- package/build/prompts/shared-helper-policy.js +135 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
- package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
- package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
- package/build/prompts/test-recommendation/recommendationShared.js +90 -16
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
- package/build/prompts/testbot/testbot-prompts.js +88 -33
- package/build/recommendation/budgeters/shared.js +105 -27
- package/build/recommendation/discriminators.js +13 -2
- package/build/recommendation/planRanker.d.ts +6 -6
- package/build/recommendation/planRanker.js +6 -61
- package/build/services/AnalyticsService.d.ts +7 -0
- package/build/services/AnalyticsService.js +7 -1
- package/build/services/ModularizationService.js +1 -3
- package/build/services/TestDiscoveryService.d.ts +0 -2
- package/build/services/TestDiscoveryService.js +2 -37
- package/build/services/TestGenerationService.d.ts +16 -0
- package/build/services/TestGenerationService.js +86 -10
- package/build/services/containerEnv.js +13 -12
- package/build/tools/code-refactor/codeReuseTool.js +279 -93
- package/build/tools/code-refactor/enhance-state.d.ts +49 -0
- package/build/tools/code-refactor/enhance-state.js +109 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
- package/build/tools/code-refactor/modularizationTool.js +9 -2
- package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
- package/build/tools/code-refactor/reuse-outcome.js +14 -4
- package/build/tools/code-refactor/reuse-state.d.ts +127 -5
- package/build/tools/code-refactor/reuse-state.js +628 -16
- package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
- package/build/tools/code-refactor/utils-verify-gates.js +100 -0
- package/build/tools/code-refactor/verify-gates.d.ts +2 -1
- package/build/tools/code-refactor/verify-gates.js +90 -25
- package/build/tools/executeSkyrampTestTool.d.ts +19 -0
- package/build/tools/executeSkyrampTestTool.js +158 -8
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
- package/build/tools/generate-tests/generateUIRestTool.js +22 -0
- package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
- package/build/tools/generate-tests/scenarioLint.js +127 -19
- package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
- package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
- package/build/tools/submitReportTool.d.ts +38 -38
- package/build/tools/submitReportTool.js +487 -104
- package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
- package/build/tools/test-management/analyzeChangesTool.js +75 -12
- package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
- package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
- package/build/tools/test-management/registerTestPlanTool.js +149 -23
- package/build/types/Recommendation.d.ts +34 -5
- package/build/types/RepositoryAnalysis.d.ts +133 -114
- package/build/types/RepositoryAnalysis.js +1 -1
- package/build/types/ReuseOutcome.d.ts +102 -6
- package/build/types/ReuseOutcome.js +16 -2
- package/build/types/TestRecommendation.js +21 -3
- package/build/types/TestTypes.js +14 -8
- package/build/types/TestbotReport.d.ts +25 -3
- package/build/types/index.d.ts +2 -2
- package/build/types/index.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +69 -1
- package/build/utils/AnalysisStateManager.js +69 -5
- package/build/utils/branchDiff.d.ts +10 -0
- package/build/utils/branchDiff.js +28 -0
- package/build/utils/changedRoutes.d.ts +29 -0
- package/build/utils/changedRoutes.js +87 -0
- package/build/utils/featureFlags.d.ts +21 -0
- package/build/utils/featureFlags.js +23 -0
- package/build/utils/frontendIntegration.js +34 -4
- package/build/utils/importerHop.d.ts +2 -8
- package/build/utils/importerHop.js +15 -53
- package/build/utils/pathMatching.d.ts +38 -0
- package/build/utils/pathMatching.js +71 -0
- package/build/utils/pathSignatures.d.ts +22 -0
- package/build/utils/pathSignatures.js +57 -0
- package/build/utils/planMatchKeys.d.ts +16 -3
- package/build/utils/planMatchKeys.js +26 -10
- package/build/utils/pluralization.d.ts +10 -0
- package/build/utils/pluralization.js +18 -0
- package/build/utils/pom-catalog-parse.d.ts +52 -0
- package/build/utils/pom-catalog-parse.js +141 -0
- package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
- package/build/utils/pom-scope/selector-extractor.js +34 -8
- package/build/utils/pom-verify/verify.d.ts +6 -5
- package/build/utils/pom-verify/verify.js +8 -6
- package/build/utils/reportLanguage.d.ts +43 -0
- package/build/utils/reportLanguage.js +125 -0
- package/build/utils/reportVerification.d.ts +74 -4
- package/build/utils/reportVerification.js +259 -3
- package/build/utils/reuseRouting.d.ts +3 -0
- package/build/utils/reuseRouting.js +50 -0
- package/build/utils/routeParsers.d.ts +2 -0
- package/build/utils/routeParsers.js +65 -8
- package/build/utils/scenarioDrafting.d.ts +1 -1
- package/build/utils/scenarioDrafting.js +57 -45
- package/build/utils/subjectEndpoints.d.ts +19 -0
- package/build/utils/subjectEndpoints.js +98 -0
- package/build/utils/testFileClassification.d.ts +11 -0
- package/build/utils/testFileClassification.js +47 -0
- package/build/utils/uiPageEnumerator.d.ts +45 -19
- package/build/utils/uiPageEnumerator.js +95 -51
- package/build/utils/utils-verify/allow.d.ts +16 -0
- package/build/utils/utils-verify/allow.js +68 -0
- package/build/utils/utils-verify/call-sites.d.ts +34 -0
- package/build/utils/utils-verify/call-sites.js +154 -0
- package/build/utils/utils-verify/index.d.ts +7 -0
- package/build/utils/utils-verify/index.js +7 -0
- package/build/utils/utils-verify/language-spec.d.ts +91 -0
- package/build/utils/utils-verify/language-spec.js +210 -0
- package/build/utils/utils-verify/locate.d.ts +39 -0
- package/build/utils/utils-verify/locate.js +199 -0
- package/build/utils/utils-verify/parse.d.ts +34 -0
- package/build/utils/utils-verify/parse.js +177 -0
- package/build/utils/utils-verify/stage.d.ts +24 -0
- package/build/utils/utils-verify/stage.js +107 -0
- package/build/utils/utils-verify/verify.d.ts +63 -0
- package/build/utils/utils-verify/verify.js +168 -0
- package/build/utils/utils.d.ts +3 -1
- package/build/utils/utils.js +3 -1
- package/build/workspace/workspace.d.ts +32 -32
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.VEfqi1qN.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
|
@@ -24,6 +24,7 @@ export declare const uiTestSchema: {
|
|
|
24
24
|
force: z.ZodDefault<z.ZodBoolean>;
|
|
25
25
|
codeReuse: z.ZodDefault<z.ZodBoolean>;
|
|
26
26
|
modularizeCode: z.ZodDefault<z.ZodDefault<z.ZodBoolean>>;
|
|
27
|
+
allowTraceReuse: z.ZodDefault<z.ZodBoolean>;
|
|
27
28
|
language: z.ZodNativeEnum<typeof import("../../types/TestTypes.js").ProgrammingLanguage>;
|
|
28
29
|
framework: z.ZodString;
|
|
29
30
|
};
|
|
@@ -5,6 +5,8 @@ import { TestGenerationService, } from "../../services/TestGenerationService.js"
|
|
|
5
5
|
import { normalizeLanguageParams, resolveParamAliases, } from "../../utils/normalizeParams.js";
|
|
6
6
|
import { getPersonaPrefix } from "../../prompts/personas.js";
|
|
7
7
|
import { isTestbotEnabled } from "../../utils/featureFlags.js";
|
|
8
|
+
import { toolError } from "../../utils/utils.js";
|
|
9
|
+
import { checkTraceReuse, recordTraceOutput } from "./trace-reuse-guard.js";
|
|
8
10
|
const TOOL_NAME = "skyramp_ui_test_generation";
|
|
9
11
|
export class UITestService extends TestGenerationService {
|
|
10
12
|
getTestType() {
|
|
@@ -32,6 +34,11 @@ export class UITestService extends TestGenerationService {
|
|
|
32
34
|
});
|
|
33
35
|
if (result.isError)
|
|
34
36
|
return result;
|
|
37
|
+
// On modularize-first flows the base hand-off sequences the whole post-gen
|
|
38
|
+
// chain (enhance → modularize → reuse); a second block here would instruct
|
|
39
|
+
// enhance twice and re-extract the consolidated helpers.
|
|
40
|
+
if (this.emitsModularizeFirstHandOff(params))
|
|
41
|
+
return result;
|
|
35
42
|
if (!params.enhanceAssertions && !params.modularizeCode)
|
|
36
43
|
return result;
|
|
37
44
|
const content = [...result.content];
|
|
@@ -83,6 +90,13 @@ export const uiTestSchema = {
|
|
|
83
90
|
force: baseSchema.shape.force,
|
|
84
91
|
codeReuse: codeRefactoringSchema.shape.codeReuse,
|
|
85
92
|
modularizeCode: codeRefactoringSchema.shape.modularizeCode.default(true),
|
|
93
|
+
allowTraceReuse: z
|
|
94
|
+
.boolean()
|
|
95
|
+
.default(false)
|
|
96
|
+
.describe("Explicit override for the trace-reuse guard. By default, generating a second differently-named spec " +
|
|
97
|
+
"from a trace zip that already produced one is rejected — one recording covers one scenario, and reusing " +
|
|
98
|
+
"it manufactures identical tests under names the recording never covered. Set true only when the same " +
|
|
99
|
+
"recording genuinely covers the new scenario end-to-end."),
|
|
86
100
|
};
|
|
87
101
|
export function registerUITestTool(server) {
|
|
88
102
|
server.registerTool(TOOL_NAME, {
|
|
@@ -123,8 +137,16 @@ This tells you exactly which frontend files changed so you record traces for the
|
|
|
123
137
|
zip: "playwrightInput",
|
|
124
138
|
});
|
|
125
139
|
normalizeLanguageParams(params);
|
|
140
|
+
const traceReuse = await checkTraceReuse(params.playwrightInput, params.output, params.outputDir, !!params.allowTraceReuse, "ui");
|
|
141
|
+
if (traceReuse.rejection)
|
|
142
|
+
return toolError(traceReuse.rejection);
|
|
126
143
|
const service = new UITestService();
|
|
127
144
|
const result = await service.generateTest(params);
|
|
145
|
+
// Register only what generation actually produced: recording at check
|
|
146
|
+
// time poisoned the registry on failures (name-validation rejections,
|
|
147
|
+
// codegen errors), refusing the corrected retry for reusing its own trace.
|
|
148
|
+
if (!result.isError && traceReuse.key && traceReuse.identity)
|
|
149
|
+
recordTraceOutput(traceReuse.key, traceReuse.identity);
|
|
128
150
|
AnalyticsService.pushTestGenerationToolEvent(TOOL_NAME, result, params).catch(() => {
|
|
129
151
|
// Silently ignore analytics errors
|
|
130
152
|
});
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* failure family. This lint checks the bait at write time:
|
|
10
10
|
*
|
|
11
11
|
* - a referenced value produced ONLY by a later step → error (step order)
|
|
12
|
+
* (with ONE remedy: reorder when that later step creates the referenced
|
|
13
|
+
* resource, else raise the entropy of its fabricated id — see below)
|
|
12
14
|
* - a reference produced by no prior step at all → warning (hardcode)
|
|
13
15
|
* - a reference produced by multiple prior steps → warning (ambiguous)
|
|
14
16
|
*
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* failure family. This lint checks the bait at write time:
|
|
10
10
|
*
|
|
11
11
|
* - a referenced value produced ONLY by a later step → error (step order)
|
|
12
|
+
* (with ONE remedy: reorder when that later step creates the referenced
|
|
13
|
+
* resource, else raise the entropy of its fabricated id — see below)
|
|
12
14
|
* - a reference produced by no prior step at all → warning (hardcode)
|
|
13
15
|
* - a reference produced by multiple prior steps → warning (ambiguous)
|
|
14
16
|
*
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
* fields with id-like keys, or keys resource-affine to a producing step's
|
|
17
19
|
* collection path) to keep noise down — mirroring the reader's own matching.
|
|
18
20
|
*/
|
|
21
|
+
import { singularize } from "../../utils/pluralization.js";
|
|
19
22
|
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
20
23
|
function isIdLikeKey(key) {
|
|
21
24
|
return /^(id|pk|uuid)$/i.test(key) || /_id$/i.test(key) || /[a-z]Id$/.test(key);
|
|
@@ -23,6 +26,14 @@ function isIdLikeKey(key) {
|
|
|
23
26
|
function isIdishSegment(seg) {
|
|
24
27
|
return /^\d+$/.test(seg) || UUID_RE.test(seg);
|
|
25
28
|
}
|
|
29
|
+
/** The collection an id path-segment hangs off: "orders" in /orders/1/confirm. */
|
|
30
|
+
function owningSegment(segs, idIndex) {
|
|
31
|
+
for (let i = idIndex - 1; i >= 0; i--) {
|
|
32
|
+
if (!isIdishSegment(segs[i]))
|
|
33
|
+
return segs[i].toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
26
37
|
function lastStaticSegment(path) {
|
|
27
38
|
const segs = path.split("/").filter((s) => s !== "");
|
|
28
39
|
for (let i = segs.length - 1; i >= 0; i--) {
|
|
@@ -31,13 +42,45 @@ function lastStaticSegment(path) {
|
|
|
31
42
|
}
|
|
32
43
|
return "";
|
|
33
44
|
}
|
|
45
|
+
/** Strip the id suffix from a body key, accepting exactly the two spellings
|
|
46
|
+
* `isIdLikeKey` accepts and no others: `_id` case-insensitively (it reads
|
|
47
|
+
* `/_id$/i`, so `provider_ID` is a reference too) and a camel `Id` after a
|
|
48
|
+
* lower-case letter. Words that merely end in those letters (`uuid`, `valid`)
|
|
49
|
+
* keep their spelling — a blanket case-insensitive strip would turn `uuid`
|
|
50
|
+
* into `uu` and match any 4-character collection. */
|
|
51
|
+
function stripIdSuffix(key) {
|
|
52
|
+
if (/_id$/i.test(key))
|
|
53
|
+
return key.slice(0, -3);
|
|
54
|
+
if (/[a-z]Id$/.test(key))
|
|
55
|
+
return key.slice(0, -2);
|
|
56
|
+
return key;
|
|
57
|
+
}
|
|
34
58
|
/** "provider" ↔ "providers" style prefix affinity between a body key and a
|
|
35
59
|
* producing step's collection segment. Minimum overlap of 4 chars keeps
|
|
36
|
-
* short generic keys from matching everything.
|
|
60
|
+
* short generic keys from matching everything.
|
|
61
|
+
*
|
|
62
|
+
* The id suffix comes off first: `product_id` and `products` are prefixes of
|
|
63
|
+
* neither direction, and an FK is spelled with that suffix far more often than
|
|
64
|
+
* without, so leaving it on made the affinity fail on the commonest shape it
|
|
65
|
+
* exists to recognise — and a mis-ordered create then received the remedy
|
|
66
|
+
* meant for a fixed-position sub-action. Both sides are then singularized,
|
|
67
|
+
* because a prefix test cannot bridge an irregular plural: "category" is a
|
|
68
|
+
* prefix of neither "categories" nor the reverse. */
|
|
37
69
|
function keyResourceAffinity(key, segment) {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
70
|
+
const rawKey = stripIdSuffix(key).toLowerCase();
|
|
71
|
+
const rawSegment = segment.toLowerCase();
|
|
72
|
+
const k = singularize(rawKey);
|
|
73
|
+
const s = singularize(rawSegment);
|
|
74
|
+
// Equal singular forms are the strongest affinity there is, so they do not
|
|
75
|
+
// need the length floor — and they must skip it, because singularizing first
|
|
76
|
+
// collapses a 4-letter plural under it (`tags` -> `tag`, and `jobs`, `orgs`,
|
|
77
|
+
// `keys`, `pets` alike). `tag_id` with a later `POST /tags` then read as a
|
|
78
|
+
// sub-action and took the remedy that ships a hardcoded value.
|
|
79
|
+
if (k === s)
|
|
80
|
+
return true;
|
|
81
|
+
// The floor reads the RAW words for the same reason: `cases` -> `cas` is a
|
|
82
|
+
// 3-character over-strip that would fail a comparison the raw pair passes.
|
|
83
|
+
if (rawKey.length < 4 || rawSegment.length < 4)
|
|
41
84
|
return false;
|
|
42
85
|
return k.startsWith(s) || s.startsWith(k);
|
|
43
86
|
}
|
|
@@ -85,27 +128,40 @@ export function lintScenarioChaining(steps) {
|
|
|
85
128
|
if (body === undefined)
|
|
86
129
|
return;
|
|
87
130
|
const segment = typeof step.path === "string" ? lastStaticSegment(step.path) : "";
|
|
131
|
+
const creates = (step.method ?? "").toUpperCase() === "POST";
|
|
132
|
+
const collectionCreate = creates &&
|
|
133
|
+
typeof step.path === "string" &&
|
|
134
|
+
!step.path.split("/").some((seg) => seg !== "" && isIdishSegment(seg));
|
|
88
135
|
walkPrimitives(body, "", (key, value) => {
|
|
89
136
|
if (!isIdLikeKey(key))
|
|
90
137
|
return;
|
|
91
138
|
const list = producers.get(value) ?? [];
|
|
92
|
-
|
|
93
|
-
|
|
139
|
+
const existing = list.find((p) => p.step === idx);
|
|
140
|
+
if (existing) {
|
|
141
|
+
if (!existing.keys.includes(key))
|
|
142
|
+
existing.keys.push(key);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
list.push({ step: idx, segment, keys: [key], creates, collectionCreate });
|
|
146
|
+
}
|
|
94
147
|
producers.set(value, list);
|
|
95
148
|
});
|
|
96
149
|
});
|
|
97
150
|
steps.forEach((step, idx) => {
|
|
98
151
|
// Collect this step's id-like references, one per distinct value, so a
|
|
99
152
|
// value referenced in both the path and the body (or repeated in an
|
|
100
|
-
// array) yields a single classification and a single message.
|
|
101
|
-
|
|
153
|
+
// array) yields a single classification and a single message. `resource`
|
|
154
|
+
// names the thing the reference points at ("provider", "orders") and only
|
|
155
|
+
// picks the remedy wording below — it never changes what is flagged.
|
|
156
|
+
const refs = new Map();
|
|
102
157
|
// Path segments that look like ids are always FK-ish (path params are ids).
|
|
103
158
|
if (typeof step.path === "string") {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
159
|
+
const segs = step.path.split("/").filter((s) => s !== "");
|
|
160
|
+
segs.forEach((seg, i) => {
|
|
161
|
+
if (!isIdishSegment(seg) || refs.has(seg))
|
|
162
|
+
return;
|
|
163
|
+
refs.set(seg, { desc: "path segment", resource: owningSegment(segs, i) });
|
|
164
|
+
});
|
|
109
165
|
}
|
|
110
166
|
// Body values: only id-like keys, or keys resource-affine to a producer.
|
|
111
167
|
const body = parseJsonObject(step.requestBody);
|
|
@@ -117,16 +173,44 @@ export function lintScenarioChaining(steps) {
|
|
|
117
173
|
const relevant = isIdLikeKey(key) || producerList.some((p) => keyResourceAffinity(key, p.segment));
|
|
118
174
|
if (!relevant)
|
|
119
175
|
return;
|
|
120
|
-
refs.set(value, `body field "${key}"
|
|
176
|
+
refs.set(value, { desc: `body field "${key}"`, resource: key, key });
|
|
121
177
|
});
|
|
122
178
|
}
|
|
123
|
-
for (const [value,
|
|
179
|
+
for (const [value, ref] of refs) {
|
|
180
|
+
const refDesc = ref.desc;
|
|
124
181
|
const all = producers.get(value) ?? [];
|
|
125
182
|
const earlier = all.filter((p) => p.step < idx);
|
|
126
183
|
// Strictly-later producers are a fixable ordering mistake; a producer in
|
|
127
184
|
// the SAME step (e.g. a create whose own response echoes the id) is just
|
|
128
185
|
// unchainable — Go only chains from strictly-prior steps.
|
|
129
186
|
const later = all.filter((p) => p.step > idx);
|
|
187
|
+
// An echo of the referencing field is not a producer. A sub-action
|
|
188
|
+
// response that mirrors the parent's child collection re-emits the very
|
|
189
|
+
// field the earlier step SENT — `items[].product_id` comes back on every
|
|
190
|
+
// confirm/ship/deliver — so no value of that field can clear the error,
|
|
191
|
+
// and the only escape is to delete the array from the fabricated
|
|
192
|
+
// response. Measured on run 32395388709: the agent did exactly that, and
|
|
193
|
+
// the assertion judge then docked 10 points for the missing array
|
|
194
|
+
// coverage. A DIFFERENT id key carrying the same value is still an error:
|
|
195
|
+
// that is the genuine `product_id: 1` vs `order_id: 1` ambiguity, where
|
|
196
|
+
// raising the fabricated id is both followable and correct.
|
|
197
|
+
// A later step that creates the very resource this reference points at.
|
|
198
|
+
// Found BEFORE the echo test, because a create is a genuine producer: a
|
|
199
|
+
// conventional `POST /orders` answering `{ order_id }` reuses the FK's own
|
|
200
|
+
// spelling and would otherwise satisfy the echo condition below, silently
|
|
201
|
+
// downgrading a real ordering mistake to a hardcoding warning.
|
|
202
|
+
// The second lookup catches an ALIASED foreign key. `customer_id` created
|
|
203
|
+
// by `POST /clients` shares no prefix with "clients", so affinity cannot
|
|
204
|
+
// see it, and the echo test below then downgraded a genuine ordering
|
|
205
|
+
// mistake to a warning. A collection create planting the referencing key
|
|
206
|
+
// is a producer whatever the key is called; a sub-action POST is excluded
|
|
207
|
+
// by `collectionCreate`, so the measured `/orders/{id}/confirm` echo is
|
|
208
|
+
// untouched.
|
|
209
|
+
const creator = later.find((p) => p.creates && keyResourceAffinity(ref.resource, p.segment)) ??
|
|
210
|
+
later.find((p) => p.collectionCreate && ref.key !== undefined && p.keys.includes(ref.key));
|
|
211
|
+
const echoesReferencingField = ref.key !== undefined &&
|
|
212
|
+
creator === undefined &&
|
|
213
|
+
later.every((p) => p.keys.every((k) => k === ref.key));
|
|
130
214
|
if (earlier.length > 0) {
|
|
131
215
|
if (earlier.length > 1) {
|
|
132
216
|
warnings.push(`Value "${value}" referenced by ${stepLabel(steps, idx)} is produced by multiple prior steps ` +
|
|
@@ -134,10 +218,34 @@ export function lintScenarioChaining(steps) {
|
|
|
134
218
|
`use distinct, high-entropy ids (e.g. fresh UUIDs) in fabricated responses.`);
|
|
135
219
|
}
|
|
136
220
|
}
|
|
137
|
-
else if (later.length > 0) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
221
|
+
else if (later.length > 0 && !echoesReferencingField) {
|
|
222
|
+
// Two causes need OPPOSITE fixes, and only one of them can be
|
|
223
|
+
// reordered. Naming both is worse than naming one: renumbering the
|
|
224
|
+
// later id ALWAYS clears the error because it deletes the producer, so
|
|
225
|
+
// an agent offered both picks it and the mis-ordered batch goes green
|
|
226
|
+
// while still hardcoding the value. Pick the remedy here instead.
|
|
227
|
+
if (creator) {
|
|
228
|
+
// The later step creates the very resource being referenced, so it
|
|
229
|
+
// is a real ordering mistake and the value is already correct.
|
|
230
|
+
errors.push(`${stepLabel(steps, idx)} references value "${value}" (${refDesc}) that is only produced by a ` +
|
|
231
|
+
`later ${stepLabel(steps, creator.step)} — that step creates the "${creator.segment}" this ` +
|
|
232
|
+
`reference points at, so reorder the batch to put step ${creator.step + 1} before ` +
|
|
233
|
+
`step ${idx + 1} and keep the value unchanged (the generated test can only chain from PRIOR steps).`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// The later step is a sub-action on a fixed rung of a state machine
|
|
237
|
+
// (confirm/ship/deliver). It cannot move, so the collision is the
|
|
238
|
+
// fabricated id being too guessable — raise its entropy.
|
|
239
|
+
const producer = later[0];
|
|
240
|
+
const shape = producer.segment
|
|
241
|
+
? `is a "${producer.segment}" sub-action, not a create of what this reference points at`
|
|
242
|
+
: `does not create what this reference points at`;
|
|
243
|
+
errors.push(`${stepLabel(steps, idx)} references value "${value}" (${refDesc}) that is only produced by a ` +
|
|
244
|
+
`later ${stepLabel(steps, producer.step)} — that step ${shape}, so its position is fixed and ` +
|
|
245
|
+
`reordering cannot fix this. Change the id in step ${producer.step + 1}'s fabricated ` +
|
|
246
|
+
`responseBody to a distinct high-entropy value (e.g. a fresh UUID or a large random integer) ` +
|
|
247
|
+
`so it stops colliding with "${value}" here.`);
|
|
248
|
+
}
|
|
141
249
|
}
|
|
142
250
|
else {
|
|
143
251
|
warnings.push(`${stepLabel(steps, idx)} ${refDesc} value "${value}" does not appear in any prior step's ` +
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Test-only: clear the in-process trace→outputs registry. */
|
|
2
|
+
export declare function resetTraceReuseGuard(): void;
|
|
3
|
+
export interface TraceReuseCheck {
|
|
4
|
+
/** Set when the call must be rejected (same zip, different output). */
|
|
5
|
+
rejection?: string;
|
|
6
|
+
/** Content hash of the zip — pass to recordTraceOutput on success. */
|
|
7
|
+
key?: string;
|
|
8
|
+
/** The resolved output identity — pass to recordTraceOutput on success. */
|
|
9
|
+
identity?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Read-only reuse check. Returns a rejection when this zip's content already
|
|
13
|
+
* generated a DIFFERENT output identity and `allowTraceReuse` is not set; on a
|
|
14
|
+
* clean check, returns the hash/identity for the caller to record after
|
|
15
|
+
* generation succeeds. Fails open (empty result) when the zip is unreadable —
|
|
16
|
+
* generation will surface its own error for that.
|
|
17
|
+
*/
|
|
18
|
+
export declare function checkTraceReuse(playwrightInput: string | undefined, output: string | undefined, outputDir: string | undefined, allowTraceReuse: boolean, scope: "ui" | "e2e"): Promise<TraceReuseCheck>;
|
|
19
|
+
/** Register a generated output for its trace. Call ONLY after generation succeeded. */
|
|
20
|
+
export declare function recordTraceOutput(key: string, identity: string): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { createReadStream } from "fs";
|
|
3
|
+
import { pipeline } from "stream/promises";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
/**
|
|
6
|
+
* SKYR-4262 — trace-reuse guard. One recording captures one flow; generating
|
|
7
|
+
* several differently-named specs from the same trace zip produces byte-identical
|
|
8
|
+
* tests whose names claim scenarios the recording never covered (observed in
|
|
9
|
+
* production: one trace fanned into 4 specs, none asserting its named behavior).
|
|
10
|
+
* Keyed by CONTENT hash, not path, so re-exporting a genuinely new recording to
|
|
11
|
+
* the same zip path never trips the guard.
|
|
12
|
+
*
|
|
13
|
+
* Scope: this registry is per MCP-server process. That is narrower than "one
|
|
14
|
+
* testbot run" — testbot retries an agent up to 3 times, each attempt spawning a
|
|
15
|
+
* fresh agent CLI and therefore a fresh server (a retry also deletes the run's
|
|
16
|
+
* state files and restarts the flow, so the reset is coherent) — and broader
|
|
17
|
+
* than one recording session in a long-lived IDE/stdio server. Both are
|
|
18
|
+
* acceptable for this guard's job: reusing an IDENTICAL zip under a new name is
|
|
19
|
+
* the failure mode wherever it happens, and a fresh recording always hashes
|
|
20
|
+
* differently.
|
|
21
|
+
*
|
|
22
|
+
* Check/record are split: `checkTraceReuse` is read-only and runs BEFORE
|
|
23
|
+
* generation; `recordTraceOutput` runs only after generation SUCCEEDS.
|
|
24
|
+
* Registering at check time poisoned the registry on any generation failure —
|
|
25
|
+
* e.g. output-name validation rejects `a.ts` with "rename to a.spec.ts", and the
|
|
26
|
+
* corrected retry was then refused for reusing its own trace.
|
|
27
|
+
*/
|
|
28
|
+
const generatedFromTrace = new Map();
|
|
29
|
+
/** Test-only: clear the in-process trace→outputs registry. */
|
|
30
|
+
export function resetTraceReuseGuard() {
|
|
31
|
+
generatedFromTrace.clear();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The registered identity of one generated spec: the RESOLVED output path, so
|
|
35
|
+
* the same filename into two different outputDirs still counts as a second,
|
|
36
|
+
* differently-named spec. An omitted `output` (Skyramp picks a default name)
|
|
37
|
+
* maps to a per-directory sentinel: two default-named generations from the same
|
|
38
|
+
* zip into the same directory overwrite each other and are allowed, while
|
|
39
|
+
* mixing a named and an unnamed generation of the same zip is a fan-out and is
|
|
40
|
+
* not.
|
|
41
|
+
*/
|
|
42
|
+
function outputIdentity(output, outputDir) {
|
|
43
|
+
return path.resolve(outputDir ?? process.cwd(), output ?? "(default output name)");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Read-only reuse check. Returns a rejection when this zip's content already
|
|
47
|
+
* generated a DIFFERENT output identity and `allowTraceReuse` is not set; on a
|
|
48
|
+
* clean check, returns the hash/identity for the caller to record after
|
|
49
|
+
* generation succeeds. Fails open (empty result) when the zip is unreadable —
|
|
50
|
+
* generation will surface its own error for that.
|
|
51
|
+
*/
|
|
52
|
+
export async function checkTraceReuse(playwrightInput, output, outputDir, allowTraceReuse, scope) {
|
|
53
|
+
if (!playwrightInput)
|
|
54
|
+
return {};
|
|
55
|
+
let key;
|
|
56
|
+
try {
|
|
57
|
+
// Stream the zip through the hash — trace zips carry full HARs and can be
|
|
58
|
+
// large, so buffering the whole file would spike the server's memory.
|
|
59
|
+
const hasher = createHash("sha256");
|
|
60
|
+
await pipeline(createReadStream(playwrightInput), hasher);
|
|
61
|
+
// Namespace by generator: a UI test and an E2E test from ONE recording are
|
|
62
|
+
// different artifacts (the e2e generator adds API correlation), not the
|
|
63
|
+
// byte-identical fan-out this guard exists to stop — only same-generator
|
|
64
|
+
// reuse of a zip is a duplicate.
|
|
65
|
+
key = `${scope}:${hasher.digest("hex")}`;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Unreadable zip fails in generation with its own error; the guard stays out of the way.
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
const identity = outputIdentity(output, outputDir);
|
|
72
|
+
const outputs = generatedFromTrace.get(key);
|
|
73
|
+
if (!allowTraceReuse && outputs && outputs.size > 0 && !outputs.has(identity)) {
|
|
74
|
+
const prior = [...outputs].map(p => path.basename(p)).join(", ");
|
|
75
|
+
return {
|
|
76
|
+
key,
|
|
77
|
+
identity,
|
|
78
|
+
rejection: `This trace zip already generated ${prior}. A recording captures ONE flow — generating a second, ` +
|
|
79
|
+
`differently-named spec (${path.basename(identity)}) from the same trace produces an identical test whose name claims a ` +
|
|
80
|
+
`scenario the recording never covered. Record a dedicated trace for this scenario with the browser_* tools ` +
|
|
81
|
+
`(including browser_assert on the behavior the test is named for) and skyramp_export_zip, then generate from ` +
|
|
82
|
+
`that. If this recording genuinely covers the scenario end-to-end, pass allowTraceReuse: true and be prepared ` +
|
|
83
|
+
`to differentiate the spec's assertions afterward via skyramp_enhance_assertions.`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { key, identity };
|
|
87
|
+
}
|
|
88
|
+
/** Register a generated output for its trace. Call ONLY after generation succeeded. */
|
|
89
|
+
export function recordTraceOutput(key, identity) {
|
|
90
|
+
const outputs = generatedFromTrace.get(key) ?? new Set();
|
|
91
|
+
outputs.add(identity);
|
|
92
|
+
generatedFromTrace.set(key, outputs);
|
|
93
|
+
}
|
|
@@ -18,9 +18,9 @@ export declare const REPORT_FILE_NAME = "testbot-result.txt";
|
|
|
18
18
|
export declare const targetElementSchema: z.ZodObject<{
|
|
19
19
|
role: z.ZodString;
|
|
20
20
|
accessibleName: z.ZodString;
|
|
21
|
-
testId: z.ZodNullable<z.ZodString>;
|
|
22
|
-
stableId: z.ZodNullable<z.ZodString>;
|
|
23
|
-
contextText: z.ZodNullable<z.ZodArray<z.ZodString, "many"
|
|
21
|
+
testId: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
|
|
22
|
+
stableId: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
|
|
23
|
+
contextText: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>, string[] | null, string[] | null | undefined>;
|
|
24
24
|
mutability: z.ZodOptional<z.ZodEnum<["mutable", "immutable", "unknown"]>>;
|
|
25
25
|
widgetType: z.ZodOptional<z.ZodEnum<["native", "custom", "unknown"]>>;
|
|
26
26
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -34,9 +34,9 @@ export declare const targetElementSchema: z.ZodObject<{
|
|
|
34
34
|
}, {
|
|
35
35
|
role: string;
|
|
36
36
|
accessibleName: string;
|
|
37
|
-
testId
|
|
38
|
-
stableId
|
|
39
|
-
contextText
|
|
37
|
+
testId?: string | null | undefined;
|
|
38
|
+
stableId?: string | null | undefined;
|
|
39
|
+
contextText?: string[] | null | undefined;
|
|
40
40
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
41
41
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
42
42
|
}>;
|
|
@@ -65,9 +65,9 @@ export declare const newTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
65
65
|
targetElements: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
66
66
|
role: z.ZodString;
|
|
67
67
|
accessibleName: z.ZodString;
|
|
68
|
-
testId: z.ZodNullable<z.ZodString>;
|
|
69
|
-
stableId: z.ZodNullable<z.ZodString>;
|
|
70
|
-
contextText: z.ZodNullable<z.ZodArray<z.ZodString, "many"
|
|
68
|
+
testId: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
|
|
69
|
+
stableId: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
|
|
70
|
+
contextText: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>, string[] | null, string[] | null | undefined>;
|
|
71
71
|
mutability: z.ZodOptional<z.ZodEnum<["mutable", "immutable", "unknown"]>>;
|
|
72
72
|
widgetType: z.ZodOptional<z.ZodEnum<["native", "custom", "unknown"]>>;
|
|
73
73
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -81,9 +81,9 @@ export declare const newTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
81
81
|
}, {
|
|
82
82
|
role: string;
|
|
83
83
|
accessibleName: string;
|
|
84
|
-
testId
|
|
85
|
-
stableId
|
|
86
|
-
contextText
|
|
84
|
+
testId?: string | null | undefined;
|
|
85
|
+
stableId?: string | null | undefined;
|
|
86
|
+
contextText?: string[] | null | undefined;
|
|
87
87
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
88
88
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
89
89
|
}>, "many">>>;
|
|
@@ -136,9 +136,9 @@ export declare const newTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
136
136
|
targetElements?: {
|
|
137
137
|
role: string;
|
|
138
138
|
accessibleName: string;
|
|
139
|
-
testId
|
|
140
|
-
stableId
|
|
141
|
-
contextText
|
|
139
|
+
testId?: string | null | undefined;
|
|
140
|
+
stableId?: string | null | undefined;
|
|
141
|
+
contextText?: string[] | null | undefined;
|
|
142
142
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
143
143
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
144
144
|
}[] | null | undefined;
|
|
@@ -186,9 +186,9 @@ export declare const newTestSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
186
186
|
targetElements?: {
|
|
187
187
|
role: string;
|
|
188
188
|
accessibleName: string;
|
|
189
|
-
testId
|
|
190
|
-
stableId
|
|
191
|
-
contextText
|
|
189
|
+
testId?: string | null | undefined;
|
|
190
|
+
stableId?: string | null | undefined;
|
|
191
|
+
contextText?: string[] | null | undefined;
|
|
192
192
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
193
193
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
194
194
|
}[] | null | undefined;
|
|
@@ -235,20 +235,20 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
235
235
|
description: z.ZodString;
|
|
236
236
|
expectedStatusCode: z.ZodOptional<z.ZodNumber>;
|
|
237
237
|
requestBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
238
|
-
responseBody: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
238
|
+
responseBody: z.ZodOptional<z.ZodUnion<[z.ZodRecord<z.ZodString, z.ZodAny>, z.ZodArray<z.ZodAny, "many">]>>;
|
|
239
239
|
}, "strip", z.ZodTypeAny, {
|
|
240
240
|
description: string;
|
|
241
241
|
path?: string | undefined;
|
|
242
242
|
method?: HttpMethod | undefined;
|
|
243
243
|
requestBody?: Record<string, any> | undefined;
|
|
244
|
-
responseBody?: Record<string, any> | undefined;
|
|
244
|
+
responseBody?: any[] | Record<string, any> | undefined;
|
|
245
245
|
expectedStatusCode?: number | undefined;
|
|
246
246
|
}, {
|
|
247
247
|
description: string;
|
|
248
248
|
path?: string | undefined;
|
|
249
249
|
method?: HttpMethod | undefined;
|
|
250
250
|
requestBody?: Record<string, any> | undefined;
|
|
251
|
-
responseBody?: Record<string, any> | undefined;
|
|
251
|
+
responseBody?: any[] | Record<string, any> | undefined;
|
|
252
252
|
expectedStatusCode?: number | undefined;
|
|
253
253
|
}>, "many">;
|
|
254
254
|
description: z.ZodString;
|
|
@@ -261,9 +261,9 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
261
261
|
targetElements: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
262
262
|
role: z.ZodString;
|
|
263
263
|
accessibleName: z.ZodString;
|
|
264
|
-
testId: z.ZodNullable<z.ZodString>;
|
|
265
|
-
stableId: z.ZodNullable<z.ZodString>;
|
|
266
|
-
contextText: z.ZodNullable<z.ZodArray<z.ZodString, "many"
|
|
264
|
+
testId: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
|
|
265
|
+
stableId: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
|
|
266
|
+
contextText: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>, string[] | null, string[] | null | undefined>;
|
|
267
267
|
mutability: z.ZodOptional<z.ZodEnum<["mutable", "immutable", "unknown"]>>;
|
|
268
268
|
widgetType: z.ZodOptional<z.ZodEnum<["native", "custom", "unknown"]>>;
|
|
269
269
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -277,9 +277,9 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
277
277
|
}, {
|
|
278
278
|
role: string;
|
|
279
279
|
accessibleName: string;
|
|
280
|
-
testId
|
|
281
|
-
stableId
|
|
282
|
-
contextText
|
|
280
|
+
testId?: string | null | undefined;
|
|
281
|
+
stableId?: string | null | undefined;
|
|
282
|
+
contextText?: string[] | null | undefined;
|
|
283
283
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
284
284
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
285
285
|
}>, "many">>>;
|
|
@@ -303,7 +303,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
303
303
|
path?: string | undefined;
|
|
304
304
|
method?: HttpMethod | undefined;
|
|
305
305
|
requestBody?: Record<string, any> | undefined;
|
|
306
|
-
responseBody?: Record<string, any> | undefined;
|
|
306
|
+
responseBody?: any[] | Record<string, any> | undefined;
|
|
307
307
|
expectedStatusCode?: number | undefined;
|
|
308
308
|
}[];
|
|
309
309
|
testId: string;
|
|
@@ -335,7 +335,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
335
335
|
path?: string | undefined;
|
|
336
336
|
method?: HttpMethod | undefined;
|
|
337
337
|
requestBody?: Record<string, any> | undefined;
|
|
338
|
-
responseBody?: Record<string, any> | undefined;
|
|
338
|
+
responseBody?: any[] | Record<string, any> | undefined;
|
|
339
339
|
expectedStatusCode?: number | undefined;
|
|
340
340
|
}[];
|
|
341
341
|
testId: string;
|
|
@@ -347,9 +347,9 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
347
347
|
targetElements?: {
|
|
348
348
|
role: string;
|
|
349
349
|
accessibleName: string;
|
|
350
|
-
testId
|
|
351
|
-
stableId
|
|
352
|
-
contextText
|
|
350
|
+
testId?: string | null | undefined;
|
|
351
|
+
stableId?: string | null | undefined;
|
|
352
|
+
contextText?: string[] | null | undefined;
|
|
353
353
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
354
354
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
355
355
|
}[] | null | undefined;
|
|
@@ -371,7 +371,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
371
371
|
path?: string | undefined;
|
|
372
372
|
method?: HttpMethod | undefined;
|
|
373
373
|
requestBody?: Record<string, any> | undefined;
|
|
374
|
-
responseBody?: Record<string, any> | undefined;
|
|
374
|
+
responseBody?: any[] | Record<string, any> | undefined;
|
|
375
375
|
expectedStatusCode?: number | undefined;
|
|
376
376
|
}[];
|
|
377
377
|
testId: string;
|
|
@@ -403,7 +403,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
403
403
|
path?: string | undefined;
|
|
404
404
|
method?: HttpMethod | undefined;
|
|
405
405
|
requestBody?: Record<string, any> | undefined;
|
|
406
|
-
responseBody?: Record<string, any> | undefined;
|
|
406
|
+
responseBody?: any[] | Record<string, any> | undefined;
|
|
407
407
|
expectedStatusCode?: number | undefined;
|
|
408
408
|
}[];
|
|
409
409
|
testId: string;
|
|
@@ -415,9 +415,9 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
415
415
|
targetElements?: {
|
|
416
416
|
role: string;
|
|
417
417
|
accessibleName: string;
|
|
418
|
-
testId
|
|
419
|
-
stableId
|
|
420
|
-
contextText
|
|
418
|
+
testId?: string | null | undefined;
|
|
419
|
+
stableId?: string | null | undefined;
|
|
420
|
+
contextText?: string[] | null | undefined;
|
|
421
421
|
mutability?: "unknown" | "mutable" | "immutable" | undefined;
|
|
422
422
|
widgetType?: "unknown" | "custom" | "native" | undefined;
|
|
423
423
|
}[] | null | undefined;
|
|
@@ -439,7 +439,7 @@ export declare const additionalRecommendationSchema: z.ZodEffects<z.ZodEffects<z
|
|
|
439
439
|
path?: string | undefined;
|
|
440
440
|
method?: HttpMethod | undefined;
|
|
441
441
|
requestBody?: Record<string, any> | undefined;
|
|
442
|
-
responseBody?: Record<string, any> | undefined;
|
|
442
|
+
responseBody?: any[] | Record<string, any> | undefined;
|
|
443
443
|
expectedStatusCode?: number | undefined;
|
|
444
444
|
}[];
|
|
445
445
|
testId: string;
|