@skyramp/mcp 0.3.8 → 0.4.0-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/commands/commandLibrary.d.ts +1 -1
- package/build/commands/commandLibrary.js +3 -3
- package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
- package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
- package/build/commands/testThisEndpointCommand.js +35 -19
- package/build/index.js +9 -3
- package/build/playwright/blueprintDigest.d.ts +15 -0
- package/build/playwright/blueprintDigest.js +152 -0
- package/build/playwright/blueprintDigestStore.d.ts +31 -0
- package/build/playwright/blueprintDigestStore.js +117 -0
- package/build/playwright/registerPlaywrightTools.js +60 -12
- package/build/playwright/traceRecordingPrompt.js +8 -7
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
- package/build/prompts/promptAssets.d.ts +20 -0
- package/build/prompts/promptAssets.js +55 -0
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
- package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
- package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
- package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
- package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
- package/build/prompts/test-recommendation/recommendationSections.js +67 -309
- package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
- package/build/prompts/test-recommendation/recommendationShared.js +49 -155
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
- package/build/prompts/testbot/planDeclarations.d.ts +6 -0
- package/build/prompts/testbot/planDeclarations.js +9 -0
- package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
- package/build/prompts/testbot/testbot-prompts.js +256 -381
- package/build/recommendation/answers.d.ts +35 -0
- package/build/recommendation/answers.js +96 -0
- package/build/recommendation/registerPlan.d.ts +49 -0
- package/build/recommendation/registerPlan.js +117 -0
- package/build/recommendation/runVerifiers.d.ts +10 -0
- package/build/recommendation/runVerifiers.js +49 -0
- package/build/recommendation/subjectStep.d.ts +42 -0
- package/build/recommendation/subjectStep.js +86 -0
- package/build/recommendation/types.d.ts +163 -0
- package/build/recommendation/types.js +20 -0
- package/build/recommendation/verifierContracts.d.ts +382 -0
- package/build/recommendation/verifierContracts.js +263 -0
- package/build/recommendation/verifiers/changedFile.d.ts +2 -0
- package/build/recommendation/verifiers/changedFile.js +82 -0
- package/build/recommendation/verifiers/citedPath.d.ts +12 -0
- package/build/recommendation/verifiers/citedPath.js +35 -0
- package/build/recommendation/verifiers/coverage.d.ts +7 -0
- package/build/recommendation/verifiers/coverage.js +617 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
- package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
- package/build/recommendation/verifiers/endpointGrounded.js +128 -0
- package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
- package/build/recommendation/verifiers/existingCoverage.js +51 -0
- package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
- package/build/recommendation/verifiers/expectedOutcome.js +105 -0
- package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
- package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
- package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
- package/build/recommendation/verifiers/reportedCategory.js +84 -0
- package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
- package/build/recommendation/verifiers/screenRoute.js +118 -0
- package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
- package/build/recommendation/verifiers/statedDifference.js +140 -0
- package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
- package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
- package/build/resources/analysisResources.js +1 -114
- package/build/resources/testbotResource.js +23 -13
- package/build/services/ModularizationService.js +2 -1
- package/build/services/TestDiscoveryService.d.ts +3 -72
- package/build/services/TestDiscoveryService.js +10 -303
- package/build/services/containerEnv.d.ts +1 -1
- package/build/services/containerEnv.js +12 -0
- package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
- package/build/skills/fixTestImportErrorsSkill.js +20 -0
- package/build/toolNames.d.ts +1 -0
- package/build/toolNames.js +1 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
- package/build/tools/code-refactor/modularizationTool.js +2 -1
- package/build/tools/executeSkyrampTestTool.d.ts +80 -0
- package/build/tools/executeSkyrampTestTool.js +246 -19
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
- package/build/tools/generate-tests/generateContractRestTool.js +3 -3
- package/build/tools/generate-tests/planGuard.d.ts +2 -2
- package/build/tools/generate-tests/planGuard.js +78 -18
- package/build/tools/one-click/oneClickTool.d.ts +0 -1
- package/build/tools/one-click/oneClickTool.js +0 -5
- package/build/tools/submitReportTool.d.ts +48 -42
- package/build/tools/submitReportTool.js +576 -193
- package/build/tools/test-management/actionsTool.js +72 -4
- package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
- package/build/tools/test-management/analyzeChangesTool.js +212 -1219
- package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
- package/build/tools/test-management/index.d.ts +1 -0
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
- package/build/tools/test-management/registerTestPlanTool.js +609 -542
- package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
- package/build/tools/test-management/resolveScreenTool.js +289 -0
- package/build/types/BlueprintDigest.d.ts +34 -0
- package/build/types/BlueprintDigest.js +1 -0
- package/build/types/RepositoryAnalysis.d.ts +20 -1559
- package/build/types/RepositoryAnalysis.js +2 -58
- package/build/types/StepMethod.d.ts +40 -0
- package/build/types/StepMethod.js +77 -0
- package/build/types/TestAnalysis.d.ts +12 -0
- package/build/types/TestExecution.d.ts +4 -0
- package/build/types/TestRecommendation.d.ts +24 -24
- package/build/types/TestRecommendation.js +91 -89
- package/build/types/TestbotPromptOptions.d.ts +0 -4
- package/build/types/TestbotReport.d.ts +64 -2
- package/build/utils/AnalysisStateManager.d.ts +79 -113
- package/build/utils/AnalysisStateManager.js +147 -57
- package/build/utils/assertion-verify/api-shared-lints.js +1 -1
- package/build/utils/assertion-verify/metrics.js +85 -36
- package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
- package/build/utils/assertion-verify/ui-lints.js +32 -0
- package/build/utils/branchDiff.d.ts +63 -31
- package/build/utils/branchDiff.js +242 -94
- package/build/utils/containedPath.d.ts +18 -0
- package/build/utils/containedPath.js +73 -0
- package/build/utils/dartRouteExtractor.d.ts +18 -34
- package/build/utils/dartRouteExtractor.js +101 -173
- package/build/utils/featureFlags.d.ts +12 -0
- package/build/utils/featureFlags.js +14 -0
- package/build/utils/frontendSelectors.d.ts +48 -27
- package/build/utils/frontendSelectors.js +241 -80
- package/build/utils/pathMatching.d.ts +2 -4
- package/build/utils/pathMatching.js +2 -4
- package/build/utils/planMatchKeys.d.ts +38 -47
- package/build/utils/planMatchKeys.js +143 -81
- package/build/utils/rebaselineSnapshots.d.ts +24 -0
- package/build/utils/rebaselineSnapshots.js +65 -0
- package/build/utils/removedUiElements.d.ts +22 -0
- package/build/utils/removedUiElements.js +106 -0
- package/build/utils/reportVerification.d.ts +2 -6
- package/build/utils/reportVerification.js +61 -2
- package/build/utils/screenRoutes.d.ts +66 -0
- package/build/utils/screenRoutes.js +727 -0
- package/build/utils/sourceRouteExtractor.js +320 -112
- package/build/utils/testFileClassification.d.ts +11 -2
- package/build/utils/testFileClassification.js +44 -2
- package/build/utils/testFixtures.d.ts +5 -0
- package/build/utils/testFixtures.js +13 -0
- package/build/utils/utils.d.ts +0 -1
- package/build/utils/utils.js +0 -11
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +12 -12
- package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
- 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/recorder/recorderSignalProcessor.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- 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/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/plugin.json +6 -0
- package/plugin/prompts/declaring-a-plan.md +20 -0
- package/plugin/prompts/generate-tests/context-fetching.md +4 -0
- package/plugin/prompts/generate-tests/execution-plan.md +63 -0
- package/plugin/prompts/generate-tests/generation.md +108 -0
- package/plugin/prompts/generate-tests/path-parameters.md +1 -0
- package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
- package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
- package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
- package/plugin/prompts/plan-tests.md +42 -0
- package/plugin/prompts/testbot-task1.md +82 -0
- package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
- package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
- package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
- package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
- package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
- package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
- package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
- package/build/recommendation/budgeters/shared.d.ts +0 -32
- package/build/recommendation/budgeters/shared.js +0 -246
- package/build/recommendation/discriminators.d.ts +0 -37
- package/build/recommendation/discriminators.js +0 -379
- package/build/recommendation/diversity.d.ts +0 -47
- package/build/recommendation/diversity.js +0 -101
- package/build/recommendation/planRanker.d.ts +0 -65
- package/build/recommendation/planRanker.js +0 -83
- package/build/recommendation/testFixtures.d.ts +0 -25
- package/build/recommendation/testFixtures.js +0 -45
- package/build/types/FrontendIntegration.d.ts +0 -28
- package/build/types/FrontendIntegration.js +0 -22
- package/build/types/Recommendation.d.ts +0 -146
- package/build/types/Recommendation.js +0 -74
- package/build/utils/changedRoutes.d.ts +0 -29
- package/build/utils/changedRoutes.js +0 -87
- package/build/utils/frontendIntegration.d.ts +0 -9
- package/build/utils/frontendIntegration.js +0 -243
- package/build/utils/importerHop.d.ts +0 -135
- package/build/utils/importerHop.js +0 -489
- package/build/utils/pathAffinityClassification.d.ts +0 -49
- package/build/utils/pathAffinityClassification.js +0 -180
- package/build/utils/pythonMountPrefixes.d.ts +0 -25
- package/build/utils/pythonMountPrefixes.js +0 -347
- package/build/utils/repoScanner.d.ts +0 -34
- package/build/utils/repoScanner.js +0 -300
- package/build/utils/routeParsers.d.ts +0 -95
- package/build/utils/routeParsers.js +0 -951
- package/build/utils/scenarioDrafting.d.ts +0 -92
- package/build/utils/scenarioDrafting.js +0 -951
- package/build/utils/subjectEndpoints.d.ts +0 -19
- package/build/utils/subjectEndpoints.js +0 -98
- package/build/utils/uiPageEnumerator.d.ts +0 -172
- package/build/utils/uiPageEnumerator.js +0 -474
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ScenarioStep } from "../types/RepositoryAnalysis.js";
|
|
2
|
+
/** Build a minimal ScenarioStep. Every field a caller could disagree about is a
|
|
3
|
+
* parameter: a default here is a value the test did not choose and cannot be
|
|
4
|
+
* read off the call. */
|
|
5
|
+
export declare function mkStep(method: string, path: string, description: string): ScenarioStep;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Build a minimal ScenarioStep. Every field a caller could disagree about is a
|
|
2
|
+
* parameter: a default here is a value the test did not choose and cannot be
|
|
3
|
+
* read off the call. */
|
|
4
|
+
export function mkStep(method, path, description) {
|
|
5
|
+
return {
|
|
6
|
+
order: 0,
|
|
7
|
+
method,
|
|
8
|
+
path,
|
|
9
|
+
description,
|
|
10
|
+
interactionType: "success",
|
|
11
|
+
expectedStatusCode: 200,
|
|
12
|
+
};
|
|
13
|
+
}
|
package/build/utils/utils.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
-
export declare function readDiffFile(diffFilePath: string | undefined): string | undefined;
|
|
3
2
|
export declare function toolError(message: string): CallToolResult;
|
|
4
3
|
/**
|
|
5
4
|
* Build a successful tool result whose payload is delivered on BOTH MCP result
|
package/build/utils/utils.js
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import * as fs from "fs";
|
|
3
2
|
import { isTestbotEnabled } from "./featureFlags.js";
|
|
4
3
|
import { logger } from "./logger.js";
|
|
5
|
-
export function readDiffFile(diffFilePath) {
|
|
6
|
-
if (!diffFilePath)
|
|
7
|
-
return undefined;
|
|
8
|
-
try {
|
|
9
|
-
return fs.readFileSync(diffFilePath, "utf-8");
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
4
|
export function toolError(message) {
|
|
16
5
|
return {
|
|
17
6
|
content: [{ type: "text", text: message }],
|
|
@@ -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.44";
|
|
2
|
+
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.44";
|
|
3
|
+
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.44";
|
|
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.44";
|
|
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`).
|
|
@@ -55,7 +55,7 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
55
55
|
}, "strict", z.ZodTypeAny, {
|
|
56
56
|
authHeader?: string | undefined;
|
|
57
57
|
authScheme?: string | undefined;
|
|
58
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
58
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
59
59
|
baseUrl?: string | undefined;
|
|
60
60
|
schemaPath?: string | undefined;
|
|
61
61
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -67,7 +67,7 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
67
67
|
}, {
|
|
68
68
|
authHeader?: string | undefined;
|
|
69
69
|
authScheme?: string | undefined;
|
|
70
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
70
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
71
71
|
baseUrl?: string | undefined;
|
|
72
72
|
schemaPath?: string | undefined;
|
|
73
73
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -146,7 +146,7 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
146
146
|
api?: {
|
|
147
147
|
authHeader?: string | undefined;
|
|
148
148
|
authScheme?: string | undefined;
|
|
149
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
149
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
150
150
|
baseUrl?: string | undefined;
|
|
151
151
|
schemaPath?: string | undefined;
|
|
152
152
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -181,7 +181,7 @@ export declare const serviceSchema: z.ZodObject<{
|
|
|
181
181
|
api?: {
|
|
182
182
|
authHeader?: string | undefined;
|
|
183
183
|
authScheme?: string | undefined;
|
|
184
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
184
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
185
185
|
baseUrl?: string | undefined;
|
|
186
186
|
schemaPath?: string | undefined;
|
|
187
187
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -292,7 +292,7 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
292
292
|
}, "strict", z.ZodTypeAny, {
|
|
293
293
|
authHeader?: string | undefined;
|
|
294
294
|
authScheme?: string | undefined;
|
|
295
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
295
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
296
296
|
baseUrl?: string | undefined;
|
|
297
297
|
schemaPath?: string | undefined;
|
|
298
298
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -304,7 +304,7 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
304
304
|
}, {
|
|
305
305
|
authHeader?: string | undefined;
|
|
306
306
|
authScheme?: string | undefined;
|
|
307
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
307
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
308
308
|
baseUrl?: string | undefined;
|
|
309
309
|
schemaPath?: string | undefined;
|
|
310
310
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -383,7 +383,7 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
383
383
|
api?: {
|
|
384
384
|
authHeader?: string | undefined;
|
|
385
385
|
authScheme?: string | undefined;
|
|
386
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
386
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
387
387
|
baseUrl?: string | undefined;
|
|
388
388
|
schemaPath?: string | undefined;
|
|
389
389
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -418,7 +418,7 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
418
418
|
api?: {
|
|
419
419
|
authHeader?: string | undefined;
|
|
420
420
|
authScheme?: string | undefined;
|
|
421
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
421
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
422
422
|
baseUrl?: string | undefined;
|
|
423
423
|
schemaPath?: string | undefined;
|
|
424
424
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -466,7 +466,7 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
466
466
|
api?: {
|
|
467
467
|
authHeader?: string | undefined;
|
|
468
468
|
authScheme?: string | undefined;
|
|
469
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
469
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
470
470
|
baseUrl?: string | undefined;
|
|
471
471
|
schemaPath?: string | undefined;
|
|
472
472
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -514,7 +514,7 @@ export declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
514
514
|
api?: {
|
|
515
515
|
authHeader?: string | undefined;
|
|
516
516
|
authScheme?: string | undefined;
|
|
517
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
517
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
518
518
|
baseUrl?: string | undefined;
|
|
519
519
|
schemaPath?: string | undefined;
|
|
520
520
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -571,7 +571,7 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
|
|
|
571
571
|
api?: {
|
|
572
572
|
authHeader?: string | undefined;
|
|
573
573
|
authScheme?: string | undefined;
|
|
574
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
574
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
575
575
|
baseUrl?: string | undefined;
|
|
576
576
|
schemaPath?: string | undefined;
|
|
577
577
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -619,7 +619,7 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
|
|
|
619
619
|
api?: {
|
|
620
620
|
authHeader?: string | undefined;
|
|
621
621
|
authScheme?: string | undefined;
|
|
622
|
-
authType?: "none" | "cookie" | "bearer" | "basic" | "oauth" | "apiKey" | "session" |
|
|
622
|
+
authType?: "none" | "cookie" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "session" | undefined;
|
|
623
623
|
baseUrl?: string | undefined;
|
|
624
624
|
schemaPath?: string | undefined;
|
|
625
625
|
defaultQueryParams?: Record<string, string> | undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var assertHiddenTool_exports = {};
|
|
20
|
+
__export(assertHiddenTool_exports, {
|
|
21
|
+
assertHiddenMcpTool: () => assertHiddenMcpTool,
|
|
22
|
+
assertHiddenToolSchema: () => assertHiddenToolSchema
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(assertHiddenTool_exports);
|
|
25
|
+
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
26
|
+
var import_tool = require("../sdk/tool");
|
|
27
|
+
const assertHiddenToolSchema = {
|
|
28
|
+
name: "browser_assert_hidden",
|
|
29
|
+
title: "Assert element absent or hidden",
|
|
30
|
+
description: [
|
|
31
|
+
"Assert that an element is absent from the page or not visible \u2014 the recorded form of expect(locator).toBeHidden().",
|
|
32
|
+
"Use it for an element a change removed, after navigating to the page that used to show it.",
|
|
33
|
+
"Takes exactly one target: testId, or role + name, or text, or attribute + value. There is no snapshot ref (a removed element has none).",
|
|
34
|
+
"role + name and text match EXACTLY (case-sensitive, whole string): the values come verbatim from the removed source, not from snapshot text.",
|
|
35
|
+
"The tool refuses to record on a page that is not a loaded http(s) application page (about:blank, an error page, a data: URL),",
|
|
36
|
+
"where every absence would be vacuously true. Only a passing assertion is recorded in the trace."
|
|
37
|
+
].join(" "),
|
|
38
|
+
inputSchema: import_mcpBundle.z.object({
|
|
39
|
+
element: import_mcpBundle.z.string().describe("Human-readable description of the element that must be absent or not visible"),
|
|
40
|
+
testId: import_mcpBundle.z.string().optional().describe("The data-testid of the element that must be absent or not visible. Pass exactly one of testId, role (+ name), text, or attribute (+ value)."),
|
|
41
|
+
role: import_mcpBundle.z.string().optional().describe('ARIA role of the element that must be absent or not visible (button, link, heading, textbox, \u2026). Requires "name".'),
|
|
42
|
+
name: import_mcpBundle.z.string().optional().describe('Accessible name that goes with "role"; matched exactly (case-sensitive, whole string).'),
|
|
43
|
+
text: import_mcpBundle.z.string().optional().describe("Exact visible text of the element that must be absent or not visible (whole string, case-sensitive)."),
|
|
44
|
+
attribute: import_mcpBundle.z.string().optional().describe("A `data-*` attribute name (e.g. `data-cy`, `data-qa`, `data-test-id`) identifying the removed element; pair with `value`. Use `testId` instead when the attribute is `data-testid`."),
|
|
45
|
+
value: import_mcpBundle.z.string().optional().describe("The literal value of `attribute`.")
|
|
46
|
+
}),
|
|
47
|
+
type: "readOnly"
|
|
48
|
+
};
|
|
49
|
+
function assertHiddenMcpTool() {
|
|
50
|
+
return (0, import_tool.toMcpTool)(assertHiddenToolSchema);
|
|
51
|
+
}
|
|
52
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
53
|
+
0 && (module.exports = {
|
|
54
|
+
assertHiddenMcpTool,
|
|
55
|
+
assertHiddenToolSchema
|
|
56
|
+
});
|
|
@@ -35,7 +35,8 @@ const assertToolSchema = {
|
|
|
35
35
|
'"checked" checks checkbox/radio state, "disabled" checks disabled/enabled state,',
|
|
36
36
|
'"visible" checks the element is visible,',
|
|
37
37
|
`"count" counts elements sharing the referenced element's role (or exact text)`,
|
|
38
|
-
"across the page, e.g. ref one option of an open dropdown to assert the number of options."
|
|
38
|
+
"across the page, e.g. ref one option of an open dropdown to assert the number of options.",
|
|
39
|
+
"To assert an element is absent or not visible, use `browser_assert_hidden`."
|
|
39
40
|
].join(" "),
|
|
40
41
|
inputSchema: import_mcpBundle.z.object({
|
|
41
42
|
type: import_mcpBundle.z.enum(["text", "value", "checked", "disabled", "visible", "count"]).describe(`Type of assertion: "text" for text content, "value" for input field value, "checked" for checkbox/radio state, "disabled" for disabled/enabled state ("false" asserts the element is enabled), "visible" for element visibility, "count" for the number of elements with the referenced element's role`),
|
|
@@ -378,6 +378,16 @@ async function performClientAction(page, actionInContext) {
|
|
|
378
378
|
throw new Error(`assertDisabled failed: expected disabled=${action.disabled}, got ${disabled}`);
|
|
379
379
|
return;
|
|
380
380
|
}
|
|
381
|
+
case "assertHidden": {
|
|
382
|
+
try {
|
|
383
|
+
await locator.waitFor({ ...opts, state: "hidden" });
|
|
384
|
+
} catch (e) {
|
|
385
|
+
if (e?.name === "TimeoutError")
|
|
386
|
+
throw new Error(`assertHidden failed: element is still visible (${e.message})`);
|
|
387
|
+
throw e;
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
381
391
|
case "assertCount": {
|
|
382
392
|
await locator.first().waitFor(opts).catch(() => {
|
|
383
393
|
});
|
|
@@ -63,6 +63,7 @@ const ACTION_NAMES = /* @__PURE__ */ new Set([
|
|
|
63
63
|
"assertVisible",
|
|
64
64
|
"assertDisabled",
|
|
65
65
|
"assertCount",
|
|
66
|
+
"assertHidden",
|
|
66
67
|
"assertSnapshot",
|
|
67
68
|
"visualSnapshot",
|
|
68
69
|
"assertTableCell",
|
|
@@ -138,7 +139,9 @@ function parseLine(line) {
|
|
|
138
139
|
}
|
|
139
140
|
if (!entry.name || !isActionName(entry.name))
|
|
140
141
|
return null;
|
|
141
|
-
const { pageGuid, pageAlias, framePath, locator
|
|
142
|
+
const { pageGuid, pageAlias, framePath, locator, ...actionFields } = entry;
|
|
143
|
+
if (entry.name === "assertHidden" && locator && typeof locator === "object")
|
|
144
|
+
actionFields.locator = locator;
|
|
142
145
|
const frame = {
|
|
143
146
|
pageGuid: pageGuid ?? "",
|
|
144
147
|
pageAlias: pageAlias ?? "page",
|
|
@@ -45,6 +45,7 @@ var import_log = require("../log");
|
|
|
45
45
|
var import_skyRampExport = require("../test/skyRampExport");
|
|
46
46
|
var import_exportTool = require("./exportTool");
|
|
47
47
|
var import_assertTool = require("./assertTool");
|
|
48
|
+
var import_assertHiddenTool = require("./assertHiddenTool");
|
|
48
49
|
var import_assertApiRequestTool = require("./assertApiRequestTool");
|
|
49
50
|
var import_loadTraceTool = require("./loadTraceTool");
|
|
50
51
|
var import_skyRampImport = require("./skyRampImport");
|
|
@@ -186,7 +187,7 @@ class TraceRecordingBackend {
|
|
|
186
187
|
}
|
|
187
188
|
async listTools() {
|
|
188
189
|
const browserTools = await this._browserBackend.listTools();
|
|
189
|
-
return [...browserTools, (0, import_exportTool.exportZipMcpTool)(), (0, import_assertTool.assertMcpTool)(), (0, import_assertApiRequestTool.assertApiRequestMcpTool)(), (0, import_loadTraceTool.loadTraceMcpTool)(), (0, import_mouseActionTool.mouseActionMcpTool)(), (0, import_tableAssertTool.tableAssertMcpTool)(), (0, import_visualSnapshotTool.visualSnapshotMcpTool)(), (0, import_gojsTool.gojsAddNodeMcpTool)(), (0, import_gojsTool.gojsLinkMcpTool)()];
|
|
190
|
+
return [...browserTools, (0, import_exportTool.exportZipMcpTool)(), (0, import_assertTool.assertMcpTool)(), (0, import_assertHiddenTool.assertHiddenMcpTool)(), (0, import_assertApiRequestTool.assertApiRequestMcpTool)(), (0, import_loadTraceTool.loadTraceMcpTool)(), (0, import_mouseActionTool.mouseActionMcpTool)(), (0, import_tableAssertTool.tableAssertMcpTool)(), (0, import_visualSnapshotTool.visualSnapshotMcpTool)(), (0, import_gojsTool.gojsAddNodeMcpTool)(), (0, import_gojsTool.gojsLinkMcpTool)()];
|
|
190
191
|
}
|
|
191
192
|
async callTool(name, args, progress) {
|
|
192
193
|
if (!this._initialized)
|
|
@@ -238,6 +239,10 @@ class TraceRecordingBackend {
|
|
|
238
239
|
const parsed = import_assertTool.assertToolSchema.inputSchema.parse(args || {});
|
|
239
240
|
return this._handleAssert(parsed);
|
|
240
241
|
}
|
|
242
|
+
if (name === import_assertHiddenTool.assertHiddenToolSchema.name) {
|
|
243
|
+
const parsed = import_assertHiddenTool.assertHiddenToolSchema.inputSchema.parse(args || {});
|
|
244
|
+
return this._handleAssertHidden(parsed, Date.now());
|
|
245
|
+
}
|
|
241
246
|
if (name === import_assertApiRequestTool.assertApiRequestSchema.name) {
|
|
242
247
|
this._trackedActions.push({
|
|
243
248
|
toolName: "browser_assert_api_request",
|
|
@@ -855,6 +860,14 @@ Continue recording with browser_* tools, then call skyramp_export_zip to write t
|
|
|
855
860
|
return { toolName: "browser_assert", code: `assertChecked:${a.selector}:${!!a.checked}`, args: { type: "checked", selector: a.selector, checked: !!a.checked } };
|
|
856
861
|
case "assertVisible":
|
|
857
862
|
return { toolName: "browser_assert", code: `assertVisible:${a.selector}`, args: { type: "visible", selector: a.selector } };
|
|
863
|
+
case "assertHidden": {
|
|
864
|
+
if (typeof a.selector !== "string" || !a.selector)
|
|
865
|
+
return null;
|
|
866
|
+
const args = { type: "hidden", selector: a.selector };
|
|
867
|
+
if (a.locator && typeof a.locator === "object")
|
|
868
|
+
args.locator = a.locator;
|
|
869
|
+
return { toolName: "browser_assert", code: `assertHidden:${a.selector}`, args };
|
|
870
|
+
}
|
|
858
871
|
case "assertDisabled":
|
|
859
872
|
if (typeof a.disabled !== "boolean")
|
|
860
873
|
return null;
|
|
@@ -1365,6 +1378,9 @@ Role "${refRole}" is not countable \u2014 reference an exemplar with a semantic
|
|
|
1365
1378
|
if (!tab) {
|
|
1366
1379
|
return { content: [{ type: "text", text: "### Assertion Failed\nNo active page." }], isError: true };
|
|
1367
1380
|
}
|
|
1381
|
+
const notReady = await this._requireLoadedPage(tab, "domcontentloaded");
|
|
1382
|
+
if (notReady)
|
|
1383
|
+
return notReady;
|
|
1368
1384
|
countSelector = `internal:role=${refRole}`;
|
|
1369
1385
|
let countActual;
|
|
1370
1386
|
try {
|
|
@@ -1520,6 +1536,149 @@ Cell row ${params.row}, column ${params.column} equals "${params.expected}".` :
|
|
|
1520
1536
|
Cell row ${params.row}, column ${params.column} has "${actual}", expected "${params.expected}".` }]
|
|
1521
1537
|
};
|
|
1522
1538
|
}
|
|
1539
|
+
static {
|
|
1540
|
+
/**
|
|
1541
|
+
* How long a still-present browser_assert_hidden target may take to disappear
|
|
1542
|
+
* before the check reports "still visible". Short on purpose: the agent
|
|
1543
|
+
* asserts after the page has settled, so a longer wait only delays a genuine
|
|
1544
|
+
* failure verdict.
|
|
1545
|
+
*/
|
|
1546
|
+
this.HIDDEN_WAIT_TIMEOUT_MS = 2e3;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Page-readiness guard shared by the checks whose live query passes
|
|
1550
|
+
* vacuously on a page that has not loaded the application:
|
|
1551
|
+
* browser_assert_hidden (waitFor({ state: 'hidden' }) resolves instantly for
|
|
1552
|
+
* zero matches) and browser_assert type:"count" (getByRole().count() is 0 on
|
|
1553
|
+
* an empty page).
|
|
1554
|
+
*
|
|
1555
|
+
* What it checks: only the URL scheme. about:blank, chrome-error:// and
|
|
1556
|
+
* data: URLs are refused with an error; an http(s)/file: page is then given
|
|
1557
|
+
* up to 5 s to reach the load event (best-effort — a slow page is not an
|
|
1558
|
+
* error, the caller's own wait/timeout decides).
|
|
1559
|
+
*
|
|
1560
|
+
* What it does NOT catch: a typo'd or stale target on a correctly loaded
|
|
1561
|
+
* page still passes here — this guard cannot tell "removed" from "never
|
|
1562
|
+
* existed". There is deliberately no rendered-element check either: a
|
|
1563
|
+
* text-only page or a canvas/Flutter app has few or no elements under <body>
|
|
1564
|
+
* and is still a loaded page. The defence against a wrong target lives on the
|
|
1565
|
+
* mcp side, which enforces (as a lint) that every absence assertion is paired
|
|
1566
|
+
* with a positive assertion on the same page.
|
|
1567
|
+
*
|
|
1568
|
+
* `loadState` is the readiness bar after the URL check: `hidden` waits for
|
|
1569
|
+
* `load` (an absence verdict wants the page fully settled), `count` only for
|
|
1570
|
+
* `domcontentloaded` — a streaming or long-polling SPA may never fire `load`,
|
|
1571
|
+
* and a live count should not pay up to 5 s of latency for that.
|
|
1572
|
+
*
|
|
1573
|
+
* Returns the isError result to hand back, or null when the page is ready.
|
|
1574
|
+
*/
|
|
1575
|
+
async _requireLoadedPage(tab, loadState = "load") {
|
|
1576
|
+
const url = tab.page.url();
|
|
1577
|
+
if (!/^(?:https?|file):/.test(url))
|
|
1578
|
+
return { content: [{ type: "text", text: `### Error
|
|
1579
|
+
The current page (${url}) is not a loaded application page; navigate to the app before asserting on it.` }], isError: true };
|
|
1580
|
+
await tab.page.waitForLoadState(loadState, { timeout: 5e3 }).catch(() => {
|
|
1581
|
+
});
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* browser_assert_hidden (SKYR-4305): the element is absent from the page or
|
|
1586
|
+
* not visible — the recorded form of expect(locator).toBeHidden().
|
|
1587
|
+
* Targets are named (testId | role+name | text | attribute+value) rather than
|
|
1588
|
+
* referenced by a snapshot ref because the typical subject no longer exists
|
|
1589
|
+
* at all (a change removed it), and the browser_assert path resolves refs via
|
|
1590
|
+
* browser_hover, which fails actionability on a hidden element anyway.
|
|
1591
|
+
*
|
|
1592
|
+
* Exactness: role+name and text are both recorded EXACT (`s` suffix /
|
|
1593
|
+
* exact: true) — one rule for this tool. The other producers
|
|
1594
|
+
* (_codeToLocator, _extractLocatorForRef) record `i` because their values come
|
|
1595
|
+
* from snapshot text, which may be trimmed or partially rendered; here the
|
|
1596
|
+
* values come verbatim from the deleted source, so exact is both safe and
|
|
1597
|
+
* needed: a substring "Session" match would keep failing until every
|
|
1598
|
+
* "Session…" occurrence on the page is gone.
|
|
1599
|
+
*
|
|
1600
|
+
* The locator object is built here next to the selector and recorded on the
|
|
1601
|
+
* tracked action (args.locator), so the exporter never has to regex-parse a
|
|
1602
|
+
* JSON-quoted value back out of the selector string.
|
|
1603
|
+
*
|
|
1604
|
+
* Verification is live: locator.waitFor({ state: 'hidden' }) has exactly
|
|
1605
|
+
* toBeHidden's contract — resolves for zero matches or one non-visible match,
|
|
1606
|
+
* times out while the match is visible, and strict-fails on multiple matches
|
|
1607
|
+
* (no `.first()`, so the recorder can never record a pass the generated
|
|
1608
|
+
* expect(locator).toBeHidden() would throw on). Because zero matches passes,
|
|
1609
|
+
* _requireLoadedPage runs first.
|
|
1610
|
+
*
|
|
1611
|
+
* Recorded as toolName 'browser_assert' with args.type 'hidden', so
|
|
1612
|
+
* assertActionToJsonl and the re-seed path share one shape with the other
|
|
1613
|
+
* assertions.
|
|
1614
|
+
*/
|
|
1615
|
+
async _handleAssertHidden(params, timestamp) {
|
|
1616
|
+
const err = (text) => ({ content: [{ type: "text", text: `### Error
|
|
1617
|
+
${text}` }], isError: true });
|
|
1618
|
+
const given = [params.testId, params.role, params.text, params.attribute].filter((v) => typeof v === "string" && v.trim() !== "");
|
|
1619
|
+
if (given.length !== 1)
|
|
1620
|
+
return err('Pass exactly one target: "testId", or "role" together with "name", or "text", or "attribute" together with "value". There is no "ref" \u2014 a removed element has no snapshot ref.');
|
|
1621
|
+
if (!params.role?.trim() && typeof params.name === "string")
|
|
1622
|
+
return err('"name" only goes with "role" (the ARIA role of the element it names).');
|
|
1623
|
+
if (!params.attribute?.trim() && typeof params.value === "string")
|
|
1624
|
+
return err('"value" only goes with "attribute" (the data-* attribute name it is the value of).');
|
|
1625
|
+
let selector;
|
|
1626
|
+
let locator;
|
|
1627
|
+
if (params.testId?.trim()) {
|
|
1628
|
+
const testId = params.testId.trim();
|
|
1629
|
+
selector = `internal:testid=[data-testid=${JSON.stringify(testId)}s]`;
|
|
1630
|
+
locator = { kind: "test-id", body: testId, options: {} };
|
|
1631
|
+
} else if (params.role?.trim()) {
|
|
1632
|
+
const role = params.role.trim();
|
|
1633
|
+
if (!TraceRecordingBackend.ASSERTABLE_ROLES.has(role))
|
|
1634
|
+
return err(`Role "${role}" is not assertable \u2014 use a semantic role (button, link, heading, textbox, \u2026) or pass "testId"/"text" instead.`);
|
|
1635
|
+
const name = params.name?.trim() ?? "";
|
|
1636
|
+
if (!name)
|
|
1637
|
+
return err('By role, "name" is required \u2014 a bare role would assert that no such element exists anywhere on the page.');
|
|
1638
|
+
selector = `internal:role=${role}[name=${JSON.stringify(name)}s]`;
|
|
1639
|
+
locator = { kind: "role", body: role, options: { attrs: [], exact: true, name } };
|
|
1640
|
+
} else if (params.attribute?.trim()) {
|
|
1641
|
+
const attribute = params.attribute.trim().toLowerCase();
|
|
1642
|
+
if (!/^data-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(attribute))
|
|
1643
|
+
return err(`Attribute "${params.attribute.trim()}" is not a data-* attribute. "attribute" accepts only data-* attributes (data-cy, data-qa, data-test-id, \u2026); target other elements by role + name or text instead.`);
|
|
1644
|
+
const value = params.value?.trim() ?? "";
|
|
1645
|
+
if (!value)
|
|
1646
|
+
return err('By attribute, a non-empty "value" is required \u2014 a bare attribute would assert that no element carries it anywhere on the page.');
|
|
1647
|
+
selector = `[${attribute}=${JSON.stringify(value)}]`;
|
|
1648
|
+
locator = { kind: "default", body: selector, options: {} };
|
|
1649
|
+
} else {
|
|
1650
|
+
const text = params.text.trim();
|
|
1651
|
+
selector = `internal:text=${JSON.stringify(text)}s`;
|
|
1652
|
+
locator = { kind: "text", body: text, options: { exact: true } };
|
|
1653
|
+
}
|
|
1654
|
+
const tab = this._browserBackend.context?.currentTab();
|
|
1655
|
+
if (!tab)
|
|
1656
|
+
return err("No active page.");
|
|
1657
|
+
const notReady = await this._requireLoadedPage(tab);
|
|
1658
|
+
if (notReady)
|
|
1659
|
+
return notReady;
|
|
1660
|
+
let passed = true;
|
|
1661
|
+
try {
|
|
1662
|
+
await tab.page.locator(selector).waitFor({ state: "hidden", timeout: TraceRecordingBackend.HIDDEN_WAIT_TIMEOUT_MS });
|
|
1663
|
+
} catch (e) {
|
|
1664
|
+
if (/strict mode violation/i.test(String(e?.message ?? "")))
|
|
1665
|
+
return err(`Target ${selector} matches multiple elements, so toBeHidden() would fail strict mode. Use a more specific target (a data-testid, or role + exact name). ${String(e.message).split("\n")[0]}`);
|
|
1666
|
+
if (e?.name !== "TimeoutError")
|
|
1667
|
+
return err(`Could not verify absence of "${params.element}": ${e?.message ?? e}`);
|
|
1668
|
+
passed = false;
|
|
1669
|
+
}
|
|
1670
|
+
let details;
|
|
1671
|
+
if (passed) {
|
|
1672
|
+
details = `Hidden assertion passed: "${params.element}" is absent or not visible.`;
|
|
1673
|
+
this._trackedActions.push({ toolName: "browser_assert", args: { type: "hidden", selector, locator }, code: `assertHidden:${selector}`, timestamp });
|
|
1674
|
+
traceDebug(`Assert: assertHidden with selector ${selector}`);
|
|
1675
|
+
} else {
|
|
1676
|
+
const count = await tab.page.locator(selector).count().catch(() => -1);
|
|
1677
|
+
details = `Hidden assertion FAILED: "${params.element}" is still visible (${count < 0 ? "unknown" : count} element(s) match ${selector}).`;
|
|
1678
|
+
}
|
|
1679
|
+
return { content: [{ type: "text", text: `### ${passed ? "Assertion Passed" : "Assertion Failed"}
|
|
1680
|
+
${details}` }] };
|
|
1681
|
+
}
|
|
1523
1682
|
/** Convert a Playwright locator expression to a selector + locator object for JSONL. */
|
|
1524
1683
|
_codeToLocator(expr) {
|
|
1525
1684
|
const testidMatch = expr.match(/getByTestId\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
@@ -444,7 +444,7 @@ function assertActionToJsonl(action, pageGuid, timestamp) {
|
|
|
444
444
|
}
|
|
445
445
|
} else {
|
|
446
446
|
const parts = code.split(":");
|
|
447
|
-
if (assertType === "visible") {
|
|
447
|
+
if (assertType === "visible" || assertType === "hidden") {
|
|
448
448
|
selector = parts.slice(1).join(":");
|
|
449
449
|
} else if (assertType === "text") {
|
|
450
450
|
substring = parts[parts.length - 1] === "true";
|
|
@@ -455,12 +455,14 @@ function assertActionToJsonl(action, pageGuid, timestamp) {
|
|
|
455
455
|
selector = parts.slice(1, parts.length - 1).join(":");
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
|
-
const locator = selectorToLocator(selector);
|
|
458
|
+
const locator = args.locator && typeof args.locator === "object" ? args.locator : selectorToLocator(selector);
|
|
459
459
|
switch (assertType) {
|
|
460
460
|
case "text":
|
|
461
461
|
return JSON.stringify({ name: "assertText", selector, text: expected, substring, locator, ...base });
|
|
462
462
|
case "visible":
|
|
463
463
|
return JSON.stringify({ name: "assertVisible", selector, locator, ...base });
|
|
464
|
+
case "hidden":
|
|
465
|
+
return JSON.stringify({ name: "assertHidden", selector, locator, ...base });
|
|
464
466
|
case "value":
|
|
465
467
|
return JSON.stringify({ name: "assertValue", selector, value: expected, locator, ...base });
|
|
466
468
|
case "checked":
|
|
@@ -50,6 +50,8 @@ class RecorderSignalProcessor {
|
|
|
50
50
|
generateGoto = false;
|
|
51
51
|
else if (signal.isReload)
|
|
52
52
|
generateGoto = !recentReloadUserAction;
|
|
53
|
+
else if (signal.fromBlankPage)
|
|
54
|
+
generateGoto = true;
|
|
53
55
|
else if (!recentNavUserAction)
|
|
54
56
|
generateGoto = true;
|
|
55
57
|
if (generateGoto) {
|
|
@@ -558,7 +558,8 @@ class Recorder extends import_events.default {
|
|
|
558
558
|
const isSameDocument = !!event && !event.newDocument;
|
|
559
559
|
this._prevFrameUrls.set(frame, frame.url());
|
|
560
560
|
const url = userInitiatedNavigationUrl(event) ?? frame.url();
|
|
561
|
-
|
|
561
|
+
const fromBlankPage = isBlankUrl(prevUrl) && !isBlankUrl(url);
|
|
562
|
+
this._signalProcessor.signal(pageAlias, frame, { name: "navigation", url, isReload, isSameDocument, fromBlankPage });
|
|
562
563
|
}
|
|
563
564
|
_onPopup(page, popup) {
|
|
564
565
|
const pageAlias = this._pageAliases.get(page);
|
|
@@ -587,6 +588,9 @@ function userInitiatedNavigationUrl(event) {
|
|
|
587
588
|
request = request.redirectedFrom();
|
|
588
589
|
return request.url();
|
|
589
590
|
}
|
|
591
|
+
function isBlankUrl(url) {
|
|
592
|
+
return !url || url === "about:blank";
|
|
593
|
+
}
|
|
590
594
|
function languageForFile(file) {
|
|
591
595
|
if (file.endsWith(".py"))
|
|
592
596
|
return "python";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{T as x,r,a as C,W as F,j as e,D as z,b as I,c as P,d as A,e as O,f as V}from"./assets/defaultSettingsView-Bwr1eMKC.js";const $=()=>{const[o,c]=r.useState(!1),[n,l]=r.useState(),[h,u]=r.useState(),[p,b]=r.useState(N),[f,j]=r.useState({done:0,total:0}),[k,v]=r.useState(!1),[y,m]=r.useState(null),[T,R]=r.useState(null),[U,E]=r.useState(!1),g=r.useCallback(t=>{const s=new URL(window.location.href);if(!t.length)return;const i=t.item(0),a=URL.createObjectURL(i);s.searchParams.append("trace",a);const d=s.toString();window.history.pushState({},"",d),l(a),u(i.name),v(!1),m(null)},[]);r.useEffect(()=>{const t=async s=>{var a;if(!((a=s.clipboardData)!=null&&a.files.length))return;const i=["application/zip","application/x-zip-compressed"];for(const d of s.clipboardData.files)if(!i.includes(d.type))return;s.preventDefault(),g(s.clipboardData.files)};return document.addEventListener("paste",t),()=>document.removeEventListener("paste",t)}),r.useEffect(()=>{const t=s=>{const{method:i,params:a}=s.data;if(i!=="load"||!((a==null?void 0:a.trace)instanceof Blob))return;const d=new File([a.trace],"trace.zip",{type:"application/zip"}),w=new DataTransfer;w.items.add(d),g(w.files)};return window.addEventListener("message",t),()=>window.removeEventListener("message",t)});const W=r.useCallback(t=>{t.preventDefault(),g(t.dataTransfer.files)},[g]),M=r.useCallback(t=>{t.preventDefault(),t.target.files&&g(t.target.files)},[g]);r.useEffect(()=>{const t=new URL(window.location.href).searchParams,s=t.get("trace");if(c(t.has("isServer")),s!=null&&s.startsWith("file:")){R(s||null);return}if(t.has("isServer")){const i=new URLSearchParams(window.location.search).get("ws"),a=new URL(`../${i}`,window.location.toString());a.protocol=window.location.protocol==="https:"?"wss:":"ws:";const d=new C(new F(a));d.onLoadTraceRequested(async w=>{l(w.traceUrl),v(!1),m(null)}),d.initialize({}).catch(()=>{})}else s&&!s.startsWith("blob:")&&l(s)},[]);const S=r.useCallback(async t=>{const s=new URLSearchParams;s.set("trace",t);const i=await fetch(`contexts?${s.toString()}`);if(!i.ok){const{error:w}=await i.json();return m(w),w}const a=await i.json(),d=new x(t,a);j({done:0,total:0}),m(null),b(d)},[]);r.useEffect(()=>{(async()=>{if(!n){b(N);return}const t=s=>{s.data.method==="progress"&&j(s.data.params)};try{navigator.serviceWorker.addEventListener("message",t),j({done:0,total:1});let s=await S(n);s!=null&&s.includes("please grant permission for Local Network Access")&&(await fetch(n,{method:"HEAD",headers:{"x-pw-serviceworker":"skip"}}),s=await S(n)),s&&(o||l(void 0))}finally{navigator.serviceWorker.removeEventListener("message",t)}})()},[o,n,h,S]);const D=f.done!==f.total&&f.total!==0&&!y;r.useEffect(()=>{if(D){const t=setTimeout(()=>{E(!0)},200);return()=>clearTimeout(t)}else E(!1)},[D]);const L=!!(!o&&!k&&!T&&(!n||y));return e.jsxs("div",{className:"vbox workbench-loader",onDragOver:t=>{t.preventDefault(),t.dataTransfer.types.includes("Files")&&v(!0)},children:[e.jsxs("div",{className:"hbox header",...L?{inert:!0}:{},children:[e.jsx("div",{className:"logo",children:e.jsx("img",{src:"playwright-logo.svg",alt:"Playwright logo"})}),e.jsx("div",{className:"product",children:"Playwright"}),p.title&&e.jsx("div",{className:"title",children:p.title}),e.jsx("div",{className:"spacer"}),e.jsx(z,{icon:"settings-gear",title:"Settings",dialogDataTestId:"settings-toolbar-dialog",children:e.jsx(I,{location:"trace-viewer"})})]}),e.jsx(P,{model:p,inert:L}),T&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{children:"Trace Viewer uses Service Workers to show traces. To view trace:"}),e.jsxs("div",{style:{paddingTop:20},children:[e.jsxs("div",{children:["1. Click ",e.jsx("a",{href:T,children:"here"})," to put your trace into the download shelf"]}),e.jsxs("div",{children:["2. Go to ",e.jsx("a",{href:"https://trace.playwright.dev",children:"trace.playwright.dev"})]}),e.jsx("div",{children:"3. Drop the trace from the download shelf into the page"})]})]}),e.jsx(A,{open:U,isModal:!0,className:"progress-dialog",children:e.jsxs("div",{className:"progress-content",children:[e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Loading Playwright Trace..."}),e.jsx("div",{className:"progress-wrapper",children:e.jsx("div",{className:"inner-progress",style:{width:f.total?100*f.done/f.total+"%":0}})})]})}),L&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{className:"processing-error",role:"alert",children:y}),e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Drop Playwright Trace to load"}),e.jsx("div",{children:"or"}),e.jsx("button",{onClick:()=>{const t=document.createElement("input");t.type="file",t.click(),t.addEventListener("change",s=>M(s))},type:"button",children:"Select file"}),e.jsx("div",{className:"info",children:"Playwright Trace Viewer is a Progressive Web App, it does not send your trace anywhere, it opens it locally."}),e.jsxs("div",{className:"version",children:["Playwright v","1.58.2-skyramp.8.14.
|
|
1
|
+
import{T as x,r,a as C,W as F,j as e,D as z,b as I,c as P,d as A,e as O,f as V}from"./assets/defaultSettingsView-Bwr1eMKC.js";const $=()=>{const[o,c]=r.useState(!1),[n,l]=r.useState(),[h,u]=r.useState(),[p,b]=r.useState(N),[f,j]=r.useState({done:0,total:0}),[k,v]=r.useState(!1),[y,m]=r.useState(null),[T,R]=r.useState(null),[U,E]=r.useState(!1),g=r.useCallback(t=>{const s=new URL(window.location.href);if(!t.length)return;const i=t.item(0),a=URL.createObjectURL(i);s.searchParams.append("trace",a);const d=s.toString();window.history.pushState({},"",d),l(a),u(i.name),v(!1),m(null)},[]);r.useEffect(()=>{const t=async s=>{var a;if(!((a=s.clipboardData)!=null&&a.files.length))return;const i=["application/zip","application/x-zip-compressed"];for(const d of s.clipboardData.files)if(!i.includes(d.type))return;s.preventDefault(),g(s.clipboardData.files)};return document.addEventListener("paste",t),()=>document.removeEventListener("paste",t)}),r.useEffect(()=>{const t=s=>{const{method:i,params:a}=s.data;if(i!=="load"||!((a==null?void 0:a.trace)instanceof Blob))return;const d=new File([a.trace],"trace.zip",{type:"application/zip"}),w=new DataTransfer;w.items.add(d),g(w.files)};return window.addEventListener("message",t),()=>window.removeEventListener("message",t)});const W=r.useCallback(t=>{t.preventDefault(),g(t.dataTransfer.files)},[g]),M=r.useCallback(t=>{t.preventDefault(),t.target.files&&g(t.target.files)},[g]);r.useEffect(()=>{const t=new URL(window.location.href).searchParams,s=t.get("trace");if(c(t.has("isServer")),s!=null&&s.startsWith("file:")){R(s||null);return}if(t.has("isServer")){const i=new URLSearchParams(window.location.search).get("ws"),a=new URL(`../${i}`,window.location.toString());a.protocol=window.location.protocol==="https:"?"wss:":"ws:";const d=new C(new F(a));d.onLoadTraceRequested(async w=>{l(w.traceUrl),v(!1),m(null)}),d.initialize({}).catch(()=>{})}else s&&!s.startsWith("blob:")&&l(s)},[]);const S=r.useCallback(async t=>{const s=new URLSearchParams;s.set("trace",t);const i=await fetch(`contexts?${s.toString()}`);if(!i.ok){const{error:w}=await i.json();return m(w),w}const a=await i.json(),d=new x(t,a);j({done:0,total:0}),m(null),b(d)},[]);r.useEffect(()=>{(async()=>{if(!n){b(N);return}const t=s=>{s.data.method==="progress"&&j(s.data.params)};try{navigator.serviceWorker.addEventListener("message",t),j({done:0,total:1});let s=await S(n);s!=null&&s.includes("please grant permission for Local Network Access")&&(await fetch(n,{method:"HEAD",headers:{"x-pw-serviceworker":"skip"}}),s=await S(n)),s&&(o||l(void 0))}finally{navigator.serviceWorker.removeEventListener("message",t)}})()},[o,n,h,S]);const D=f.done!==f.total&&f.total!==0&&!y;r.useEffect(()=>{if(D){const t=setTimeout(()=>{E(!0)},200);return()=>clearTimeout(t)}else E(!1)},[D]);const L=!!(!o&&!k&&!T&&(!n||y));return e.jsxs("div",{className:"vbox workbench-loader",onDragOver:t=>{t.preventDefault(),t.dataTransfer.types.includes("Files")&&v(!0)},children:[e.jsxs("div",{className:"hbox header",...L?{inert:!0}:{},children:[e.jsx("div",{className:"logo",children:e.jsx("img",{src:"playwright-logo.svg",alt:"Playwright logo"})}),e.jsx("div",{className:"product",children:"Playwright"}),p.title&&e.jsx("div",{className:"title",children:p.title}),e.jsx("div",{className:"spacer"}),e.jsx(z,{icon:"settings-gear",title:"Settings",dialogDataTestId:"settings-toolbar-dialog",children:e.jsx(I,{location:"trace-viewer"})})]}),e.jsx(P,{model:p,inert:L}),T&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{children:"Trace Viewer uses Service Workers to show traces. To view trace:"}),e.jsxs("div",{style:{paddingTop:20},children:[e.jsxs("div",{children:["1. Click ",e.jsx("a",{href:T,children:"here"})," to put your trace into the download shelf"]}),e.jsxs("div",{children:["2. Go to ",e.jsx("a",{href:"https://trace.playwright.dev",children:"trace.playwright.dev"})]}),e.jsx("div",{children:"3. Drop the trace from the download shelf into the page"})]})]}),e.jsx(A,{open:U,isModal:!0,className:"progress-dialog",children:e.jsxs("div",{className:"progress-content",children:[e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Loading Playwright Trace..."}),e.jsx("div",{className:"progress-wrapper",children:e.jsx("div",{className:"inner-progress",style:{width:f.total?100*f.done/f.total+"%":0}})})]})}),L&&e.jsxs("div",{className:"drop-target",children:[e.jsx("div",{className:"processing-error",role:"alert",children:y}),e.jsx("div",{className:"title",role:"heading","aria-level":1,children:"Drop Playwright Trace to load"}),e.jsx("div",{children:"or"}),e.jsx("button",{onClick:()=>{const t=document.createElement("input");t.type="file",t.click(),t.addEventListener("change",s=>M(s))},type:"button",children:"Select file"}),e.jsx("div",{className:"info",children:"Playwright Trace Viewer is a Progressive Web App, it does not send your trace anywhere, it opens it locally."}),e.jsxs("div",{className:"version",children:["Playwright v","1.58.2-skyramp.8.14.9"]})]}),o&&!n&&e.jsx("div",{className:"drop-target",children:e.jsx("div",{className:"title",children:"Select test to see the trace"})}),k&&e.jsx("div",{className:"drop-target",onDragLeave:()=>{v(!1)},onDrop:t=>W(t),children:e.jsx("div",{className:"title",children:"Release to analyse the Playwright Trace"})})]})},N=new x("",[]),q=({traceJson:o})=>{const[c,n]=r.useState(void 0),[l,h]=r.useState(0),u=r.useRef(null);return r.useEffect(()=>(u.current&&clearTimeout(u.current),u.current=setTimeout(async()=>{try{const p=await B(o);n(p)}catch{const p=new x("",[]);n(p)}finally{h(l+1)}},500),()=>{u.current&&clearTimeout(u.current)}),[o,l]),e.jsx(P,{isLive:!0,model:c})};async function B(o){const c=new URLSearchParams;c.set("trace",o);const l=await(await fetch(`contexts?${c.toString()}`)).json();return new x(o,l)}(async()=>{const o=new URLSearchParams(window.location.search);if(O(),window.location.protocol!=="file:"){if(o.get("isUnderTest")==="true"&&await new Promise(h=>setTimeout(h,1e3)),!navigator.serviceWorker)throw new Error(`Service workers are not supported.
|
|
2
2
|
Make sure to serve the Trace Viewer (${window.location}) via HTTPS or localhost.`);navigator.serviceWorker.register("sw.bundle.js"),navigator.serviceWorker.controller||await new Promise(h=>{navigator.serviceWorker.oncontrollerchange=()=>h()}),setInterval(function(){fetch("ping")},1e4)}const c=o.get("trace"),l=(c==null?void 0:c.endsWith(".json"))?e.jsx(q,{traceJson:c}):e.jsx($,{});V.createRoot(document.querySelector("#root")).render(l)})();
|
package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="icon" href="./playwright-logo.svg" type="image/svg+xml">
|
|
8
8
|
<link rel="manifest" href="./manifest.webmanifest">
|
|
9
9
|
<title>Playwright Trace Viewer</title>
|
|
10
|
-
<script type="module" crossorigin src="./index
|
|
10
|
+
<script type="module" crossorigin src="./index.B7KbSQcC.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/defaultSettingsView-Bwr1eMKC.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./defaultSettingsView.7ch9cixO.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./index.BVu7tZDe.css">
|
|
@@ -117,7 +117,7 @@ const _ACTION_NAMES = new Set<string>([
|
|
|
117
117
|
'check', 'click', 'hover', 'closePage', 'fill',
|
|
118
118
|
'navigate', 'openPage', 'press', 'select', 'uncheck',
|
|
119
119
|
'setInputFiles', 'assertText', 'assertValue', 'assertChecked',
|
|
120
|
-
'assertVisible', 'assertDisabled', 'assertCount', 'assertSnapshot', 'visualSnapshot', 'assertTableCell',
|
|
120
|
+
'assertVisible', 'assertDisabled', 'assertCount', 'assertHidden', 'assertSnapshot', 'visualSnapshot', 'assertTableCell',
|
|
121
121
|
'dragTo', 'diagramNodeAdd', 'diagramLinkAdd', 'selectArea', 'tableSnapshot', 'domSnapshot', 'fileChooser',
|
|
122
122
|
'penTool', 'mouse.wheel', 'mouse.move', 'mouse.down', 'mouse.up',
|
|
123
123
|
'modalOpen', 'modalClose', 'waitForTimeout', 'evaluate',
|
|
@@ -63,6 +63,13 @@ export class RecorderSignalProcessor {
|
|
|
63
63
|
generateGoto = false; // SPA pushState/replaceState/popstate: not a real navigation
|
|
64
64
|
else if (signal.isReload)
|
|
65
65
|
generateGoto = !recentReloadUserAction; // Browser reload button: capture only when no recent user action triggered it
|
|
66
|
+
// First navigation off the blank start page: never suppress it. A real recording
|
|
67
|
+
// starts on an empty about:blank, so a click there is a stray focus click and the
|
|
68
|
+
// navigation came from the address bar. A blank page given content programmatically
|
|
69
|
+
// could in principle navigate itself, which would make this goto redundant -- but a
|
|
70
|
+
// redundant goto is a far cheaper failure than a spec with no navigation at all.
|
|
71
|
+
else if (signal.fromBlankPage)
|
|
72
|
+
generateGoto = true;
|
|
66
73
|
else if (!recentNavUserAction)
|
|
67
74
|
generateGoto = true;
|
|
68
75
|
|