@skyramp/mcp 0.3.5 → 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 +80 -34
- 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 +411 -114
- package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
- package/build/tools/test-management/analyzeChangesTool.js +71 -10
- package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
- package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
- package/build/tools/test-management/registerTestPlanTool.js +70 -12
- package/build/types/Recommendation.d.ts +34 -5
- package/build/types/RepositoryAnalysis.d.ts +133 -114
- package/build/types/RepositoryAnalysis.js +1 -1
- package/build/types/ReuseOutcome.d.ts +102 -6
- package/build/types/ReuseOutcome.js +16 -2
- package/build/types/TestRecommendation.js +21 -3
- package/build/types/TestTypes.js +14 -8
- package/build/types/TestbotReport.d.ts +10 -1
- package/build/types/index.d.ts +2 -2
- package/build/types/index.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +57 -1
- package/build/utils/AnalysisStateManager.js +54 -5
- package/build/utils/branchDiff.d.ts +10 -0
- package/build/utils/branchDiff.js +28 -0
- package/build/utils/changedRoutes.d.ts +29 -0
- package/build/utils/changedRoutes.js +87 -0
- package/build/utils/featureFlags.d.ts +21 -0
- package/build/utils/featureFlags.js +23 -0
- package/build/utils/frontendIntegration.js +34 -4
- package/build/utils/importerHop.d.ts +2 -8
- package/build/utils/importerHop.js +15 -53
- package/build/utils/pathMatching.d.ts +38 -0
- package/build/utils/pathMatching.js +71 -0
- package/build/utils/pathSignatures.d.ts +22 -0
- package/build/utils/pathSignatures.js +57 -0
- package/build/utils/planMatchKeys.d.ts +16 -3
- package/build/utils/planMatchKeys.js +26 -10
- package/build/utils/pluralization.d.ts +10 -0
- package/build/utils/pluralization.js +18 -0
- package/build/utils/pom-catalog-parse.d.ts +52 -0
- package/build/utils/pom-catalog-parse.js +141 -0
- package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
- package/build/utils/pom-scope/selector-extractor.js +34 -8
- package/build/utils/pom-verify/verify.d.ts +6 -5
- package/build/utils/pom-verify/verify.js +8 -6
- package/build/utils/reportVerification.d.ts +64 -4
- package/build/utils/reportVerification.js +228 -3
- package/build/utils/reuseRouting.d.ts +3 -0
- package/build/utils/reuseRouting.js +50 -0
- package/build/utils/routeParsers.d.ts +2 -0
- package/build/utils/routeParsers.js +65 -8
- package/build/utils/scenarioDrafting.d.ts +1 -1
- package/build/utils/scenarioDrafting.js +57 -45
- package/build/utils/subjectEndpoints.d.ts +19 -0
- package/build/utils/subjectEndpoints.js +98 -0
- package/build/utils/testFileClassification.d.ts +11 -0
- package/build/utils/testFileClassification.js +47 -0
- package/build/utils/uiPageEnumerator.d.ts +45 -19
- package/build/utils/uiPageEnumerator.js +95 -51
- package/build/utils/utils-verify/allow.d.ts +16 -0
- package/build/utils/utils-verify/allow.js +68 -0
- package/build/utils/utils-verify/call-sites.d.ts +34 -0
- package/build/utils/utils-verify/call-sites.js +154 -0
- package/build/utils/utils-verify/index.d.ts +7 -0
- package/build/utils/utils-verify/index.js +7 -0
- package/build/utils/utils-verify/language-spec.d.ts +91 -0
- package/build/utils/utils-verify/language-spec.js +210 -0
- package/build/utils/utils-verify/locate.d.ts +39 -0
- package/build/utils/utils-verify/locate.js +199 -0
- package/build/utils/utils-verify/parse.d.ts +34 -0
- package/build/utils/utils-verify/parse.js +177 -0
- package/build/utils/utils-verify/stage.d.ts +24 -0
- package/build/utils/utils-verify/stage.js +107 -0
- package/build/utils/utils-verify/verify.d.ts +63 -0
- package/build/utils/utils-verify/verify.js +168 -0
- package/build/utils/utils.d.ts +3 -1
- package/build/utils/utils.js +3 -1
- package/build/workspace/workspace.d.ts +32 -32
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.VEfqi1qN.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a plural resource name to its singular form for use in field names
|
|
3
|
+
* and step descriptions.
|
|
4
|
+
*
|
|
5
|
+
* Handles the most common irregular plurals found in REST API paths:
|
|
6
|
+
* -ies → -y (categories → category, companies → company)
|
|
7
|
+
* -ses → -s (statuses → status, classes → class)
|
|
8
|
+
* Falls back to removing the trailing "s" for regular plurals.
|
|
9
|
+
*/
|
|
10
|
+
export function singularize(word) {
|
|
11
|
+
if (word.endsWith("ies") && word.length > 3) {
|
|
12
|
+
return word.slice(0, -3) + "y";
|
|
13
|
+
}
|
|
14
|
+
if (word.endsWith("ses") && word.length > 4) {
|
|
15
|
+
return word.slice(0, -2); // statuses → status
|
|
16
|
+
}
|
|
17
|
+
return word.endsWith("s") && word.length > 1 ? word.slice(0, -1) : word;
|
|
18
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReuseMissedEntry } from "../types/ReuseOutcome.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parses the agent-written POM catalog (`.skyramp/skyramp-pom-catalog.md`, path owned
|
|
4
|
+
* by `pom-catalog.ts`) and joins it against the raw locators a delivered spec still
|
|
5
|
+
* carries inline.
|
|
6
|
+
*
|
|
7
|
+
* Why the catalog and not `selectScopedPoms`: that join is a whole-token substring
|
|
8
|
+
* test (`pomContent.includes(specToken)`), which is structurally blind to
|
|
9
|
+
* prefix-parameterized selectors — the exact thing a good page object does. Measured
|
|
10
|
+
* on openobserve run 31662701508 it misses the covering file entirely and returns two
|
|
11
|
+
* unrelated pages; widening it to prefix matching finds the right file but pulls in 32
|
|
12
|
+
* more. The catalog is already scoped and records the wildcard form, so the same match
|
|
13
|
+
* is precise there.
|
|
14
|
+
*
|
|
15
|
+
* Everything here is informational — no caller gates on it — so the bias throughout is
|
|
16
|
+
* to under-report rather than assert. A malformed catalog yields no members, and a
|
|
17
|
+
* member with no recorded selector simply covers nothing.
|
|
18
|
+
*/
|
|
19
|
+
/** One catalogued property or method, with its selectors as wildcard patterns. */
|
|
20
|
+
export interface CatalogMember {
|
|
21
|
+
className: string;
|
|
22
|
+
/** As the catalog names it: `propertyName` or `methodName(a, b)`. */
|
|
23
|
+
member: string;
|
|
24
|
+
kind: "property" | "method";
|
|
25
|
+
/** Basename of the class's `Import:` path — extension-less, see ReuseMissedEntry. */
|
|
26
|
+
pageObject: string;
|
|
27
|
+
/** Wildcard patterns; `*` matches any run of characters. */
|
|
28
|
+
selectors: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Every property and method in the catalog. Returns `[]` for anything that is not a
|
|
32
|
+
* catalog — this runs on a file the agent wrote, so unparseable input is an expected
|
|
33
|
+
* state, not an error.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parsePomCatalog(markdown: string): CatalogMember[];
|
|
36
|
+
/**
|
|
37
|
+
* Raw locators the delivered spec still carries, and which of them a catalogued member
|
|
38
|
+
* covers.
|
|
39
|
+
*
|
|
40
|
+
* The spec side is `extractRawLocatorTokens`, a sibling of the POM-detection
|
|
41
|
+
* `extractSelectors` that excludes `getByRole`'s role token and the bare `name:`
|
|
42
|
+
* field — those are detection evidence, not locators, and `name:` in particular can
|
|
43
|
+
* capture a POM method's own data argument (e.g. `fp.createFunction({ name: "myFunction" })`).
|
|
44
|
+
* Both numbers come from one pass so the denominator and the misses cannot disagree.
|
|
45
|
+
*
|
|
46
|
+
* First match wins per locator. A locator covered by two members is still one missed
|
|
47
|
+
* reuse, and naming one of them is enough to send a reviewer to the right file.
|
|
48
|
+
*/
|
|
49
|
+
export declare function findMissedReuse(specContent: string, members: CatalogMember[]): {
|
|
50
|
+
rawLocatorsInline: number;
|
|
51
|
+
missed: ReuseMissedEntry[];
|
|
52
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { extractRawLocatorTokens } from "./pom-scope/selector-extractor.js";
|
|
3
|
+
import { escapeRegExp } from "./regex.js";
|
|
4
|
+
const CLASS_HEADING_RE = /^##\s+(.+?)\s*$/;
|
|
5
|
+
const IMPORT_RE = /^-\s+\*\*Import:\*\*\s+`([^`]+)`/;
|
|
6
|
+
const PROPERTIES_RE = /^-\s+\*\*Properties:\*\*/;
|
|
7
|
+
const METHODS_RE = /^-\s+\*\*Methods:\*\*/;
|
|
8
|
+
const OTHER_FIELD_RE = /^-\s+\*\*/;
|
|
9
|
+
/** A member bullet: indented, backticked name, dash separator, then the description. */
|
|
10
|
+
const MEMBER_RE = /^\s+-\s+`([^`]+)`\s*[—–-]\s*(.*)$/;
|
|
11
|
+
const BACKTICKED_RE = /`([^`]+)`/g;
|
|
12
|
+
/** `[data-test="value"]`, including prefix/suffix matchers — the value is the token. */
|
|
13
|
+
const ATTR_VALUE_RE = /\[[A-Za-z-]+[$^*~|]?="([^"]+)"\]/g;
|
|
14
|
+
/** A bare token that looks like a selector value rather than prose. */
|
|
15
|
+
const BARE_TOKEN_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*\*?$/;
|
|
16
|
+
/** Below this, a bare token is too generic to claim coverage of anything. Attribute
|
|
17
|
+
* values are exempt: `[data-test="x"]` is unambiguous evidence regardless of length. */
|
|
18
|
+
const MIN_BARE_TOKEN_LENGTH = 6;
|
|
19
|
+
/** `${...}` and `*` both mean "any run of characters" — normalize to one form. */
|
|
20
|
+
function normalizeWildcards(token) {
|
|
21
|
+
return token.replace(/\$\{[^}]*\}/g, "*");
|
|
22
|
+
}
|
|
23
|
+
/** Selector patterns in a member's DESCRIPTION — the text after the dash, never the
|
|
24
|
+
* member name itself. Scanning the whole bullet would make a property register its own
|
|
25
|
+
* name as a selector (`addFunctionButton` is a valid bare token), so a spec locator that
|
|
26
|
+
* happened to equal a property name would read as covered by that property, which is
|
|
27
|
+
* circular. */
|
|
28
|
+
function selectorsFrom(description) {
|
|
29
|
+
const out = new Set();
|
|
30
|
+
for (const [, backticked] of description.matchAll(BACKTICKED_RE)) {
|
|
31
|
+
const token = normalizeWildcards(backticked);
|
|
32
|
+
let sawAttr = false;
|
|
33
|
+
for (const [, value] of token.matchAll(ATTR_VALUE_RE)) {
|
|
34
|
+
out.add(value);
|
|
35
|
+
sawAttr = true;
|
|
36
|
+
}
|
|
37
|
+
// A bare token is only a selector if the whole backticked run is one — otherwise
|
|
38
|
+
// it is prose, or an attribute selector already harvested above.
|
|
39
|
+
if (!sawAttr && BARE_TOKEN_RE.test(token) && token.length >= MIN_BARE_TOKEN_LENGTH) {
|
|
40
|
+
out.add(token);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return [...out];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Every property and method in the catalog. Returns `[]` for anything that is not a
|
|
47
|
+
* catalog — this runs on a file the agent wrote, so unparseable input is an expected
|
|
48
|
+
* state, not an error.
|
|
49
|
+
*/
|
|
50
|
+
export function parsePomCatalog(markdown) {
|
|
51
|
+
const members = [];
|
|
52
|
+
let className;
|
|
53
|
+
let pageObject = "";
|
|
54
|
+
let section;
|
|
55
|
+
for (const line of markdown.split("\n")) {
|
|
56
|
+
const heading = CLASS_HEADING_RE.exec(line);
|
|
57
|
+
if (heading) {
|
|
58
|
+
// Free-function module headings carry a trailing gloss — `bookingHelpers
|
|
59
|
+
// (module functions, not a class)` (SKYR-4157 finding 4) — strip it so
|
|
60
|
+
// `className` is the bare module/class name, not prose.
|
|
61
|
+
className = heading[1].replace(/\s*\([^)]*\)\s*$/, "");
|
|
62
|
+
pageObject = "";
|
|
63
|
+
section = undefined;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!className)
|
|
67
|
+
continue;
|
|
68
|
+
const imported = IMPORT_RE.exec(line);
|
|
69
|
+
if (imported) {
|
|
70
|
+
// Extension-less by construction: the catalog records an import specifier, and
|
|
71
|
+
// `path.basename` of `../../pages/functionsPages/functionsPage` is what a reader
|
|
72
|
+
// needs to find the file.
|
|
73
|
+
pageObject = path.basename(imported[1]).replace(/\.(ts|js|mjs|cjs)$/, "");
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (PROPERTIES_RE.test(line)) {
|
|
77
|
+
section = "property";
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (METHODS_RE.test(line)) {
|
|
81
|
+
section = "method";
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Any other `- **Field:**` line (Iframe, depth markers) closes the current section.
|
|
85
|
+
if (OTHER_FIELD_RE.test(line)) {
|
|
86
|
+
section = undefined;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!section)
|
|
90
|
+
continue;
|
|
91
|
+
const member = MEMBER_RE.exec(line);
|
|
92
|
+
if (!member)
|
|
93
|
+
continue;
|
|
94
|
+
members.push({
|
|
95
|
+
className,
|
|
96
|
+
member: member[1],
|
|
97
|
+
kind: section,
|
|
98
|
+
pageObject,
|
|
99
|
+
// Description only — see selectorsFrom on why the member name is excluded.
|
|
100
|
+
selectors: selectorsFrom(member[2]),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return members;
|
|
104
|
+
}
|
|
105
|
+
/** Anchored matcher for one wildcard pattern. */
|
|
106
|
+
function toMatcher(pattern) {
|
|
107
|
+
return new RegExp(`^${pattern.split("*").map(escapeRegExp).join(".*")}$`);
|
|
108
|
+
}
|
|
109
|
+
/** The member name without its parameter list, for the `Class.member` label. */
|
|
110
|
+
function bareMemberName(member) {
|
|
111
|
+
return member.replace(/\(.*$/, "");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Raw locators the delivered spec still carries, and which of them a catalogued member
|
|
115
|
+
* covers.
|
|
116
|
+
*
|
|
117
|
+
* The spec side is `extractRawLocatorTokens`, a sibling of the POM-detection
|
|
118
|
+
* `extractSelectors` that excludes `getByRole`'s role token and the bare `name:`
|
|
119
|
+
* field — those are detection evidence, not locators, and `name:` in particular can
|
|
120
|
+
* capture a POM method's own data argument (e.g. `fp.createFunction({ name: "myFunction" })`).
|
|
121
|
+
* Both numbers come from one pass so the denominator and the misses cannot disagree.
|
|
122
|
+
*
|
|
123
|
+
* First match wins per locator. A locator covered by two members is still one missed
|
|
124
|
+
* reuse, and naming one of them is enough to send a reviewer to the right file.
|
|
125
|
+
*/
|
|
126
|
+
export function findMissedReuse(specContent, members) {
|
|
127
|
+
const locators = extractRawLocatorTokens(specContent);
|
|
128
|
+
const compiled = members.map((m) => ({ m, matchers: m.selectors.map(toMatcher) }));
|
|
129
|
+
const missed = [];
|
|
130
|
+
for (const locator of locators) {
|
|
131
|
+
const hit = compiled.find(({ matchers }) => matchers.some((re) => re.test(locator)));
|
|
132
|
+
if (!hit)
|
|
133
|
+
continue;
|
|
134
|
+
missed.push({
|
|
135
|
+
locator,
|
|
136
|
+
pageObject: hit.m.pageObject,
|
|
137
|
+
member: `${hit.m.className}.${bareMemberName(hit.m.member)}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return { rawLocatorsInline: locators.length, missed };
|
|
141
|
+
}
|
|
@@ -4,4 +4,16 @@
|
|
|
4
4
|
* these strings to find which page objects cover the test's actions.
|
|
5
5
|
*/
|
|
6
6
|
export declare const DEFAULT_TESTID_ATTRS: string[];
|
|
7
|
+
/** POM-detection token set (SKYR-4157 finding 1: also used for scoring, unchanged
|
|
8
|
+
* behaviour) — includes role names and bare `name:` values as evidence, even
|
|
9
|
+
* though those are not themselves raw locators. Used by the POM scoping path
|
|
10
|
+
* (`pom-scope/index.ts`); do not repurpose for coverage counting. */
|
|
7
11
|
export declare function extractSelectors(specContent: string, extraTestIdAttrs?: string[]): string[];
|
|
12
|
+
/** Genuine raw LOCATOR tokens only: test-id attribute values, `getByTestId`,
|
|
13
|
+
* `getByText`/`getByLabel`/`getByPlaceholder`, and raw `.locator()`/
|
|
14
|
+
* `.frameLocator()` strings. Excludes `getByRole`'s role token and the bare
|
|
15
|
+
* `name:` field — neither is a locator, and `name:` in particular can capture a
|
|
16
|
+
* POM method's own data argument (SKYR-4157 finding 1). Used for coverage
|
|
17
|
+
* counting (`pom-catalog-parse.ts`), where the denominator must be actual
|
|
18
|
+
* locators still inline, not detection evidence. */
|
|
19
|
+
export declare function extractRawLocatorTokens(specContent: string, extraTestIdAttrs?: string[]): string[];
|
|
@@ -16,12 +16,19 @@ const attrPattern = (attrs) => new RegExp(`\\[(?:${attrs.map(esc).join("|")})[$^
|
|
|
16
16
|
// string is actually a family-attr selector, vs. one that merely CONTAINS the
|
|
17
17
|
// substring (e.g. ".data-testing-notes" contains "data-test" but isn't one).
|
|
18
18
|
const attrAnchor = (attrs) => new RegExp(`\\[(?:${attrs.map(esc).join("|")})[$^*~|]?=`);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
// getByTestId("X"), getByText/Label/Placeholder("T") — these capture genuine raw
|
|
20
|
+
// locator values, shared by both extractSelectors and extractRawLocatorTokens.
|
|
21
|
+
const GET_BY_TEST_ID = /getByTestId\(\s*["']([^"']+)["']/g;
|
|
22
|
+
const GET_BY_TEXT_LABEL_PLACEHOLDER = /getBy(?:Text|Label|Placeholder)\(\s*["']([^"']+)["']/g;
|
|
23
|
+
const LOCATOR_PATTERNS = [GET_BY_TEST_ID, GET_BY_TEXT_LABEL_PLACEHOLDER];
|
|
24
|
+
// getByRole("role", ...) and bare `name: "..."` object keys are useful evidence for
|
|
25
|
+
// POM *detection* (scoring which page objects a test's actions touch), but they are
|
|
26
|
+
// NOT raw locator values: a role name isn't a selector, and `name:` matches ANY bare
|
|
27
|
+
// object key so named — including a POM method's own data argument (SKYR-4157
|
|
28
|
+
// finding 1). Only extractSelectors (the detection path) includes these.
|
|
29
|
+
const GET_BY_ROLE = /getByRole\(\s*["']([^"']+)["']/g;
|
|
30
|
+
const BARE_NAME_FIELD = /name:\s*["']([^"']+)["']/g;
|
|
31
|
+
const OTHER_PATTERNS = [GET_BY_TEST_ID, GET_BY_ROLE, BARE_NAME_FIELD, GET_BY_TEXT_LABEL_PLACEHOLDER];
|
|
25
32
|
// Raw .locator("...")/.frameLocator("...") strings that are NOT test-id-attribute based
|
|
26
33
|
// (e.g. ".step-deduplication", "iframe#asset-list").
|
|
27
34
|
// Quote-type-aware alternates: a double-quoted arg may contain unescaped single quotes
|
|
@@ -35,11 +42,13 @@ function normalize(token) {
|
|
|
35
42
|
function isUseful(token) {
|
|
36
43
|
return token.length >= 3 && !/^\d+$/.test(token);
|
|
37
44
|
}
|
|
38
|
-
|
|
45
|
+
/** Shared core: test-id-attribute values, the given value-bearing patterns, and raw
|
|
46
|
+
* `.locator()`/`.frameLocator()` strings not already covered by an attribute match. */
|
|
47
|
+
function collectTokens(specContent, patterns, extraTestIdAttrs) {
|
|
39
48
|
const testIdAttrs = [...DEFAULT_TESTID_ATTRS, ...extraTestIdAttrs];
|
|
40
49
|
const anchor = attrAnchor(testIdAttrs);
|
|
41
50
|
const tokens = new Set();
|
|
42
|
-
for (const re of [attrPattern(testIdAttrs), ...
|
|
51
|
+
for (const re of [attrPattern(testIdAttrs), ...patterns]) {
|
|
43
52
|
for (const m of specContent.matchAll(re)) {
|
|
44
53
|
const t = normalize(m[1]);
|
|
45
54
|
if (isUseful(t))
|
|
@@ -55,3 +64,20 @@ export function extractSelectors(specContent, extraTestIdAttrs = []) {
|
|
|
55
64
|
}
|
|
56
65
|
return [...tokens];
|
|
57
66
|
}
|
|
67
|
+
/** POM-detection token set (SKYR-4157 finding 1: also used for scoring, unchanged
|
|
68
|
+
* behaviour) — includes role names and bare `name:` values as evidence, even
|
|
69
|
+
* though those are not themselves raw locators. Used by the POM scoping path
|
|
70
|
+
* (`pom-scope/index.ts`); do not repurpose for coverage counting. */
|
|
71
|
+
export function extractSelectors(specContent, extraTestIdAttrs = []) {
|
|
72
|
+
return collectTokens(specContent, OTHER_PATTERNS, extraTestIdAttrs);
|
|
73
|
+
}
|
|
74
|
+
/** Genuine raw LOCATOR tokens only: test-id attribute values, `getByTestId`,
|
|
75
|
+
* `getByText`/`getByLabel`/`getByPlaceholder`, and raw `.locator()`/
|
|
76
|
+
* `.frameLocator()` strings. Excludes `getByRole`'s role token and the bare
|
|
77
|
+
* `name:` field — neither is a locator, and `name:` in particular can capture a
|
|
78
|
+
* POM method's own data argument (SKYR-4157 finding 1). Used for coverage
|
|
79
|
+
* counting (`pom-catalog-parse.ts`), where the denominator must be actual
|
|
80
|
+
* locators still inline, not detection evidence. */
|
|
81
|
+
export function extractRawLocatorTokens(specContent, extraTestIdAttrs = []) {
|
|
82
|
+
return collectTokens(specContent, LOCATOR_PATTERNS, extraTestIdAttrs);
|
|
83
|
+
}
|
|
@@ -7,13 +7,14 @@ export type ReuseViolation = {
|
|
|
7
7
|
export type VerifyResult = {
|
|
8
8
|
ok: boolean;
|
|
9
9
|
violations: ReuseViolation[];
|
|
10
|
-
|
|
10
|
+
unverifiableCalls: string[];
|
|
11
|
+
unrecognizedBindings: string[];
|
|
11
12
|
parseError?: string;
|
|
12
13
|
checkedCalls: number;
|
|
13
|
-
/** Every POM call found in the spec, verifiable or not.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* POM members the delivered spec actually reuses. */
|
|
14
|
+
/** Every POM call found in the spec, verifiable or not. Equals
|
|
15
|
+
* `checkedCalls + unverifiableCalls.length`: `unrecognizedBindings` is kept
|
|
16
|
+
* separate precisely because those are not calls and would overcount. This is
|
|
17
|
+
* the count of POM members the delivered spec actually reuses. */
|
|
17
18
|
reusedCalls: number;
|
|
18
19
|
};
|
|
19
20
|
export declare function verifyReuse(testFile: string, language: string): Promise<VerifyResult>;
|
|
@@ -126,18 +126,19 @@ export async function verifyReuse(testFile, language) {
|
|
|
126
126
|
const calls = extractPomCalls(spec, bindings);
|
|
127
127
|
const bindingsByName = new Map(bindings.map((b) => [b.name, b]));
|
|
128
128
|
const violations = [];
|
|
129
|
-
const
|
|
129
|
+
const unverifiableCalls = [];
|
|
130
|
+
const unrecognizedBindings = [];
|
|
130
131
|
let checkedCalls = 0;
|
|
131
132
|
for (const call of calls) {
|
|
132
133
|
const binding = bindingsByName.get(call.binding);
|
|
133
134
|
if (!binding) {
|
|
134
135
|
// Shouldn't happen -- extractPomCalls only matches against the bindings it was given --
|
|
135
136
|
// but degrade to unverifiable rather than throw if the invariant ever breaks.
|
|
136
|
-
|
|
137
|
+
unverifiableCalls.push([call.binding, ...call.chain].join("."));
|
|
137
138
|
continue;
|
|
138
139
|
}
|
|
139
140
|
if (!call.strict) {
|
|
140
|
-
|
|
141
|
+
unverifiableCalls.push([call.binding, ...call.chain].join("."));
|
|
141
142
|
continue;
|
|
142
143
|
}
|
|
143
144
|
checkedCalls++;
|
|
@@ -156,7 +157,7 @@ export async function verifyReuse(testFile, language) {
|
|
|
156
157
|
// remap/demote loop the agent cannot win (no candidates to remap to → forced demote
|
|
157
158
|
// of possibly-correct reuse). Degrade to unverifiable — the fail-open contract at
|
|
158
159
|
// the reachability level, matching resolve.ts's permissive stance on shapes.
|
|
159
|
-
|
|
160
|
+
unverifiableCalls.push([call.binding, ...call.chain].join("."));
|
|
160
161
|
checkedCalls--;
|
|
161
162
|
continue;
|
|
162
163
|
}
|
|
@@ -164,7 +165,7 @@ export async function verifyReuse(testFile, language) {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
for (const u of unrecognized)
|
|
167
|
-
|
|
168
|
+
unrecognizedBindings.push(u);
|
|
168
169
|
let parseError;
|
|
169
170
|
if (language === "javascript") {
|
|
170
171
|
try {
|
|
@@ -179,7 +180,8 @@ export async function verifyReuse(testFile, language) {
|
|
|
179
180
|
return {
|
|
180
181
|
ok: violations.length === 0 && parseError === undefined,
|
|
181
182
|
violations,
|
|
182
|
-
|
|
183
|
+
unverifiableCalls,
|
|
184
|
+
unrecognizedBindings,
|
|
183
185
|
parseError,
|
|
184
186
|
checkedCalls,
|
|
185
187
|
reusedCalls: calls.length,
|
|
@@ -25,7 +25,7 @@ import { DriftAction } from "../types/TestAnalysis.js";
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function listChangedFilesAcross(roots: Array<string | undefined>): Promise<string[]>;
|
|
27
27
|
export declare function listChangedFiles(repoRoot: string): Promise<string[]>;
|
|
28
|
-
export interface
|
|
28
|
+
export interface UnchangedFileClaimsInput {
|
|
29
29
|
/** Absolute path of the primary repo checkout the report pertains to. */
|
|
30
30
|
repoRoot: string;
|
|
31
31
|
/** Repo-relative paths that have a working-tree change (from listChangedFiles). */
|
|
@@ -49,8 +49,14 @@ export interface UnbackedClaimsInput {
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* Cross-check report claims against files that actually changed in the working
|
|
52
|
-
* tree, returning a human-readable description for each claim
|
|
53
|
-
*
|
|
52
|
+
* tree, returning a human-readable description for each claim whose file has no
|
|
53
|
+
* change. An empty result means every claim is backed.
|
|
54
|
+
*
|
|
55
|
+
* Not to be confused with `findInvalidSourceCitations` below. This one is about
|
|
56
|
+
* files the agent says IT wrote — a created test, a maintained test — so the test
|
|
57
|
+
* is "did this file change in this run". That one is about application code the
|
|
58
|
+
* agent POINTS AT as the home of a defect, which it never edits, so the test is
|
|
59
|
+
* "does this file and symbol exist in the checkout at all".
|
|
54
60
|
*
|
|
55
61
|
* The delivery path can only ship what the agent actually edited (SKYR-3883), so
|
|
56
62
|
* a report must never claim file work the tree doesn't reflect. This is the
|
|
@@ -73,4 +79,58 @@ export interface UnbackedClaimsInput {
|
|
|
73
79
|
* - Claims for files outside this checkout (cross-repo `repository`, or an
|
|
74
80
|
* absolute path resolving outside `repoRoot`) — a different checkout owns them.
|
|
75
81
|
*/
|
|
76
|
-
export declare function
|
|
82
|
+
export declare function findUnchangedFileClaims(input: UnchangedFileClaimsInput): string[];
|
|
83
|
+
/** One checkout of the run, and the owner/repo it holds. */
|
|
84
|
+
export interface RepoCheckout {
|
|
85
|
+
/** Absolute checkout path. */
|
|
86
|
+
root: string;
|
|
87
|
+
/** owner/repo of this checkout, when the run recorded one. Undefined means the
|
|
88
|
+
* checkout cannot be named, so nothing resolved in it can be attributed. */
|
|
89
|
+
repository?: string;
|
|
90
|
+
/** True for the run's primary checkout — the repo an entry with no `repository`
|
|
91
|
+
* already means downstream, so its citations are never stamped. */
|
|
92
|
+
primary?: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface SourceCitationInput {
|
|
95
|
+
/** Checkouts to resolve a citation against: the primary repo plus every related
|
|
96
|
+
* repo in the run. Empty means no checkout is known — see below. */
|
|
97
|
+
checkouts: RepoCheckout[];
|
|
98
|
+
/** LLM-supplied issuesFound entries in submission order. The index is quoted back
|
|
99
|
+
* to the agent so it can find the entry it has to fix. */
|
|
100
|
+
issues: {
|
|
101
|
+
sourceFile?: string;
|
|
102
|
+
sourceSymbol?: string;
|
|
103
|
+
repository?: string;
|
|
104
|
+
}[];
|
|
105
|
+
}
|
|
106
|
+
export interface SourceCitationCheck {
|
|
107
|
+
/** One message per DEFINITE mismatch. Empty means the citations may ship. */
|
|
108
|
+
invalid: string[];
|
|
109
|
+
/** owner/repo to stamp on the entry at the same index of `issues`, or undefined
|
|
110
|
+
* to ship the attribution exactly as submitted. */
|
|
111
|
+
repository: (string | undefined)[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check every `sourceFile`/`sourceSymbol` citation on the submitted issuesFound
|
|
115
|
+
* entries against the repository, and say which repo each one belongs to.
|
|
116
|
+
* Returns one message per DEFINITE mismatch — an empty `invalid` means the report
|
|
117
|
+
* may be written — plus, per entry, the owner/repo the citation resolved in.
|
|
118
|
+
*
|
|
119
|
+
* The attribution is the reason this resolves against every checkout instead of
|
|
120
|
+
* the first hit: the consumer must not have to guess a cited file's repo from the
|
|
121
|
+
* path, which is what makes it link the primary repo's copy of a related repo's
|
|
122
|
+
* file. Where the citation resolves is the answer, so it is returned rather than
|
|
123
|
+
* discarded. Only a related repo is stamped: an absent `repository` already means
|
|
124
|
+
* the primary repo downstream, so a primary-repo citation ships as submitted.
|
|
125
|
+
*
|
|
126
|
+
* Rejects four things: a path that no known checkout contains, a symbol absent
|
|
127
|
+
* from the file that was found, an attribution that names a different repo than
|
|
128
|
+
* the one holding the code, and a file that resolves in several named checkouts
|
|
129
|
+
* with no attribution to choose between them. Everything uncertain is ACCEPTED —
|
|
130
|
+
* no checkout, an unreadable file, a checkout with no owner/repo, a directory
|
|
131
|
+
* listing that fails. The check must behave the same on any repo, in any
|
|
132
|
+
* language, at any size, and it must never become a loop the agent cannot exit.
|
|
133
|
+
* Symbol matching is a plain substring search for the same reason: no parser, so
|
|
134
|
+
* no language it silently mishandles.
|
|
135
|
+
*/
|
|
136
|
+
export declare function findInvalidSourceCitations(input: SourceCitationInput): Promise<SourceCitationCheck>;
|