@skyramp/mcp 0.4.4 → 0.5.0-rc.10
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/entitlements/entitlementGate.d.ts +8 -0
- package/build/entitlements/entitlementGate.js +57 -0
- package/build/entitlements/runCodes.d.ts +18 -0
- package/build/entitlements/runCodes.js +16 -0
- package/build/index.js +32 -6
- package/build/playwright/PlaywrightTraceService.d.ts +17 -0
- package/build/playwright/PlaywrightTraceService.js +29 -1
- package/build/playwright/dataSafetyConfig.d.ts +74 -0
- package/build/playwright/dataSafetyConfig.js +109 -0
- package/build/playwright/essentialTools.d.ts +1 -0
- package/build/playwright/essentialTools.js +44 -0
- package/build/playwright/registerPlaywrightTools.d.ts +36 -1
- package/build/playwright/registerPlaywrightTools.js +87 -43
- package/build/playwright/safeResolveDataSafety.d.ts +24 -0
- package/build/playwright/safeResolveDataSafety.js +33 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
- package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
- package/build/services/FleetExecutionService.d.ts +87 -16
- package/build/services/FleetExecutionService.js +487 -48
- package/build/services/FleetRunRegistry.d.ts +49 -0
- package/build/services/FleetRunRegistry.js +178 -0
- package/build/services/containerEnv.js +8 -2
- package/build/services/fleetLimitConfirmation.d.ts +19 -0
- package/build/services/fleetLimitConfirmation.js +126 -0
- package/build/tool-phases.d.ts +14 -8
- package/build/tool-phases.js +88 -43
- package/build/tools/code-refactor/assertion-state.js +3 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -1
- package/build/tools/code-refactor/utils-verify-gates.js +1 -1
- package/build/tools/executeFleetTool.d.ts +6 -4
- package/build/tools/executeFleetTool.js +67 -23
- package/build/tools/fixErrorTool.js +2 -1
- package/build/tools/fleetLifecycleTools.d.ts +20 -0
- package/build/tools/fleetLifecycleTools.js +124 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
- package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
- package/build/tools/submitReportTool.js +4 -122
- package/build/tools/test-management/registerTestPlanTool.js +22 -6
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +111 -9
- package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
- package/build/types/AssertionOutcome.d.ts +3 -0
- package/build/types/TestbotReport.d.ts +0 -37
- package/build/types/TestbotReport.js +0 -22
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js +1 -1
- package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
- package/build/utils/assertion-verify/created-add-guard.js +173 -0
- package/build/utils/assertion-verify/index.d.ts +1 -0
- package/build/utils/assertion-verify/index.js +1 -0
- package/build/utils/assertion-verify/verify.d.ts +7 -3
- package/build/utils/assertion-verify/verify.js +9 -3
- package/build/utils/featureFlags.js +6 -3
- package/build/utils/repositorySlug.d.ts +14 -0
- package/build/utils/repositorySlug.js +46 -1
- package/build/utils/telemetry.d.ts +2 -0
- package/build/utils/telemetry.js +12 -0
- package/build/utils/utils-verify/action-key.d.ts +59 -2
- package/build/utils/utils-verify/action-key.js +162 -6
- package/build/utils/utils-verify/action-sites.d.ts +7 -0
- package/build/utils/utils-verify/action-sites.js +277 -35
- package/build/utils/utils-verify/call-sites.d.ts +8 -2
- package/build/utils/utils-verify/language-spec.js +1 -1
- package/build/utils/utils-verify/parse.d.ts +4 -0
- package/build/utils/utils-verify/parse.js +2 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +63 -121
- package/build/workspace/workspace.js +45 -48
- package/build/workspace/workspaceVerifier.d.ts +22 -0
- package/build/workspace/workspaceVerifier.js +65 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
- package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
- package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
- package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.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.GLqsx5cI.js → uiMode.Dl7smBJn.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/cli/program.ts +5 -0
- package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -4
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/prompts/testbot-task1.md +1 -1
- package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
- package/plugin/skills/setup-testbot-target-sut/SKILL.md +60 -0
- package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
- package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
- package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
- package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
- package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
- package/build/prompts/sut-setup/shared.d.ts +0 -62
- package/build/prompts/sut-setup/shared.js +0 -196
- package/build/resources/sutSetupResource.d.ts +0 -2
- package/build/resources/sutSetupResource.js +0 -63
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DTp2-8Si.js +0 -809
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type UtilsLanguageSpec } from "./language-spec.js";
|
|
2
|
+
import { type ActionPattern } from "./action-key.js";
|
|
2
3
|
import { type HelperSleep } from "./fixed-sleep.js";
|
|
3
4
|
import { type SourceSpan } from "../pom-scope/strip.js";
|
|
4
5
|
export interface HelperAssertion {
|
|
@@ -56,6 +57,9 @@ export interface UtilsHelper {
|
|
|
56
57
|
* step per entry, for the sibling scan that matches step by step. */
|
|
57
58
|
actionKey?: string;
|
|
58
59
|
actionSteps?: string[];
|
|
60
|
+
/** The sequence as the sibling scan matches it, a parameter-fed locator as a
|
|
61
|
+
* wildcard (SKYR-4537). See actionPatternOf. */
|
|
62
|
+
actionPattern?: ActionPattern;
|
|
59
63
|
}
|
|
60
64
|
/** The declared parameters of a signature, raw text trimmed, empty entries dropped
|
|
61
65
|
* (a trailing comma). Reads the signature from the definition line to its body
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizePath } from "./language-spec.js";
|
|
2
|
-
import { actionStepKeys, actionStepsOf, } from "./action-key.js";
|
|
2
|
+
import { actionPatternOf, actionStepKeys, actionStepsOf, } from "./action-key.js";
|
|
3
3
|
/** A helper's action steps and their joined key, read once from its body. */
|
|
4
4
|
function actionSequence(body, language) {
|
|
5
5
|
const steps = actionStepsOf(body, language);
|
|
@@ -282,6 +282,7 @@ export function parseUtilsFile(content, spec) {
|
|
|
282
282
|
// Comments blanked, strings kept: a commented-out click is not a step, and
|
|
283
283
|
// the selector literals are the key.
|
|
284
284
|
...actionSequence(blankComments(body.join("\n"), spec), spec.language),
|
|
285
|
+
actionPattern: actionPatternOf(blankComments(body.join("\n"), spec), spec.language, params ?? []),
|
|
285
286
|
};
|
|
286
287
|
let requests = 0;
|
|
287
288
|
for (let k = 0; k < body.length; k++) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
2
|
-
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.
|
|
3
|
-
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.
|
|
1
|
+
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.53";
|
|
2
|
+
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.53";
|
|
3
|
+
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.53";
|
|
4
4
|
export declare const WORKER_CONTROL_PORT = 35142;
|
package/build/utils/versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
1
|
+
export const SKYRAMP_IMAGE_VERSION = "v1.3.53";
|
|
2
2
|
export const EXECUTOR_DOCKER_IMAGE = `skyramp/executor:${SKYRAMP_IMAGE_VERSION}`;
|
|
3
3
|
export const WORKER_DOCKER_IMAGE = `skyramp/worker:${SKYRAMP_IMAGE_VERSION}`;
|
|
4
4
|
// Control port the Skyramp worker listens on (SDK `CONTAINER_PORT`).
|
|
@@ -5,24 +5,8 @@ export { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
|
|
|
5
5
|
export declare const serviceSchema: z.ZodObject<{
|
|
6
6
|
serviceName: z.ZodString;
|
|
7
7
|
repository: z.ZodOptional<z.ZodString>;
|
|
8
|
-
language: z.ZodOptional<z.
|
|
9
|
-
|
|
10
|
-
typescript: "typescript";
|
|
11
|
-
javascript: "javascript";
|
|
12
|
-
java: "java";
|
|
13
|
-
}>>;
|
|
14
|
-
framework: z.ZodOptional<z.ZodEnum<{
|
|
15
|
-
playwright: "playwright";
|
|
16
|
-
pytest: "pytest";
|
|
17
|
-
robot: "robot";
|
|
18
|
-
junit: "junit";
|
|
19
|
-
vitest: "vitest";
|
|
20
|
-
jest: "jest";
|
|
21
|
-
mocha: "mocha";
|
|
22
|
-
go: "go";
|
|
23
|
-
rspec: "rspec";
|
|
24
|
-
jasmine: "jasmine";
|
|
25
|
-
}>>;
|
|
8
|
+
language: z.ZodOptional<z.ZodString>;
|
|
9
|
+
framework: z.ZodOptional<z.ZodString>;
|
|
26
10
|
testDirectory: z.ZodOptional<z.ZodString>;
|
|
27
11
|
api: z.ZodOptional<z.ZodObject<{
|
|
28
12
|
schemaPath: z.ZodOptional<z.ZodString>;
|
|
@@ -46,49 +30,30 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
46
30
|
baseUrl: z.ZodOptional<z.ZodString>;
|
|
47
31
|
skipTLSVerify: z.ZodOptional<z.ZodBoolean>;
|
|
48
32
|
}, z.core.$strict>>;
|
|
49
|
-
runtimeDetails: z.ZodOptional<z.ZodObject<{
|
|
33
|
+
runtimeDetails: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
50
34
|
serverStartCommand: z.ZodOptional<z.ZodString>;
|
|
51
35
|
serverStopCommand: z.ZodOptional<z.ZodString>;
|
|
52
36
|
runtime: z.ZodPreprocess<z.ZodEnum<typeof import("../types/TestTypes.js").RuntimeEnvironment>, unknown>;
|
|
53
37
|
dockerNetwork: z.ZodOptional<z.ZodString>;
|
|
54
38
|
k8sNamespace: z.ZodOptional<z.ZodString>;
|
|
55
39
|
k8sContext: z.ZodOptional<z.ZodString>;
|
|
56
|
-
testSuites: z.ZodOptional<z.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
unproven: "unproven";
|
|
74
|
-
}>>;
|
|
75
|
-
canaryDetail: z.ZodOptional<z.ZodString>;
|
|
76
|
-
testEnv: z.ZodOptional<z.ZodUnknown>;
|
|
77
|
-
}, z.core.$strict>, z.ZodTransform<{
|
|
78
|
-
testRunCommand: string;
|
|
79
|
-
framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
|
|
80
|
-
pathGlobs?: string[] | undefined;
|
|
81
|
-
canaryStatus?: "proven" | "unproven" | undefined;
|
|
82
|
-
canaryDetail?: string | undefined;
|
|
83
|
-
}, {
|
|
84
|
-
testRunCommand: string;
|
|
85
|
-
framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
|
|
86
|
-
pathGlobs?: string[] | undefined;
|
|
87
|
-
canaryStatus?: "proven" | "unproven" | undefined;
|
|
88
|
-
canaryDetail?: string | undefined;
|
|
89
|
-
testEnv?: unknown;
|
|
90
|
-
}>>>>;
|
|
91
|
-
}, z.core.$strict>>;
|
|
40
|
+
testSuites: z.ZodOptional<z.ZodUnknown>;
|
|
41
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
42
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
43
|
+
serverStartCommand?: string | undefined;
|
|
44
|
+
serverStopCommand?: string | undefined;
|
|
45
|
+
dockerNetwork?: string | undefined;
|
|
46
|
+
k8sNamespace?: string | undefined;
|
|
47
|
+
k8sContext?: string | undefined;
|
|
48
|
+
}, {
|
|
49
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
50
|
+
serverStartCommand?: string | undefined;
|
|
51
|
+
serverStopCommand?: string | undefined;
|
|
52
|
+
dockerNetwork?: string | undefined;
|
|
53
|
+
k8sNamespace?: string | undefined;
|
|
54
|
+
k8sContext?: string | undefined;
|
|
55
|
+
testSuites?: unknown;
|
|
56
|
+
}>>>;
|
|
92
57
|
}, z.core.$strict>;
|
|
93
58
|
export declare const workspaceConfigSchema: z.ZodObject<{
|
|
94
59
|
workspace: z.ZodOptional<z.ZodObject<{
|
|
@@ -102,27 +67,19 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
102
67
|
createdAt: z.ZodOptional<z.ZodString>;
|
|
103
68
|
updatedAt: z.ZodOptional<z.ZodString>;
|
|
104
69
|
}, z.core.$strict>>;
|
|
70
|
+
dataSafety: z.ZodOptional<z.ZodObject<{
|
|
71
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
72
|
+
readonly: "readonly";
|
|
73
|
+
strict: "strict";
|
|
74
|
+
off: "off";
|
|
75
|
+
}>>;
|
|
76
|
+
offLimits: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
77
|
+
}, z.core.$strict>>;
|
|
105
78
|
services: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
106
79
|
serviceName: z.ZodString;
|
|
107
80
|
repository: z.ZodOptional<z.ZodString>;
|
|
108
|
-
language: z.ZodOptional<z.
|
|
109
|
-
|
|
110
|
-
typescript: "typescript";
|
|
111
|
-
javascript: "javascript";
|
|
112
|
-
java: "java";
|
|
113
|
-
}>>;
|
|
114
|
-
framework: z.ZodOptional<z.ZodEnum<{
|
|
115
|
-
playwright: "playwright";
|
|
116
|
-
pytest: "pytest";
|
|
117
|
-
robot: "robot";
|
|
118
|
-
junit: "junit";
|
|
119
|
-
vitest: "vitest";
|
|
120
|
-
jest: "jest";
|
|
121
|
-
mocha: "mocha";
|
|
122
|
-
go: "go";
|
|
123
|
-
rspec: "rspec";
|
|
124
|
-
jasmine: "jasmine";
|
|
125
|
-
}>>;
|
|
81
|
+
language: z.ZodOptional<z.ZodString>;
|
|
82
|
+
framework: z.ZodOptional<z.ZodString>;
|
|
126
83
|
testDirectory: z.ZodOptional<z.ZodString>;
|
|
127
84
|
api: z.ZodOptional<z.ZodObject<{
|
|
128
85
|
schemaPath: z.ZodOptional<z.ZodString>;
|
|
@@ -146,55 +103,35 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
146
103
|
baseUrl: z.ZodOptional<z.ZodString>;
|
|
147
104
|
skipTLSVerify: z.ZodOptional<z.ZodBoolean>;
|
|
148
105
|
}, z.core.$strict>>;
|
|
149
|
-
runtimeDetails: z.ZodOptional<z.ZodObject<{
|
|
106
|
+
runtimeDetails: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
150
107
|
serverStartCommand: z.ZodOptional<z.ZodString>;
|
|
151
108
|
serverStopCommand: z.ZodOptional<z.ZodString>;
|
|
152
109
|
runtime: z.ZodPreprocess<z.ZodEnum<typeof import("../types/TestTypes.js").RuntimeEnvironment>, unknown>;
|
|
153
110
|
dockerNetwork: z.ZodOptional<z.ZodString>;
|
|
154
111
|
k8sNamespace: z.ZodOptional<z.ZodString>;
|
|
155
112
|
k8sContext: z.ZodOptional<z.ZodString>;
|
|
156
|
-
testSuites: z.ZodOptional<z.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
unproven: "unproven";
|
|
174
|
-
}>>;
|
|
175
|
-
canaryDetail: z.ZodOptional<z.ZodString>;
|
|
176
|
-
testEnv: z.ZodOptional<z.ZodUnknown>;
|
|
177
|
-
}, z.core.$strict>, z.ZodTransform<{
|
|
178
|
-
testRunCommand: string;
|
|
179
|
-
framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
|
|
180
|
-
pathGlobs?: string[] | undefined;
|
|
181
|
-
canaryStatus?: "proven" | "unproven" | undefined;
|
|
182
|
-
canaryDetail?: string | undefined;
|
|
183
|
-
}, {
|
|
184
|
-
testRunCommand: string;
|
|
185
|
-
framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
|
|
186
|
-
pathGlobs?: string[] | undefined;
|
|
187
|
-
canaryStatus?: "proven" | "unproven" | undefined;
|
|
188
|
-
canaryDetail?: string | undefined;
|
|
189
|
-
testEnv?: unknown;
|
|
190
|
-
}>>>>;
|
|
191
|
-
}, z.core.$strict>>;
|
|
113
|
+
testSuites: z.ZodOptional<z.ZodUnknown>;
|
|
114
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
115
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
116
|
+
serverStartCommand?: string | undefined;
|
|
117
|
+
serverStopCommand?: string | undefined;
|
|
118
|
+
dockerNetwork?: string | undefined;
|
|
119
|
+
k8sNamespace?: string | undefined;
|
|
120
|
+
k8sContext?: string | undefined;
|
|
121
|
+
}, {
|
|
122
|
+
runtime: import("../types/TestTypes.js").RuntimeEnvironment;
|
|
123
|
+
serverStartCommand?: string | undefined;
|
|
124
|
+
serverStopCommand?: string | undefined;
|
|
125
|
+
dockerNetwork?: string | undefined;
|
|
126
|
+
k8sNamespace?: string | undefined;
|
|
127
|
+
k8sContext?: string | undefined;
|
|
128
|
+
testSuites?: unknown;
|
|
129
|
+
}>>>;
|
|
192
130
|
}, z.core.$strict>>>;
|
|
193
131
|
}, z.core.$strict>;
|
|
194
132
|
export type Service = z.infer<typeof serviceSchema>;
|
|
195
133
|
export type ServiceApi = NonNullable<Service["api"]>;
|
|
196
134
|
export type ServiceRuntimeDetails = NonNullable<Service["runtimeDetails"]>;
|
|
197
|
-
export type TestSuite = NonNullable<ServiceRuntimeDetails["testSuites"]>[number];
|
|
198
135
|
export type WorkspaceConfig = z.infer<typeof workspaceConfigSchema>;
|
|
199
136
|
export type WorkspaceSection = NonNullable<WorkspaceConfig["workspace"]>;
|
|
200
137
|
export type MetadataSection = NonNullable<WorkspaceConfig["metadata"]>;
|
|
@@ -211,11 +148,15 @@ export declare function validateWorkspaceConfig(config: unknown): z.ZodSafeParse
|
|
|
211
148
|
createdAt?: string | undefined;
|
|
212
149
|
updatedAt?: string | undefined;
|
|
213
150
|
} | undefined;
|
|
151
|
+
dataSafety?: {
|
|
152
|
+
mode?: "readonly" | "strict" | "off" | undefined;
|
|
153
|
+
offLimits?: string[] | undefined;
|
|
154
|
+
} | undefined;
|
|
214
155
|
services?: {
|
|
215
156
|
serviceName: string;
|
|
216
157
|
repository?: string | undefined;
|
|
217
|
-
language?:
|
|
218
|
-
framework?:
|
|
158
|
+
language?: string | undefined;
|
|
159
|
+
framework?: string | undefined;
|
|
219
160
|
testDirectory?: string | undefined;
|
|
220
161
|
api?: {
|
|
221
162
|
schemaPath?: string | undefined;
|
|
@@ -237,13 +178,6 @@ export declare function validateWorkspaceConfig(config: unknown): z.ZodSafeParse
|
|
|
237
178
|
dockerNetwork?: string | undefined;
|
|
238
179
|
k8sNamespace?: string | undefined;
|
|
239
180
|
k8sContext?: string | undefined;
|
|
240
|
-
testSuites?: {
|
|
241
|
-
testRunCommand: string;
|
|
242
|
-
framework?: "playwright" | "pytest" | "robot" | "junit" | "vitest" | "jest" | "mocha" | "go" | "rspec" | "jasmine" | undefined;
|
|
243
|
-
pathGlobs?: string[] | undefined;
|
|
244
|
-
canaryStatus?: "proven" | "unproven" | undefined;
|
|
245
|
-
canaryDetail?: string | undefined;
|
|
246
|
-
}[] | undefined;
|
|
247
181
|
} | undefined;
|
|
248
182
|
}[] | undefined;
|
|
249
183
|
}>;
|
|
@@ -273,9 +207,17 @@ export declare class WorkspaceConfigManager {
|
|
|
273
207
|
/**
|
|
274
208
|
* Initialize workspace — creates .skyramp/workspace.yml with repo info.
|
|
275
209
|
* Auto-detects repoName and repoUrl from git when not provided.
|
|
276
|
-
* Produces an empty services array and default metadata timestamps.
|
|
210
|
+
* Produces an empty services array and default metadata timestamps. Over an
|
|
211
|
+
* existing file it keeps that file's createdAt: the workspace was created
|
|
212
|
+
* then, however often it is rewritten.
|
|
277
213
|
*/
|
|
278
214
|
initialize(workspaceInfo?: WorkspaceSection): Promise<WorkspaceConfig>;
|
|
215
|
+
/**
|
|
216
|
+
* The existing file's metadata.createdAt, read leniently: a rewrite is often
|
|
217
|
+
* the repair of a file that no longer validates, and its createdAt still
|
|
218
|
+
* stands. Undefined when there is no file or it records none.
|
|
219
|
+
*/
|
|
220
|
+
private existingCreatedAt;
|
|
279
221
|
/**
|
|
280
222
|
* Update the metadata section of an existing workspace config.
|
|
281
223
|
* Only the provided fields are merged; metadata.updatedAt is refreshed
|
|
@@ -14,7 +14,6 @@ import { execFile } from "child_process";
|
|
|
14
14
|
import { promisify } from "util";
|
|
15
15
|
import yaml from "js-yaml";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
-
import { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
|
|
18
17
|
import { runtimeEnvironmentInputSchema } from "../types/TestTypes.js";
|
|
19
18
|
import { parseOwnerRepo } from "../utils/repositorySlug.js";
|
|
20
19
|
const execFileAsync = promisify(execFile);
|
|
@@ -23,8 +22,8 @@ const execFileAsync = promisify(execFile);
|
|
|
23
22
|
// ---------------------------------------------------------------------------
|
|
24
23
|
export const WORKSPACE_DIR = ".skyramp";
|
|
25
24
|
export const WORKSPACE_FILENAME = "workspace.yml";
|
|
26
|
-
// SUPPORTED_FRAMEWORKS lives in the dependency-free ./frameworks module
|
|
27
|
-
//
|
|
25
|
+
// SUPPORTED_FRAMEWORKS lives in the dependency-free ./frameworks module and is
|
|
26
|
+
// re-exported here for existing importers.
|
|
28
27
|
export { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
|
|
29
28
|
// ---------------------------------------------------------------------------
|
|
30
29
|
// Zod schemas (source of truth — types are inferred from these below)
|
|
@@ -32,12 +31,14 @@ export { SUPPORTED_FRAMEWORKS } from "./frameworks.js";
|
|
|
32
31
|
export const serviceSchema = z
|
|
33
32
|
.object({
|
|
34
33
|
serviceName: z.string(),
|
|
35
|
-
// owner/repo this service belongs to (multi-repo workspaces). Absent → the
|
|
34
|
+
// owner/repo (org/project/repo on Azure DevOps) this service belongs to (multi-repo workspaces). Absent → the
|
|
36
35
|
// primary repo. Service identity is the composite (repository, serviceName),
|
|
37
36
|
// so the same serviceName can appear once per repo without colliding.
|
|
38
37
|
repository: z.string().optional(),
|
|
39
|
-
language
|
|
40
|
-
framework
|
|
38
|
+
// Any language and test framework, as the repository names them: the scan is
|
|
39
|
+
// language- and framework-agnostic.
|
|
40
|
+
language: z.string().optional(),
|
|
41
|
+
framework: z.string().optional(),
|
|
41
42
|
testDirectory: z.string().optional(),
|
|
42
43
|
api: z
|
|
43
44
|
.object({
|
|
@@ -105,49 +106,13 @@ export const serviceSchema = z
|
|
|
105
106
|
dockerNetwork: z.string().optional(),
|
|
106
107
|
k8sNamespace: z.string().optional(),
|
|
107
108
|
k8sContext: z.string().optional(),
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
// (falling back to the service's), testRunCommand, and pathGlobs
|
|
113
|
-
// — the repo-relative paths that suite owns, which the agent reads to find
|
|
114
|
-
// the suite of a test file. One service can host multiple frameworks.
|
|
115
|
-
testSuites: z
|
|
116
|
-
.array(
|
|
117
|
-
// A workspace.yml written before testEnv was removed still loads: the
|
|
118
|
-
// key is accepted and then dropped, so the next write leaves it out.
|
|
119
|
-
z
|
|
120
|
-
.object({
|
|
121
|
-
framework: z.enum(SUPPORTED_FRAMEWORKS).optional(),
|
|
122
|
-
testRunCommand: z.string().min(1),
|
|
123
|
-
pathGlobs: z.array(z.string()).optional(),
|
|
124
|
-
// Did SUT setup actually run one test from this suite?
|
|
125
|
-
//
|
|
126
|
-
// "Is the environment proven" and "should maintenance work on this
|
|
127
|
-
// suite" are independent, and collapsing them costs one way or the
|
|
128
|
-
// other. The prompt used to make recording free, so a suite could
|
|
129
|
-
// read as covered while never having executed. Requiring proof
|
|
130
|
-
// instead would drop exactly the suite a user most wants
|
|
131
|
-
// maintained: a self-provisioning e2e suite whose substrate the
|
|
132
|
-
// setup cannot stand up. Run 30402703836 shows why that trade is
|
|
133
|
-
// bad — with its Playwright environment dead, static analysis
|
|
134
|
-
// still found the stale POM testid and fixed it correctly.
|
|
135
|
-
//
|
|
136
|
-
// So an unproven suite STAYS and is still maintained; this records
|
|
137
|
-
// that its environment was never validated, and canaryDetail says
|
|
138
|
-
// what it needs. Absent means unproven (older artifacts predate
|
|
139
|
-
// this field), which is the safe reading.
|
|
140
|
-
canaryStatus: z.enum(["proven", "unproven"]).optional(),
|
|
141
|
-
/** Why it could not be proven — required by the prompt when unproven. */
|
|
142
|
-
canaryDetail: z.string().optional(),
|
|
143
|
-
// Accepted only so an older file parses; the transform removes it.
|
|
144
|
-
testEnv: z.unknown().optional(),
|
|
145
|
-
})
|
|
146
|
-
.strict()
|
|
147
|
-
.transform(({ testEnv: _testEnv, ...rest }) => rest))
|
|
148
|
-
.optional(),
|
|
109
|
+
// Deprecated: testSuites (with its pathGlobs) is no longer recorded or read.
|
|
110
|
+
// A workspace.yml that still has it loads; the key is dropped, so the next
|
|
111
|
+
// write leaves it out.
|
|
112
|
+
testSuites: z.unknown().optional(),
|
|
149
113
|
})
|
|
150
114
|
.strict()
|
|
115
|
+
.transform(({ testSuites: _testSuites, ...rest }) => rest)
|
|
151
116
|
.optional(),
|
|
152
117
|
})
|
|
153
118
|
.strict();
|
|
@@ -174,6 +139,16 @@ export const workspaceConfigSchema = z
|
|
|
174
139
|
})
|
|
175
140
|
.strict()
|
|
176
141
|
.optional(),
|
|
142
|
+
/** Data safety for UI recording: scratch-entity discipline mode and
|
|
143
|
+
* customer off-limits URL patterns. Consumed by the Playwright trace
|
|
144
|
+
* backend and by testbot's config resolution. */
|
|
145
|
+
dataSafety: z
|
|
146
|
+
.object({
|
|
147
|
+
mode: z.enum(["strict", "readonly", "off"]).optional(),
|
|
148
|
+
offLimits: z.array(z.string()).optional(),
|
|
149
|
+
})
|
|
150
|
+
.strict()
|
|
151
|
+
.optional(),
|
|
177
152
|
services: z.array(serviceSchema).optional(),
|
|
178
153
|
})
|
|
179
154
|
.strict();
|
|
@@ -346,13 +321,18 @@ export class WorkspaceConfigManager {
|
|
|
346
321
|
/**
|
|
347
322
|
* Initialize workspace — creates .skyramp/workspace.yml with repo info.
|
|
348
323
|
* Auto-detects repoName and repoUrl from git when not provided.
|
|
349
|
-
* Produces an empty services array and default metadata timestamps.
|
|
324
|
+
* Produces an empty services array and default metadata timestamps. Over an
|
|
325
|
+
* existing file it keeps that file's createdAt: the workspace was created
|
|
326
|
+
* then, however often it is rewritten.
|
|
350
327
|
*/
|
|
351
328
|
async initialize(workspaceInfo = {}) {
|
|
352
329
|
await fs.mkdir(this.skyrampDir, { recursive: true });
|
|
353
330
|
// Auto-detect repo info from workspace root when not explicitly provided
|
|
354
331
|
const detected = await detectRepoInfo(this.workspacePath);
|
|
355
332
|
const config = createDefaultConfig();
|
|
333
|
+
const createdAt = await this.existingCreatedAt();
|
|
334
|
+
if (createdAt && config.metadata)
|
|
335
|
+
config.metadata.createdAt = createdAt;
|
|
356
336
|
config.workspace = {
|
|
357
337
|
repoName: workspaceInfo.repoName || detected.repoName || undefined,
|
|
358
338
|
repoUrl: workspaceInfo.repoUrl || detected.repoUrl || undefined,
|
|
@@ -361,6 +341,23 @@ export class WorkspaceConfigManager {
|
|
|
361
341
|
await writeWorkspaceFile(this.configPath, validated);
|
|
362
342
|
return validated;
|
|
363
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* The existing file's metadata.createdAt, read leniently: a rewrite is often
|
|
346
|
+
* the repair of a file that no longer validates, and its createdAt still
|
|
347
|
+
* stands. Undefined when there is no file or it records none.
|
|
348
|
+
*/
|
|
349
|
+
async existingCreatedAt() {
|
|
350
|
+
try {
|
|
351
|
+
const doc = yaml.load(await fs.readFile(this.configPath, "utf8"));
|
|
352
|
+
const createdAt = doc?.metadata?.createdAt;
|
|
353
|
+
return typeof createdAt === "string" && createdAt.trim()
|
|
354
|
+
? createdAt
|
|
355
|
+
: undefined;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
364
361
|
/**
|
|
365
362
|
* Update the metadata section of an existing workspace config.
|
|
366
363
|
* Only the provided fields are merged; metadata.updatedAt is refreshed
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies the services written to .skyramp/workspace.yml against the rules
|
|
3
|
+
* the scan instructions state and that a file can show deterministically:
|
|
4
|
+
* service identity, runtime fields and API addressing and auth.
|
|
5
|
+
* skyramp_init_workspace refuses a write that breaks one, and the SUT eval runs
|
|
6
|
+
* it on the file a run produced. It reports only what is certainly wrong.
|
|
7
|
+
*/
|
|
8
|
+
import type { Service } from "./workspace.js";
|
|
9
|
+
/** A repository of the Target other than the workspace's own. */
|
|
10
|
+
export interface VerifierRepository {
|
|
11
|
+
repository: string;
|
|
12
|
+
repositoryPath: string;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkspaceVerifierOptions {
|
|
15
|
+
/** The workspace's own repository root, where a service with no `repository` lives. */
|
|
16
|
+
workspacePath: string;
|
|
17
|
+
/** The other repositories the scan named. When given, every service's
|
|
18
|
+
* `repository` is one of them. */
|
|
19
|
+
relatedRepositories?: VerifierRepository[];
|
|
20
|
+
}
|
|
21
|
+
/** Every problem with `services`, one line each, naming the service and the fix. */
|
|
22
|
+
export declare function verifyWorkspaceServices(services: readonly Service[], opts: WorkspaceVerifierOptions): string[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies the services written to .skyramp/workspace.yml against the rules
|
|
3
|
+
* the scan instructions state and that a file can show deterministically:
|
|
4
|
+
* service identity, runtime fields and API addressing and auth.
|
|
5
|
+
* skyramp_init_workspace refuses a write that breaks one, and the SUT eval runs
|
|
6
|
+
* it on the file a run produced. It reports only what is certainly wrong.
|
|
7
|
+
*/
|
|
8
|
+
import { isRepositorySlug } from "../utils/repositorySlug.js";
|
|
9
|
+
/** Every problem with `services`, one line each, naming the service and the fix. */
|
|
10
|
+
export function verifyWorkspaceServices(services, opts) {
|
|
11
|
+
const issues = [];
|
|
12
|
+
const related = opts.relatedRepositories;
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
for (const service of services) {
|
|
15
|
+
const name = service.serviceName;
|
|
16
|
+
const where = service.repository ? `${service.repository}/${name}` : name;
|
|
17
|
+
const key = `${service.repository ?? ""}\u0000${name}`;
|
|
18
|
+
if (seen.has(key))
|
|
19
|
+
issues.push(`${where}: listed twice. Each (repository, serviceName) pair is one service; merge the entries.`);
|
|
20
|
+
seen.add(key);
|
|
21
|
+
if (service.repository !== undefined) {
|
|
22
|
+
if (!isRepositorySlug(service.repository))
|
|
23
|
+
issues.push(`${where}: repository "${service.repository}" is not owner/repo, or org/project/repo on Azure DevOps.`);
|
|
24
|
+
else if (related?.length &&
|
|
25
|
+
!related.some((r) => r.repository.toLowerCase() === service.repository.toLowerCase()))
|
|
26
|
+
issues.push(`${where}: repository "${service.repository}" is not one the scan named (${related.map((r) => r.repository).join(", ")}). Set it to the repository the service was found in, or leave it out for the workspace's own repository.`);
|
|
27
|
+
}
|
|
28
|
+
issues.push(...runtimeIssues(service, where));
|
|
29
|
+
issues.push(...apiIssues(service, where));
|
|
30
|
+
}
|
|
31
|
+
return issues;
|
|
32
|
+
}
|
|
33
|
+
function runtimeIssues(service, where) {
|
|
34
|
+
const issues = [];
|
|
35
|
+
const rt = service.runtimeDetails;
|
|
36
|
+
if (!rt)
|
|
37
|
+
return issues;
|
|
38
|
+
const runtime = rt.runtime;
|
|
39
|
+
if (runtime !== "docker" && rt.dockerNetwork)
|
|
40
|
+
issues.push(`${where}: dockerNetwork is set only for runtime docker; remove it.`);
|
|
41
|
+
if (runtime !== "k8s" && (rt.k8sNamespace || rt.k8sContext))
|
|
42
|
+
issues.push(`${where}: k8sNamespace and k8sContext are set only for runtime k8s; remove them.`);
|
|
43
|
+
return issues;
|
|
44
|
+
}
|
|
45
|
+
function apiIssues(service, where) {
|
|
46
|
+
const issues = [];
|
|
47
|
+
const api = service.api;
|
|
48
|
+
if (!api)
|
|
49
|
+
return issues;
|
|
50
|
+
if (api.baseUrl !== undefined) {
|
|
51
|
+
let ok = false;
|
|
52
|
+
try {
|
|
53
|
+
const url = new URL(api.baseUrl);
|
|
54
|
+
ok = url.protocol === "http:" || url.protocol === "https:";
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
ok = false;
|
|
58
|
+
}
|
|
59
|
+
if (!ok)
|
|
60
|
+
issues.push(`${where}: api.baseUrl "${api.baseUrl}" is not an absolute http(s) URL.`);
|
|
61
|
+
}
|
|
62
|
+
if (api.authType === "apiKey" && !api.authHeader?.trim())
|
|
63
|
+
issues.push(`${where}: authType apiKey needs authHeader, the header that carries the key. If it cannot be found, use authType none.`);
|
|
64
|
+
return issues;
|
|
65
|
+
}
|
|
@@ -595,7 +595,7 @@ async function domEvaluationScript(options) {
|
|
|
595
595
|
}
|
|
596
596
|
return key;
|
|
597
597
|
}
|
|
598
|
-
function isDynamicIdClient(id) {
|
|
598
|
+
function isDynamicIdClient(id, kinds) {
|
|
599
599
|
if (/^react-aria\d+/.test(id)) return true;
|
|
600
600
|
if (/^mui-\d+/.test(id)) return true;
|
|
601
601
|
if (/^(mat|cdk)-[a-z]+-\d+$/.test(id)) return true;
|
|
@@ -604,9 +604,10 @@ async function domEvaluationScript(options) {
|
|
|
604
604
|
if (/^\d+$/.test(id)) return true;
|
|
605
605
|
if (/\d{4,}$/.test(id)) return true;
|
|
606
606
|
if (/[-_][0-9a-f]{6,}$/i.test(id)) return true;
|
|
607
|
-
const m = id.match(
|
|
608
|
-
if (m && /[0-9]/.test(m[1])) return true;
|
|
609
|
-
|
|
607
|
+
const m = id.match(/^(.*?)[-_]([0-9a-f]{3,5})$/i);
|
|
608
|
+
if (m && (/[0-9]/.test(m[2]) || m[2].length >= 4 && kinds?.includes(m[1].toLowerCase()))) return true;
|
|
609
|
+
const counter = id.match(/^(.*?[a-zA-Z])([0-9]+)$/);
|
|
610
|
+
if (counter && (counter[2].length >= 3 || kinds?.includes(counter[1].toLowerCase()))) return true;
|
|
610
611
|
if (id.includes(":")) return true;
|
|
611
612
|
if (/«r[0-9a-z]*»|(^|[-_])_?r_[0-9a-z]+_/.test(id)) return true;
|
|
612
613
|
if (/^.+__search_[a-zA-Z0-9]{4,}$/.test(id)) return true;
|
|
@@ -627,7 +628,7 @@ async function domEvaluationScript(options) {
|
|
|
627
628
|
const ancestorTestId = resolveTestId(cur);
|
|
628
629
|
if (ancestorTestId) return ancestorTestId.value;
|
|
629
630
|
const id = cur.getAttribute("id");
|
|
630
|
-
if (id && !isDynamicIdClient(id)) return id;
|
|
631
|
+
if (id && !isDynamicIdClient(id, [cur.localName, getRole(cur).role])) return id;
|
|
631
632
|
}
|
|
632
633
|
cur = cur.parentElement;
|
|
633
634
|
depth++;
|
|
@@ -845,7 +846,7 @@ async function domEvaluationScript(options) {
|
|
|
845
846
|
const idAttr = el.getAttribute("id");
|
|
846
847
|
const resolvedTestId = resolveTestId(el);
|
|
847
848
|
const fingerprint = widgetType === "native" ? null : await computeElementFingerprint(el);
|
|
848
|
-
const stableIdValue = idAttr && idUnique(idAttr) && !isDynamicIdClient(idAttr) ? idAttr : null;
|
|
849
|
+
const stableIdValue = idAttr && idUnique(idAttr) && !isDynamicIdClient(idAttr, [el.localName, role]) ? idAttr : null;
|
|
849
850
|
return {
|
|
850
851
|
role,
|
|
851
852
|
accessibleName,
|
|
@@ -21,7 +21,7 @@ __export(dynamicId_exports, {
|
|
|
21
21
|
isDynamicId: () => isDynamicId
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(dynamicId_exports);
|
|
24
|
-
function isDynamicId(id) {
|
|
24
|
+
function isDynamicId(id, kinds) {
|
|
25
25
|
if (/^react-aria\d+/.test(id)) return true;
|
|
26
26
|
if (/^mui-\d+/.test(id)) return true;
|
|
27
27
|
if (/^(mat|cdk)-[a-z]+-\d+$/.test(id)) return true;
|
|
@@ -30,9 +30,10 @@ function isDynamicId(id) {
|
|
|
30
30
|
if (/^\d+$/.test(id)) return true;
|
|
31
31
|
if (/\d{4,}$/.test(id)) return true;
|
|
32
32
|
if (/[-_][0-9a-f]{6,}$/i.test(id)) return true;
|
|
33
|
-
const shortHexMatch = id.match(
|
|
34
|
-
if (shortHexMatch && /[0-9]/.test(shortHexMatch[1])) return true;
|
|
35
|
-
|
|
33
|
+
const shortHexMatch = id.match(/^(.*?)[-_]([0-9a-f]{3,5})$/i);
|
|
34
|
+
if (shortHexMatch && (/[0-9]/.test(shortHexMatch[2]) || shortHexMatch[2].length >= 4 && kinds?.includes(shortHexMatch[1].toLowerCase()))) return true;
|
|
35
|
+
const counterMatch = id.match(/^(.*?[a-zA-Z])([0-9]+)$/);
|
|
36
|
+
if (counterMatch && (counterMatch[2].length >= 3 || kinds?.includes(counterMatch[1].toLowerCase()))) return true;
|
|
36
37
|
if (id.includes(":")) return true;
|
|
37
38
|
if (/«r[0-9a-z]*»|(^|[-_])_?r_[0-9a-z]+_/.test(id)) return true;
|
|
38
39
|
if (/^.+__search_[a-zA-Z0-9]{4,}$/.test(id)) return true;
|
|
@@ -71,6 +71,18 @@ test("isDynamicId: React 19 useId forms", () => {
|
|
|
71
71
|
assertTrue((0, import_dynamicId.isDynamicId)("_r_1k_"), "_r_1k_ (bare React 19.1)");
|
|
72
72
|
assertFalse((0, import_dynamicId.isDynamicId)("user_r_name"), "user_r_name (word, not a useId)");
|
|
73
73
|
});
|
|
74
|
+
test("isDynamicId: the element's own kind plus a counter or short hash", () => {
|
|
75
|
+
assertTrue((0, import_dynamicId.isDynamicId)("input-cbcc", ["input", "textbox"]), "input-cbcc on an input");
|
|
76
|
+
assertTrue((0, import_dynamicId.isDynamicId)("checkbox7", ["input", "checkbox"]), "checkbox7 on a checkbox");
|
|
77
|
+
assertTrue((0, import_dynamicId.isDynamicId)("textbox-fdda", ["input", "textbox"]), "textbox-fdda on a textbox");
|
|
78
|
+
assertFalse((0, import_dynamicId.isDynamicId)("input-cbcc"), "input-cbcc without the element: a word-like suffix");
|
|
79
|
+
assertFalse((0, import_dynamicId.isDynamicId)("checkbox7"), "checkbox7 without the element: below the 3-digit threshold");
|
|
80
|
+
assertFalse((0, import_dynamicId.isDynamicId)("news-feed", ["input", "textbox"]), "news-feed: authored stem");
|
|
81
|
+
assertFalse((0, import_dynamicId.isDynamicId)("option2", ["input", "checkbox"]), "option2: stem is not the element kind");
|
|
82
|
+
assertFalse((0, import_dynamicId.isDynamicId)("IDToken2", ["input", "textbox"]), "IDToken2: authored stem");
|
|
83
|
+
assertFalse((0, import_dynamicId.isDynamicId)("password-login", ["input", "textbox"]), "password-login");
|
|
84
|
+
assertFalse((0, import_dynamicId.isDynamicId)("button-add", ["button"]), "button-add: 3 letters stay a word");
|
|
85
|
+
});
|
|
74
86
|
let failed = 0;
|
|
75
87
|
for (const { name, run } of cases) {
|
|
76
88
|
try {
|