@skyramp/mcp 0.4.2 → 0.4.3-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/execution/wrapperConfig.d.ts +4 -0
- package/build/execution/wrapperConfig.js +14 -2
- package/build/prompts/code-reuse.d.ts +5 -1
- package/build/prompts/code-reuse.js +16 -10
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/pom-aware-code-reuse.d.ts +4 -1
- package/build/prompts/pom-aware-code-reuse.js +38 -6
- package/build/prompts/reuse-hand-off.d.ts +4 -0
- package/build/prompts/reuse-hand-off.js +10 -5
- package/build/prompts/shared-helper-policy.d.ts +5 -0
- package/build/prompts/shared-helper-policy.js +5 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +2 -1
- package/build/prompts/testbot/testbot-prompts.js +33 -16
- package/build/recommendation/fakeVerifyContext.d.ts +3 -0
- package/build/recommendation/fakeVerifyContext.js +16 -0
- package/build/recommendation/runVerifiers.js +2 -0
- package/build/recommendation/types.d.ts +12 -0
- package/build/recommendation/verifierContracts.d.ts +24 -2
- package/build/recommendation/verifierContracts.js +32 -4
- package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
- package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
- package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
- package/build/recommendation/verifiers/requirementSourced.js +141 -11
- package/build/services/TestGenerationService.js +4 -0
- package/build/tools/code-refactor/caller-gate.d.ts +105 -0
- package/build/tools/code-refactor/caller-gate.js +174 -0
- package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
- package/build/tools/code-refactor/changed-helper-report.js +114 -0
- package/build/tools/code-refactor/codeReuseTool.js +131 -62
- package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
- package/build/tools/code-refactor/helper-callers.d.ts +65 -0
- package/build/tools/code-refactor/helper-callers.js +85 -0
- package/build/tools/code-refactor/modularizationTool.js +10 -0
- package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
- package/build/tools/code-refactor/pom-pass-state.js +115 -0
- package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
- package/build/tools/code-refactor/retrofit-state.js +11 -12
- package/build/tools/code-refactor/reuse-outcome.d.ts +6 -0
- package/build/tools/code-refactor/reuse-record-store.d.ts +52 -0
- package/build/tools/code-refactor/reuse-record-store.js +126 -0
- package/build/tools/code-refactor/reuse-state.d.ts +18 -6
- package/build/tools/code-refactor/reuse-state.js +78 -131
- package/build/tools/code-refactor/utils-verify-gates.js +3 -3
- package/build/tools/code-refactor/verify-gates.d.ts +4 -0
- package/build/tools/code-refactor/verify-gates.js +8 -1
- package/build/tools/generate-tests/generateContractRestTool.js +7 -7
- package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
- package/build/tools/generate-tests/generateUIRestTool.js +3 -3
- package/build/tools/submitReportTool.js +91 -29
- package/build/tools/test-management/actionsTool.js +7 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
- package/build/tools/test-management/registerTestPlanTool.js +70 -22
- package/build/types/RepositoryAnalysis.d.ts +20 -20
- package/build/types/ReuseOutcome.d.ts +96 -0
- package/build/types/TestExecution.d.ts +11 -0
- package/build/types/TestExecution.js +19 -0
- package/build/types/index.d.ts +1 -1
- package/build/utils/branchDiff.d.ts +1 -1
- package/build/utils/branchDiff.js +1 -1
- package/build/utils/changedRuns.d.ts +13 -0
- package/build/utils/changedRuns.js +56 -0
- package/build/utils/featureFlags.d.ts +8 -10
- package/build/utils/featureFlags.js +23 -21
- package/build/utils/normalizeSkyrampImports.d.ts +4 -1
- package/build/utils/normalizeSkyrampImports.js +24 -17
- package/build/utils/pom-scope/ownership.d.ts +19 -0
- package/build/utils/pom-scope/ownership.js +31 -0
- package/build/utils/pom-scope/pom-files.d.ts +8 -0
- package/build/utils/pom-scope/pom-files.js +8 -0
- package/build/utils/pom-scope/scoring.js +13 -1
- package/build/utils/pom-scope/strip.d.ts +10 -0
- package/build/utils/pom-scope/strip.js +9 -0
- package/build/utils/pom-verify/verify.js +13 -2
- package/build/utils/reuseRouting.d.ts +30 -2
- package/build/utils/reuseRouting.js +36 -15
- package/build/utils/testDependencyPolicy.js +4 -16
- package/build/utils/utils-verify/action-sites.d.ts +2 -2
- package/build/utils/utils-verify/body-reach.d.ts +2 -2
- package/build/utils/utils-verify/body-reach.js +4 -1
- package/build/utils/utils-verify/call-sites.d.ts +16 -6
- package/build/utils/utils-verify/call-sites.js +12 -6
- package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
- package/build/utils/utils-verify/changed-helpers.js +91 -0
- package/build/utils/utils-verify/head.d.ts +48 -0
- package/build/utils/utils-verify/head.js +115 -0
- package/build/utils/utils-verify/importers.d.ts +59 -10
- package/build/utils/utils-verify/importers.js +121 -31
- package/build/utils/utils-verify/in-house.js +26 -31
- package/build/utils/utils-verify/index.d.ts +3 -0
- package/build/utils/utils-verify/index.js +3 -0
- package/build/utils/utils-verify/language-spec.js +10 -2
- package/build/utils/utils-verify/locate.d.ts +3 -0
- package/build/utils/utils-verify/locate.js +6 -6
- package/build/utils/utils-verify/parse.d.ts +13 -1
- package/build/utils/utils-verify/parse.js +122 -49
- package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
- package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
- package/build/utils/utils-verify/stage.js +4 -16
- package/build/utils/utils-verify/verify.d.ts +9 -3
- package/build/utils/utils-verify/verify.js +26 -12
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +303 -150
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BAkLd5DX.js → index.ZYuEFHsi.js} +1 -1
- 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.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
- 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/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +2 -2
- package/plugin/prompts/code-reuse/hand-off.md +13 -1
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -0
|
@@ -26,6 +26,10 @@ export declare function findRepoPlaywrightConfig(testFile: string, cwd: string):
|
|
|
26
26
|
* refuses `--browser` when the config defines projects (`common/config.js`:
|
|
27
27
|
* "Cannot use --browser option when configuration file defines projects").
|
|
28
28
|
* See `utils/rebaselineSnapshots.ts` for the layout.
|
|
29
|
+
* - `channel: "chromium"` on every Chromium project that names no channel. Without it
|
|
30
|
+
* headless Chromium runs Playwright's separate headless-shell binary, which crashed
|
|
31
|
+
* on `newPage` on a host where the full browser ran (SKYR-4509). A project that
|
|
32
|
+
* inherits a channel or another browser from the top level gets none.
|
|
29
33
|
* No reporter: the verdict is the exit code.
|
|
30
34
|
*/
|
|
31
35
|
export declare function buildWrapperConfig(opts: {
|
|
@@ -57,6 +57,10 @@ export function findRepoPlaywrightConfig(testFile, cwd) {
|
|
|
57
57
|
* refuses `--browser` when the config defines projects (`common/config.js`:
|
|
58
58
|
* "Cannot use --browser option when configuration file defines projects").
|
|
59
59
|
* See `utils/rebaselineSnapshots.ts` for the layout.
|
|
60
|
+
* - `channel: "chromium"` on every Chromium project that names no channel. Without it
|
|
61
|
+
* headless Chromium runs Playwright's separate headless-shell binary, which crashed
|
|
62
|
+
* on `newPage` on a host where the full browser ran (SKYR-4509). A project that
|
|
63
|
+
* inherits a channel or another browser from the top level gets none.
|
|
60
64
|
* No reporter: the verdict is the exit code.
|
|
61
65
|
*/
|
|
62
66
|
export function buildWrapperConfig(opts) {
|
|
@@ -71,7 +75,7 @@ export function buildWrapperConfig(opts) {
|
|
|
71
75
|
// the flag when the config declares projects, so there it must stay empty.
|
|
72
76
|
const chromiumProject = (useExpr) => opts.commandPassesBrowserFlag
|
|
73
77
|
? ""
|
|
74
|
-
: `{ name: "chromium", outputDir: ${outputDir}, use: { ...${useExpr}, browserName: "chromium" } }`;
|
|
78
|
+
: `{ name: "chromium", outputDir: ${outputDir}, use: { channel: "chromium", ...${useExpr}, browserName: "chromium" } }`;
|
|
75
79
|
const testMatchOverlay = opts.testFile
|
|
76
80
|
? `...(base.testMatch ? { testMatch: [...asList(base.testMatch), ${JSON.stringify(opts.testFile)}] } : {}),`
|
|
77
81
|
: "";
|
|
@@ -91,6 +95,14 @@ export default config;
|
|
|
91
95
|
return `import base from ${JSON.stringify(`./${opts.repoConfigBasename}`)};
|
|
92
96
|
|
|
93
97
|
const withVideo = (use) => ({ ...(use ?? {}), video: "on" });
|
|
98
|
+
// A project also inherits the top-level use, so both decide its browser and channel.
|
|
99
|
+
const withChannel = (use) => {
|
|
100
|
+
const seen = { ...(base.use ?? {}), ...(use ?? {}) };
|
|
101
|
+
const browser = seen.browserName ?? seen.defaultBrowserType ?? "chromium";
|
|
102
|
+
return seen.channel !== undefined || browser !== "chromium"
|
|
103
|
+
? use
|
|
104
|
+
: { ...(use ?? {}), channel: "chromium" };
|
|
105
|
+
};
|
|
94
106
|
const reuseServer = (server) => ({ ...server, reuseExistingServer: true });
|
|
95
107
|
|
|
96
108
|
const asList = (m) => (Array.isArray(m) ? m : [m]);
|
|
@@ -112,7 +124,7 @@ const config = {
|
|
|
112
124
|
projects: base.projects.map((p) => ({
|
|
113
125
|
...p,
|
|
114
126
|
outputDir: ${outputDir},
|
|
115
|
-
use: withVideo(p.use),
|
|
127
|
+
use: withVideo(withChannel(p.use)),
|
|
116
128
|
})),
|
|
117
129
|
}
|
|
118
130
|
: ${chromiumProject("withVideo(base.use)")
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
import { type ReuseRoute } from "../utils/reuseRouting.js";
|
|
1
2
|
import type { ScopedPomInput } from "../utils/pom-scope/index.js";
|
|
2
|
-
export declare function getCodeReusePrompt(testFile: string, language: string, framework
|
|
3
|
+
export declare function getCodeReusePrompt(testFile: string, language: string, framework: string | undefined, scopedPoms: ScopedPomInput | undefined, testType: string | undefined,
|
|
4
|
+
/** Resolved once by the caller (reuseRouteFor): which pass this call serves, and
|
|
5
|
+
* whether the two passes compose for this target. */
|
|
6
|
+
route: ReuseRoute): string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { generateSkyrampHeader, SKYRAMP_UTILS_HEADER } from "../utils/utils.js";
|
|
2
2
|
import { isUtilsReuseEnabled } from "../utils/featureFlags.js";
|
|
3
|
-
import { inlineCallSiteFamily, isModularizeFirstTarget,
|
|
3
|
+
import { inlineCallSiteFamily, isModularizeFirstTarget, } from "../utils/reuseRouting.js";
|
|
4
4
|
import { getPomAwareCodeReusePrompt } from "./pom-aware-code-reuse.js";
|
|
5
|
-
import { NO_SHADOWING_RULE, SELECTOR_OWNING_MODULE_RULE, selectHelperPolicy, } from "./shared-helper-policy.js";
|
|
5
|
+
import { AFTER_POM_PASS_RULE, NO_SHADOWING_RULE, SELECTOR_OWNING_MODULE_RULE, selectHelperPolicy, } from "./shared-helper-policy.js";
|
|
6
6
|
import { UTILS_LANGUAGE_SPECS } from "../utils/utils-verify/language-spec.js";
|
|
7
7
|
/** The prescribed module name comes from the verifier's language table, which is the
|
|
8
8
|
* ONE declaration of it: the instruction the agent reads and the name the verify pass
|
|
@@ -73,10 +73,13 @@ const pyImportRule = (fileName) => {
|
|
|
73
73
|
return `**Import form (Python)**: if the test's directory contains \`__init__.py\`, a bare \`from ${stem} import …\` will NOT resolve when the customer runs pytest from their repo root — the test dies at collection. Write the fallback pair, which works there AND in the Skyramp executor: \`try:\` / \` from .${stem} import …\` / \`except ImportError:\` / \` from ${stem} import …\` — with \`…\` the names this test actually uses, unchanged between the two arms. Without \`__init__.py\` the bare form alone is correct — do not add the fallback.`;
|
|
74
74
|
};
|
|
75
75
|
const pyImportRuleLine = (fileName) => `- ${pyImportRule(fileName)}`;
|
|
76
|
-
export function getCodeReusePrompt(testFile, language, framework, scopedPoms, testType
|
|
76
|
+
export function getCodeReusePrompt(testFile, language, framework, scopedPoms, testType,
|
|
77
|
+
/** Resolved once by the caller (reuseRouteFor): which pass this call serves, and
|
|
78
|
+
* whether the two passes compose for this target. */
|
|
79
|
+
route) {
|
|
77
80
|
const lang = language.toLowerCase();
|
|
78
|
-
if (
|
|
79
|
-
return getPomAwareCodeReusePrompt(testFile, lang, scopedPoms);
|
|
81
|
+
if (route.pass === "pom") {
|
|
82
|
+
return getPomAwareCodeReusePrompt(testFile, lang, scopedPoms, route.composed);
|
|
80
83
|
}
|
|
81
84
|
const languageEntry = LANGUAGE_MAP[lang] ?? LANGUAGE_MAP.python;
|
|
82
85
|
const ext = languageEntry.extension;
|
|
@@ -116,12 +119,15 @@ export function getCodeReusePrompt(testFile, language, framework, scopedPoms, te
|
|
|
116
119
|
: `**STOP HERE IF NO OTHER SKYRAMP-GENERATED TEST FILES FOUND**
|
|
117
120
|
**IF NO OTHER SKYRAMP-GENERATED TEST FILES ARE FOUND, SKIP TO STEP 6 - DO NOT CREATE ANY UTILS FILES.**`;
|
|
118
121
|
// The repository's own browser helper modules. UI only, and only on the seeded
|
|
119
|
-
// (utils reuse on
|
|
122
|
+
// (utils reuse on) flow — after the page-object pass when that is on too: a browser test and the helpers it
|
|
120
123
|
// calls drive one library, so the verifier's action key compares a step to the
|
|
121
124
|
// customer's helper; the API side has no such key and is a separate change.
|
|
122
125
|
const inHouseBrowser = seedsUtils && policy.kind === "browser";
|
|
126
|
+
// Both reuse settings on: this flow runs after the page-object pass on the same
|
|
127
|
+
// test, and takes only what that pass left inline.
|
|
128
|
+
const pomFirst = route.composed;
|
|
123
129
|
const inHouseDiscoveryStep = inHouseBrowser
|
|
124
|
-
? inHouseDiscovery(testFile, fileName)
|
|
130
|
+
? inHouseDiscovery(testFile, fileName, pomFirst)
|
|
125
131
|
: "";
|
|
126
132
|
const inHouseMatchStep = inHouseBrowser
|
|
127
133
|
? inHouseMatch(testFile, fileName, policy)
|
|
@@ -203,7 +209,7 @@ ${site.afterSteps}
|
|
|
203
209
|
const precondition = modularizeFirst
|
|
204
210
|
? `
|
|
205
211
|
**PRECONDITION — check your own tool-call history, not the file**: this flow moves the helpers skyramp_modularization created into \`${fileName}\` (STEP 5b)${inHouseBrowser ? " — or into the repository's own browser helper module when it has one (STEP 3b)" : ""}. If you have NOT called skyramp_modularization on ${testFile} in this run, STOP: call it now with \`testFile\` set to ${testFile}, \`testType: "${testType}"\`, and \`isTraceBased: true\`, apply its instructions, then call skyramp_reuse_code again with the same arguments as this call. If you HAVE called it and the file still defines no helper functions, that is a legitimate outcome — proceed; STEP 5b will simply have nothing to move.
|
|
206
|
-
`
|
|
212
|
+
${pomFirst ? `\n${AFTER_POM_PASS_RULE}\n` : ""}`
|
|
207
213
|
: "";
|
|
208
214
|
return `# CODE REUSE - 6 CLEAR STEPS
|
|
209
215
|
**CRITICAL WARNING: VIOLATION OF THESE RULES WILL RESULT IN ERROR**
|
|
@@ -409,13 +415,13 @@ SUMMARIZE THE CODE REUSE PROCESS AND THE RESULTS.
|
|
|
409
415
|
* so a module nobody enumerated is still found; a module not written for Playwright
|
|
410
416
|
* is then reported as unreadable rather than filtered out in silence.
|
|
411
417
|
*/
|
|
412
|
-
function inHouseDiscovery(testFile, fileName) {
|
|
418
|
+
function inHouseDiscovery(testFile, fileName, pomFirst) {
|
|
413
419
|
return `
|
|
414
420
|
## STEP 2b: FIND THE REPOSITORY'S OWN BROWSER HELPER MODULES
|
|
415
421
|
|
|
416
422
|
The header grep in STEP 2 finds only modules Skyramp wrote. The repository may already hold browser helpers of its own — a module the team wrote, carrying no Skyramp header — and a test that ignores it ends with two helper layers for the same steps. Find them.
|
|
417
423
|
|
|
418
|
-
1. **Search the directory of ${testFile} and each of its parent directories up to the repository root.** Use Grep for Playwright locator and action calls (\`getBy\`, \`.locator(\`, \`page.\`, \`.click(\`, \`.fill(\`) in files that are NOT test files. Do NOT limit the search to a fixed list of file or directory names (\`pages/\`, \`helpers/\`, \`*.page.ts\` are hints, not the search), and do NOT limit it to one extension or language. Exclude \`node_modules\`, \`${fileName}\` and every other file carrying the \`${SKYRAMP_UTILS_HEADER}\` header (STEP 2 owns those), and test files — by BASENAME only: a basename ending in \`.spec.ts\`, \`.spec.js\`, \`.test.ts\`, \`.test.js\` is a test; a directory called \`tests/\` is not.
|
|
424
|
+
1. **Search the directory of ${testFile} and each of its parent directories up to the repository root.** Use Grep for Playwright locator and action calls (\`getBy\`, \`.locator(\`, \`page.\`, \`.click(\`, \`.fill(\`) in files that are NOT test files. Do NOT limit the search to a fixed list of file or directory names (\`pages/\`, \`helpers/\`, \`*.page.ts\` are hints, not the search), and do NOT limit it to one extension or language. Exclude \`node_modules\`, \`${fileName}\` and every other file carrying the \`${SKYRAMP_UTILS_HEADER}\` header (STEP 2 owns those), and test files — by BASENAME only: a basename ending in \`.spec.ts\`, \`.spec.js\`, \`.test.ts\`, \`.test.js\` is a test; a directory called \`tests/\` is not.${pomFirst ? " Exclude every module that declares a class as well: that is a page object, and the page-object pass already owned it." : ""}
|
|
419
425
|
2. **Read every candidate.** ${SELECTOR_OWNING_MODULE_RULE} A module written for a browser library other than Playwright shares no vocabulary with this test: read it, record it, and report it as unreadable — do not call it and do not extend it.
|
|
420
426
|
3. **Catalog each qualifying module** to chat before changing anything: for each helper its name, **whether the module exports it UNDER THAT NAME** (an \`export\` on the definition, or an unaliased entry in an \`export { … }\` list of its own bindings — a default export and an aliased one export something else, and a helper this test cannot import is one it cannot call), its parameters (with defaults), what it returns, and its ACTION KEY — the sequence of Playwright actions it performs, in body order, each as the verb plus the locator chain it is called on (\`fill page.getByTestId("email"); click page.getByRole("button", { name: "Sign in" })\`). Argument VALUES are not part of the key, with two exceptions that ARE: a \`goto\` target and a \`dragTo\` destination — where a navigation goes and where a drag lands are the identity of those steps, so a helper that opens another page never matches. The key is UNDEFINED for a helper whose locator is not built from plain string literals — a variable, a template literal, a regex, a parameter; record it as undefined.
|
|
421
427
|
4. **Record every file the search read** — qualifying or not — as a repository-relative path. You pass this list to the verify call at the end (\`inHouse.filesRead\`). A file you did not read is not on it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { isModularizeFirstTarget } from "../../utils/reuseRouting.js";
|
|
1
|
+
import { isModularizeFirstTarget, isPomFirstTarget, } from "../../utils/reuseRouting.js";
|
|
2
2
|
import { PromptPlan } from "../test-recommendation/promptPlan.js";
|
|
3
|
-
import { ASSERTION_BOUNDARY_RULE_EXTRACT, BROWSER_HELPERS, BROWSER_VALUE_ASSERTIONS, } from "../shared-helper-policy.js";
|
|
3
|
+
import { AFTER_POM_PASS_RULE, ASSERTION_BOUNDARY_RULE_EXTRACT, BROWSER_HELPERS, BROWSER_VALUE_ASSERTIONS, } from "../shared-helper-policy.js";
|
|
4
4
|
import { renderModularizationPrompt, } from "./render.js";
|
|
5
5
|
// On modularize-first UI flows (utils reuse on, POM reuse off), downstream
|
|
6
6
|
// skyramp_reuse_code consolidates this file's helpers into the shared
|
|
@@ -196,12 +196,19 @@ export function getModularizationPrompt(filePath, testType, language) {
|
|
|
196
196
|
filePath,
|
|
197
197
|
utilsReuse: isModularizeFirstTarget(testType, language),
|
|
198
198
|
};
|
|
199
|
+
// Both reuse settings on: this file already went through the page-object pass.
|
|
200
|
+
const afterPomPassRules = isPomFirstTarget(language, testType)
|
|
201
|
+
? [
|
|
202
|
+
`DO NOT MOVE A PAGE-OBJECT CALL, OR A STEP UNDER A \`// kept inline:\` LINE, INTO A HELPER. ${AFTER_POM_PASS_RULE}`,
|
|
203
|
+
]
|
|
204
|
+
: [];
|
|
199
205
|
return renderModularizationPrompt({
|
|
200
206
|
title: "UI TEST MODULARIZATION - IMPROVE READABILITY AND REDUCE DUPLICATION",
|
|
201
207
|
forbidden: [
|
|
202
208
|
"DO NOT CREATE INTERFACES, CLASSES, TYPES, NEW FILES, OR NEW DATA STRUCTURES",
|
|
203
209
|
`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)" : ""}`,
|
|
204
210
|
"DO NOT CREATE/UPDATE DEPENDENCY FILES package.json, requirements.txt, pom.xml, build.gradle file",
|
|
211
|
+
...afterPomPassRules,
|
|
205
212
|
],
|
|
206
213
|
plan: _plan,
|
|
207
214
|
params,
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import type { ScopedPomInput } from "../utils/pom-scope/index.js";
|
|
2
|
-
export declare function getPomAwareCodeReusePrompt(testFile: string, language?: string, scopedPoms?: ScopedPomInput
|
|
2
|
+
export declare function getPomAwareCodeReusePrompt(testFile: string, language?: string, scopedPoms?: ScopedPomInput,
|
|
3
|
+
/** Both reuse settings on (isPomFirstTarget): this is the FIRST pass, and the
|
|
4
|
+
* SkyrampUtils flow takes what it leaves inline. */
|
|
5
|
+
pomFirst?: boolean): string;
|
|
@@ -2,7 +2,30 @@ import { generateSkyrampHeader, SKYRAMP_UTILS_HEADER } from "../utils/utils.js";
|
|
|
2
2
|
import { resolvePomCatalogPath } from "../utils/pom-catalog.js";
|
|
3
3
|
import { UTILS_LANGUAGE_SPECS } from "../utils/utils-verify/language-spec.js";
|
|
4
4
|
import { SELECTOR_OWNING_MODULE_RULE } from "./shared-helper-policy.js";
|
|
5
|
-
|
|
5
|
+
/** Both reuse settings on: which modules this pass owns, and what it leaves for the
|
|
6
|
+
* SkyrampUtils flow that runs after it. The ownership rule is the one
|
|
7
|
+
* pom-scope/ownership.ts applies to detection and to the in-house verifier. */
|
|
8
|
+
const COMPOSED_PASS_RULE = `
|
|
9
|
+
**This is the FIRST of two reuse passes on this test.** This pass maps the test onto the repository's page-object CLASSES. A module that declares no class — a free-function helper library such as \`support/pageActions.ts\` or \`testUtils.ts\` — is NOT a page object on this path: do not map onto it, even when STEP 1 lists it; the second pass reuses such modules. The second pass (skyramp_modularization, then skyramp_reuse_code again) moves the steps this pass leaves inline into a shared helper module. Page-object calls you write here stay in the test body in that pass.
|
|
10
|
+
`;
|
|
11
|
+
/** Nick's routing for a step no page-object member covers, on a page the page objects
|
|
12
|
+
* model: it stays inline and is marked, so the second pass leaves it there. A step on
|
|
13
|
+
* a page nothing models needs no marker — the second pass moves it. */
|
|
14
|
+
const KEPT_INLINE_ON_MODELED_PAGE = `Where an action is on a page one of the page-object classes models but no member of that class covers it, keep it inline and put \`// kept inline: <that file's basename> — <reason>\` on a line of its own above it: such a step stays inline in the delivered test, and the second pass leaves it where it is. Do NOT add a member or a class to the customer's page objects. An action on a page no page object models needs no marker — the second pass moves it into the shared helper module.`;
|
|
15
|
+
function composedFallback(testFile) {
|
|
16
|
+
return `## STEP 6: FALLBACK — NO PAGE-OBJECT MAPPING
|
|
17
|
+
|
|
18
|
+
**You should only reach this step if:**
|
|
19
|
+
- No page-object class was detected in STEP 1, OR
|
|
20
|
+
- Zero locators/actions in the test mapped to any page-object member in STEP 2
|
|
21
|
+
|
|
22
|
+
Make no change to the test's actions, and do NOT create a shared helper file or extract helpers in this pass. If STEP 1 listed page-object files, document each one with \`// kept inline: <file basename> — <reason it cannot cover this test>\` on a line of its own in ${testFile}. Then call \`skyramp_reuse_code\` with the same \`testFile\`/\`language\`/\`framework\`/\`testType\` and \`verify: true\`, and follow its report until it reports PASSED. The page-object pass is then finished: continue with the next step the generation result named for this file.
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
export function getPomAwareCodeReusePrompt(testFile, language = "typescript", scopedPoms,
|
|
26
|
+
/** Both reuse settings on (isPomFirstTarget): this is the FIRST pass, and the
|
|
27
|
+
* SkyrampUtils flow takes what it leaves inline. */
|
|
28
|
+
pomFirst = false) {
|
|
6
29
|
// Resolved here, not left to the agent: the catalog only survives to the next
|
|
7
30
|
// run if it lands where delivery stages it (SKYR-4141).
|
|
8
31
|
const catalogPath = resolvePomCatalogPath(testFile);
|
|
@@ -14,6 +37,11 @@ export function getPomAwareCodeReusePrompt(testFile, language = "typescript", sc
|
|
|
14
37
|
const codegenMarker = "// Generated by Skyramp v";
|
|
15
38
|
const headerLang = isJs ? "javascript" : "typescript";
|
|
16
39
|
const scopedActive = scopedPoms && scopedPoms.tier1.length + scopedPoms.tier2.length > 0;
|
|
40
|
+
if (!scopedActive && scopedPoms?.scannedNoOverlap && pomFirst) {
|
|
41
|
+
return `No reusable POM layer detected: no page-object class in this repository shares a selector with this test. The page-object pass for ${testFile} is finished — make no change to the file here, and do NOT call this tool with \`verify: true\` for this answer.
|
|
42
|
+
|
|
43
|
+
Continue with the next step the generation result named for this file. The steps after it — skyramp_modularization, then skyramp_reuse_code again — move this test's steps into the shared helper module, or call the repository's own browser helpers where it has them.`;
|
|
44
|
+
}
|
|
17
45
|
if (!scopedActive && scopedPoms?.scannedNoOverlap) {
|
|
18
46
|
return `No reusable POM layer detected: a repo-wide scan found zero overlap between this test's selectors and any non-test source file. Code reuse is skipped — continue with the generated test as-is.
|
|
19
47
|
|
|
@@ -78,7 +106,7 @@ If you catch yourself about to skip a step, stop and execute it.
|
|
|
78
106
|
**POM-Aware Code Reuse** means refactoring a generated test to use the project's existing Page Object Model (POM) classes and methods, replacing raw inline locators and action sequences with the proper abstractions the customer already has.
|
|
79
107
|
|
|
80
108
|
This is NOT the same as SkyrampUtils consolidation. Do NOT create a SkyrampUtils file on the POM path.
|
|
81
|
-
|
|
109
|
+
${pomFirst ? COMPOSED_PASS_RULE : ""}
|
|
82
110
|
---
|
|
83
111
|
|
|
84
112
|
${step1Section}
|
|
@@ -376,7 +404,9 @@ Do not skip this lookup for any action. The mapping table is your source of trut
|
|
|
376
404
|
- Screenshot assertions (\`toHaveScreenshot\`): always keep inline; if mid-method sequence, inline that method's steps around it
|
|
377
405
|
- Page readiness checks: use module methods such as \`pageToBeVisible()\` if they exist
|
|
378
406
|
|
|
379
|
-
|
|
407
|
+
${pomFirst
|
|
408
|
+
? `**IMPORTANT:** Do NOT create a \`${utilsFile}\` or any SkyrampUtils file in this pass, and do NOT extract helper functions. Every action no page-object member covers stays raw and inline in this pass — a later pass handles it. ${KEPT_INLINE_ON_MODELED_PAGE}`
|
|
409
|
+
: `**IMPORTANT:** Do NOT create a \`${utilsFile}\` or any SkyrampUtils file on this path. Do NOT call \`skyramp_modularization\` after completing POM-aware code reuse — POM refactoring replaces that step.`}
|
|
380
410
|
|
|
381
411
|
---
|
|
382
412
|
|
|
@@ -388,11 +418,13 @@ Do not output a section breakdown, modularization summary, or any analysis to th
|
|
|
388
418
|
|
|
389
419
|
**A low substitution count can be the correct outcome.** When many actions stay inline because the POM layer simply does not model that flow (iframe-gate rejections, orphan frames, vocabulary drift from the cataloged classes), that is honest reuse, not a shortfall — do not pad the mapping table to inflate the count. In your one-line confirmation, state how many actions were substituted vs. kept inline and why in one clause (e.g. "6 substituted; 9 kept inline — POM layer does not model the apply-from-asset flow").
|
|
390
420
|
|
|
391
|
-
**Do NOT run the test.** After writing the file, you MUST call \`skyramp_reuse_code\` once more with the same \`testFile\`/\`language\`/\`framework\` and \`verify: true\`, and follow its report: re-verify after every fix pass; a violation may be remapped at most once — if flagged again, demote it to the raw inline form; finish only when it reports PASSED (unverifiable notes are acceptable). Include \`verification: passed\` (or the unresolved count) in your final confirmation line
|
|
421
|
+
**Do NOT run the test.** After writing the file, you MUST call \`skyramp_reuse_code\` once more with the same \`testFile\`/\`language\`/\`framework\` and \`verify: true\`, and follow its report: re-verify after every fix pass; a violation may be remapped at most once — if flagged again, demote it to the raw inline form; finish only when it reports PASSED (unverifiable notes are acceptable). Include \`verification: passed\` (or the unresolved count) in your final confirmation line.${pomFirst ? ` When it reports PASSED, the page-object pass is finished: continue with the next step the generation result named for this file.` : ""}
|
|
392
422
|
|
|
393
423
|
---
|
|
394
424
|
|
|
395
|
-
|
|
425
|
+
${pomFirst
|
|
426
|
+
? composedFallback(testFile)
|
|
427
|
+
: `## STEP 6: FALLBACK — STANDARD SKYRAMPUTILS PATH
|
|
396
428
|
|
|
397
429
|
**You should only reach this step if:**
|
|
398
430
|
- No POM files were detected in STEP 1, OR
|
|
@@ -604,6 +636,6 @@ For each helper defined locally in ${testFile}:
|
|
|
604
636
|
|
|
605
637
|
**Do NOT call skyramp_modularization.** Code reuse is complete. The test file has been refactored in place — no further tools should be called on this FALLBACK path. (If you took the POM-aware path instead, STEP 5's verification requirement still applies.)
|
|
606
638
|
|
|
607
|
-
SUMMARIZE THE CODE REUSE PROCESS AND THE RESULTS.
|
|
639
|
+
`}SUMMARIZE THE CODE REUSE PROCESS AND THE RESULTS.
|
|
608
640
|
`;
|
|
609
641
|
}
|
|
@@ -61,4 +61,8 @@ export declare function buildModularizeFirstNextSteps(params: {
|
|
|
61
61
|
language: UtilsLanguage;
|
|
62
62
|
framework: string;
|
|
63
63
|
enhanceFirst: boolean;
|
|
64
|
+
/** Both reuse settings on (isPomFirstTarget): the page-object pass comes first,
|
|
65
|
+
* so its gates judge the spec while its selectors are all still inline, and
|
|
66
|
+
* the enhancer keeps the place it has on the page-object path — after it. */
|
|
67
|
+
pomFirst?: boolean;
|
|
64
68
|
}): string;
|
|
@@ -110,7 +110,7 @@ function handOffStep(heading, values) {
|
|
|
110
110
|
return fillPlaceholders(sectionBody(HAND_OFF_MD, `## ${heading}`), values);
|
|
111
111
|
}
|
|
112
112
|
export function buildModularizeFirstNextSteps(params) {
|
|
113
|
-
const { testType, language, framework, enhanceFirst } = params;
|
|
113
|
+
const { testType, language, framework, enhanceFirst, pomFirst } = params;
|
|
114
114
|
const enhanceCall = enhanceAssertionsCall(testType, "generated test file");
|
|
115
115
|
const values = {
|
|
116
116
|
testType,
|
|
@@ -118,13 +118,18 @@ export function buildModularizeFirstNextSteps(params) {
|
|
|
118
118
|
enhanceCall: enhanceCall.charAt(0).toUpperCase() + enhanceCall.slice(1),
|
|
119
119
|
};
|
|
120
120
|
const steps = [
|
|
121
|
-
...(
|
|
121
|
+
...(pomFirst ? [handOffStep("Page-object pass", values)] : []),
|
|
122
|
+
...(enhanceFirst ? [handOffStep("Assertion enhancement", values)] : []),
|
|
122
123
|
handOffStep("Modularize", values),
|
|
123
124
|
handOffStep("Reuse", values),
|
|
124
125
|
];
|
|
125
|
-
const ordering = handOffStep(
|
|
126
|
-
?
|
|
127
|
-
|
|
126
|
+
const ordering = handOffStep(pomFirst
|
|
127
|
+
? enhanceFirst
|
|
128
|
+
? "Page objects with the enhancer"
|
|
129
|
+
: "Page objects without the enhancer"
|
|
130
|
+
: enhanceFirst
|
|
131
|
+
? "Ordering with the enhancer"
|
|
132
|
+
: "Ordering without the enhancer", values);
|
|
128
133
|
return `⏭️**CRITICAL NEXT STEPS — ${ordering}**:
|
|
129
134
|
${steps.map((text, i) => `${String.fromCharCode(97 + i)}. ${text}`).join("\n")}`;
|
|
130
135
|
}
|
|
@@ -85,6 +85,11 @@ export declare const SELECTOR_OWNING_MODULE_RULE = "**A reuse target does not ha
|
|
|
85
85
|
* and the local definition would then describe two different behaviours under
|
|
86
86
|
* one name, and a later reader (or a deterministic gate) cannot tell which runs.
|
|
87
87
|
*/
|
|
88
|
+
/** Both reuse settings on: the page-object pass already ran on this test. Stated at
|
|
89
|
+
* the top of the seeded flow and in the modularization prompt, because both passes
|
|
90
|
+
* that move code could otherwise carry a page-object call into a helper, where the
|
|
91
|
+
* page-object record no longer sees it. */
|
|
92
|
+
export declare const AFTER_POM_PASS_RULE = "**This test already went through the page-object pass.** Its calls on page-object instances (a method or property of a page-object class) stay in the test body, exactly where they are \u2014 never move one into a helper or into a shared module. A step under a `// kept inline:` line stays inline, with its line: it is on a page a page object models, and it is not moved. Everything else still inline is what this pass works on.";
|
|
88
93
|
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.";
|
|
89
94
|
export interface HelperPolicy {
|
|
90
95
|
kind: "api" | "browser";
|
|
@@ -163,6 +163,11 @@ export const SELECTOR_OWNING_MODULE_RULE = `**A reuse target does not have to be
|
|
|
163
163
|
* and the local definition would then describe two different behaviours under
|
|
164
164
|
* one name, and a later reader (or a deterministic gate) cannot tell which runs.
|
|
165
165
|
*/
|
|
166
|
+
/** Both reuse settings on: the page-object pass already ran on this test. Stated at
|
|
167
|
+
* the top of the seeded flow and in the modularization prompt, because both passes
|
|
168
|
+
* that move code could otherwise carry a page-object call into a helper, where the
|
|
169
|
+
* page-object record no longer sees it. */
|
|
170
|
+
export const AFTER_POM_PASS_RULE = `**This test already went through the page-object pass.** Its calls on page-object instances (a method or property of a page-object class) stay in the test body, exactly where they are — never move one into a helper or into a shared module. A step under a \`// kept inline:\` line stays inline, with its line: it is on a page a page object models, and it is not moved. Everything else still inline is what this pass works on.`;
|
|
166
171
|
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.";
|
|
167
172
|
export const API_HELPERS = {
|
|
168
173
|
kind: "api",
|
|
@@ -55,6 +55,8 @@ ${opts.domainNotes}
|
|
|
55
55
|
|
|
56
56
|
**When uncertain, use VERIFY not IGNORE:** If the diff touches code this test could exercise but you cannot confirm impact without reading more context, assign VERIFY — do not default to IGNORE.
|
|
57
57
|
|
|
58
|
+
**A stated requirement wins over the code.** Before you write a new value or refresh a screenshot in an existing test, compare it with what the pull request title, the description and any requirements file they name state. If the new value breaks a stated requirement, do not write it: keep that assertion on the requirement's value and make only the other edits. If no other edit remains, mark the test VERIFY. The test stays red; do not relax it. If the requirements file is unchanged and the description presents the new behaviour as intended, the file is out of date: update the test.
|
|
59
|
+
|
|
58
60
|
**Pre-commit verification — confirm before finalizing UPDATE/REGENERATE/DELETE:**
|
|
59
61
|
1. You can quote a specific diff line${opts.blueprintSuffix} this test observes that triggered the action.
|
|
60
62
|
2. ${opts.reachabilityCheck}
|
|
@@ -45,7 +45,7 @@ Build a detection list first: for each matched diff line, write one line: \`{pat
|
|
|
45
45
|
**Rules (assign exactly one action per test; severity order above):**
|
|
46
46
|
- **DELETE** when the test's target is gone: all routes/pages it covers no longer exist; all critical selectors were removed with no migration path; or, for a component/widget test, the component it renders was deleted (source removed with no re-export; report-only for external tests).
|
|
47
47
|
- **REGENERATE** when the interaction model changed so fundamentally that the test's action sequence is broken with no migration path: the flow itself changed, not just selector values (e.g. a form replaced by a modal, buttons replaced by a dropdown, a click-to-update button replaced by an auto-updating stepper). Re-recording from scratch is the only viable approach. Broken-selector count alone does not decide REGENERATE; if the flow still works with targeted edits, it is UPDATE.
|
|
48
|
-
- **UPDATE** when the test's flow still holds but something it relies on shifted on a page or component it exercises: a selector (className, data-testid, aria-*) was renamed or moved, a new selector was added that the test should adopt or assert (including a dynamic class like \`+ \`btn--\${variant}\`\`), an assertion needs adjusting, or a \`toHaveScreenshot()\` baseline captures something whose appearance the diff changed (see **Visual-snapshot baselines** above — name the stale files in \`rebaselineSnapshots\`). Fix it in place in the existing file, never a new spec for the same page or component. An added selector is still drift, not a no-op. Read the test/POM to confirm the component is targeted and prefer UPDATE; fall back to VERIFY only when reading leaves impact undetermined.
|
|
48
|
+
- **UPDATE** when the test's flow still holds but something it relies on shifted on a page or component it exercises: a selector (className, data-testid, aria-*) was renamed or moved, a new selector was added that the test should adopt or assert (including a dynamic class like \`+ \`btn--\${variant}\`\`), an assertion needs adjusting, or a \`toHaveScreenshot()\` baseline captures something whose appearance the diff changed (see **Visual-snapshot baselines** above — name the stale files in \`rebaselineSnapshots\`). Fix it in place in the existing file, never a new spec for the same page or component. An added selector is still drift, not a no-op. Read the test/POM to confirm the component is targeted and prefer UPDATE; fall back to VERIFY only when reading leaves impact undetermined. If the UPDATE changes the value the test enters or selects, keep the assertion only if the diff, the PR description, or a referenced spec gives that same result for the new value. If they give a different result, change the assertion to that result. Do not choose a value because the running app still gives the old result for it.
|
|
49
49
|
- **VERIFY** when the test may be impacted but you cannot confirm breakage without reading more context (e.g. the affected selectors live in a page object or indirect import you have not read). Read that file before escalating to UPDATE.
|
|
50
50
|
- **IGNORE** when the changed code cannot reach this test's routes or rendered components (the diff touches different routes or components). If you cannot point to a diff line affecting this test's routes, selectors, or rendered components, the action is IGNORE or VERIFY, not UPDATE.
|
|
51
51
|
</ui_decision_rules>`;
|
|
@@ -113,6 +113,7 @@ Page objects can live anywhere in the repo (not just \`page-objects/\` or \`fixt
|
|
|
113
113
|
2. For each imported file, read it and extract the selectors it encapsulates — \`locator()\`, \`getByTestId()\`, \`getByRole()\`, \`getByLabel()\`, \`data-test-*\`, \`aria-label\`, CSS class strings.
|
|
114
114
|
3. Compare those selectors against the diff: if a selector value the page object uses was renamed or removed in a \`-\` diff line → the page object needs **UPDATE**.
|
|
115
115
|
4. **Update the page object file, not the spec file** — the page object is the selector source of truth. In the recommendation: set \`testFile\` to the **spec file** (so it shows correctly in the maintenance report) and set \`pomFile\` to the **page object path** (so \`skyramp_actions\` edits the right file). If the edit is rejected at action time, the agent or developer can address it separately.
|
|
116
|
+
**Exception — Skyramp's own shared helper module is never a page object.** An imported file whose first lines carry the \`Generated by Skyramp on\` header (e.g. \`skyrampUtils.ts\`, wherever it lives, \`tests/lib/\` included) never goes in \`pomFile\`. When a selector it holds was renamed or removed, give the module its OWN recommendation: an **UPDATE** whose \`testFilePath\` is the module itself, and repair the helper there — change its body, keep its name and parameter list exactly as they are, because other tests call it. Each spec that calls the helper keeps its own recommendation with \`testFilePath\` set to the spec.
|
|
116
117
|
5. If the imported file is missing or unreadable → **VERIFY** (cannot confirm without the file).
|
|
117
118
|
|
|
118
119
|
A clean spec with no raw selector calls is not a reason to assign VERIFY — it is expected when page objects are used. Assign the action based on the POM's selectors.
|
|
@@ -45,11 +45,14 @@ const UTILS_REUSE_ENABLED = isUtilsReuseEnabled();
|
|
|
45
45
|
const FIX_ERRORS_RETRY_HINT = isSkillsLoaded()
|
|
46
46
|
? ` — ${fixErrorsInstruction()} before the retry`
|
|
47
47
|
: "";
|
|
48
|
-
// The UI flow seeds a shared utils file (modularize-first)
|
|
49
|
-
// on
|
|
50
|
-
//
|
|
51
|
-
//
|
|
48
|
+
// The UI flow seeds a shared utils file (modularize-first) whenever utils reuse is
|
|
49
|
+
// on — the condition isModularizeFirstTarget applies server-side. With the POM path
|
|
50
|
+
// also on, the page-object pass runs first and the seeded flow takes what it left
|
|
51
|
+
// inline (isPomFirstTarget). Every UI-chain statement in this prompt keys on these
|
|
52
|
+
// constants so no line can ban the modularization call the generation result
|
|
53
|
+
// instructs.
|
|
52
54
|
const UI_UTILS_REUSE = UTILS_REUSE_ENABLED && !POM_REUSE_ENABLED;
|
|
55
|
+
const UI_COMPOSED_REUSE = UTILS_REUSE_ENABLED && POM_REUSE_ENABLED;
|
|
53
56
|
// ── Hand-numbered list labels ─────────────────────────────────────────────────
|
|
54
57
|
// Each list below is hand-written in the prompt body, so PromptPlan cannot number
|
|
55
58
|
// it without changing what the agent reads. The definition site and every
|
|
@@ -128,23 +131,37 @@ export const EXEC_FAIL = orderedLabels([
|
|
|
128
131
|
function capitalize(text) {
|
|
129
132
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
130
133
|
}
|
|
131
|
-
// The UI enhance entry of the post-generation list. On
|
|
132
|
-
// flow the enhance call is
|
|
134
|
+
// The UI enhance entry of the post-generation list. On a modularize-first UI
|
|
135
|
+
// flow the enhance call is a sub-step of the generation result's chain — but
|
|
133
136
|
// that chain exists only for a TS/JS UI generation with enhanceAssertions on,
|
|
134
137
|
// which this prompt cannot see per call. So the step keeps the call and lets
|
|
135
|
-
// the agent skip it only when it already made it for that file.
|
|
138
|
+
// the agent skip it only when it already made it for that file. `chainPosition`
|
|
139
|
+
// names where in that chain the call sits; absent, the call stands alone.
|
|
140
|
+
function uiEnhanceStep(chainPosition) {
|
|
141
|
+
const call = capitalize(enhanceAssertionsCall("ui", "generated UI test file"));
|
|
142
|
+
if (!chainPosition)
|
|
143
|
+
return `${call}.`;
|
|
144
|
+
return `${call} — UNLESS you already did it for this file as ${chainPosition} of step ${POSTGEN.UI_CODE_REUSE}'s chain, in which case skip it (never enhance the same file twice; never leave a generated UI test un-enhanced). The guidance below on which assertions to add applies to whichever call enhances the file.`;
|
|
145
|
+
}
|
|
136
146
|
const UI_ENHANCE_STEP = UI_UTILS_REUSE
|
|
137
|
-
?
|
|
138
|
-
:
|
|
147
|
+
? uiEnhanceStep("the first sub-step")
|
|
148
|
+
: UI_COMPOSED_REUSE
|
|
149
|
+
? uiEnhanceStep("a sub-step")
|
|
150
|
+
: uiEnhanceStep();
|
|
139
151
|
// Post-generation code-reuse step for UI tests. The POM catalog, the `verify: true`
|
|
140
152
|
// loop and the `.raw.bak` restore only exist on the POM-aware path — when that path
|
|
141
153
|
// is flagged off, `skyramp_reuse_code` returns the SkyrampUtils workflow instead, so
|
|
142
154
|
// the agent must not be sent looking for artifacts nothing produces.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
// The `.raw.bak` restore and the cleanup that follows it: the page-object pass
|
|
156
|
+
// writes the backup whether or not the SkyrampUtils flow runs after it.
|
|
157
|
+
const POM_RESTORE_AND_CLEANUP = `If a reused test later fails execution and the failure points at a substituted POM call, restore the saved \`<testFile>.raw.bak\` over the test file and re-run — do NOT hand-edit the customer's POM methods (this re-run counts toward the per-file execution attempt cap; when the budget allows no re-run, restore the file for delivery and report the failure instead of executing again — prefer this restore over the generic timeout fix-up when the failing locator came from a POM substitution). **Cleanup before reporting:** \`.raw.bak\` files are internal scratch — after ALL test executions are complete (pass or fail) and before calling \`skyramp_submit_report\`, delete every \`*.raw.bak\` you created so they are not committed to the customer-facing branch. The \`skyramp-pom-catalog.md\` is NOT scratch — leave it in place (later runs reuse it).`;
|
|
158
|
+
const UI_CODE_REUSE_STEP = UI_COMPOSED_REUSE
|
|
159
|
+
? `${POSTGEN.UI_CODE_REUSE}. **[MANDATORY] After \`skyramp_ui_test_generation\` with \`codeReuse: true\`**: for a TypeScript or JavaScript Playwright test, the generation result carries CRITICAL NEXT STEPS — \`skyramp_reuse_code\` (the page-object pass, INCLUDING its verification loop until it reports PASSED, or its "No reusable POM layer detected" answer), then \`skyramp_enhance_assertions\` when it lists it, then \`skyramp_modularization\`, then \`skyramp_reuse_code\` again — with the exact arguments for each. Do them all, in that order and to completion, for EVERY generated UI test file; the enhance sub-step, when listed, is that file's step ${POSTGEN.UI_ENHANCE} below (do not call \`skyramp_enhance_assertions\` a second time for the same file). The page-object pass maps the test onto the repository's page objects; the second reuse call moves what it left inline into the shared helper module — or calls the repository's own browser helpers where it has them — so the test file IS expected to change twice. Where the generation result names only \`skyramp_reuse_code\`, follow that one call exactly. ${POM_RESTORE_AND_CLEANUP}`
|
|
160
|
+
: POM_REUSE_ENABLED
|
|
161
|
+
? `${POSTGEN.UI_CODE_REUSE}. **[MANDATORY] After \`skyramp_ui_test_generation\` with \`codeReuse: true\`**: the generation result directs you to call \`skyramp_reuse_code\` — do this BEFORE enhancing assertions. Two outcomes: (1) a response starting "No reusable POM layer detected" — this is a normal outcome, continue immediately (do NOT retry); (2) a refactoring workflow — follow it to completion INCLUDING its verification loop (\`skyramp_reuse_code\` with \`verify: true\`), finish only when it reports PASSED. ${POM_RESTORE_AND_CLEANUP}`
|
|
162
|
+
: UI_UTILS_REUSE
|
|
163
|
+
? `${POSTGEN.UI_CODE_REUSE}. **[MANDATORY] After \`skyramp_ui_test_generation\` with \`codeReuse: true\`**: the generation result carries three CRITICAL NEXT STEPS — \`skyramp_enhance_assertions\` (on the freshly generated file, while its selectors are still inline), then \`skyramp_modularization\`, then \`skyramp_reuse_code\` — with the exact arguments for each. Do all three, in that order and to completion, for EVERY generated UI test file — the enhance sub-step is that file's step ${POSTGEN.UI_ENHANCE} below (do not call \`skyramp_enhance_assertions\` a second time for the same file, and do not skip it for later files). Sibling tests offering nothing to reuse is a normal outcome, but the reuse steps still move this test's own helpers into the shared utils file — or, where the repository already has a browser helper module of its own, call its helpers and add to that module what it lacks — and import them back, so the test file IS expected to change. Then continue (do NOT retry).`
|
|
164
|
+
: `${POSTGEN.UI_CODE_REUSE}. **[MANDATORY] After \`skyramp_ui_test_generation\` with \`codeReuse: true\`**: the generation result directs you to call \`skyramp_reuse_code\` — do this BEFORE enhancing assertions. Follow the returned steps exactly. If it finds no existing helper functions to reuse, that is a normal outcome — leave the test file unchanged and continue immediately (do NOT retry).`;
|
|
148
165
|
// Generation-call clause for the integration pipeline: only ask for codeReuse when
|
|
149
166
|
// utils reuse is enabled — with the flag off, integration generation behaves exactly
|
|
150
167
|
// as it did before the feature existed (no file-rewriting post-steps).
|
|
@@ -567,7 +584,7 @@ ${CONTRACT_MODE_GUIDANCE}
|
|
|
567
584
|
|
|
568
585
|
**The Blueprint Citation Invariant applies during recording too.** Every assertion you emit cites element names — those names must come from blueprint captures, not invention. For N user-intent-level actions, expect about N+1 \`browser_blueprint\` calls (the first returns full, the rest return deltas). Nothing counts them. Traces that follow the pattern produce assertions grounded in observable state changes; traces that skip captures fall back to author-inferred assertions and risk citing names that don't exist in the rendered DOM.
|
|
569
586
|
|
|
570
|
-
The rest of the UI workflow stays the same: trace plan, browser auth, navigation, export (\`skyramp_export_zip\`), generation (\`skyramp_ui_test_generation\`), then the post-calls the generation result lists (${UI_UTILS_REUSE ? "`skyramp_enhance_assertions`, `skyramp_modularization`, `skyramp_reuse_code`, in that order" : "`skyramp_reuse_code` (when `codeReuse: true`) and `skyramp_enhance_assertions`"}). Capture-act-capture adds blueprint captures alongside the existing steps; it doesn't replace anything.
|
|
587
|
+
The rest of the UI workflow stays the same: trace plan, browser auth, navigation, export (\`skyramp_export_zip\`), generation (\`skyramp_ui_test_generation\`), then the post-calls the generation result lists (${UI_UTILS_REUSE ? "`skyramp_enhance_assertions`, `skyramp_modularization`, `skyramp_reuse_code`, in that order" : UI_COMPOSED_REUSE ? "`skyramp_reuse_code` (the page-object pass), `skyramp_enhance_assertions`, `skyramp_modularization`, `skyramp_reuse_code` again, in that order" : "`skyramp_reuse_code` (when `codeReuse: true`) and `skyramp_enhance_assertions`"}). Capture-act-capture adds blueprint captures alongside the existing steps; it doesn't replace anything.
|
|
571
588
|
- **E2E**: Only if BOTH a backend trace \`.json\` AND a Playwright \`.zip\` already exist in the repo. Without both, leave the planned test in your plan and say in \`businessCaseAnalysis\` that both traces have to be recorded first.
|
|
572
589
|
- Skip smoke tests entirely.
|
|
573
590
|
|
|
@@ -678,7 +695,7 @@ ${task3CountRule ? `${task3CountRule}\n\n` : ""}${reportLanguageBlock}Call \`sky
|
|
|
678
695
|
? ` Items derived from a related repo's diff (from the \`<related_repositories>\` analysis) carry that repo's \`repository\` value. In \`businessCaseAnalysis\`, include a short per-repo subsection and call out any cross-repo correlations you found.`
|
|
679
696
|
: ""}
|
|
680
697
|
|
|
681
|
-
${getTraceRecordingPromptText({ outputDir: `${repositoryPath}/.skyramp`, modularize: false, modularizeViaGenerationResult: UI_UTILS_REUSE })}`;
|
|
698
|
+
${getTraceRecordingPromptText({ outputDir: `${repositoryPath}/.skyramp`, modularize: false, modularizeViaGenerationResult: UI_UTILS_REUSE || UI_COMPOSED_REUSE })}`;
|
|
682
699
|
// Neither path reaches the agent any more: SKYR-4147 made the report derive from the
|
|
683
700
|
// state file's directory, and that directory comes from the environment via
|
|
684
701
|
// runArtifactDir(). Keep it that way — a path the model retypes out of this prose is a
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** A stand-in VerifyContext for tests: every field empty or answering "no", overridden per test. */
|
|
2
|
+
export function verifyContext(over = {}) {
|
|
3
|
+
return {
|
|
4
|
+
changedFiles: {},
|
|
5
|
+
reposWithoutDiff: [],
|
|
6
|
+
uiCaptures: [],
|
|
7
|
+
removedUiElements: [],
|
|
8
|
+
retiredUiElements: [],
|
|
9
|
+
changedRuns: {},
|
|
10
|
+
pullRequest: { title: "", description: "" },
|
|
11
|
+
citedFileExists: () => false,
|
|
12
|
+
readSpecFile: () => undefined,
|
|
13
|
+
routesRenderingFile: () => undefined,
|
|
14
|
+
...over,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -10,6 +10,7 @@ import { expectedValueSourced } from "./verifiers/expectedValueSourced.js";
|
|
|
10
10
|
import { removedElementGuarded } from "./verifiers/removedElementGuarded.js";
|
|
11
11
|
import { requirementSourced } from "./verifiers/requirementSourced.js";
|
|
12
12
|
import { defects } from "./verifiers/defects.js";
|
|
13
|
+
import { changedLinesClaimed } from "./verifiers/changedLinesClaimed.js";
|
|
13
14
|
/** Verifiers that need only the plan and the run's facts. The order is the order
|
|
14
15
|
* objections are reported in; it carries no priority. */
|
|
15
16
|
export const PLAN_TIME_VERIFIERS = [
|
|
@@ -25,6 +26,7 @@ export const PLAN_TIME_VERIFIERS = [
|
|
|
25
26
|
removedElementGuarded,
|
|
26
27
|
requirementSourced,
|
|
27
28
|
defects,
|
|
29
|
+
changedLinesClaimed,
|
|
28
30
|
];
|
|
29
31
|
/** How a crashed verifier reads back. The id is the verifier's name plus a fixed
|
|
30
32
|
* word, so it stays stable across registrations and can be answered. */
|
|
@@ -39,6 +39,7 @@ export interface PlannedScenario {
|
|
|
39
39
|
}
|
|
40
40
|
import type { BlueprintCaptureDigest } from "../types/BlueprintDigest.js";
|
|
41
41
|
import type { RemovedUiElement } from "../utils/removedUiElements.js";
|
|
42
|
+
import type { ChangedRun } from "../utils/changedRuns.js";
|
|
42
43
|
/** One element a UI planned test targets. EVERY field is optional and three are
|
|
43
44
|
* nullable: a declaration reaches a verifier unvalidated, so a missing or null
|
|
44
45
|
* field is an objection, not a crash. Not `Partial<BlueprintElementDigest>` —
|
|
@@ -66,6 +67,9 @@ export interface PlanChange {
|
|
|
66
67
|
/** Where a user or caller meets this change. Every change states it: an optional
|
|
67
68
|
* field the agent skips gives the check nothing to read. */
|
|
68
69
|
surfaces: Array<"api" | "page">;
|
|
70
|
+
/** The changed lines this change is: `path:12` or `path:12-20`, in the new file's
|
|
71
|
+
* numbering. The changed-lines check reads it. */
|
|
72
|
+
lines?: string[];
|
|
69
73
|
/** The inputs that prove this change: what a route must accept and what it must
|
|
70
74
|
* reject. Exactly one of `value` and `absent` per entry. The coverage check reads
|
|
71
75
|
* it; nothing else on the server does. */
|
|
@@ -237,6 +241,9 @@ export interface VerifyContext {
|
|
|
237
241
|
/** Identifiers a rename replaced, empty when the diff renames nothing. Only the
|
|
238
242
|
* server pairs the two sides of a diff. */
|
|
239
243
|
retiredUiElements: RemovedUiElement[];
|
|
244
|
+
/** Per repository, every run of changed lines the diff holds, in the new file's
|
|
245
|
+
* numbering. Read from the same diff as `changedFiles`. */
|
|
246
|
+
changedRuns: Record<string, ChangedRun[]>;
|
|
240
247
|
/** The pull request's title and description, as the run rendered its prompt with.
|
|
241
248
|
* The plan tool fills it from the process the prompt was rendered in. Both fields
|
|
242
249
|
* blank MEANS the run had no title and no description. */
|
|
@@ -248,6 +255,11 @@ export interface VerifyContext {
|
|
|
248
255
|
* the verifiers filesystem-free. Named a repository it asks that one, and a name
|
|
249
256
|
* this run does not know answers false. Omitted, it asks every one. */
|
|
250
257
|
citedFileExists(relativePath: string, repository?: string): boolean;
|
|
258
|
+
/** The text of a `spec:` file a plan change cites, from the first repository that
|
|
259
|
+
* holds it. UNDEFINED for a path no repository holds, a path outside every
|
|
260
|
+
* checkout, or a file too large to read. Injected for the same reason as
|
|
261
|
+
* `citedFileExists`. */
|
|
262
|
+
readSpecFile(relativePath: string): string | undefined;
|
|
251
263
|
/** The route patterns that mount a changed frontend file, as the framework
|
|
252
264
|
* spells them (`/secrets/$envSlug`, `/orders/:id`). Injected for the same
|
|
253
265
|
* reason as `citedFileExists`: the scan reads the repository, the verifier
|
|
@@ -255,8 +255,16 @@ export declare const REQUIREMENT_SOURCED_CONTRACT: {
|
|
|
255
255
|
readonly suggestion: "Cite a change you read in the pull request or in a requirements file, or report this test as something other than a requirement conflict.";
|
|
256
256
|
};
|
|
257
257
|
readonly notNamed: {
|
|
258
|
-
readonly message: "Change `{change}` cites `{source}`, and the pull request names
|
|
259
|
-
readonly suggestion: "Cite a file the title or description names. If
|
|
258
|
+
readonly message: "Change `{change}` cites `{source}`, and the pull request neither names nor changes such a file.";
|
|
259
|
+
readonly suggestion: "Cite a file the title or description names, or one the diff changes. If none does, you read the change in the repository, so set `source` to `diff`.";
|
|
260
|
+
};
|
|
261
|
+
readonly itemUnlinked: {
|
|
262
|
+
readonly message: "`{source}` lists item {number}, \"{item}\", and no change in this plan names it.";
|
|
263
|
+
readonly suggestion: "Set `source` to `{source}#{number}` on the change that covers this item, or answer with the reason it needs no test.";
|
|
264
|
+
};
|
|
265
|
+
readonly unnumbered: {
|
|
266
|
+
readonly message: "`{source}` lists {count} items, and no change names one of them by its number.";
|
|
267
|
+
readonly suggestion: "Name the item each change covers in its source, as `{source}#<number>` (`#7,8` for two), so each item can be checked.";
|
|
260
268
|
};
|
|
261
269
|
readonly emptyPullRequest: {
|
|
262
270
|
readonly message: "Change `{change}` says it was read from the pull request, and the pull request has no title or description.";
|
|
@@ -307,6 +315,20 @@ export declare const DEFECTS_CONTRACT: {
|
|
|
307
315
|
};
|
|
308
316
|
readonly suggestion: "Which planned test proves each defect the review found, and for one nothing proves, why does it need no test?";
|
|
309
317
|
};
|
|
318
|
+
export declare const CHANGED_LINES_CLAIMED_CONTRACT: {
|
|
319
|
+
readonly id: "changedLinesClaimed";
|
|
320
|
+
readonly objections: {
|
|
321
|
+
readonly unclaimed: {
|
|
322
|
+
readonly message: "These changed lines are in a file a planned test covers, and no change in the plan names them in its `lines`.";
|
|
323
|
+
readonly suggestion: "Add these lines to the `lines` of the change they belong to, or declare a change for them, with a case that states what a test must see. If the lines change nothing a test can observe, say so as the answer.";
|
|
324
|
+
};
|
|
325
|
+
readonly noCase: {
|
|
326
|
+
readonly message: "These changed lines belong to a change that states no case, so no test is held to what they do.";
|
|
327
|
+
readonly suggestion: "Give the change a case whose `expectedValue` or `derived` states what a test must see after these lines. If the lines change nothing a test can observe, say so as the answer.";
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
readonly suggestion: "Which change owns each run of changed lines in a tested file, and what must a test see because of it?";
|
|
331
|
+
};
|
|
310
332
|
/** Fills a contract sentence's `{placeholder}` spans from the run's or the
|
|
311
333
|
* planned test's own values. A placeholder with no value is left as written rather
|
|
312
334
|
* than blanked, so a missed key shows up as `{knob}` instead of reading as a gap
|