@skyramp/mcp 0.3.4 → 0.3.6-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/playwright/registerPlaywrightTools.js +92 -30
- package/build/playwright/traceRecordingPrompt.d.ts +6 -0
- package/build/playwright/traceRecordingPrompt.js +6 -2
- package/build/prompts/code-reuse.d.ts +1 -2
- package/build/prompts/code-reuse.js +182 -77
- package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
- package/build/prompts/modularization/integration-test-modularization.js +83 -41
- package/build/prompts/modularization/render.d.ts +18 -0
- package/build/prompts/modularization/render.js +12 -0
- package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
- package/build/prompts/modularization/ui-test-modularization.js +89 -47
- package/build/prompts/pom-aware-code-reuse.js +3 -1
- package/build/prompts/shared-helper-policy.d.ts +57 -0
- package/build/prompts/shared-helper-policy.js +135 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
- package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
- package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
- package/build/prompts/test-recommendation/recommendationShared.js +90 -16
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
- package/build/prompts/testbot/testbot-prompts.js +88 -33
- package/build/recommendation/budgeters/shared.js +105 -27
- package/build/recommendation/discriminators.js +13 -2
- package/build/recommendation/planRanker.d.ts +6 -6
- package/build/recommendation/planRanker.js +6 -61
- package/build/services/AnalyticsService.d.ts +7 -0
- package/build/services/AnalyticsService.js +7 -1
- package/build/services/ModularizationService.js +1 -3
- package/build/services/TestDiscoveryService.d.ts +0 -2
- package/build/services/TestDiscoveryService.js +2 -37
- package/build/services/TestGenerationService.d.ts +16 -0
- package/build/services/TestGenerationService.js +86 -10
- package/build/services/containerEnv.js +13 -12
- package/build/tools/code-refactor/codeReuseTool.js +279 -93
- package/build/tools/code-refactor/enhance-state.d.ts +49 -0
- package/build/tools/code-refactor/enhance-state.js +109 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
- package/build/tools/code-refactor/modularizationTool.js +9 -2
- package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
- package/build/tools/code-refactor/reuse-outcome.js +14 -4
- package/build/tools/code-refactor/reuse-state.d.ts +127 -5
- package/build/tools/code-refactor/reuse-state.js +628 -16
- package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
- package/build/tools/code-refactor/utils-verify-gates.js +100 -0
- package/build/tools/code-refactor/verify-gates.d.ts +2 -1
- package/build/tools/code-refactor/verify-gates.js +90 -25
- package/build/tools/executeSkyrampTestTool.d.ts +19 -0
- package/build/tools/executeSkyrampTestTool.js +158 -8
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
- package/build/tools/generate-tests/generateUIRestTool.js +22 -0
- package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
- package/build/tools/generate-tests/scenarioLint.js +127 -19
- package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
- package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
- package/build/tools/submitReportTool.d.ts +38 -38
- package/build/tools/submitReportTool.js +487 -104
- package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
- package/build/tools/test-management/analyzeChangesTool.js +75 -12
- package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
- package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
- package/build/tools/test-management/registerTestPlanTool.js +149 -23
- package/build/types/Recommendation.d.ts +34 -5
- package/build/types/RepositoryAnalysis.d.ts +133 -114
- package/build/types/RepositoryAnalysis.js +1 -1
- package/build/types/ReuseOutcome.d.ts +102 -6
- package/build/types/ReuseOutcome.js +16 -2
- package/build/types/TestRecommendation.js +21 -3
- package/build/types/TestTypes.js +14 -8
- package/build/types/TestbotReport.d.ts +25 -3
- package/build/types/index.d.ts +2 -2
- package/build/types/index.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +69 -1
- package/build/utils/AnalysisStateManager.js +69 -5
- package/build/utils/branchDiff.d.ts +10 -0
- package/build/utils/branchDiff.js +28 -0
- package/build/utils/changedRoutes.d.ts +29 -0
- package/build/utils/changedRoutes.js +87 -0
- package/build/utils/featureFlags.d.ts +21 -0
- package/build/utils/featureFlags.js +23 -0
- package/build/utils/frontendIntegration.js +34 -4
- package/build/utils/importerHop.d.ts +2 -8
- package/build/utils/importerHop.js +15 -53
- package/build/utils/pathMatching.d.ts +38 -0
- package/build/utils/pathMatching.js +71 -0
- package/build/utils/pathSignatures.d.ts +22 -0
- package/build/utils/pathSignatures.js +57 -0
- package/build/utils/planMatchKeys.d.ts +16 -3
- package/build/utils/planMatchKeys.js +26 -10
- package/build/utils/pluralization.d.ts +10 -0
- package/build/utils/pluralization.js +18 -0
- package/build/utils/pom-catalog-parse.d.ts +52 -0
- package/build/utils/pom-catalog-parse.js +141 -0
- package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
- package/build/utils/pom-scope/selector-extractor.js +34 -8
- package/build/utils/pom-verify/verify.d.ts +6 -5
- package/build/utils/pom-verify/verify.js +8 -6
- package/build/utils/reportLanguage.d.ts +43 -0
- package/build/utils/reportLanguage.js +125 -0
- package/build/utils/reportVerification.d.ts +74 -4
- package/build/utils/reportVerification.js +259 -3
- package/build/utils/reuseRouting.d.ts +3 -0
- package/build/utils/reuseRouting.js +50 -0
- package/build/utils/routeParsers.d.ts +2 -0
- package/build/utils/routeParsers.js +65 -8
- package/build/utils/scenarioDrafting.d.ts +1 -1
- package/build/utils/scenarioDrafting.js +57 -45
- package/build/utils/subjectEndpoints.d.ts +19 -0
- package/build/utils/subjectEndpoints.js +98 -0
- package/build/utils/testFileClassification.d.ts +11 -0
- package/build/utils/testFileClassification.js +47 -0
- package/build/utils/uiPageEnumerator.d.ts +45 -19
- package/build/utils/uiPageEnumerator.js +95 -51
- package/build/utils/utils-verify/allow.d.ts +16 -0
- package/build/utils/utils-verify/allow.js +68 -0
- package/build/utils/utils-verify/call-sites.d.ts +34 -0
- package/build/utils/utils-verify/call-sites.js +154 -0
- package/build/utils/utils-verify/index.d.ts +7 -0
- package/build/utils/utils-verify/index.js +7 -0
- package/build/utils/utils-verify/language-spec.d.ts +91 -0
- package/build/utils/utils-verify/language-spec.js +210 -0
- package/build/utils/utils-verify/locate.d.ts +39 -0
- package/build/utils/utils-verify/locate.js +199 -0
- package/build/utils/utils-verify/parse.d.ts +34 -0
- package/build/utils/utils-verify/parse.js +177 -0
- package/build/utils/utils-verify/stage.d.ts +24 -0
- package/build/utils/utils-verify/stage.js +107 -0
- package/build/utils/utils-verify/verify.d.ts +63 -0
- package/build/utils/utils-verify/verify.js +168 -0
- package/build/utils/utils.d.ts +3 -1
- package/build/utils/utils.js +3 -1
- package/build/workspace/workspace.d.ts +32 -32
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.VEfqi1qN.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|