@skyramp/mcp 0.3.5 → 0.3.6-rc.2.ac20
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/adapters/jestAdapter.js +3 -0
- package/build/adapters/mochaAdapter.js +2 -0
- package/build/adapters/playwrightAdapter.js +3 -0
- package/build/adapters/pytestAdapter.js +12 -0
- 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 +81 -35
- 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/runExistingTestsTool.d.ts +34 -2
- package/build/tools/runExistingTestsTool.js +104 -4
- package/build/tools/submitReportTool.d.ts +38 -38
- package/build/tools/submitReportTool.js +537 -120
- package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
- package/build/tools/test-management/analyzeChangesTool.js +71 -10
- 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 +70 -12
- package/build/types/ExternalTestExecution.d.ts +67 -1
- 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 +10 -1
- package/build/types/index.d.ts +2 -2
- package/build/types/index.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +57 -1
- package/build/utils/AnalysisStateManager.js +54 -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/reportVerification.d.ts +64 -4
- package/build/utils/reportVerification.js +228 -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
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import { isModularizeFirstTarget } from "../../utils/reuseRouting.js";
|
|
2
|
+
import { PromptPlan } from "../test-recommendation/promptPlan.js";
|
|
3
|
+
import { ASSERTION_BOUNDARY_RULE_EXTRACT, BROWSER_HELPERS, BROWSER_VALUE_ASSERTIONS, } from "../shared-helper-policy.js";
|
|
4
|
+
import { renderModularizationPrompt, } from "./render.js";
|
|
5
|
+
// On modularize-first UI flows (utils reuse on, POM reuse off), downstream
|
|
6
|
+
// skyramp_reuse_code consolidates this file's helpers into the shared
|
|
7
|
+
// SkyrampUtils file, where the next scenario's test calls them. That changes
|
|
8
|
+
// what a helper may be named and may assert — the mechanism (logical-section
|
|
9
|
+
// extraction) is the same either way. E2E shares this prompt but is not a
|
|
10
|
+
// modularize-first target, so it never sees these rules.
|
|
11
|
+
const _plan = new PromptPlan()
|
|
12
|
+
.addPhase("main", "UI Test Modularization", {
|
|
13
|
+
headerLevel: "hidden",
|
|
14
|
+
stepFormat: "hash",
|
|
15
|
+
})
|
|
16
|
+
.step("READ", "READ THE ORIGINAL TEST", (p) => `Read ${p.filePath} completely and identify:
|
|
14
17
|
- All test data values (names, prices, quantities, totals)
|
|
15
|
-
- All test data including calculations and expected results
|
|
18
|
+
- All test data including calculations and expected results
|
|
16
19
|
- The exact test flow
|
|
17
|
-
- **Check if code is already modularized** (e.g., \`breakpoint_section_0\`, \`breakpoint_section_1\` functions)
|
|
18
|
-
|
|
19
|
-
## STEP 2: HANDLE PRE-MODULARIZED CODE
|
|
20
|
-
|
|
21
|
-
**IF CODE IS ALREADY MODULARIZED** (has functions like \`breakpoint_section_0\`, \`breakpoint_section_1\`):
|
|
20
|
+
- **Check if code is already modularized** (e.g., \`breakpoint_section_0\`, \`breakpoint_section_1\` functions)`)
|
|
21
|
+
.step("PREMOD", "HANDLE PRE-MODULARIZED CODE", (p) => `**IF CODE IS ALREADY MODULARIZED** (has functions like \`breakpoint_section_0\`, \`breakpoint_section_1\`):
|
|
22
22
|
1. **RENAME** functions with meaningful names that describe what they do
|
|
23
23
|
- BAD: \`breakpoint_section_0\`, \`breakpoint_section_1\`
|
|
24
24
|
- GOOD: \`createProduct\`, \`editProductPrice\`, \`createOrder\`
|
|
25
25
|
2. **PARAMETERIZE** hardcoded values in these functions
|
|
26
26
|
3. **DO NOT** change the function logic or structure
|
|
27
|
-
4. **DO NOT** create new functions - work with existing ones
|
|
28
|
-
|
|
27
|
+
4. **DO NOT** create new functions - work with existing ones${p.utilsReuse
|
|
28
|
+
? ` — with ONE exception, item 5
|
|
29
|
+
5. **APPLY THE SHARED HELPER SHAPE** (the rules block inside Step ${_plan.labels.EXTRACT}; read that block, then return here): canonical intent names; value assertions moved out of the helper into the test body right after its call; a value assertion that sits BETWEEN two actions splits the function at that point into two intent-named functions — the only new functions this branch may create`
|
|
30
|
+
: ""}
|
|
31
|
+
${p.utilsReuse ? "6" : "5"}. **SKIP to Step ${_plan.labels.VERIFY}** for verification
|
|
29
32
|
|
|
30
|
-
**IF CODE IS NOT MODULARIZED**, proceed to
|
|
31
|
-
|
|
32
|
-
## STEP 3: IDENTIFY EXTRACTION OPPORTUNITIES
|
|
33
|
-
|
|
34
|
-
Extract code in TWO scenarios:
|
|
33
|
+
**IF CODE IS NOT MODULARIZED**, proceed to Step ${_plan.labels.IDENTIFY}.`)
|
|
34
|
+
.step("IDENTIFY", "IDENTIFY EXTRACTION OPPORTUNITIES", (p) => `Extract code in TWO scenarios:
|
|
35
35
|
|
|
36
36
|
**A) REPETITIVE CODE** - Code that repeats 2+ times with IDENTICAL structure but different data
|
|
37
37
|
- Extract EACH occurrence as a separate helper call
|
|
@@ -39,15 +39,17 @@ Extract code in TWO scenarios:
|
|
|
39
39
|
|
|
40
40
|
**B) LOGICAL SECTIONS** - Self-contained operations (5+ lines) that improve readability
|
|
41
41
|
- Examples: "create product", "fill form", "verify result"
|
|
42
|
-
- Must be cohesive - related operations that achieve one goal
|
|
42
|
+
- Must be cohesive - related operations that achieve one goal${p.utilsReuse
|
|
43
|
+
? `
|
|
44
|
+
- This project consolidates test helpers into a shared utils file, so EVERY logical section that performs a user action (open a form, fill and submit it, add or remove an item, apply an option) becomes a named helper — the section does NOT need to repeat. Another scenario's test will call the same helper with its own values.`
|
|
45
|
+
: ""}
|
|
43
46
|
|
|
44
47
|
**Don't extract:**
|
|
45
|
-
- Code less than 5 lines (unless highly repetitive
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
**GOLDEN RULE: Copy the original code exactly, only add parameters for values that differ**
|
|
48
|
+
- Code less than 5 lines (unless highly repetitive${p.utilsReuse
|
|
49
|
+
? ", or a cohesive user-action section — those become helpers regardless of length, per B above"
|
|
50
|
+
: ""})
|
|
51
|
+
- Navigation sequences (\`navbar-\` clicks, \`page.goto\`)`)
|
|
52
|
+
.step("EXTRACT", "EXTRACT INTO HELPERS", (p) => `**GOLDEN RULE: Copy the original code exactly, only add parameters for values that differ**
|
|
51
53
|
|
|
52
54
|
**CRITICAL - AVOID BUGS:**
|
|
53
55
|
- Copy the code block EXACTLY as-is from the original test
|
|
@@ -62,9 +64,11 @@ Extract code in TWO scenarios:
|
|
|
62
64
|
- **Replace ALL hardcoded values with parameters** - No leftover hardcoded fills (selectors are the ONLY exception: selectors must remain exactly as in the original code)
|
|
63
65
|
- **NEVER create new conditional logic** (no ternaries, no if/else, no loops to derive parameter values)
|
|
64
66
|
- **NEVER add logic that wasn't in the original code**
|
|
65
|
-
- Keep ALL existing logic, calculations, and assertions unchanged
|
|
67
|
+
- Keep ALL existing logic, calculations, and assertions unchanged${p.utilsReuse
|
|
68
|
+
? " — ONE exception: a value assertion that would land inside a helper is placed in the test body instead (same selector, same expected value, same relative order): after the helper call if it trailed the helper's actions, or between two helper calls — splitting the helper there — if it sat between actions. That relocation is the only assertion move allowed; see SHARED HELPER SHAPE below"
|
|
69
|
+
: ""}
|
|
66
70
|
- Keep ALL lines in the same order (except: remove fills with wrong/temporary hardcoded values)
|
|
67
|
-
|
|
71
|
+
${p.utilsReuse ? `\n${sharedHelperShape()}\n` : ""}
|
|
68
72
|
**PARAMETERIZATION BEST PRACTICES:**
|
|
69
73
|
1. **Use individual parameters, not data objects** - Makes helper usage clearer
|
|
70
74
|
- GOOD: \`createProduct(page, name, price, category)\`
|
|
@@ -129,25 +133,27 @@ await addItem(page, "item1");
|
|
|
129
133
|
await addItem(page, "item2");
|
|
130
134
|
await addItem(page, "item3");
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Use the EXACT same values from the original test when calling helpers.
|
|
136
|
-
|
|
137
|
-
## STEP 5: VERIFY - CRITICAL CHECKS
|
|
138
|
-
|
|
139
|
-
**BUGS TO AVOID:**
|
|
136
|
+
\`\`\``)
|
|
137
|
+
.step("USE", "CALL HELPERS WITH EXACT SAME VALUES", () => `Use the EXACT same values from the original test when calling helpers.`)
|
|
138
|
+
.step("VERIFY", "VERIFY - CRITICAL CHECKS", (p) => `**BUGS TO AVOID:**
|
|
140
139
|
- [ ] **NO TYPE ANNOTATIONS FOR \`page\` IN FUNCTION SIGNATURES** - Parameters must be untyped (e.g., \`page\`, not \`page: Page\`)
|
|
141
140
|
- [ ] **NO RETURN TYPES** - Do not add \`: Promise<void>\` or any return type annotations
|
|
142
|
-
- [ ] **ALL field mappings verified** - EVERY \`.fill()\` uses the correct parameter. Check field gets quantity parameter, NOT hardcoded
|
|
141
|
+
- [ ] **ALL field mappings verified** - EVERY \`.fill()\` uses the correct parameter. Check field gets quantity parameter, NOT hardcoded
|
|
143
142
|
- [ ] **No duplicate helpers** - Consolidate similar helpers (e.g., one createOrder vs createSimpleOrder + createOrderWithItems)
|
|
144
143
|
- [ ] **No nested helpers** - Helpers don't call other helpers unnecessarily
|
|
145
144
|
- [ ] **Clear parameters** - Individual parameters (name, price) not data objects
|
|
146
145
|
- [ ] **No duplicate actions** - Don't click "Add Product" button in test AND in helper - choose one place
|
|
147
146
|
- [ ] **No new loops** - Exception: acceptable if it improves readability and items are explicit at call site
|
|
148
|
-
|
|
147
|
+
${p.utilsReuse
|
|
148
|
+
? `
|
|
149
|
+
**SHARED HELPER SHAPE:**
|
|
150
|
+
- [ ] Every helper name is a canonical intent (\`openEditOrderForm\`, \`applyDiscount\`) with no scenario value or adjective in it
|
|
151
|
+
- [ ] No helper contains a value assertion (${BROWSER_VALUE_ASSERTIONS}) — each sits in the test body, right after the helper call it verifies
|
|
152
|
+
- [ ] Every selector inside a helper is byte-identical to the original test
|
|
153
|
+
`
|
|
154
|
+
: ""}
|
|
149
155
|
**DATA INTEGRITY:**
|
|
150
|
-
- [ ] Helpers are exact copies of original + parameters only
|
|
156
|
+
- [ ] Helpers are exact copies of original + parameters only${p.utilsReuse ? " (minus the value assertions relocated to the test body)" : ""}
|
|
151
157
|
- [ ] All data values match the original test exactly
|
|
152
158
|
- [ ] All calculations produce identical results
|
|
153
159
|
- [ ] Helper calls use the same values as original
|
|
@@ -167,5 +173,41 @@ Use the EXACT same values from the original test when calling helpers.
|
|
|
167
173
|
|
|
168
174
|
If ANY answer is "no", fix it before submitting.
|
|
169
175
|
|
|
170
|
-
**When in doubt, don't extract - keep original code unchanged
|
|
176
|
+
**When in doubt, don't extract - keep original code unchanged.**`)
|
|
177
|
+
.done();
|
|
178
|
+
// Shared-file helper shape: the reuse pass moves these helpers into SkyrampUtils,
|
|
179
|
+
// where the next scenario's test calls them with its own values. A helper is
|
|
180
|
+
// therefore named for the action it performs and carries no claim about this
|
|
181
|
+
// scenario's data — a `toHaveText("Total: $899.98")` inside a shared helper
|
|
182
|
+
// fails every other caller. Selectors stay verbatim regardless (a selector is
|
|
183
|
+
// not scenario data; parameterizing it invents a template the recorder never saw).
|
|
184
|
+
function sharedHelperShape() {
|
|
185
|
+
return `**SHARED HELPER SHAPE (this project consolidates helpers into a shared utils file):**
|
|
186
|
+
- ${BROWSER_HELPERS.namingRule}
|
|
187
|
+
- **PARAMETERS**: \`page\`, plus every value this scenario types or selects (${BROWSER_HELPERS.liftableLiterals}). Selectors are NEVER parameters — they stay verbatim inside the helper body.
|
|
188
|
+
- ${BROWSER_HELPERS.assertionRule} ${ASSERTION_BOUNDARY_RULE_EXTRACT}
|
|
189
|
+
- Helpers are module-level named functions in this file; the test body becomes navigation + a sequence of helper calls + this scenario's assertions, in the original order.`;
|
|
190
|
+
}
|
|
191
|
+
export function getModularizationPrompt(filePath, testType, language) {
|
|
192
|
+
const params = {
|
|
193
|
+
filePath,
|
|
194
|
+
utilsReuse: isModularizeFirstTarget(testType, language),
|
|
195
|
+
};
|
|
196
|
+
return renderModularizationPrompt({
|
|
197
|
+
title: "UI TEST MODULARIZATION - IMPROVE READABILITY AND REDUCE DUPLICATION",
|
|
198
|
+
forbidden: [
|
|
199
|
+
"DO NOT CREATE INTERFACES, CLASSES, TYPES, NEW FILES, OR NEW DATA STRUCTURES",
|
|
200
|
+
`DO NOT CHANGE TEST LOGIC, DATA VALUES, CALCULATIONS, OR ASSERTIONS${params.utilsReuse ? " (moving a value assertion out of a helper into the test body, verbatim, is a placement — not a change)" : ""}`,
|
|
201
|
+
"DO NOT CREATE/UPDATE DEPENDENCY FILES package.json, requirements.txt, pom.xml, build.gradle file",
|
|
202
|
+
],
|
|
203
|
+
plan: _plan,
|
|
204
|
+
params,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Structured view of which steps render for the given inputs (for tests). */
|
|
208
|
+
export function previewModularizationPlan(filePath, testType, language) {
|
|
209
|
+
return _plan.preview({
|
|
210
|
+
filePath,
|
|
211
|
+
utilsReuse: isModularizeFirstTarget(testType, language),
|
|
212
|
+
});
|
|
171
213
|
}
|
|
@@ -148,9 +148,11 @@ Use this exact format for the file:
|
|
|
148
148
|
- **Properties:**
|
|
149
149
|
- \`propertyName\` — selector: \`exact-selector-string\`
|
|
150
150
|
- **Methods:**
|
|
151
|
-
- \`methodName(param1, param2)\` — description; ⏱ timing-aware: waitForTimeout(Xms) + sleep(Ys) | 🌐 network-aware: waitForResponse('**/pattern**') | (omit tags if neither)
|
|
151
|
+
- \`methodName(param1, param2)\` — description; selectors: \`login-submit-btn\`, \`table-row-*\`; ⏱ timing-aware: waitForTimeout(Xms) + sleep(Ys) | 🌐 network-aware: waitForResponse('**/pattern**') | (omit tags if neither)
|
|
152
152
|
\`\`\`
|
|
153
153
|
|
|
154
|
+
**Selectors on methods are REQUIRED.** List every selector string the method touches, each in backticks, in the same form the source uses. If the method builds a selector from a parameter or matches a family of elements, record the WILDCARD form, not one concrete instance — \`o2-table-row-*\` for \`starts-with(@data-test,"o2-table-row-")\`, \`function-list-name-cell-*\` for \`[data-test="function-list-name-cell-\${name}"]\`. A method that touches no selector (a pure wait, a getter over another method) records \`selectors: none\`.
|
|
155
|
+
|
|
154
156
|
**Tier-2 entries in the catalog:** for files cataloged at signature level, the entry contains the class name, the import path, a \`- **Methods:**\` list of names/signatures only, and the matched selectors from STEP 1 under \`- **Properties:**\`. Mark such entries with \`<!-- depth: signature -->\` on the line after the class heading so a later run knows the entry is shallow.
|
|
155
157
|
|
|
156
158
|
**Free-function helper modules** use the same format with the module basename as the heading (add \`(module functions, not a class)\` after it), each exported function as a Method, and no Iframe line unless a function scopes one. They substitute as imported calls — \`import { bookFirstEvent } from '<path>'; await bookFirstEvent(page);\` — under the same evidence and confidence rules as class methods, and are verified the same way.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Why the assertion rule exists — quoted wherever the rule is stated. */
|
|
2
|
+
export declare const SHARED_HELPER_WHY = "A scenario-specific assertion inside a helper carries one scenario's data into a shared function and fails the next scenario that calls it \u2014 the helper is shared, the claims are not.";
|
|
3
|
+
export declare const BROWSER_STRUCTURAL_WAITS = "`waitForResponse`, `waitForLoadState`, `waitFor({ state: \"visible\" })`, and `toBeVisible` on a STRUCTURAL locator (a test id, or a role + name of a control) \u2014 a check that an element rendered, not what it says";
|
|
4
|
+
/**
|
|
5
|
+
* Relocation never reorders execution. Two variants, because the two ends of the
|
|
6
|
+
* chain may do different things with a helper whose value assertion sits BETWEEN
|
|
7
|
+
* two actions: modularization (which authors helpers) splits it; reuse (which
|
|
8
|
+
* only moves existing helpers) leaves it local.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ASSERTION_BOUNDARY_RULE_EXTRACT = "Relocation must preserve the original order: an assertion that sits BETWEEN two actions is a HELPER BOUNDARY \u2014 end the helper before it and start the next helper after it (each named per the naming rule), so the assertion sits between the two calls in the test body exactly where it was. Never move an assertion across an action.";
|
|
11
|
+
export declare const ASSERTION_BOUNDARY_RULE_MOVE = "Relocation must preserve the original order: only a TRAILING assertion (after the helper's last action) may move to the test body right after the call. A helper whose excluded assertion sits BETWEEN two actions is NOT moved and NOT split \u2014 leave it local in the test file; splitting is modularization's job, not this step's.";
|
|
12
|
+
export declare const BROWSER_VALUE_ASSERTIONS = "any `expect` whose matcher OR locator carries scenario data: `toHaveText`, `toContainText`, `toHaveValue`, `toHaveCount`, `toHaveURL` with a literal, or ANY matcher \u2014 `toBeVisible` included \u2014 on a locator that embeds scenario text such as `getByText(\"Total: $899.98\")`";
|
|
13
|
+
/**
|
|
14
|
+
* A helper kept local must never share a name with a utils export — the import
|
|
15
|
+
* and the local definition would then describe two different behaviours under
|
|
16
|
+
* one name, and a later reader (or a deterministic gate) cannot tell which runs.
|
|
17
|
+
*/
|
|
18
|
+
export declare const NO_SHADOWING_RULE = "If a helper stays local for any reason, it must NOT share a name with any export of the utils file \u2014 rename the local one to state what differs (e.g. `submitEditOrderFormNoWait`). A test file never defines a helper with the same name as a utils export.";
|
|
19
|
+
export interface HelperPolicy {
|
|
20
|
+
kind: "api" | "browser";
|
|
21
|
+
/** How two helpers are recognised as "the same step" (lower-case phrase). */
|
|
22
|
+
sameStep: string;
|
|
23
|
+
/** The literals that may legitimately differ between two versions of a step. */
|
|
24
|
+
liftableLiterals: string;
|
|
25
|
+
/**
|
|
26
|
+
* Differences that are generator noise, not structure — two bodies that differ
|
|
27
|
+
* only by these are the same step. Empty when the kind has none.
|
|
28
|
+
*/
|
|
29
|
+
structuralNoise: string;
|
|
30
|
+
/** Canonical naming rule (full sentence(s)). */
|
|
31
|
+
namingRule: string;
|
|
32
|
+
/** What a shared helper may and may not assert (full sentence(s)). */
|
|
33
|
+
assertionRule: string;
|
|
34
|
+
/** Where a relocated assertion goes in the test body. */
|
|
35
|
+
relocationTarget: string;
|
|
36
|
+
/**
|
|
37
|
+
* Order-preserving rule for MOVING an existing helper (reuse). Empty when the
|
|
38
|
+
* kind's helpers cannot carry a between-actions assertion (API: one request
|
|
39
|
+
* plus trailing assertions).
|
|
40
|
+
*/
|
|
41
|
+
moveRule: string;
|
|
42
|
+
/** Naming example for STEP 4b's procedure, matching the rendered worked example. */
|
|
43
|
+
namingExample: string;
|
|
44
|
+
/** "One X = one helper" closing line for a utils file. */
|
|
45
|
+
oneHelperRule: (fileName: string) => string;
|
|
46
|
+
/** Kind-specific near-duplicate / merge section for STEP 4b of code reuse. */
|
|
47
|
+
nearDupRules: (fileName: string) => string;
|
|
48
|
+
/**
|
|
49
|
+
* Replacement for the generic STEP 4b worked example when helpers are headed
|
|
50
|
+
* to the shared file. Empty string → no example beyond the kind's own section.
|
|
51
|
+
*/
|
|
52
|
+
nearDupExample: (testFile: string) => string;
|
|
53
|
+
}
|
|
54
|
+
export declare const API_HELPERS: HelperPolicy;
|
|
55
|
+
export declare const BROWSER_HELPERS: HelperPolicy;
|
|
56
|
+
/** Which helper policy a test type's helpers follow. */
|
|
57
|
+
export declare function selectHelperPolicy(testType?: string): HelperPolicy;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared-helper policy — the ONE place that says what a helper destined for the
|
|
3
|
+
* shared SkyrampUtils file may be named and may assert. Both the modularization
|
|
4
|
+
* prompts (which create the helpers) and the code-reuse prompt (which moves and
|
|
5
|
+
* merges them) read these fragments, so the rule cannot drift between the two
|
|
6
|
+
* ends of the modularize → reuse chain.
|
|
7
|
+
*
|
|
8
|
+
* Two kinds of helper exist, keyed by what the test type drives:
|
|
9
|
+
* - API helpers (integration) wrap one SDK request: equivalence is method+path,
|
|
10
|
+
* scenario values travel via `data_override`, the only assertion is the
|
|
11
|
+
* status code.
|
|
12
|
+
* - Browser helpers (ui) wrap a Playwright action sequence: equivalence is the
|
|
13
|
+
* primitive sequence with identical selectors, scenario values are fill
|
|
14
|
+
* values / literal arguments, the helper holds actions + structural waits.
|
|
15
|
+
*/
|
|
16
|
+
import { isBrowserTestType } from "../utils/reuseRouting.js";
|
|
17
|
+
/** Why the assertion rule exists — quoted wherever the rule is stated. */
|
|
18
|
+
export const SHARED_HELPER_WHY = "A scenario-specific assertion inside a helper carries one scenario's data into a shared function and fails the next scenario that calls it — the helper is shared, the claims are not.";
|
|
19
|
+
export const BROWSER_STRUCTURAL_WAITS = '`waitForResponse`, `waitForLoadState`, `waitFor({ state: "visible" })`, and `toBeVisible` on a STRUCTURAL locator (a test id, or a role + name of a control) — a check that an element rendered, not what it says';
|
|
20
|
+
/**
|
|
21
|
+
* Relocation never reorders execution. Two variants, because the two ends of the
|
|
22
|
+
* chain may do different things with a helper whose value assertion sits BETWEEN
|
|
23
|
+
* two actions: modularization (which authors helpers) splits it; reuse (which
|
|
24
|
+
* only moves existing helpers) leaves it local.
|
|
25
|
+
*/
|
|
26
|
+
export const ASSERTION_BOUNDARY_RULE_EXTRACT = "Relocation must preserve the original order: an assertion that sits BETWEEN two actions is a HELPER BOUNDARY — end the helper before it and start the next helper after it (each named per the naming rule), so the assertion sits between the two calls in the test body exactly where it was. Never move an assertion across an action.";
|
|
27
|
+
export const ASSERTION_BOUNDARY_RULE_MOVE = "Relocation must preserve the original order: only a TRAILING assertion (after the helper's last action) may move to the test body right after the call. A helper whose excluded assertion sits BETWEEN two actions is NOT moved and NOT split — leave it local in the test file; splitting is modularization's job, not this step's.";
|
|
28
|
+
export const BROWSER_VALUE_ASSERTIONS = 'any `expect` whose matcher OR locator carries scenario data: `toHaveText`, `toContainText`, `toHaveValue`, `toHaveCount`, `toHaveURL` with a literal, or ANY matcher — `toBeVisible` included — on a locator that embeds scenario text such as `getByText("Total: $899.98")`';
|
|
29
|
+
const BROWSER_LIFTABLE = "fill values, option labels, or literal arguments";
|
|
30
|
+
const API_LIFTABLE = "body literals, `data_override` values, or expected status";
|
|
31
|
+
const API_ASSERTION_MERGE_LINE = `a merged helper asserts ONLY the status code. If either version carries response-body assertions, relocate them to that version's test body (applied to the returned response) before merging. ${SHARED_HELPER_WHY}`;
|
|
32
|
+
// (API helpers are one request + trailing assertions, so the move variant's
|
|
33
|
+
// between-actions case does not arise; API_HELPERS.moveRule is empty.)
|
|
34
|
+
function oneHelperLine(fileName, unit) {
|
|
35
|
+
return `One ${unit} = one helper in \`${fileName}\`.`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A helper kept local must never share a name with a utils export — the import
|
|
39
|
+
* and the local definition would then describe two different behaviours under
|
|
40
|
+
* one name, and a later reader (or a deterministic gate) cannot tell which runs.
|
|
41
|
+
*/
|
|
42
|
+
export const NO_SHADOWING_RULE = "If a helper stays local for any reason, it must NOT share a name with any export of the utils file — rename the local one to state what differs (e.g. `submitEditOrderFormNoWait`). A test file never defines a helper with the same name as a utils export.";
|
|
43
|
+
export const API_HELPERS = {
|
|
44
|
+
kind: "api",
|
|
45
|
+
sameStep: "the SAME method+path",
|
|
46
|
+
liftableLiterals: API_LIFTABLE,
|
|
47
|
+
structuralNoise: "",
|
|
48
|
+
namingRule: "**NAME** each helper by method + resource ONLY: `create_product`, `update_order`, `get_order`, `get_orders_list`. Scenario adjectives in names are FORBIDDEN (`create_discount_test_product`, `update_order_50_discount`) — other tests define the same step, and a shared utils file can hold only one canonical name per step. Scenario data belongs in ARGUMENTS, never in the name.",
|
|
49
|
+
assertionRule: `**THE ONLY ASSERTION INSIDE A SHARED HELPER IS THE STATUS CODE**, written in the test's own assertion syntax (Python \`assert response.status_code == expected_status\`; TypeScript/JavaScript \`expect(response.status).toBe(expectedStatus)\`), with the expected status a parameter defaulted to this scenario's expected code. EVERY response-body assertion (echo-backs, computed totals, field checks) stays in the test body, applied to the response the helper returns. ${SHARED_HELPER_WHY}`,
|
|
50
|
+
relocationTarget: "the test body, applied to the response the helper returns",
|
|
51
|
+
moveRule: "",
|
|
52
|
+
namingExample: "E.g. `update_order`, not `update_order_discount` or `update_order_50_discount`.",
|
|
53
|
+
oneHelperRule: (fileName) => `One method+path = one helper in \`${fileName}\`.`,
|
|
54
|
+
// The API section below carries its own worked example; the generic
|
|
55
|
+
// Playwright one would teach intent-naming and locator merges to a Python prompt.
|
|
56
|
+
nearDupExample: () => "",
|
|
57
|
+
// API request helpers are one SDK call + assertions, so near-duplicate
|
|
58
|
+
// equivalence is judged by method+path, not Playwright primitives — and the
|
|
59
|
+
// SDK's data_override makes literal-lifting mechanical, which is why this
|
|
60
|
+
// merge may lift body literals that the Playwright rules refuse.
|
|
61
|
+
nearDupRules: (fileName) => `
|
|
62
|
+
### API request helpers: near-duplicate definition and merge (this flow's tests)
|
|
63
|
+
|
|
64
|
+
For helpers that wrap one SDK request call (e.g. \`client.send_request(...)\`), judge near-duplication by the REQUEST, not by the criteria above. Two such helpers are near-duplicates when **ALL** of the following are true:
|
|
65
|
+
|
|
66
|
+
1. **Same method and same path** (path parameters count as the same path).
|
|
67
|
+
2. **Same call structure** — same request-call shape; no extra steps in one but not the other.
|
|
68
|
+
3. **Differences are limited to**: literals inside the request-body string, values in \`data_override\`, the expected status code, or which values are lifted to parameters.
|
|
69
|
+
|
|
70
|
+
Merge procedure for an API near-duplicate pair — these rules OVERRIDE the generic procedure below on two points: body literals ARE liftable differences (via \`data_override\`), and defaults are REQUIRED (the generic "no defaults" rule applies to Playwright helpers only):
|
|
71
|
+
- **Name**: method + resource only (\`create_product\`, \`update_order\`) — never a scenario adjective.
|
|
72
|
+
- **Lift each differing body literal into a parameter applied via \`data_override\`, with the parameter's DEFAULT set to the literal the incumbent version used.** Defaults make the merge additive: every existing call site keeps its behavior without edits; only the other test passes its values explicitly.
|
|
73
|
+
- **Union the parameter lists**; a parameter one version lacked gets the default from that version's literal. A differing expected status lifts the same way: \`expected_status\` parameter, default from the incumbent.
|
|
74
|
+
- **Assertions**: ${API_ASSERTION_MERGE_LINE}
|
|
75
|
+
- Structural differences (different items-array shape, extra request) → NOT near-duplicates; leave them alone.
|
|
76
|
+
|
|
77
|
+
Worked example: \`update_order_discount(client, headers, order_id, product_id)\` with \`"discount_percent": 25\` in the body, and \`update_order_50_discount(client, headers, order_id, product_id, quantity)\` with \`"discount_percent": 50\` → ONE helper \`update_order(client, headers, order_id, product_id, discount_percent=25, quantity=2)\` that applies \`discount_percent\` and \`quantity\` via \`data_override\`. ${oneHelperLine(fileName, "method+path")}
|
|
78
|
+
`,
|
|
79
|
+
};
|
|
80
|
+
export const BROWSER_HELPERS = {
|
|
81
|
+
kind: "browser",
|
|
82
|
+
sameStep: "the SAME action sequence (same Playwright primitives in the same order, selectors identical)",
|
|
83
|
+
liftableLiterals: BROWSER_LIFTABLE,
|
|
84
|
+
structuralNoise: "`waitForTimeout(...)` lines and the `getValue(playwrightRequestN, ...)` value wrapper around a fill are RECORDER NOISE, not structure — two bodies that differ only by them perform the same step; keep the utils version (it carries the waits) and import it",
|
|
85
|
+
namingRule: "**NAME** each helper by the user INTENT it performs, shared across tests: `openEditOrderForm`, `applyDiscount`, `submitProductForm`, `removeOrderItem`. Scenario values or adjectives in names are FORBIDDEN (`applyFiftyPercentDiscount`, `editOrder1Discount`, `createExpensiveProduct`) — another test performs the same action with different data, and a shared utils file holds ONE canonical name per action. Scenario data belongs in ARGUMENTS, never in the name.",
|
|
86
|
+
assertionRule: `**A SHARED HELPER CONTAINS ACTIONS AND STRUCTURAL WAITS ONLY**: clicks, fills, selects, ${BROWSER_STRUCTURAL_WAITS}. EVERY VALUE ASSERTION (${BROWSER_VALUE_ASSERTIONS}) stays in the test body, placed immediately after the helper call it verifies, with its selector verbatim and its expected value unchanged. ${SHARED_HELPER_WHY}`,
|
|
87
|
+
relocationTarget: "the test body, immediately after the helper call, selector verbatim",
|
|
88
|
+
moveRule: ASSERTION_BOUNDARY_RULE_MOVE,
|
|
89
|
+
namingExample: "E.g. `applyDiscount`, not `applyTwentyPercentDiscount`.",
|
|
90
|
+
oneHelperRule: (fileName) => `One action sequence = one helper in \`${fileName}\`.`,
|
|
91
|
+
nearDupExample: (testFile) => `### Worked example (shared-file browser helpers)
|
|
92
|
+
|
|
93
|
+
\`edit_order_apply_discount.spec.ts\` has:
|
|
94
|
+
\`\`\`ts
|
|
95
|
+
export async function applyDiscount(page) {
|
|
96
|
+
await page.getByTestId("edit-order-input-discount").fill("10");
|
|
97
|
+
await page.getByTestId("edit-order-submit-btn").click();
|
|
98
|
+
}
|
|
99
|
+
\`\`\`
|
|
100
|
+
|
|
101
|
+
\`${testFile}\` (current) has:
|
|
102
|
+
\`\`\`ts
|
|
103
|
+
export async function applyTwentyPercentDiscount(page) {
|
|
104
|
+
await page.getByTestId("edit-order-input-discount").fill("20");
|
|
105
|
+
await page.getByTestId("edit-order-submit-btn").click();
|
|
106
|
+
}
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
These ARE near-duplicates: same primitive sequence, selectors byte-identical, the only difference is the fill literal. Correct merge (name = intent, the literal becomes a parameter passed exactly as it was written — no conversion or other logic added, selectors untouched):
|
|
110
|
+
|
|
111
|
+
\`\`\`ts
|
|
112
|
+
export async function applyDiscount(page, discountPercent) {
|
|
113
|
+
await page.getByTestId("edit-order-input-discount").fill(discountPercent);
|
|
114
|
+
await page.getByTestId("edit-order-submit-btn").click();
|
|
115
|
+
}
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
Both test files import and call \`applyDiscount(page, "10")\` / \`applyDiscount(page, "20")\`. A pair whose selectors differ (\`getByTestId("edit-order-submit-btn")\` vs \`getByRole("button", { name: "Save" })\`, or one side carrying \`.first()\`) is NOT a near-duplicate — leave both in place.`,
|
|
119
|
+
// Browser helpers already have the Playwright near-duplicate criteria in the
|
|
120
|
+
// generic STEP 4b text; what they lack for a shared file is the
|
|
121
|
+
// defaulted-parameter merge (so an incumbent utils helper can absorb a new
|
|
122
|
+
// scenario without touching its existing call sites) and the assertion rule.
|
|
123
|
+
nearDupRules: (fileName) => `
|
|
124
|
+
### Browser action helpers: merging into an existing \`${fileName}\` helper (this flow's tests)
|
|
125
|
+
|
|
126
|
+
For this flow the rules below OVERRIDE two points of the generic text above and below: (a) selectors are NOT liftable differences — two helpers whose selectors differ are NOT near-duplicates (criterion 2 applies to ${BROWSER_LIFTABLE} only); (b) the procedure's "no defaults" rule applies to cross-test pairs (both call sites pass values explicitly) but NOT to merges into a helper that already lives in \`${fileName}\` (STEP 3 / STEP 5b), which MUST use defaults so existing call sites stay untouched. Merge additively:
|
|
127
|
+
- **Name**: the INTENT the action sequence performs (\`openEditOrderForm\`, \`applyDiscount\`, \`submitProductForm\`) — never a scenario adjective or value (\`applyFiftyPercentDiscount\`, \`editOrder1Discount\`). ${oneHelperLine(fileName, "intent")}
|
|
128
|
+
- **Lift each differing literal into a parameter with its DEFAULT set to the literal the incumbent version used** (${BROWSER_LIFTABLE}). Defaults make the merge additive: every existing call site keeps its behavior without edits; only the new test passes its values explicitly. Selectors stay verbatim in the body.
|
|
129
|
+
- **Assertions**: before merging, apply the assertion rule to both versions — relocate a trailing value assertion to that version's test body immediately after the helper call. ${ASSERTION_BOUNDARY_RULE_MOVE} ${SHARED_HELPER_WHY}
|
|
130
|
+
`,
|
|
131
|
+
};
|
|
132
|
+
/** Which helper policy a test type's helpers follow. */
|
|
133
|
+
export function selectHelperPolicy(testType) {
|
|
134
|
+
return isBrowserTestType(testType) ? BROWSER_HELPERS : API_HELPERS;
|
|
135
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CandidateSource, computeCandidateId } from "../../types/Recommendation.js";
|
|
2
|
+
import { fixedNBudgeter } from "../../recommendation/budgeters/fixedNBudgeter.js";
|
|
2
3
|
import { AUTH_MIDDLEWARE_PATTERNS_STR } from "../../utils/workspaceAuth.js";
|
|
3
4
|
import { resolveServiceDetailsRef } from "../../utils/utils.js";
|
|
4
|
-
import { logger } from "../../utils/logger.js";
|
|
5
5
|
import { TEST_CATEGORIES, } from "../../types/TestRecommendation.js";
|
|
6
6
|
import { buildScopeAssessmentSection } from "./scopeAssessment.js";
|
|
7
7
|
import { PromptPlan } from "./promptPlan.js";
|
|
8
8
|
import { buildTestPatternGuidelines, buildTestQualityCriteria, buildGenerationRules, MAX_CRITICAL_TESTS, } from "./recommendationSections.js";
|
|
9
|
-
import { TASK_ANALYZE_MAINTAIN, TESTBOT_TASK1_STEP_CODE_REVIEW,
|
|
9
|
+
import { TASK_ANALYZE_MAINTAIN, TESTBOT_TASK1_STEP_CODE_REVIEW, taskStepRef, } from "./recommendationShared.js";
|
|
10
10
|
// ── Step body functions ───────────────────────────────────────────────────────
|
|
11
11
|
function _execCodeReviewBody(_ctx) {
|
|
12
12
|
const codeReviewRef = taskStepRef(TASK_ANALYZE_MAINTAIN, TESTBOT_TASK1_STEP_CODE_REVIEW);
|
|
@@ -69,7 +69,7 @@ INSERT a non-bug source-code-derived candidate into the ranked list **only if AL
|
|
|
69
69
|
3. It is not already covered by a structural candidate in the list below
|
|
70
70
|
|
|
71
71
|
If these conditions are not met, add it to ADDITIONAL only — do NOT displace a pre-ranked GENERATE item.
|
|
72
|
-
**
|
|
72
|
+
**Attack-surface \`security_boundary\` items should never be displaced by non-bug candidates** — they test sibling destructive endpoints that could bypass the changed auth boundary. However, bug-catching tests CAN displace them only after all lower-value non-bug slots are exhausted.
|
|
73
73
|
|
|
74
74
|
When a qualifying candidate is inserted: place it HIGH before MEDIUM before LOW; within the same priority, source-code-derived candidates go BEFORE structural ones. Re-number ranks after insertion. The top ${ctx.maxGen} ranked items become GENERATE candidates.
|
|
75
75
|
|
|
@@ -80,8 +80,8 @@ When a qualifying candidate is inserted: place it HIGH before MEDIUM before LOW;
|
|
|
80
80
|
function _execDiversityBody(_ctx) {
|
|
81
81
|
return `**Bug-coverage gate (runs BEFORE dedup):**
|
|
82
82
|
Verify that the highest-severity \`<bug_found>\` flaw from Step ${EXEC_STEP_CODE_REVIEW} has exactly one GENERATE item with category \`bug_caught\` targeting it — meaning the test would FAIL on the current buggy code and PASS once the flaw is fixed. At most one promotion per run (per Step ${EXEC_STEP_CODE_REVIEW} cap). If the promoted flaw has no targeting \`bug_caught\` GENERATE item:
|
|
83
|
-
- Check ADDITIONAL for a matching test → promote it into the lowest-priority non-bug, non-CRITICAL GENERATE slot first (lowest category rank per \`crud > error_handling > workflow > data_validation > data_integrity > business_rule\`; preserve attack-surface \`security_boundary\`
|
|
84
|
-
- If no ADDITIONAL candidate matches, create a new \`bug_caught\` test and insert it, displacing the lowest-priority non-bug, non-CRITICAL GENERATE item first; displace an attack-surface \`security_boundary\`
|
|
83
|
+
- Check ADDITIONAL for a matching test → promote it into the lowest-priority non-bug, non-CRITICAL GENERATE slot first (lowest category rank per \`crud > error_handling > workflow > data_validation > data_integrity > business_rule\`; preserve attack-surface \`security_boundary\` items unless no lower-priority slot exists).
|
|
84
|
+
- If no ADDITIONAL candidate matches, create a new \`bug_caught\` test and insert it, displacing the lowest-priority non-bug, non-CRITICAL GENERATE item first; displace an attack-surface \`security_boundary\` item only when every GENERATE slot is higher priority.
|
|
85
85
|
A \`bug_caught\` test is NEVER considered a "duplicate" of a non-bug test during the dedup below.
|
|
86
86
|
|
|
87
87
|
Each GENERATE item must exercise a **distinct code path** — not just different input values on the same path.
|
|
@@ -98,7 +98,7 @@ Same step sequence with only payload differences (e.g. 10% vs 5% discount both r
|
|
|
98
98
|
function _execRegisterBody(_ctx) {
|
|
99
99
|
return `Register your complete candidate list — every test you would generate OR recommend — via \`skyramp_register_test_plan\` (\`stateFile\` required). Include a discriminator claim (\`discriminator\` field — valid kinds and anchor rules are in the tool schema) for candidates probing the changed logic identified in Step ${EXEC_STEP_CODE_REVIEW}/Step ${EXEC_STEP_ENRICH}.
|
|
100
100
|
|
|
101
|
-
The returned GENERATE list is mandatory and final — generation tools reject unregistered scenarios. If the tool demotes a discriminator claim (returned in \`demotions\` with a reason), either strengthen the claim — a step that actually exercises the declared \`kind\`, or a verbatim anchor that occurs in the diff — or drop it
|
|
101
|
+
The returned GENERATE list is mandatory and final — generation tools reject unregistered scenarios. If the tool demotes a discriminator claim (returned in \`demotions\` with a reason), either strengthen the claim — a step that actually exercises the declared \`kind\`, or a verbatim anchor that occurs in the diff — or drop it. A demoted candidate normally stays in the plan. If it also left the plan for another recorded reason, the report lists it under "Demoted AND removed".`;
|
|
102
102
|
}
|
|
103
103
|
function _execExecuteBody(ctx) {
|
|
104
104
|
return `Replace any scenario that pairs unrelated resources with one reflecting actual foreign-key relationships in the codebase.
|
|
@@ -149,20 +149,6 @@ export const EXEC_STEP_EXECUTE = _execPlan.labels.EXECUTE; // "4"
|
|
|
149
149
|
/** "5" — Register test plan (SKYR-3879 Path B checkpoint) */
|
|
150
150
|
export const EXEC_STEP_REGISTER = _execPlan.labels.REGISTER; // "5"
|
|
151
151
|
const SERVICE_REFS = resolveServiceDetailsRef();
|
|
152
|
-
function prioritizeAttackSurfaceBundles(items) {
|
|
153
|
-
const reordered = [];
|
|
154
|
-
for (const item of items) {
|
|
155
|
-
if (isAttackSurfaceSecurityBoundary(item.scenario)) {
|
|
156
|
-
const firstDirectAuthIndex = reordered.findIndex(candidate => isOrdinaryDirectAuthBoundary(candidate.scenario));
|
|
157
|
-
if (firstDirectAuthIndex >= 0) {
|
|
158
|
-
reordered.splice(firstDirectAuthIndex, 0, item);
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
reordered.push(item);
|
|
163
|
-
}
|
|
164
|
-
return reordered;
|
|
165
|
-
}
|
|
166
152
|
export function buildExecutionPlan(scored, maxGen, topN, baseUrl, authHeaderValue, authSchemeSnippet, authTypeValue, seed, endpointCount, isUIOnlyPR, hasFrontendChanges = false, hasTraces = false, externalCoverage = new Set(), relevantExternalTestPaths = [],
|
|
167
153
|
/**
|
|
168
154
|
* Whether the diff classified at least one new/modified/removed endpoint.
|
|
@@ -184,40 +170,58 @@ hasApiChanges = true) {
|
|
|
184
170
|
: hasFrontendChanges
|
|
185
171
|
? Math.max(0, maxGen - 1)
|
|
186
172
|
: maxGen;
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// This
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
173
|
+
// Both this renderer and the register-plan path run the SAME selection
|
|
174
|
+
// pipeline — `runBudget` — instead of two copies of it (SKYR-4214 item 7).
|
|
175
|
+
// This file used to hold its own external dedup, attack-surface ordering,
|
|
176
|
+
// round-robin GENERATE pick and ADDITIONAL set-difference, so a fix in one
|
|
177
|
+
// copy left the other wrong.
|
|
178
|
+
//
|
|
179
|
+
// The two are NOT guaranteed to produce the same GENERATE set, and this
|
|
180
|
+
// comment must not claim they are: `selectPlan` re-ranks and calls
|
|
181
|
+
// `diversityBalancedBudgeter` (planRanker.ts:96) where this calls
|
|
182
|
+
// `fixedNBudgeter`, and the two differ in the `pick` step —
|
|
183
|
+
// diversityBalancedBudgeter.test.ts covers that divergence. What is now
|
|
184
|
+
// shared is the dedup rule, the ordering and the ADDITIONAL cap.
|
|
185
|
+
//
|
|
186
|
+
// `runBudget` needs a Candidate, which carries provenance and a stable id
|
|
187
|
+
// that only the register-plan path fills. Everything reaching this renderer
|
|
188
|
+
// is server-drafted, and `computeCandidateId` is a pure function of the
|
|
189
|
+
// scenario, so both fields are derivable here.
|
|
190
|
+
//
|
|
191
|
+
// The context deliberately reports no frontend work, whatever this PR
|
|
192
|
+
// contains: this renderer never had the UI-promotion loop, and the backend
|
|
193
|
+
// count it computed above already applied the UI reservation. Passing the
|
|
194
|
+
// real flags would introduce UI slots into the prompt's GENERATE list, which
|
|
195
|
+
// is a behaviour change this item does not carry.
|
|
196
|
+
const candidates = scored.map((item) => ({
|
|
197
|
+
...item,
|
|
198
|
+
source: CandidateSource.SERVER,
|
|
199
|
+
candidateId: computeCandidateId(item.scenario),
|
|
200
|
+
}));
|
|
201
|
+
const selection = fixedNBudgeter.select(candidates, {
|
|
202
|
+
maxGenerate: backendGenerateCount,
|
|
203
|
+
maxTotal: topN,
|
|
204
|
+
isUIOnlyPR: false,
|
|
205
|
+
hasFrontendChanges: false,
|
|
206
|
+
externalCoverage,
|
|
207
|
+
// This renderer never receives the branch diff, so it cannot tell whether the
|
|
208
|
+
// PR wrote tests of its own. `true` is the same abstain direction the analyze
|
|
209
|
+
// end takes for an unknown (SKYR-4024): the covered-candidate reserve stays
|
|
210
|
+
// shut here, and those candidates are reported in the Removed section below
|
|
211
|
+
// rather than promoted to ADDITIONAL.
|
|
212
|
+
diffChangesTestFiles: true,
|
|
213
|
+
});
|
|
214
|
+
const generateItems = selection.generate;
|
|
215
|
+
const additionalItems = selection.additional;
|
|
216
|
+
// A candidate the selection stage removed — covered by an external test, or
|
|
217
|
+
// past the maxTotal cap — leaves no trace in the two lists above. In the
|
|
218
|
+
// recommendation-only flow (analyze_changes plus this prompt, with no
|
|
219
|
+
// register call) this section is the ONLY place the agent can read that a
|
|
220
|
+
// candidate existed and why it went, which is what SKYR-4214 exists to fix.
|
|
221
|
+
const scenarioNameById = new Map(candidates.map((c) => [c.candidateId, c.scenario.scenarioName]));
|
|
222
|
+
const removedLines = selection.dropped
|
|
223
|
+
.map((drop) => `- ${scenarioNameById.get(drop.candidateId) ?? drop.candidateId}: ${drop.reason}`)
|
|
224
|
+
.join("\n");
|
|
221
225
|
const hasWorkspaceAuthType = !!authTypeValue && authTypeValue !== "none";
|
|
222
226
|
// For skyramp_integration_test_generation with scenarioFile:
|
|
223
227
|
// - If workspace has authType set: omit auth entirely — workspace handles Bearer prefix.
|
|
@@ -432,7 +436,9 @@ ${isUIOnlyPR
|
|
|
432
436
|
### ADDITIONAL (list in additionalRecommendations in this order after Step ${EXEC_STEP_ENRICH} insertion)
|
|
433
437
|
|
|
434
438
|
${additionalLines || " (none pre-ranked)"}
|
|
435
|
-
${
|
|
439
|
+
${removedLines
|
|
440
|
+
? `\n### Removed (${selection.dropped.length}) — candidates the selection stage dropped before ranking. They are NOT in the lists above; do not re-add them.\n\n${removedLines}\n`
|
|
441
|
+
: ""}${uiGroundingGuidance}
|
|
436
442
|
${uiGuidance}
|
|
437
443
|
${supplementNote}
|
|
438
444
|
|