@skyramp/mcp 0.4.3 → 0.4.4-rc.2

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 (168) hide show
  1. package/build/index.js +239 -210
  2. package/build/playwright/PlaywrightTraceService.d.ts +1 -1
  3. package/build/playwright/blueprintDigestStore.d.ts +1 -1
  4. package/build/playwright/registerPlaywrightTools.d.ts +1 -1
  5. package/build/playwright/traceRecordingPrompt.d.ts +1 -1
  6. package/build/playwright/traceRecordingPrompt.js +4 -3
  7. package/build/prompts/local-dev/local-dev-prompts.d.ts +1 -1
  8. package/build/prompts/local-dev/local-dev-prompts.js +2 -2
  9. package/build/prompts/orderAssertionRule.d.ts +9 -0
  10. package/build/prompts/orderAssertionRule.js +9 -0
  11. package/build/prompts/startTraceCollectionPrompts.d.ts +1 -1
  12. package/build/prompts/startTraceCollectionPrompts.js +2 -3
  13. package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
  14. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +1 -1
  15. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +2 -2
  16. package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
  17. package/build/prompts/testbot/testbot-prompts.js +9 -9
  18. package/build/recommendation/answers.d.ts +3 -3
  19. package/build/recommendation/answers.js +3 -2
  20. package/build/recommendation/verifierContracts.d.ts +4 -4
  21. package/build/recommendation/verifierContracts.js +6 -5
  22. package/build/recommendation/verifiers/coverage.js +25 -25
  23. package/build/recommendation/verifiers/deliveredMatchesPlan.js +3 -3
  24. package/build/resources/analysisResources.d.ts +1 -1
  25. package/build/resources/analysisResources.js +1 -1
  26. package/build/resources/progressResource.d.ts +1 -1
  27. package/build/resources/sutSetupResource.d.ts +1 -1
  28. package/build/resources/sutSetupResource.js +1 -1
  29. package/build/resources/testbotResource.d.ts +1 -1
  30. package/build/resources/testbotResource.js +1 -1
  31. package/build/services/AnalyticsService.d.ts +1 -1
  32. package/build/services/FleetExecutionService.d.ts +130 -0
  33. package/build/services/FleetExecutionService.js +391 -0
  34. package/build/services/ModularizationService.d.ts +1 -1
  35. package/build/services/TestExecutionService.d.ts +7 -0
  36. package/build/services/TestExecutionService.js +21 -0
  37. package/build/services/TestGenerationService.d.ts +1 -1
  38. package/build/tool-phases.js +2 -0
  39. package/build/tools/auth/loginTool.d.ts +1 -1
  40. package/build/tools/auth/loginTool.js +2 -2
  41. package/build/tools/auth/logoutTool.d.ts +1 -1
  42. package/build/tools/auth/logoutTool.js +2 -2
  43. package/build/tools/code-refactor/alignAssertionsTool.d.ts +1 -1
  44. package/build/tools/code-refactor/alignAssertionsTool.js +1 -1
  45. package/build/tools/code-refactor/assertionSkillTools.d.ts +1 -1
  46. package/build/tools/code-refactor/assertionSkillTools.js +2 -2
  47. package/build/tools/code-refactor/codeReuseTool.d.ts +1 -1
  48. package/build/tools/code-refactor/codeReuseTool.js +2 -2
  49. package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +1 -1
  50. package/build/tools/code-refactor/enhanceAssertionsTool.js +1 -1
  51. package/build/tools/code-refactor/modularizationTool.d.ts +1 -1
  52. package/build/tools/code-refactor/modularizationTool.js +2 -2
  53. package/build/tools/enrichTestWithMocksTool.d.ts +1 -1
  54. package/build/tools/enrichTestWithMocksTool.js +2 -2
  55. package/build/tools/executeFleetTool.d.ts +28 -0
  56. package/build/tools/executeFleetTool.js +107 -0
  57. package/build/tools/executeSkyrampTestTool.d.ts +8 -5
  58. package/build/tools/executeSkyrampTestTool.js +13 -5
  59. package/build/tools/fixErrorTool.d.ts +1 -1
  60. package/build/tools/fixErrorTool.js +1 -1
  61. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +6 -57
  62. package/build/tools/generate-tests/batchMockGenerationTool.js +3 -3
  63. package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +9 -45
  64. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +29 -11
  65. package/build/tools/generate-tests/generateContractRestTool.d.ts +1 -2
  66. package/build/tools/generate-tests/generateContractRestTool.js +1 -1
  67. package/build/tools/generate-tests/generateE2ERestTool.d.ts +2 -2
  68. package/build/tools/generate-tests/generateE2ERestTool.js +10 -2
  69. package/build/tools/generate-tests/generateFuzzRestTool.d.ts +1 -1
  70. package/build/tools/generate-tests/generateFuzzRestTool.js +1 -1
  71. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +1 -2
  72. package/build/tools/generate-tests/generateIntegrationRestTool.js +1 -1
  73. package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -2
  74. package/build/tools/generate-tests/generateLoadRestTool.js +2 -1
  75. package/build/tools/generate-tests/generateMockRestTool.d.ts +1 -2
  76. package/build/tools/generate-tests/generateMockRestTool.js +3 -3
  77. package/build/tools/generate-tests/generateSmokeRestTool.d.ts +1 -1
  78. package/build/tools/generate-tests/generateSmokeRestTool.js +1 -1
  79. package/build/tools/generate-tests/generateUILoadRestTool.d.ts +64 -0
  80. package/build/tools/generate-tests/generateUILoadRestTool.js +512 -0
  81. package/build/tools/generate-tests/generateUIRestTool.d.ts +8 -7
  82. package/build/tools/generate-tests/generateUIRestTool.js +10 -2
  83. package/build/tools/generate-tests/loadTestSchema.d.ts +15 -15
  84. package/build/tools/generate-tests/trace-reuse-guard.d.ts +1 -1
  85. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +1 -2
  86. package/build/tools/generateEnrichedIntegrationTestTool.js +1 -1
  87. package/build/tools/localDevWorkerComposeTool.d.ts +1 -1
  88. package/build/tools/localDevWorkerComposeTool.js +2 -2
  89. package/build/tools/one-click/oneClickTool.d.ts +1 -1
  90. package/build/tools/one-click/oneClickTool.js +1 -1
  91. package/build/tools/preflightMockCheckTool.d.ts +1 -1
  92. package/build/tools/preflightMockCheckTool.js +3 -3
  93. package/build/tools/queryProxyMocksTool.d.ts +1 -1
  94. package/build/tools/queryProxyMocksTool.js +3 -3
  95. package/build/tools/submitReportTool.d.ts +111 -414
  96. package/build/tools/submitReportTool.js +88 -38
  97. package/build/tools/test-management/actionsTool.d.ts +1 -1
  98. package/build/tools/test-management/actionsTool.js +7 -7
  99. package/build/tools/test-management/analyzeChangesTool.d.ts +22 -118
  100. package/build/tools/test-management/analyzeChangesTool.js +4 -4
  101. package/build/tools/test-management/analyzeTestHealthTool.d.ts +1 -1
  102. package/build/tools/test-management/analyzeTestHealthTool.js +4 -4
  103. package/build/tools/test-management/registerTestPlanTool.d.ts +107 -733
  104. package/build/tools/test-management/registerTestPlanTool.js +12 -12
  105. package/build/tools/test-management/resolveScreenTool.d.ts +1 -1
  106. package/build/tools/test-management/resolveScreenTool.js +6 -2
  107. package/build/tools/trace/startTraceCollectionTool.d.ts +1 -1
  108. package/build/tools/trace/startTraceCollectionTool.js +4 -4
  109. package/build/tools/trace/stopTraceCollectionTool.d.ts +1 -1
  110. package/build/tools/trace/stopTraceCollectionTool.js +2 -2
  111. package/build/tools/verifyTestDependenciesTool.d.ts +1 -1
  112. package/build/tools/verifyTestDependenciesTool.js +1 -1
  113. package/build/tools/workspace/initScanWorkspaceTool.d.ts +1 -1
  114. package/build/tools/workspace/initScanWorkspaceTool.js +1 -1
  115. package/build/tools/workspace/initializeWorkspaceTool.d.ts +1 -1
  116. package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
  117. package/build/types/RepositoryAnalysis.d.ts +164 -1170
  118. package/build/types/RepositoryAnalysis.js +12 -10
  119. package/build/types/StepMethod.d.ts +24 -1
  120. package/build/types/StepMethod.js +1 -1
  121. package/build/types/TestTypes.d.ts +41 -133
  122. package/build/types/TestTypes.js +25 -3
  123. package/build/types/TestbotReport.d.ts +4 -1
  124. package/build/utils/assertion-verify/metrics.js +12 -0
  125. package/build/utils/docker.d.ts +29 -7
  126. package/build/utils/docker.js +83 -27
  127. package/build/utils/findUp.d.ts +20 -0
  128. package/build/utils/findUp.js +59 -0
  129. package/build/utils/initAgent.d.ts +3 -3
  130. package/build/utils/initAgent.js +58 -26
  131. package/build/utils/language-helper.js +1 -1
  132. package/build/utils/progress.d.ts +3 -4
  133. package/build/utils/progress.js +4 -4
  134. package/build/utils/repoSdkVersion.d.ts +16 -0
  135. package/build/utils/repoSdkVersion.js +88 -0
  136. package/build/utils/testDependencyPolicy.d.ts +20 -0
  137. package/build/utils/testDependencyPolicy.js +64 -4
  138. package/build/utils/utils-verify/typecheck.js +3 -18
  139. package/build/utils/utils.d.ts +1 -1
  140. package/build/utils/versions.d.ts +3 -3
  141. package/build/utils/versions.js +1 -1
  142. package/build/workspace/workspace.d.ts +117 -538
  143. package/build/workspace/workspace.js +2 -2
  144. package/node_modules/playwright/lib/mcp/skyramp/assertOrder.js +66 -0
  145. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +12 -3
  146. package/node_modules/playwright/lib/mcp/skyramp/common/liveReadError.js +40 -0
  147. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +21 -0
  148. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -1
  149. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +96 -16
  150. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +10 -0
  151. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  152. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +22 -0
  153. package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/elementOrder.js +57 -0
  154. package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
  155. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.uiPTFncY.js → index.0MJW0uG7.js} +1 -1
  156. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  157. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  158. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  159. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +26 -1
  160. package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/elementOrder.ts +83 -0
  161. package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
  162. package/node_modules/playwright/package.json +1 -1
  163. package/package.json +9 -6
  164. package/plugin/prompts/plan-tests.md +9 -17
  165. package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -1
  166. package/plugin/skills/enhance-assertions/reference/ui.md +2 -2
  167. package/plugin/skills/fix-test-import-errors/SKILL.md +1 -1
  168. package/plugin/skills/repair-staged-files/SKILL.md +17 -0
@@ -61,7 +61,7 @@ export const serviceSchema = z
61
61
  * the auth header). Literal values only — no env-var interpolation.
62
62
  * Explicit per-request queryParams always win on a key collision
63
63
  * (SKYR-4050). */
64
- defaultQueryParams: z.record(z.string()).optional(),
64
+ defaultQueryParams: z.record(z.string(), z.string()).optional(),
65
65
  /** Path-scoped overrides layered on top of defaultQueryParams
66
66
  * (SKYR-4127). For a given request, the single most-specific matching
67
67
  * pathPattern wins and its values override the base map key by key;
@@ -84,7 +84,7 @@ export const serviceSchema = z
84
84
  .array(z
85
85
  .object({
86
86
  pathPattern: z.string().min(1),
87
- values: z.record(z.string()),
87
+ values: z.record(z.string(), z.string()),
88
88
  })
89
89
  .strict())
90
90
  .optional(),
@@ -0,0 +1,66 @@
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 assertOrder_exports = {};
20
+ __export(assertOrder_exports, {
21
+ checkOrderArgs: () => checkOrderArgs,
22
+ containmentError: () => containmentError,
23
+ contains: () => contains,
24
+ describeOrderResult: () => describeOrderResult
25
+ });
26
+ module.exports = __toCommonJS(assertOrder_exports);
27
+ var import_utils = require("playwright-core/lib/utils");
28
+ function checkOrderArgs(params) {
29
+ if (!(0, import_utils.isOrderRelation)(params.expected))
30
+ return { error: `For "order" assertions, "expected" must be one of ${import_utils.ORDER_RELATIONS.map((r) => `"${r}"`).join(", ")}.` };
31
+ if (!params.otherRef?.trim())
32
+ return { error: 'For "order" assertions, "otherRef" (the element this one is compared with) is required.' };
33
+ if (params.otherRef === params.ref)
34
+ return { error: '"ref" and "otherRef" are the same element; an order assertion compares two different elements.' };
35
+ return { relation: params.expected, otherElement: params.otherElement?.trim() || `ref ${params.otherRef}` };
36
+ }
37
+ function contains(outer, inner) {
38
+ return outer.x <= inner.x && outer.y <= inner.y && outer.x + outer.width >= inner.x + inner.width && outer.y + outer.height >= inner.y + inner.height;
39
+ }
40
+ function containmentError(a, b, boxA, boxB) {
41
+ if (!contains(boxA, boxB) && !contains(boxB, boxA))
42
+ return void 0;
43
+ const [outer, inner] = contains(boxA, boxB) ? [a, b] : [b, a];
44
+ return `"${outer.element}" contains "${inner.element}", so they have no order. Assert the order of two sibling blocks (e.g. the two columns), not of a block and something inside it.`;
45
+ }
46
+ function describeOrderResult(relation, a, b, boxA, boxB) {
47
+ const result = (0, import_utils.orderHolds)(relation, boxA, boxB);
48
+ const { axis, edgeA, edgeB } = result;
49
+ const round = (v) => Math.round(v * 10) / 10;
50
+ const words = (r) => r.replace("-", " ");
51
+ if (result.ok)
52
+ return { result, details: `Order assertion passed: "${a.element}" (${axis}=${round(edgeA)}) is ${words(relation)} "${b.element}" (${axis}=${round(edgeB)}).` };
53
+ const actual = edgeA === edgeB ? `at the same ${axis} as` : axis === "x" ? edgeA < edgeB ? "left of" : "right of" : edgeA < edgeB ? "above" : "below";
54
+ const lowFirst = relation === "left-of" || relation === "above";
55
+ return {
56
+ result,
57
+ details: `Order assertion FAILED: "${a.element}" (${axis}=${round(edgeA)}) is ${actual} "${b.element}" (${axis}=${round(edgeB)}), expected ${words(relation)}. Nothing was recorded. If a requirement demands ${relation}, add the comparison to the generated test with these locators (JavaScript syntax; write them in the test's language): ${(0, import_utils.asLocator)("javascript", a.selector)} and ${(0, import_utils.asLocator)("javascript", b.selector)} (compare boundingBox().${axis}; the first must be ${lowFirst ? "smaller" : "greater"}).`
58
+ };
59
+ }
60
+ // Annotate the CommonJS export names for ESM import in node:
61
+ 0 && (module.exports = {
62
+ checkOrderArgs,
63
+ containmentError,
64
+ contains,
65
+ describeOrderResult
66
+ });
@@ -18,11 +18,13 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var assertTool_exports = {};
20
20
  __export(assertTool_exports, {
21
+ ORDER_RELATIONS: () => import_utils.ORDER_RELATIONS,
21
22
  assertMcpTool: () => assertMcpTool,
22
23
  assertToolSchema: () => assertToolSchema
23
24
  });
24
25
  module.exports = __toCommonJS(assertTool_exports);
25
26
  var import_mcpBundle = require("playwright-core/lib/mcpBundle");
27
+ var import_utils = require("playwright-core/lib/utils");
26
28
  var import_tool = require("../sdk/tool");
27
29
  const assertToolSchema = {
28
30
  name: "browser_assert",
@@ -38,17 +40,23 @@ const assertToolSchema = {
38
40
  "across the page, e.g. ref one option of an open dropdown to assert the number of options.",
39
41
  '"css" checks a computed style property (property "background-color", expected "rgb(37, 99, 235)"; a 3- or 6-digit #hex colour is accepted and recorded as the computed rgb),',
40
42
  '"class" checks the element carries a class ("expected" = one class name),',
41
- '"attribute" checks an attribute value ("property" = the attribute name, "expected" = its value).',
43
+ '"attribute" checks an attribute value ("property" = the attribute name, "expected" = its value),',
44
+ '"order" checks where the element sits relative to another one; "otherRef" and "expected" are REQUIRED for it ("otherRef" = the other element, "expected" = "left-of", "right-of", "above" or "below").',
45
+ 'It compares where the two elements START: the left edges (x) for left-of/right-of, the top edges (y) for above/below, strictly \u2014 "left-of" means "starts further left", not "wholly to the left", and equal edges fail (side-by-side columns are not "above" each other; full-width stacked blocks are not "left-of" each other).',
46
+ "Pick the axis the PR actually changes. Both elements must be visible, and one containing the other is refused: assert two sibling blocks.",
47
+ 'Use "order" when the PR swaps, moves or reorders blocks (a left/right column swap, a moved sidebar): it fails when the swap is undone.',
42
48
  "Use css/class/attribute for a colour, style or class change the PR makes: a screenshot baseline created on this run cannot fail on it.",
43
49
  'css/class/attribute are read with the pointer moved OFF the element, so a value that exists only on hover is never recorded; unlike "count", they cannot be asserted on an option of a menu that closes when the pointer leaves it.',
44
50
  "To assert an element is absent or not visible, use `browser_assert_hidden`."
45
51
  ].join(" "),
46
52
  inputSchema: import_mcpBundle.z.object({
47
- type: import_mcpBundle.z.enum(["text", "value", "checked", "disabled", "visible", "count", "css", "class", "attribute"]).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, "css" for a computed style property, "class" for a class the element carries, "attribute" for an attribute value`),
53
+ type: import_mcpBundle.z.enum(["text", "value", "checked", "disabled", "visible", "count", "css", "class", "attribute", "order"]).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, "css" for a computed style property, "class" for a class the element carries, "attribute" for an attribute value, "order" for the element's position relative to another element`),
48
54
  ref: import_mcpBundle.z.string().describe('Element reference from the latest snapshot. For "count", reference one exemplar element (e.g. one option in an open dropdown).'),
49
55
  element: import_mcpBundle.z.string().describe("Human-readable description of the element being asserted"),
56
+ otherRef: import_mcpBundle.z.string().optional().describe(`Required for "order": the other element's reference from the latest snapshot. Not used by other types.`),
57
+ otherElement: import_mcpBundle.z.string().optional().describe('For "order": human-readable description of the other element. Not used by other types.'),
50
58
  property: import_mcpBundle.z.string().optional().describe('For "css": the CSS property (e.g. "background-color", "flex-direction"). For "attribute": the attribute name (e.g. "aria-expanded", "data-state"). Not used by other types.'),
51
- expected: import_mcpBundle.z.string().optional().describe('Expected text or value ("text"/"value"), "true"/"false" ("checked"/"disabled", default "true"; "disabled" with "false" asserts enabled), the expected element count ("count"), the computed style value ("css"; a 3- or 6-digit #hex colour is converted to rgb), one class name ("class"), the attribute value ("attribute"). Not used for "visible".'),
59
+ expected: import_mcpBundle.z.string().optional().describe('Expected text or value ("text"/"value"), "true"/"false" ("checked"/"disabled", default "true"; "disabled" with "false" asserts enabled), the expected element count ("count"), the computed style value ("css"; a 3- or 6-digit #hex colour is converted to rgb), one class name ("class"), the attribute value ("attribute"), "left-of" / "right-of" / "above" / "below" ("order"). Not used for "visible".'),
52
60
  substring: import_mcpBundle.z.boolean().optional().default(true).describe("For text assertions: match as substring (true) or exact match (false)")
53
61
  }),
54
62
  type: "readOnly"
@@ -58,6 +66,7 @@ function assertMcpTool() {
58
66
  }
59
67
  // Annotate the CommonJS export names for ESM import in node:
60
68
  0 && (module.exports = {
69
+ ORDER_RELATIONS,
61
70
  assertMcpTool,
62
71
  assertToolSchema
63
72
  });
@@ -0,0 +1,40 @@
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 liveReadError_exports = {};
20
+ __export(liveReadError_exports, {
21
+ liveReadError: () => liveReadError
22
+ });
23
+ module.exports = __toCommonJS(liveReadError_exports);
24
+ function liveReadError(e, o) {
25
+ const err = (text) => ({ content: [{ type: "text", text: `### Error
26
+ ${text}` }], isError: true });
27
+ const msg = String(e?.message ?? e);
28
+ if (/strict mode violation/i.test(msg))
29
+ return err(`Target ${o.selector} matches multiple elements, so ${o.ambiguity}${o.pinFailure ? `, and ${o.pinFailure}` : ""}. Use a more specific target (a data-testid, or role + exact name). ${msg.split("\n")[0]}`);
30
+ if (e?.name === "TimeoutError") {
31
+ if (o.timeoutMs === void 0)
32
+ return void 0;
33
+ return err(`"${o.element}" (${o.selector}) did not resolve within ${o.timeoutMs} ms. Re-snapshot and retry with a fresh ref.`);
34
+ }
35
+ return err(`Could not ${o.failedTo} "${o.element}": ${msg}`);
36
+ }
37
+ // Annotate the CommonJS export names for ESM import in node:
38
+ 0 && (module.exports = {
39
+ liveReadError
40
+ });
@@ -113,6 +113,8 @@ function describeStep(action, index) {
113
113
  detail += ` .${a.className}`;
114
114
  else if (a.name === "assertAttribute")
115
115
  detail += ` [${a.attrName}=${JSON.stringify(a.attrValue)}]`;
116
+ else if (a.name === "assertOrder")
117
+ detail += ` ${a.relation} ${a.otherSelector}`;
116
118
  else if (a.name === "press" && a.key !== void 0)
117
119
  detail += ` ${a.key}`;
118
120
  else if (a.name === "visualSnapshot")
@@ -389,6 +391,25 @@ async function performClientAction(page, actionInContext) {
389
391
  throw new Error(`assertAttribute failed: expected ${action.attrName}=${JSON.stringify(action.attrValue)}, got ${JSON.stringify(actual)}`);
390
392
  return;
391
393
  }
394
+ // SKYR-4544: the comparison the recorder verified and the generated test
395
+ // runs — each box's origin, the left edge (x) for left-of/right-of and the
396
+ // top edge (y) for above/below, compared strictly — after waiting for both
397
+ // elements to be visible, as the generated test does.
398
+ case "assertOrder": {
399
+ const fields = (0, import_utils.parseOrderFields)(action);
400
+ if (!fields)
401
+ throw new Error(`assertOrder failed: trace line needs "otherSelector" and a relation of ${import_utils.ORDER_RELATIONS.join("/")}, got ${JSON.stringify(action.relation)}`);
402
+ const other = page.locator(buildFullSelector(actionInContext.frame.framePath, fields.otherSelector));
403
+ await locator.waitFor({ state: "visible", ...opts });
404
+ await other.waitFor({ state: "visible", ...opts });
405
+ const [boxA, boxB] = [await locator.boundingBox(opts), await other.boundingBox(opts)];
406
+ if (!boxA || !boxB)
407
+ throw new Error(`assertOrder failed: ${!boxA ? fields.selector : fields.otherSelector} has no box on the page`);
408
+ const { ok, axis, edgeA, edgeB } = (0, import_utils.orderHolds)(fields.relation, boxA, boxB);
409
+ if (!ok)
410
+ throw new Error(`assertOrder failed: expected ${fields.selector} ${fields.relation} ${fields.otherSelector}, got ${axis} ${edgeA} vs ${edgeB}`);
411
+ return;
412
+ }
392
413
  case "assertChecked": {
393
414
  const checked = await locator.isChecked(opts);
394
415
  if (checked !== !!action.checked)
@@ -70,6 +70,7 @@ const ACTION_NAMES = /* @__PURE__ */ new Set([
70
70
  "assertCSS",
71
71
  "assertClass",
72
72
  "assertAttribute",
73
+ "assertOrder",
73
74
  "dragTo",
74
75
  "dragAndDrop",
75
76
  "diagramNodeAdd",
@@ -143,7 +144,7 @@ function parseLine(line) {
143
144
  if (!entry.name || !isActionName(entry.name))
144
145
  return null;
145
146
  const { pageGuid, pageAlias, framePath, locator, ...actionFields } = entry;
146
- if (entry.name === "assertHidden" && locator && typeof locator === "object")
147
+ if ((entry.name === "assertHidden" || entry.name === "assertOrder") && locator && typeof locator === "object")
147
148
  actionFields.locator = locator;
148
149
  const frame = {
149
150
  pageGuid: pageGuid ?? "",
@@ -45,6 +45,8 @@ 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_assertOrder = require("./assertOrder");
49
+ var import_liveReadError = require("./common/liveReadError");
48
50
  var import_assertHiddenTool = require("./assertHiddenTool");
49
51
  var import_assertApiRequestTool = require("./assertApiRequestTool");
50
52
  var import_loadTraceTool = require("./loadTraceTool");
@@ -246,7 +248,9 @@ class TraceRecordingBackend {
246
248
  }
247
249
  if (name === import_assertTool.assertToolSchema.name) {
248
250
  const parsed = import_assertTool.assertToolSchema.inputSchema.parse(args || {});
249
- return this._handleAssert(parsed);
251
+ if (parsed.type === "order")
252
+ return this._handleAssertOrder(parsed);
253
+ return this._handleAssert({ ...parsed, type: parsed.type });
250
254
  }
251
255
  if (name === import_assertHiddenTool.assertHiddenToolSchema.name) {
252
256
  const parsed = import_assertHiddenTool.assertHiddenToolSchema.inputSchema.parse(args || {});
@@ -933,6 +937,14 @@ Continue recording with browser_* tools, then call skyramp_export_zip to write t
933
937
  return null;
934
938
  return { toolName: "browser_assert", code: `assertAttribute:${a.selector}:${a.attrName.trim()}:${a.attrValue}`, args: { type: "attribute", selector: a.selector, property: a.attrName.trim(), expected: a.attrValue } };
935
939
  }
940
+ // SKYR-4544: an order assertion needs both ends and its relation; a line
941
+ // missing one is dropped rather than seeded with a hole.
942
+ case "assertOrder": {
943
+ const fields = (0, import_utils.parseOrderFields)(a);
944
+ if (!fields)
945
+ return null;
946
+ return { toolName: "browser_assert", code: `assertOrder:${fields.selector}:${fields.relation}:${fields.otherSelector}`, args: { type: "order", ...fields } };
947
+ }
936
948
  case "assertCount":
937
949
  if (typeof a.count !== "number" || !Number.isInteger(a.count) || a.count < 0)
938
950
  return null;
@@ -1340,8 +1352,9 @@ Linked GoJS nodes ${params.fromKey} \u2192 ${params.toKey} (${params.fromPort}\u
1340
1352
  * proper Playwright selector (testid > role > text), then verify
1341
1353
  * text/value/checked/disabled/visible/count against the snapshot data, and
1342
1354
  * css/class/attribute against the live element (getComputedStyle / classList
1343
- * / getAttribute, read with the pointer moved off the element). Only passing
1344
- * assertions are recorded.
1355
+ * / getAttribute, read with the pointer moved off the element). "order" is
1356
+ * dispatched to _handleAssertOrder instead: two refs, a live boundingBox()
1357
+ * comparison, no pointer-off read. Only passing assertions are recorded.
1345
1358
  */
1346
1359
  async _handleAssert(params) {
1347
1360
  const timestamp = Date.now();
@@ -1471,15 +1484,7 @@ Could not read the un-hovered state of "${params.element}": the pointer could no
1471
1484
  details = passed ? `Attribute assertion passed: "${params.element}" has ${name}="${actual}".` : `Attribute assertion FAILED: "${params.element}" has ${name}=${actual === null ? "(absent)" : `"${actual}"`}, expected "${params.expected}".`;
1472
1485
  }
1473
1486
  } catch (e) {
1474
- const msg = String(e?.message ?? e);
1475
- if (/strict mode violation/i.test(msg))
1476
- return { content: [{ type: "text", text: `### Error
1477
- Target ${parsed.selector} matches multiple elements, so the ${kind} read is ambiguous${unique.pinFailure ? `, and ${unique.pinFailure}` : ""}. Use a more specific target (a data-testid, or role + exact name). ${msg.split("\n")[0]}` }], isError: true };
1478
- if (e?.name === "TimeoutError")
1479
- return { content: [{ type: "text", text: `### Error
1480
- "${params.element}" (${parsed.selector}) did not resolve within ${LIVE_READ_TIMEOUT_MS} ms. Re-snapshot and retry with a fresh ref.` }], isError: true };
1481
- return { content: [{ type: "text", text: `### Error
1482
- Could not read ${kind} of "${params.element}": ${msg}` }], isError: true };
1487
+ return (0, import_liveReadError.liveReadError)(e, { selector: parsed.selector, element: params.element, ambiguity: `the ${kind} read is ambiguous`, failedTo: `read ${kind} of`, pinFailure: unique.pinFailure, timeoutMs: LIVE_READ_TIMEOUT_MS });
1483
1488
  }
1484
1489
  if (passed)
1485
1490
  details += unique.note;
@@ -1624,6 +1629,82 @@ Could not count "${refRole}" elements: ${e.message}` }], isError: true };
1624
1629
  ${details}` }]
1625
1630
  };
1626
1631
  }
1632
+ /**
1633
+ * browser_assert type "order" (SKYR-4544): the ref's element sits left of /
1634
+ * right of / above / below the other ref's element. A swap or a moved block
1635
+ * is a relation between two elements, which no single-element kind can
1636
+ * express, so both ends are resolved through the same durable-locator path
1637
+ * as the other kinds and their live boxes compared on the relation's axis.
1638
+ * The comparison is the one the generated test runs: each boundingBox()'s
1639
+ * origin — the left edge (x) for left-of/right-of, the top edge (y) for
1640
+ * above/below — compared strictly. It says where each element STARTS, not
1641
+ * that one lies wholly beyond the other.
1642
+ */
1643
+ async _handleAssertOrder(params) {
1644
+ const timestamp = Date.now();
1645
+ const error = (text) => ({ content: [{ type: "text", text: `### Error
1646
+ ${text}` }], isError: true });
1647
+ const failed = (text) => ({ content: [{ type: "text", text: `### Assertion Failed
1648
+ ${text}` }], isError: true });
1649
+ const checked = (0, import_assertOrder.checkOrderArgs)(params);
1650
+ if ("error" in checked)
1651
+ return error(checked.error);
1652
+ const { relation, otherElement } = checked;
1653
+ const tab = this._browserBackend.context?.currentTab();
1654
+ if (!tab)
1655
+ return failed("No active page.");
1656
+ const notReady = await this._requireLoadedPage(tab, "domcontentloaded");
1657
+ if (notReady)
1658
+ return notReady;
1659
+ const ends = [];
1660
+ for (const [ref, element] of [[params.ref, params.element], [params.otherRef, otherElement]]) {
1661
+ const resolved = await this._resolveRef(ref, element);
1662
+ if ("error" in resolved)
1663
+ return failed(resolved.error);
1664
+ const lineText = (0, import_snapshotLocators.snapshotLineText)(resolved.refLine) ?? "";
1665
+ const unique = await this._makeUnique(resolved.parsed, { snapLines: resolved.snapLines, ref, element, lineText, exact: false });
1666
+ if (unique.refusal)
1667
+ return unique.refusal;
1668
+ ends.push({ parsed: unique.parsed, note: unique.note, pinFailure: unique.pinFailure, element, selector: unique.parsed.selector });
1669
+ }
1670
+ const [a, b] = ends;
1671
+ if (a.selector === b.selector)
1672
+ return error(`"${a.element}" and "${b.element}" resolve to the same locator (${a.selector}), so their order cannot be asserted. Use refs of two different elements.`);
1673
+ const boxes = [];
1674
+ for (const end of ends) {
1675
+ let box;
1676
+ try {
1677
+ box = await tab.page.locator(end.selector).boundingBox({ timeout: LIVE_READ_TIMEOUT_MS });
1678
+ } catch (e) {
1679
+ return (0, import_liveReadError.liveReadError)(e, { selector: end.selector, element: end.element, ambiguity: "its position is ambiguous", failedTo: "read the position of", pinFailure: end.pinFailure, timeoutMs: LIVE_READ_TIMEOUT_MS });
1680
+ }
1681
+ if (!box)
1682
+ return failed(`"${end.element}" (${end.selector}) has no box on the page (it is not visible), so its position cannot be compared.`);
1683
+ boxes.push(box);
1684
+ }
1685
+ const [boxA, boxB] = boxes;
1686
+ const nested = (0, import_assertOrder.containmentError)(a, b, boxA, boxB);
1687
+ if (nested)
1688
+ return error(nested);
1689
+ const { result, details } = (0, import_assertOrder.describeOrderResult)(relation, a, b, boxA, boxB);
1690
+ if (!result.ok)
1691
+ return { content: [{ type: "text", text: `### Assertion Failed
1692
+ ${details}` }] };
1693
+ const args = {
1694
+ type: "order",
1695
+ ref: params.ref,
1696
+ selector: a.selector,
1697
+ locator: a.parsed.locator,
1698
+ otherRef: params.otherRef,
1699
+ otherSelector: b.selector,
1700
+ otherLocator: b.parsed.locator,
1701
+ relation
1702
+ };
1703
+ this._trackedActions.push({ toolName: "browser_assert", args, code: `assertOrder:${a.selector}:${relation}:${b.selector}`, timestamp, pageAlias: this._currentPageAlias });
1704
+ traceDebug(`Assert: assertOrder ${a.selector} ${relation} ${b.selector}`);
1705
+ return { content: [{ type: "text", text: `### Assertion Passed
1706
+ ${details}${a.note}${b.note}` }] };
1707
+ }
1627
1708
  /**
1628
1709
  * Handle browser_assert_table_cell by resolving the cell from (row, column),
1629
1710
  * reading its value directly from the page, and recording an assertTableCell
@@ -1838,10 +1919,9 @@ ${text}` }], isError: true });
1838
1919
  try {
1839
1920
  await tab.page.locator(selector).waitFor({ state: "hidden", timeout: TraceRecordingBackend.HIDDEN_WAIT_TIMEOUT_MS });
1840
1921
  } catch (e) {
1841
- if (/strict mode violation/i.test(String(e?.message ?? "")))
1842
- 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]}`);
1843
- if (e?.name !== "TimeoutError")
1844
- return err(`Could not verify absence of "${params.element}": ${e?.message ?? e}`);
1922
+ const error = (0, import_liveReadError.liveReadError)(e, { selector, element: params.element, ambiguity: "toBeHidden() would fail strict mode", failedTo: "verify absence of" });
1923
+ if (error)
1924
+ return error;
1845
1925
  passed = false;
1846
1926
  }
1847
1927
  let details;
@@ -509,6 +509,16 @@ function assertActionToJsonl(action, pageGuid, timestamp) {
509
509
  return null;
510
510
  return JSON.stringify({ name: "assertAttribute", selector, attrName, attrValue: expected, locator, ...base });
511
511
  }
512
+ // SKYR-4544: where the element sits relative to another one — a
513
+ // boundingBox() comparison downstream. Both ends and the relation are
514
+ // needed; a line missing any of them is dropped (reported as skipped).
515
+ case "order": {
516
+ const fields = (0, import_utils.parseOrderFields)({ ...args, selector });
517
+ if (!fields)
518
+ return null;
519
+ const otherLocator = fields.otherLocator ?? selectorToLocator(fields.otherSelector);
520
+ return JSON.stringify({ name: "assertOrder", selector, otherSelector: fields.otherSelector, relation: fields.relation, locator, otherLocator, ...base });
521
+ }
512
522
  default:
513
523
  return null;
514
524
  }
@@ -137,6 +137,7 @@ const _ACTION_NAMES = /* @__PURE__ */ new Set([
137
137
  "assertCSS",
138
138
  "assertClass",
139
139
  "assertAttribute",
140
+ "assertOrder",
140
141
  "dragTo",
141
142
  "diagramNodeAdd",
142
143
  "diagramLinkAdd",
@@ -196,6 +196,28 @@ async function performActionImpl(progress, mainFrame, actionInContext) {
196
196
  });
197
197
  return;
198
198
  }
199
+ if (raw.name === "assertOrder") {
200
+ const fields = (0, import_utils.parseOrderFields)(raw);
201
+ if (!fields)
202
+ throw new Error(`assertOrder failed: trace line needs "otherSelector" and a relation of ${import_utils.ORDER_RELATIONS.join("/")}, got ${JSON.stringify(raw.relation)}`);
203
+ const otherSelector = (0, import_recorderUtils.buildFullSelector)(actionInContext.frame.framePath, fields.otherSelector);
204
+ const box = async (s) => {
205
+ const handle = await mainFrame.waitForSelector(progress, s, false, { strict: true, state: "visible" });
206
+ try {
207
+ const rect = await handle?.boundingBox();
208
+ if (!rect)
209
+ throw new Error(`assertOrder: ${s} has no box on the page`);
210
+ return rect;
211
+ } finally {
212
+ handle?.dispose();
213
+ }
214
+ };
215
+ const [a, b] = [await box(selector), await box(otherSelector)];
216
+ const { ok, axis, edgeA, edgeB } = (0, import_utils.orderHolds)(fields.relation, a, b);
217
+ if (!ok)
218
+ throw new Error(`assertOrder failed: expected ${selector} ${fields.relation} ${otherSelector}, got ${axis} ${edgeA} vs ${edgeB}`);
219
+ return;
220
+ }
199
221
  throw new Error("Internal error: unexpected action " + action.name);
200
222
  }
201
223
  function toClickOptions(action) {
@@ -0,0 +1,57 @@
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 elementOrder_exports = {};
20
+ __export(elementOrder_exports, {
21
+ ORDER_RELATIONS: () => ORDER_RELATIONS,
22
+ isOrderRelation: () => isOrderRelation,
23
+ orderHolds: () => orderHolds,
24
+ parseOrderFields: () => parseOrderFields
25
+ });
26
+ module.exports = __toCommonJS(elementOrder_exports);
27
+ const ORDER_RELATIONS = ["left-of", "right-of", "above", "below"];
28
+ function isOrderRelation(value) {
29
+ return typeof value === "string" && ORDER_RELATIONS.includes(value);
30
+ }
31
+ function orderHolds(relation, a, b) {
32
+ const axis = relation === "left-of" || relation === "right-of" ? "x" : "y";
33
+ const edgeA = a[axis];
34
+ const edgeB = b[axis];
35
+ const ok = relation === "left-of" || relation === "above" ? edgeA < edgeB : edgeA > edgeB;
36
+ return { ok, axis, edgeA, edgeB };
37
+ }
38
+ function parseOrderFields(raw) {
39
+ if (!raw || typeof raw !== "object")
40
+ return null;
41
+ const { selector, otherSelector, relation, locator, otherLocator } = raw;
42
+ if (typeof selector !== "string" || !selector || typeof otherSelector !== "string" || !otherSelector || !isOrderRelation(relation))
43
+ return null;
44
+ const fields = { selector, otherSelector, relation };
45
+ if (locator && typeof locator === "object")
46
+ fields.locator = locator;
47
+ if (otherLocator && typeof otherLocator === "object")
48
+ fields.otherLocator = otherLocator;
49
+ return fields;
50
+ }
51
+ // Annotate the CommonJS export names for ESM import in node:
52
+ 0 && (module.exports = {
53
+ ORDER_RELATIONS,
54
+ isOrderRelation,
55
+ orderHolds,
56
+ parseOrderFields
57
+ });
@@ -25,6 +25,7 @@ module.exports = __toCommonJS(utils_exports);
25
25
  __reExport(utils_exports, require("./utils/isomorphic/ariaSnapshot"), module.exports);
26
26
  __reExport(utils_exports, require("./utils/isomorphic/assert"), module.exports);
27
27
  __reExport(utils_exports, require("./utils/isomorphic/colors"), module.exports);
28
+ __reExport(utils_exports, require("./utils/isomorphic/elementOrder"), module.exports);
28
29
  __reExport(utils_exports, require("./utils/isomorphic/headers"), module.exports);
29
30
  __reExport(utils_exports, require("./utils/isomorphic/locatorGenerators"), module.exports);
30
31
  __reExport(utils_exports, require("./utils/isomorphic/locatorParser"), module.exports);
@@ -72,6 +73,7 @@ var import_utilsBundle = require("./utilsBundle");
72
73
  ...require("./utils/isomorphic/ariaSnapshot"),
73
74
  ...require("./utils/isomorphic/assert"),
74
75
  ...require("./utils/isomorphic/colors"),
76
+ ...require("./utils/isomorphic/elementOrder"),
75
77
  ...require("./utils/isomorphic/headers"),
76
78
  ...require("./utils/isomorphic/locatorGenerators"),
77
79
  ...require("./utils/isomorphic/locatorParser"),
@@ -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-DTp2-8Si.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.13"]})]}),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-DTp2-8Si.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.14"]})]}),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.uiPTFncY.js"></script>
10
+ <script type="module" crossorigin src="./index.0MJW0uG7.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="./assets/defaultSettingsView-DTp2-8Si.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.13",
3
+ "version": "1.58.2-skyramp.8.14.14",
4
4
  "description": "A high-level API to automate web browsers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -118,7 +118,7 @@ const _ACTION_NAMES = new Set<string>([
118
118
  'navigate', 'openPage', 'press', 'select', 'uncheck',
119
119
  'setInputFiles', 'assertText', 'assertValue', 'assertChecked',
120
120
  'assertVisible', 'assertDisabled', 'assertCount', 'assertHidden', 'assertSnapshot', 'visualSnapshot', 'assertTableCell',
121
- 'assertCSS', 'assertClass', 'assertAttribute',
121
+ 'assertCSS', 'assertClass', 'assertAttribute', 'assertOrder',
122
122
  'dragTo', 'diagramNodeAdd', 'diagramLinkAdd', 'selectArea', 'tableSnapshot', 'domSnapshot', 'fileChooser',
123
123
  'penTool', 'mouse.wheel', 'mouse.move', 'mouse.down', 'mouse.up',
124
124
  'modalOpen', 'modalClose', 'waitForTimeout', 'evaluate',
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { escapeRegExp, serializeExpectedTextValues } from '../../utils';
17
+ import { escapeRegExp, ORDER_RELATIONS, orderHolds, parseOrderFields, serializeExpectedTextValues } from '../../utils';
18
18
  import { toKeyboardModifiers } from '../codegen/language';
19
19
  import { buildFullSelector, mainFrameForAction } from './recorderUtils';
20
20
  import { Progress, ProgressController } from '../progress';
@@ -240,6 +240,31 @@ async function performActionImpl(progress: Progress, mainFrame: Frame, actionInC
240
240
  return;
241
241
  }
242
242
 
243
+ if (raw.name === 'assertOrder') {
244
+ // SKYR-4544: the order contract (utils/isomorphic/elementOrder) the MCP
245
+ // recorder and replay use too; both ends must be visible first.
246
+ const fields = parseOrderFields(raw);
247
+ if (!fields)
248
+ throw new Error(`assertOrder failed: trace line needs "otherSelector" and a relation of ${ORDER_RELATIONS.join('/')}, got ${JSON.stringify(raw.relation)}`);
249
+ const otherSelector = buildFullSelector(actionInContext.frame.framePath, fields.otherSelector);
250
+ const box = async (s: string) => {
251
+ const handle = await mainFrame.waitForSelector(progress, s, false, { strict: true, state: 'visible' });
252
+ try {
253
+ const rect = await handle?.boundingBox();
254
+ if (!rect)
255
+ throw new Error(`assertOrder: ${s} has no box on the page`);
256
+ return rect;
257
+ } finally {
258
+ handle?.dispose();
259
+ }
260
+ };
261
+ const [a, b] = [await box(selector), await box(otherSelector)];
262
+ const { ok, axis, edgeA, edgeB } = orderHolds(fields.relation, a, b);
263
+ if (!ok)
264
+ throw new Error(`assertOrder failed: expected ${selector} ${fields.relation} ${otherSelector}, got ${axis} ${edgeA} vs ${edgeB}`);
265
+ return;
266
+ }
267
+
243
268
  throw new Error('Internal error: unexpected action ' + (action as any).name);
244
269
  }
245
270