@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.
Files changed (229) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -1
  2. package/build/commands/commandLibrary.js +3 -3
  3. package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
  4. package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
  5. package/build/commands/testThisEndpointCommand.js +35 -19
  6. package/build/index.js +9 -3
  7. package/build/playwright/blueprintDigest.d.ts +15 -0
  8. package/build/playwright/blueprintDigest.js +152 -0
  9. package/build/playwright/blueprintDigestStore.d.ts +31 -0
  10. package/build/playwright/blueprintDigestStore.js +117 -0
  11. package/build/playwright/registerPlaywrightTools.js +60 -12
  12. package/build/playwright/traceRecordingPrompt.js +8 -7
  13. package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
  14. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
  15. package/build/prompts/promptAssets.d.ts +20 -0
  16. package/build/prompts/promptAssets.js +55 -0
  17. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
  18. package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
  19. package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
  20. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
  21. package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
  22. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
  23. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
  24. package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
  25. package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
  26. package/build/prompts/test-recommendation/recommendationSections.js +67 -309
  27. package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
  28. package/build/prompts/test-recommendation/recommendationShared.js +49 -155
  29. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
  30. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
  31. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
  32. package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
  33. package/build/prompts/testbot/planDeclarations.d.ts +6 -0
  34. package/build/prompts/testbot/planDeclarations.js +9 -0
  35. package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
  36. package/build/prompts/testbot/testbot-prompts.js +256 -381
  37. package/build/recommendation/answers.d.ts +35 -0
  38. package/build/recommendation/answers.js +96 -0
  39. package/build/recommendation/registerPlan.d.ts +49 -0
  40. package/build/recommendation/registerPlan.js +117 -0
  41. package/build/recommendation/runVerifiers.d.ts +10 -0
  42. package/build/recommendation/runVerifiers.js +49 -0
  43. package/build/recommendation/subjectStep.d.ts +42 -0
  44. package/build/recommendation/subjectStep.js +86 -0
  45. package/build/recommendation/types.d.ts +163 -0
  46. package/build/recommendation/types.js +20 -0
  47. package/build/recommendation/verifierContracts.d.ts +382 -0
  48. package/build/recommendation/verifierContracts.js +263 -0
  49. package/build/recommendation/verifiers/changedFile.d.ts +2 -0
  50. package/build/recommendation/verifiers/changedFile.js +82 -0
  51. package/build/recommendation/verifiers/citedPath.d.ts +12 -0
  52. package/build/recommendation/verifiers/citedPath.js +35 -0
  53. package/build/recommendation/verifiers/coverage.d.ts +7 -0
  54. package/build/recommendation/verifiers/coverage.js +617 -0
  55. package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
  56. package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
  57. package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
  58. package/build/recommendation/verifiers/endpointGrounded.js +128 -0
  59. package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
  60. package/build/recommendation/verifiers/existingCoverage.js +51 -0
  61. package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
  62. package/build/recommendation/verifiers/expectedOutcome.js +105 -0
  63. package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
  64. package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
  65. package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
  66. package/build/recommendation/verifiers/reportedCategory.js +84 -0
  67. package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
  68. package/build/recommendation/verifiers/screenRoute.js +118 -0
  69. package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
  70. package/build/recommendation/verifiers/statedDifference.js +140 -0
  71. package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
  72. package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
  73. package/build/resources/analysisResources.js +1 -114
  74. package/build/resources/testbotResource.js +23 -13
  75. package/build/services/ModularizationService.js +2 -1
  76. package/build/services/TestDiscoveryService.d.ts +3 -72
  77. package/build/services/TestDiscoveryService.js +10 -303
  78. package/build/services/containerEnv.d.ts +1 -1
  79. package/build/services/containerEnv.js +12 -0
  80. package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
  81. package/build/skills/fixTestImportErrorsSkill.js +20 -0
  82. package/build/toolNames.d.ts +1 -0
  83. package/build/toolNames.js +1 -0
  84. package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
  85. package/build/tools/code-refactor/modularizationTool.js +2 -1
  86. package/build/tools/executeSkyrampTestTool.d.ts +80 -0
  87. package/build/tools/executeSkyrampTestTool.js +246 -19
  88. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
  89. package/build/tools/generate-tests/generateContractRestTool.js +3 -3
  90. package/build/tools/generate-tests/planGuard.d.ts +2 -2
  91. package/build/tools/generate-tests/planGuard.js +78 -18
  92. package/build/tools/one-click/oneClickTool.d.ts +0 -1
  93. package/build/tools/one-click/oneClickTool.js +0 -5
  94. package/build/tools/submitReportTool.d.ts +48 -42
  95. package/build/tools/submitReportTool.js +576 -193
  96. package/build/tools/test-management/actionsTool.js +72 -4
  97. package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
  98. package/build/tools/test-management/analyzeChangesTool.js +212 -1219
  99. package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
  100. package/build/tools/test-management/index.d.ts +1 -0
  101. package/build/tools/test-management/index.js +1 -0
  102. package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
  103. package/build/tools/test-management/registerTestPlanTool.js +609 -542
  104. package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
  105. package/build/tools/test-management/resolveScreenTool.js +289 -0
  106. package/build/types/BlueprintDigest.d.ts +34 -0
  107. package/build/types/BlueprintDigest.js +1 -0
  108. package/build/types/RepositoryAnalysis.d.ts +20 -1559
  109. package/build/types/RepositoryAnalysis.js +2 -58
  110. package/build/types/StepMethod.d.ts +40 -0
  111. package/build/types/StepMethod.js +77 -0
  112. package/build/types/TestAnalysis.d.ts +12 -0
  113. package/build/types/TestExecution.d.ts +4 -0
  114. package/build/types/TestRecommendation.d.ts +24 -24
  115. package/build/types/TestRecommendation.js +91 -89
  116. package/build/types/TestbotPromptOptions.d.ts +0 -4
  117. package/build/types/TestbotReport.d.ts +64 -2
  118. package/build/utils/AnalysisStateManager.d.ts +79 -113
  119. package/build/utils/AnalysisStateManager.js +147 -57
  120. package/build/utils/assertion-verify/api-shared-lints.js +1 -1
  121. package/build/utils/assertion-verify/metrics.js +85 -36
  122. package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
  123. package/build/utils/assertion-verify/ui-lints.js +32 -0
  124. package/build/utils/branchDiff.d.ts +63 -31
  125. package/build/utils/branchDiff.js +242 -94
  126. package/build/utils/containedPath.d.ts +18 -0
  127. package/build/utils/containedPath.js +73 -0
  128. package/build/utils/dartRouteExtractor.d.ts +18 -34
  129. package/build/utils/dartRouteExtractor.js +101 -173
  130. package/build/utils/featureFlags.d.ts +12 -0
  131. package/build/utils/featureFlags.js +14 -0
  132. package/build/utils/frontendSelectors.d.ts +48 -27
  133. package/build/utils/frontendSelectors.js +241 -80
  134. package/build/utils/pathMatching.d.ts +2 -4
  135. package/build/utils/pathMatching.js +2 -4
  136. package/build/utils/planMatchKeys.d.ts +38 -47
  137. package/build/utils/planMatchKeys.js +143 -81
  138. package/build/utils/rebaselineSnapshots.d.ts +24 -0
  139. package/build/utils/rebaselineSnapshots.js +65 -0
  140. package/build/utils/removedUiElements.d.ts +22 -0
  141. package/build/utils/removedUiElements.js +106 -0
  142. package/build/utils/reportVerification.d.ts +2 -6
  143. package/build/utils/reportVerification.js +61 -2
  144. package/build/utils/screenRoutes.d.ts +66 -0
  145. package/build/utils/screenRoutes.js +727 -0
  146. package/build/utils/sourceRouteExtractor.js +320 -112
  147. package/build/utils/testFileClassification.d.ts +11 -2
  148. package/build/utils/testFileClassification.js +44 -2
  149. package/build/utils/testFixtures.d.ts +5 -0
  150. package/build/utils/testFixtures.js +13 -0
  151. package/build/utils/utils.d.ts +0 -1
  152. package/build/utils/utils.js +0 -11
  153. package/build/utils/versions.d.ts +3 -3
  154. package/build/utils/versions.js +1 -1
  155. package/build/workspace/workspace.d.ts +12 -12
  156. package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
  157. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
  158. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
  159. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
  160. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
  161. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
  162. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  163. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -0
  164. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
  165. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
  166. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  167. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  168. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  169. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
  170. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
  171. package/node_modules/playwright/package.json +1 -1
  172. package/package.json +4 -3
  173. package/plugin/.claude-plugin/plugin.json +8 -0
  174. package/plugin/plugin.json +6 -0
  175. package/plugin/prompts/declaring-a-plan.md +20 -0
  176. package/plugin/prompts/generate-tests/context-fetching.md +4 -0
  177. package/plugin/prompts/generate-tests/execution-plan.md +63 -0
  178. package/plugin/prompts/generate-tests/generation.md +108 -0
  179. package/plugin/prompts/generate-tests/path-parameters.md +1 -0
  180. package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
  181. package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
  182. package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
  183. package/plugin/prompts/plan-tests.md +42 -0
  184. package/plugin/prompts/testbot-task1.md +82 -0
  185. package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
  186. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
  187. package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
  188. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
  189. package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
  190. package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
  191. package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
  192. package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
  193. package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
  194. package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
  195. package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
  196. package/build/recommendation/budgeters/shared.d.ts +0 -32
  197. package/build/recommendation/budgeters/shared.js +0 -246
  198. package/build/recommendation/discriminators.d.ts +0 -37
  199. package/build/recommendation/discriminators.js +0 -379
  200. package/build/recommendation/diversity.d.ts +0 -47
  201. package/build/recommendation/diversity.js +0 -101
  202. package/build/recommendation/planRanker.d.ts +0 -65
  203. package/build/recommendation/planRanker.js +0 -83
  204. package/build/recommendation/testFixtures.d.ts +0 -25
  205. package/build/recommendation/testFixtures.js +0 -45
  206. package/build/types/FrontendIntegration.d.ts +0 -28
  207. package/build/types/FrontendIntegration.js +0 -22
  208. package/build/types/Recommendation.d.ts +0 -146
  209. package/build/types/Recommendation.js +0 -74
  210. package/build/utils/changedRoutes.d.ts +0 -29
  211. package/build/utils/changedRoutes.js +0 -87
  212. package/build/utils/frontendIntegration.d.ts +0 -9
  213. package/build/utils/frontendIntegration.js +0 -243
  214. package/build/utils/importerHop.d.ts +0 -135
  215. package/build/utils/importerHop.js +0 -489
  216. package/build/utils/pathAffinityClassification.d.ts +0 -49
  217. package/build/utils/pathAffinityClassification.js +0 -180
  218. package/build/utils/pythonMountPrefixes.d.ts +0 -25
  219. package/build/utils/pythonMountPrefixes.js +0 -347
  220. package/build/utils/repoScanner.d.ts +0 -34
  221. package/build/utils/repoScanner.js +0 -300
  222. package/build/utils/routeParsers.d.ts +0 -95
  223. package/build/utils/routeParsers.js +0 -951
  224. package/build/utils/scenarioDrafting.d.ts +0 -92
  225. package/build/utils/scenarioDrafting.js +0 -951
  226. package/build/utils/subjectEndpoints.d.ts +0 -19
  227. package/build/utils/subjectEndpoints.js +0 -98
  228. package/build/utils/uiPageEnumerator.d.ts +0 -172
  229. 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
+ }
@@ -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
@@ -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.40";
2
- export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.40";
3
- export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.40";
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;
@@ -1,4 +1,4 @@
1
- export const SKYRAMP_IMAGE_VERSION = "v1.3.40";
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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" | "token" | undefined;
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: _locator, ...actionFields } = entry;
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":
@@ -130,6 +130,7 @@ const _ACTION_NAMES = /* @__PURE__ */ new Set([
130
130
  "assertVisible",
131
131
  "assertDisabled",
132
132
  "assertCount",
133
+ "assertHidden",
133
134
  "assertSnapshot",
134
135
  "visualSnapshot",
135
136
  "assertTableCell",
@@ -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
- this._signalProcessor.signal(pageAlias, frame, { name: "navigation", url, isReload, isSameDocument });
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.6"]})]}),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.
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)})();
@@ -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.-Id052Lr.js"></script>
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">
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright-core",
3
- "version": "1.58.2-skyramp.8.14.6",
3
+ "version": "1.58.2-skyramp.8.14.9",
4
4
  "description": "A high-level API to automate web browsers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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