@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.
- package/build/index.js +239 -210
- package/build/playwright/PlaywrightTraceService.d.ts +1 -1
- package/build/playwright/blueprintDigestStore.d.ts +1 -1
- package/build/playwright/registerPlaywrightTools.d.ts +1 -1
- package/build/playwright/traceRecordingPrompt.d.ts +1 -1
- package/build/playwright/traceRecordingPrompt.js +4 -3
- package/build/prompts/local-dev/local-dev-prompts.d.ts +1 -1
- package/build/prompts/local-dev/local-dev-prompts.js +2 -2
- package/build/prompts/orderAssertionRule.d.ts +9 -0
- package/build/prompts/orderAssertionRule.js +9 -0
- package/build/prompts/startTraceCollectionPrompts.d.ts +1 -1
- package/build/prompts/startTraceCollectionPrompts.js +2 -3
- package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +1 -1
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +2 -2
- package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
- package/build/prompts/testbot/testbot-prompts.js +9 -9
- package/build/recommendation/answers.d.ts +3 -3
- package/build/recommendation/answers.js +3 -2
- package/build/recommendation/verifierContracts.d.ts +4 -4
- package/build/recommendation/verifierContracts.js +6 -5
- package/build/recommendation/verifiers/coverage.js +25 -25
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +3 -3
- package/build/resources/analysisResources.d.ts +1 -1
- package/build/resources/analysisResources.js +1 -1
- package/build/resources/progressResource.d.ts +1 -1
- package/build/resources/sutSetupResource.d.ts +1 -1
- package/build/resources/sutSetupResource.js +1 -1
- package/build/resources/testbotResource.d.ts +1 -1
- package/build/resources/testbotResource.js +1 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/FleetExecutionService.d.ts +130 -0
- package/build/services/FleetExecutionService.js +391 -0
- package/build/services/ModularizationService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +7 -0
- package/build/services/TestExecutionService.js +21 -0
- package/build/services/TestGenerationService.d.ts +1 -1
- package/build/tool-phases.js +2 -0
- package/build/tools/auth/loginTool.d.ts +1 -1
- package/build/tools/auth/loginTool.js +2 -2
- package/build/tools/auth/logoutTool.d.ts +1 -1
- package/build/tools/auth/logoutTool.js +2 -2
- package/build/tools/code-refactor/alignAssertionsTool.d.ts +1 -1
- package/build/tools/code-refactor/alignAssertionsTool.js +1 -1
- package/build/tools/code-refactor/assertionSkillTools.d.ts +1 -1
- package/build/tools/code-refactor/assertionSkillTools.js +2 -2
- package/build/tools/code-refactor/codeReuseTool.d.ts +1 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -2
- package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +1 -1
- package/build/tools/code-refactor/enhanceAssertionsTool.js +1 -1
- package/build/tools/code-refactor/modularizationTool.d.ts +1 -1
- package/build/tools/code-refactor/modularizationTool.js +2 -2
- package/build/tools/enrichTestWithMocksTool.d.ts +1 -1
- package/build/tools/enrichTestWithMocksTool.js +2 -2
- package/build/tools/executeFleetTool.d.ts +28 -0
- package/build/tools/executeFleetTool.js +107 -0
- package/build/tools/executeSkyrampTestTool.d.ts +8 -5
- package/build/tools/executeSkyrampTestTool.js +13 -5
- package/build/tools/fixErrorTool.d.ts +1 -1
- package/build/tools/fixErrorTool.js +1 -1
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +6 -57
- package/build/tools/generate-tests/batchMockGenerationTool.js +3 -3
- package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +9 -45
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +29 -11
- package/build/tools/generate-tests/generateContractRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateContractRestTool.js +1 -1
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +10 -2
- package/build/tools/generate-tests/generateFuzzRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateFuzzRestTool.js +1 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateIntegrationRestTool.js +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateLoadRestTool.js +2 -1
- package/build/tools/generate-tests/generateMockRestTool.d.ts +1 -2
- package/build/tools/generate-tests/generateMockRestTool.js +3 -3
- package/build/tools/generate-tests/generateSmokeRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateSmokeRestTool.js +1 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +64 -0
- package/build/tools/generate-tests/generateUILoadRestTool.js +512 -0
- package/build/tools/generate-tests/generateUIRestTool.d.ts +8 -7
- package/build/tools/generate-tests/generateUIRestTool.js +10 -2
- package/build/tools/generate-tests/loadTestSchema.d.ts +15 -15
- package/build/tools/generate-tests/trace-reuse-guard.d.ts +1 -1
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +1 -2
- package/build/tools/generateEnrichedIntegrationTestTool.js +1 -1
- package/build/tools/localDevWorkerComposeTool.d.ts +1 -1
- package/build/tools/localDevWorkerComposeTool.js +2 -2
- package/build/tools/one-click/oneClickTool.d.ts +1 -1
- package/build/tools/one-click/oneClickTool.js +1 -1
- package/build/tools/preflightMockCheckTool.d.ts +1 -1
- package/build/tools/preflightMockCheckTool.js +3 -3
- package/build/tools/queryProxyMocksTool.d.ts +1 -1
- package/build/tools/queryProxyMocksTool.js +3 -3
- package/build/tools/submitReportTool.d.ts +111 -414
- package/build/tools/submitReportTool.js +88 -38
- package/build/tools/test-management/actionsTool.d.ts +1 -1
- package/build/tools/test-management/actionsTool.js +7 -7
- package/build/tools/test-management/analyzeChangesTool.d.ts +22 -118
- package/build/tools/test-management/analyzeChangesTool.js +4 -4
- package/build/tools/test-management/analyzeTestHealthTool.d.ts +1 -1
- package/build/tools/test-management/analyzeTestHealthTool.js +4 -4
- package/build/tools/test-management/registerTestPlanTool.d.ts +107 -733
- package/build/tools/test-management/registerTestPlanTool.js +12 -12
- package/build/tools/test-management/resolveScreenTool.d.ts +1 -1
- package/build/tools/test-management/resolveScreenTool.js +6 -2
- package/build/tools/trace/startTraceCollectionTool.d.ts +1 -1
- package/build/tools/trace/startTraceCollectionTool.js +4 -4
- package/build/tools/trace/stopTraceCollectionTool.d.ts +1 -1
- package/build/tools/trace/stopTraceCollectionTool.js +2 -2
- package/build/tools/verifyTestDependenciesTool.d.ts +1 -1
- package/build/tools/verifyTestDependenciesTool.js +1 -1
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +1 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +1 -1
- package/build/tools/workspace/initializeWorkspaceTool.d.ts +1 -1
- package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
- package/build/types/RepositoryAnalysis.d.ts +164 -1170
- package/build/types/RepositoryAnalysis.js +12 -10
- package/build/types/StepMethod.d.ts +24 -1
- package/build/types/StepMethod.js +1 -1
- package/build/types/TestTypes.d.ts +41 -133
- package/build/types/TestTypes.js +25 -3
- package/build/types/TestbotReport.d.ts +4 -1
- package/build/utils/assertion-verify/metrics.js +12 -0
- package/build/utils/docker.d.ts +29 -7
- package/build/utils/docker.js +83 -27
- package/build/utils/findUp.d.ts +20 -0
- package/build/utils/findUp.js +59 -0
- package/build/utils/initAgent.d.ts +3 -3
- package/build/utils/initAgent.js +58 -26
- package/build/utils/language-helper.js +1 -1
- package/build/utils/progress.d.ts +3 -4
- package/build/utils/progress.js +4 -4
- package/build/utils/repoSdkVersion.d.ts +16 -0
- package/build/utils/repoSdkVersion.js +88 -0
- package/build/utils/testDependencyPolicy.d.ts +20 -0
- package/build/utils/testDependencyPolicy.js +64 -4
- package/build/utils/utils-verify/typecheck.js +3 -18
- package/build/utils/utils.d.ts +1 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +117 -538
- package/build/workspace/workspace.js +2 -2
- package/node_modules/playwright/lib/mcp/skyramp/assertOrder.js +66 -0
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +12 -3
- package/node_modules/playwright/lib/mcp/skyramp/common/liveReadError.js +40 -0
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +21 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +96 -16
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +10 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +22 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/elementOrder.js +57 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.uiPTFncY.js → index.0MJW0uG7.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +26 -1
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/elementOrder.ts +83 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
- package/node_modules/playwright/package.json +1 -1
- package/package.json +9 -6
- package/plugin/prompts/plan-tests.md +9 -17
- package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -1
- package/plugin/skills/enhance-assertions/reference/ui.md +2 -2
- package/plugin/skills/fix-test-import-errors/SKILL.md +1 -1
- 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
|
-
|
|
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).
|
|
1344
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
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
|
}
|
package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js
CHANGED
|
@@ -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) {
|
package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/elementOrder.js
ADDED
|
@@ -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.
|
|
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)})();
|
package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="icon" href="./playwright-logo.svg" type="image/svg+xml">
|
|
8
8
|
<link rel="manifest" href="./manifest.webmanifest">
|
|
9
9
|
<title>Playwright Trace Viewer</title>
|
|
10
|
-
<script type="module" crossorigin src="./index.
|
|
10
|
+
<script type="module" crossorigin src="./index.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">
|
|
@@ -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',
|
package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts
CHANGED
|
@@ -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
|
|