@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.
Files changed (159) hide show
  1. package/build/playwright/registerPlaywrightTools.js +92 -30
  2. package/build/playwright/traceRecordingPrompt.d.ts +6 -0
  3. package/build/playwright/traceRecordingPrompt.js +6 -2
  4. package/build/prompts/code-reuse.d.ts +1 -2
  5. package/build/prompts/code-reuse.js +182 -77
  6. package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
  7. package/build/prompts/modularization/integration-test-modularization.js +83 -41
  8. package/build/prompts/modularization/render.d.ts +18 -0
  9. package/build/prompts/modularization/render.js +12 -0
  10. package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
  11. package/build/prompts/modularization/ui-test-modularization.js +89 -47
  12. package/build/prompts/pom-aware-code-reuse.js +3 -1
  13. package/build/prompts/shared-helper-policy.d.ts +57 -0
  14. package/build/prompts/shared-helper-policy.js +135 -0
  15. package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
  16. package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
  17. package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
  18. package/build/prompts/test-recommendation/recommendationShared.js +90 -16
  19. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
  21. package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
  22. package/build/prompts/testbot/testbot-prompts.js +88 -33
  23. package/build/recommendation/budgeters/shared.js +105 -27
  24. package/build/recommendation/discriminators.js +13 -2
  25. package/build/recommendation/planRanker.d.ts +6 -6
  26. package/build/recommendation/planRanker.js +6 -61
  27. package/build/services/AnalyticsService.d.ts +7 -0
  28. package/build/services/AnalyticsService.js +7 -1
  29. package/build/services/ModularizationService.js +1 -3
  30. package/build/services/TestDiscoveryService.d.ts +0 -2
  31. package/build/services/TestDiscoveryService.js +2 -37
  32. package/build/services/TestGenerationService.d.ts +16 -0
  33. package/build/services/TestGenerationService.js +86 -10
  34. package/build/services/containerEnv.js +13 -12
  35. package/build/tools/code-refactor/codeReuseTool.js +279 -93
  36. package/build/tools/code-refactor/enhance-state.d.ts +49 -0
  37. package/build/tools/code-refactor/enhance-state.js +109 -0
  38. package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
  39. package/build/tools/code-refactor/modularizationTool.js +9 -2
  40. package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
  41. package/build/tools/code-refactor/reuse-outcome.js +14 -4
  42. package/build/tools/code-refactor/reuse-state.d.ts +127 -5
  43. package/build/tools/code-refactor/reuse-state.js +628 -16
  44. package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
  45. package/build/tools/code-refactor/utils-verify-gates.js +100 -0
  46. package/build/tools/code-refactor/verify-gates.d.ts +2 -1
  47. package/build/tools/code-refactor/verify-gates.js +90 -25
  48. package/build/tools/executeSkyrampTestTool.d.ts +19 -0
  49. package/build/tools/executeSkyrampTestTool.js +158 -8
  50. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
  51. package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
  52. package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
  53. package/build/tools/generate-tests/generateUIRestTool.js +22 -0
  54. package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
  55. package/build/tools/generate-tests/scenarioLint.js +127 -19
  56. package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
  57. package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
  58. package/build/tools/submitReportTool.d.ts +38 -38
  59. package/build/tools/submitReportTool.js +487 -104
  60. package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
  61. package/build/tools/test-management/analyzeChangesTool.js +75 -12
  62. package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
  63. package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
  64. package/build/tools/test-management/registerTestPlanTool.js +149 -23
  65. package/build/types/Recommendation.d.ts +34 -5
  66. package/build/types/RepositoryAnalysis.d.ts +133 -114
  67. package/build/types/RepositoryAnalysis.js +1 -1
  68. package/build/types/ReuseOutcome.d.ts +102 -6
  69. package/build/types/ReuseOutcome.js +16 -2
  70. package/build/types/TestRecommendation.js +21 -3
  71. package/build/types/TestTypes.js +14 -8
  72. package/build/types/TestbotReport.d.ts +25 -3
  73. package/build/types/index.d.ts +2 -2
  74. package/build/types/index.js +1 -1
  75. package/build/utils/AnalysisStateManager.d.ts +69 -1
  76. package/build/utils/AnalysisStateManager.js +69 -5
  77. package/build/utils/branchDiff.d.ts +10 -0
  78. package/build/utils/branchDiff.js +28 -0
  79. package/build/utils/changedRoutes.d.ts +29 -0
  80. package/build/utils/changedRoutes.js +87 -0
  81. package/build/utils/featureFlags.d.ts +21 -0
  82. package/build/utils/featureFlags.js +23 -0
  83. package/build/utils/frontendIntegration.js +34 -4
  84. package/build/utils/importerHop.d.ts +2 -8
  85. package/build/utils/importerHop.js +15 -53
  86. package/build/utils/pathMatching.d.ts +38 -0
  87. package/build/utils/pathMatching.js +71 -0
  88. package/build/utils/pathSignatures.d.ts +22 -0
  89. package/build/utils/pathSignatures.js +57 -0
  90. package/build/utils/planMatchKeys.d.ts +16 -3
  91. package/build/utils/planMatchKeys.js +26 -10
  92. package/build/utils/pluralization.d.ts +10 -0
  93. package/build/utils/pluralization.js +18 -0
  94. package/build/utils/pom-catalog-parse.d.ts +52 -0
  95. package/build/utils/pom-catalog-parse.js +141 -0
  96. package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
  97. package/build/utils/pom-scope/selector-extractor.js +34 -8
  98. package/build/utils/pom-verify/verify.d.ts +6 -5
  99. package/build/utils/pom-verify/verify.js +8 -6
  100. package/build/utils/reportLanguage.d.ts +43 -0
  101. package/build/utils/reportLanguage.js +125 -0
  102. package/build/utils/reportVerification.d.ts +74 -4
  103. package/build/utils/reportVerification.js +259 -3
  104. package/build/utils/reuseRouting.d.ts +3 -0
  105. package/build/utils/reuseRouting.js +50 -0
  106. package/build/utils/routeParsers.d.ts +2 -0
  107. package/build/utils/routeParsers.js +65 -8
  108. package/build/utils/scenarioDrafting.d.ts +1 -1
  109. package/build/utils/scenarioDrafting.js +57 -45
  110. package/build/utils/subjectEndpoints.d.ts +19 -0
  111. package/build/utils/subjectEndpoints.js +98 -0
  112. package/build/utils/testFileClassification.d.ts +11 -0
  113. package/build/utils/testFileClassification.js +47 -0
  114. package/build/utils/uiPageEnumerator.d.ts +45 -19
  115. package/build/utils/uiPageEnumerator.js +95 -51
  116. package/build/utils/utils-verify/allow.d.ts +16 -0
  117. package/build/utils/utils-verify/allow.js +68 -0
  118. package/build/utils/utils-verify/call-sites.d.ts +34 -0
  119. package/build/utils/utils-verify/call-sites.js +154 -0
  120. package/build/utils/utils-verify/index.d.ts +7 -0
  121. package/build/utils/utils-verify/index.js +7 -0
  122. package/build/utils/utils-verify/language-spec.d.ts +91 -0
  123. package/build/utils/utils-verify/language-spec.js +210 -0
  124. package/build/utils/utils-verify/locate.d.ts +39 -0
  125. package/build/utils/utils-verify/locate.js +199 -0
  126. package/build/utils/utils-verify/parse.d.ts +34 -0
  127. package/build/utils/utils-verify/parse.js +177 -0
  128. package/build/utils/utils-verify/stage.d.ts +24 -0
  129. package/build/utils/utils-verify/stage.js +107 -0
  130. package/build/utils/utils-verify/verify.d.ts +63 -0
  131. package/build/utils/utils-verify/verify.js +168 -0
  132. package/build/utils/utils.d.ts +3 -1
  133. package/build/utils/utils.js +3 -1
  134. package/build/workspace/workspace.d.ts +32 -32
  135. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
  136. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
  137. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
  138. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
  139. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
  140. package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
  141. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
  142. package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
  143. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -0
  145. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
  146. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
  147. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
  148. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
  149. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  150. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.VEfqi1qN.js +5 -0
  151. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  152. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  153. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  154. package/node_modules/playwright/package.json +1 -1
  155. package/package.json +2 -2
  156. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
  157. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
  158. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
  159. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
@@ -1,44 +1,44 @@
1
- export function getModularizationPrompt(filePath) {
2
- return `# INTEGRATION TEST MODULARIZATION - SIMPLE AND SAFE
3
-
4
- **CRITICAL: Use the 'write' tool to save the modularized code to: ${filePath}**
5
- **DO NOT use search_replace - use the 'write' tool to overwrite the entire file with the modularized version**
6
-
7
- **ABSOLUTELY FORBIDDEN:**
8
- - DO NOT CREATE ANY INTERFACES, CLASSES, TYPES, OR NEW DATA STRUCTURES. USE INLINE TYPES ONLY.
9
- - DO NOT CREATE/UPDATE package.json or requirements.txt or pom.xml or build.gradle file
10
- - DO NOT CHANGE TEST LOGIC, DATA VALUES, ASSERTIONS, OR API CALLS
11
- - DO NOT MODIFY EXPECTED RESPONSES OR STATUS CODES
12
-
13
- ## STEP 1: READ AND UNDERSTAND THE ORIGINAL TEST
14
- **CRITICAL: Before making ANY changes:**
15
- 1. Read ${filePath} completely
1
+ import { isModularizeFirstTarget } from "../../utils/reuseRouting.js";
2
+ import { PromptPlan } from "../test-recommendation/promptPlan.js";
3
+ import { API_HELPERS, ASSERTION_BOUNDARY_RULE_EXTRACT, } from "../shared-helper-policy.js";
4
+ import { renderModularizationPrompt, } from "./render.js";
5
+ // On modularize-first flows (see isModularizeFirstTarget), downstream
6
+ // skyramp_reuse_code consolidates this file's helpers into the shared
7
+ // SkyrampUtils file — so EVERY request step must become a named helper, even
8
+ // when nothing repeats in-file (a linear CRUD scenario calls each endpoint once,
9
+ // so repetition-based extraction yields zero helpers and the utils file can
10
+ // never materialize).
11
+ const _plan = new PromptPlan()
12
+ .addPhase("main", "Integration Test Modularization", {
13
+ headerLevel: "hidden",
14
+ stepFormat: "hash",
15
+ })
16
+ .step("READ", "READ AND UNDERSTAND THE ORIGINAL TEST", (p) => `**CRITICAL: Before making ANY changes:**
17
+ 1. Read ${p.filePath} completely
16
18
  2. Identify all API endpoints and their parameters
17
19
  3. Identify all test data (request bodies, expected responses)
18
20
  4. Identify all assertions and expected values
19
21
  5. Document the test flow
20
- 6. **DO NOT PROCEED** until you fully understand what the test does
21
-
22
- ## STEP 2: FIND REPETITIVE CODE
23
- Look for API calls that repeat 2+ times with IDENTICAL structure but different data.
24
-
25
- ## STEP 3: CREATE HELPER FUNCTIONS - EXTRACT ONLY, NO CHANGES
26
-
27
- **GOLDEN RULE: COPY-PASTE THE ORIGINAL CODE INTO HELPERS, JUST ADD PARAMETERS AND REPLACE HARDCODED VALUES WITH PARAMETERS**
22
+ 6. **DO NOT PROCEED** until you fully understand what the test does`)
23
+ .step("IDENTIFY", (p) => (p.utilsReuse ? "IDENTIFY REQUEST STEPS" : "FIND REPETITIVE CODE"), (p) => p.utilsReuse
24
+ ? `A request step = one API call plus the assertions that verify its response.
25
+ This project consolidates test helpers into a shared utils file, so EVERY request step becomes a named helper function — the step does NOT need to repeat.
26
+ List every request step in the test (e.g. create product, create order, update order, get order).`
27
+ : `Look for API calls that repeat 2+ times with IDENTICAL structure but different data.`)
28
+ .step("EXTRACT", "CREATE HELPER FUNCTIONS - EXTRACT ONLY, NO CHANGES", (p) => `**GOLDEN RULE: COPY-PASTE THE ORIGINAL CODE INTO HELPERS, JUST ADD PARAMETERS AND REPLACE HARDCODED VALUES WITH PARAMETERS**
28
29
 
29
30
  **CRITICAL RULES:**
30
31
  1. **EXACT CODE COPY** - Copy the API call code EXACTLY as-is from the original test
31
- 2. **ONLY ADD PARAMETERS** - The ONLY change allowed is converting hardcoded values to parameters
32
+ 2. **ONLY ADD PARAMETERS** - The ONLY change allowed is converting hardcoded values to parameters${p.utilsReuse
33
+ ? " — plus ONE placement exception: a response-body assertion that would land inside a helper is placed in the test body, applied to the response the helper returns (same check, same expected value, same relative order). That relocation is the only assertion move allowed; see HELPER SHAPE below"
34
+ : ""}
32
35
  3. **NEVER GENERALIZE SELECTORS** - Keep all selectors/locators exactly as they are in the original code (do not refactor into dynamic template strings)
33
- 4. **NO LOGIC CHANGES** - Do not modify conditions, validations, or assertions
36
+ 4. **NO LOGIC CHANGES** - Do not modify conditions, validations, or assertions${p.utilsReuse ? " (relocating a body assertion per rule 2 is a placement, not a change)" : ""}
34
37
  5. **NO DATA CHANGES** - Do not change any request/response values or expected results
35
38
  6. **PRESERVE PATH PARAMETERS** - API path parameters must be passed as function parameters
36
39
  7. **PRESERVE ORDER** - Keep all operations in the exact same order
37
40
 
38
- **PARAMETER CONVERSION RULES:**
39
- - Find the values that DIFFER between repetitions (e.g., user IDs, product names)
40
- - Make ONLY those differing values into parameters
41
- - Keep everything else EXACTLY the same
41
+ ${p.utilsReuse ? sharedHelperShape(p) : repetitionParameterRules()}
42
42
 
43
43
  **EXAMPLE OF CORRECT EXTRACTION:**
44
44
  \`\`\`typescript
@@ -49,12 +49,18 @@ expect(response1.status).toBe(200);
49
49
  const response2 = await fetch("/api/users/2");
50
50
  expect(response2.status).toBe(200);
51
51
 
52
- // Correct helper (EXACT copy + parameters):
53
- async function getUser(userId: number) {
52
+ // Correct helper (EXACT copy + parameters${p.utilsReuse ? "; the expected status is a defaulted parameter per HELPER SHAPE" : ""}):
53
+ ${p.utilsReuse
54
+ ? `async function getUser(userId: number, expectedStatus = 200) {
55
+ const response = await fetch(\`/api/users/\${userId}\`);
56
+ expect(response.status).toBe(expectedStatus);
57
+ return response;
58
+ }`
59
+ : `async function getUser(userId: number) {
54
60
  const response = await fetch(\`/api/users/\${userId}\`);
55
61
  expect(response.status).toBe(200);
56
62
  return response;
57
- }
63
+ }`}
58
64
  \`\`\`
59
65
 
60
66
  **WHAT NOT TO DO:**
@@ -64,14 +70,9 @@ Modifying expected status codes
64
70
  Changing request/response validation
65
71
  Reordering operations
66
72
  "Improving" error handling
67
- Adding new assertions
68
-
69
- ## STEP 4: USE HELPERS WITH EXACT SAME DATA
70
- When calling the helpers, use the EXACT same values from the original test.
71
-
72
- ## STEP 5: FINAL VERIFICATION
73
-
74
- **BEFORE WRITING ANY CODE, VERIFY:**
73
+ Adding new assertions`)
74
+ .step("USE", "USE HELPERS WITH EXACT SAME DATA", () => `When calling the helpers, use the EXACT same values from the original test.`)
75
+ .step("VERIFY", "FINAL VERIFICATION", () => `**BEFORE WRITING ANY CODE, VERIFY:**
75
76
  - [ ] I have read and understood the original test completely
76
77
  - [ ] I have identified all API calls and their parameters
77
78
  - [ ] I have documented all expected responses and assertions
@@ -91,5 +92,46 @@ Run through the modularized test mentally:
91
92
  2. Does it use the exact same request data?
92
93
  3. Does it expect the exact same responses?
93
94
 
94
- If ANY answer is "no", DO NOT submit - fix the modularization first.`;
95
+ If ANY answer is "no", DO NOT submit - fix the modularization first.`)
96
+ .done();
97
+ // Shared-file helper shape: the helpers this step creates are consolidated into
98
+ // SkyrampUtils by the reuse pass, so each one must be nameable and callable by
99
+ // the next scenario — canonical names, scenario values as arguments, and no
100
+ // scenario-specific claims inside the helper.
101
+ function sharedHelperShape(p) {
102
+ return `**HELPER SHAPE (one helper per request step from Step ${_plan.labels.IDENTIFY}):**
103
+ - ${API_HELPERS.namingRule}
104
+ - **PARAMETERS**: pass in the client/session object, plus every value the step reads from a previous step's response (IDs used in paths or bodies), plus every body value this scenario varies or its assertions reference (names, prices, quantities, percentages). Values that never vary and that no assertion reads may stay hardcoded — do not invent parameters nothing passes differently.
105
+ - **SCENARIO VALUES GO THROUGH \`data_override\`, NOT THE BODY STRING**: apply each parameterized body value via the request's \`data_override\` map (that is what it is for) and leave the raw body string as generated. A scenario literal edited into the body string makes this helper unmergeable with the same step from another test.
106
+ - **RETURN** the response object so later steps can chain and assert from it.
107
+ - ${API_HELPERS.assertionRule} ${ASSERTION_BOUNDARY_RULE_EXTRACT}
108
+ - Helpers are module-level named functions in ${p.filePath}; the test body becomes a sequence of helper calls plus this scenario's assertions, in the original order.
109
+ - Keep everything else EXACTLY the same.`;
110
+ }
111
+ function repetitionParameterRules() {
112
+ return `**PARAMETER CONVERSION RULES:**
113
+ - Find the values that DIFFER between repetitions (e.g., user IDs, product names)
114
+ - Make ONLY those differing values into parameters
115
+ - Keep everything else EXACTLY the same`;
116
+ }
117
+ export function getModularizationPrompt(filePath) {
118
+ const utilsReuse = isModularizeFirstTarget("integration");
119
+ return renderModularizationPrompt({
120
+ title: "INTEGRATION TEST MODULARIZATION - SIMPLE AND SAFE",
121
+ forbidden: [
122
+ "DO NOT CREATE ANY INTERFACES, CLASSES, TYPES, OR NEW DATA STRUCTURES. USE INLINE TYPES ONLY.",
123
+ "DO NOT CREATE/UPDATE package.json or requirements.txt or pom.xml or build.gradle file",
124
+ `DO NOT CHANGE TEST LOGIC, DATA VALUES, ASSERTIONS, OR API CALLS${utilsReuse ? " (moving a response-body assertion out of a helper into the test body, verbatim, is a placement — not a change)" : ""}`,
125
+ "DO NOT MODIFY EXPECTED RESPONSES OR STATUS CODES",
126
+ ],
127
+ plan: _plan,
128
+ params: { filePath, utilsReuse },
129
+ });
130
+ }
131
+ /** Structured view of which steps render for the given inputs (for tests). */
132
+ export function previewModularizationPlan(filePath) {
133
+ return _plan.preview({
134
+ filePath,
135
+ utilsReuse: isModularizeFirstTarget("integration"),
136
+ });
95
137
  }
@@ -0,0 +1,18 @@
1
+ import type { PromptPlan } from "../test-recommendation/promptPlan.js";
2
+ /**
3
+ * Parameters both modularization plans render against.
4
+ * `utilsReuse` is true on modularize-first flows (see isModularizeFirstTarget):
5
+ * downstream skyramp_reuse_code consolidates this file's helpers into the shared
6
+ * SkyrampUtils file, which changes what a helper may be named and may assert.
7
+ */
8
+ export interface ModularizationParams {
9
+ filePath: string;
10
+ utilsReuse: boolean;
11
+ }
12
+ /** The scaffold shared by the UI and integration modularization prompts. */
13
+ export declare function renderModularizationPrompt(opts: {
14
+ title: string;
15
+ forbidden: string[];
16
+ plan: PromptPlan<ModularizationParams>;
17
+ params: ModularizationParams;
18
+ }): string;
@@ -0,0 +1,12 @@
1
+ /** The scaffold shared by the UI and integration modularization prompts. */
2
+ export function renderModularizationPrompt(opts) {
3
+ return `# ${opts.title}
4
+
5
+ **CRITICAL: Use the 'write' tool to save the modularized code to: ${opts.params.filePath}**
6
+ **DO NOT use search_replace - use the 'write' tool to overwrite the entire file with the modularized version**
7
+
8
+ **ABSOLUTELY FORBIDDEN:**
9
+ ${opts.forbidden.map((f) => `- ${f}`).join("\n")}
10
+
11
+ ${opts.plan.render(opts.params)}`;
12
+ }
@@ -1 +1,3 @@
1
- export declare function getModularizationPrompt(filePath: string): string;
1
+ export declare function getModularizationPrompt(filePath: string, testType: string, language?: string): string;
2
+ /** Structured view of which steps render for the given inputs (for tests). */
3
+ export declare function previewModularizationPlan(filePath: string, testType: string, language?: string): import("../test-recommendation/promptPlan.js").PlanPreview;
@@ -1,37 +1,37 @@
1
- export function getModularizationPrompt(filePath) {
2
- return `# UI TEST MODULARIZATION - IMPROVE READABILITY AND REDUCE DUPLICATION
3
-
4
- **CRITICAL: Use the 'write' tool to save the modularized code to: ${filePath}**
5
- **DO NOT use search_replace - use the 'write' tool to overwrite the entire file with the modularized version**
6
-
7
- **ABSOLUTELY FORBIDDEN:**
8
- - DO NOT CREATE INTERFACES, CLASSES, TYPES, NEW FILES, OR NEW DATA STRUCTURES
9
- - DO NOT CHANGE TEST LOGIC, DATA VALUES, CALCULATIONS, OR ASSERTIONS
10
- - DO NOT CREATE/UPDATE DEPENDENCY FILES package.json, requirements.txt, pom.xml, build.gradle file
11
-
12
- ## STEP 1: READ THE ORIGINAL TEST
13
- Read ${filePath} completely and identify:
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
- 5. **SKIP to STEP 5** for verification
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 STEP 3.
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
- - Navigation sequences (\`navbar-\` clicks, \`page.goto\`)
47
-
48
- ## STEP 3: EXTRACT INTO HELPERS
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
- ## STEP 4: CALL HELPERS WITH EXACT SAME VALUES
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
+ }