@skyramp/mcp 0.3.4 → 0.3.6-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/build/playwright/registerPlaywrightTools.js +92 -30
  2. package/build/playwright/traceRecordingPrompt.d.ts +6 -0
  3. package/build/playwright/traceRecordingPrompt.js +6 -2
  4. package/build/prompts/code-reuse.d.ts +1 -2
  5. package/build/prompts/code-reuse.js +182 -77
  6. package/build/prompts/modularization/integration-test-modularization.d.ts +2 -0
  7. package/build/prompts/modularization/integration-test-modularization.js +83 -41
  8. package/build/prompts/modularization/render.d.ts +18 -0
  9. package/build/prompts/modularization/render.js +12 -0
  10. package/build/prompts/modularization/ui-test-modularization.d.ts +3 -1
  11. package/build/prompts/modularization/ui-test-modularization.js +89 -47
  12. package/build/prompts/pom-aware-code-reuse.js +3 -1
  13. package/build/prompts/shared-helper-policy.d.ts +57 -0
  14. package/build/prompts/shared-helper-policy.js +135 -0
  15. package/build/prompts/test-recommendation/diffExecutionPlan.js +62 -56
  16. package/build/prompts/test-recommendation/fullRepoCatalog.js +19 -8
  17. package/build/prompts/test-recommendation/recommendationShared.d.ts +28 -6
  18. package/build/prompts/test-recommendation/recommendationShared.js +90 -16
  19. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +22 -0
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -2
  21. package/build/prompts/test-recommendation/test-recommendation-prompt.js +3 -3
  22. package/build/prompts/testbot/testbot-prompts.js +88 -33
  23. package/build/recommendation/budgeters/shared.js +105 -27
  24. package/build/recommendation/discriminators.js +13 -2
  25. package/build/recommendation/planRanker.d.ts +6 -6
  26. package/build/recommendation/planRanker.js +6 -61
  27. package/build/services/AnalyticsService.d.ts +7 -0
  28. package/build/services/AnalyticsService.js +7 -1
  29. package/build/services/ModularizationService.js +1 -3
  30. package/build/services/TestDiscoveryService.d.ts +0 -2
  31. package/build/services/TestDiscoveryService.js +2 -37
  32. package/build/services/TestGenerationService.d.ts +16 -0
  33. package/build/services/TestGenerationService.js +86 -10
  34. package/build/services/containerEnv.js +13 -12
  35. package/build/tools/code-refactor/codeReuseTool.js +279 -93
  36. package/build/tools/code-refactor/enhance-state.d.ts +49 -0
  37. package/build/tools/code-refactor/enhance-state.js +109 -0
  38. package/build/tools/code-refactor/enhanceAssertionsTool.js +34 -1
  39. package/build/tools/code-refactor/modularizationTool.js +9 -2
  40. package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
  41. package/build/tools/code-refactor/reuse-outcome.js +14 -4
  42. package/build/tools/code-refactor/reuse-state.d.ts +127 -5
  43. package/build/tools/code-refactor/reuse-state.js +628 -16
  44. package/build/tools/code-refactor/utils-verify-gates.d.ts +26 -0
  45. package/build/tools/code-refactor/utils-verify-gates.js +100 -0
  46. package/build/tools/code-refactor/verify-gates.d.ts +2 -1
  47. package/build/tools/code-refactor/verify-gates.js +90 -25
  48. package/build/tools/executeSkyrampTestTool.d.ts +19 -0
  49. package/build/tools/executeSkyrampTestTool.js +158 -8
  50. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -2
  51. package/build/tools/generate-tests/generateE2ERestTool.js +16 -0
  52. package/build/tools/generate-tests/generateUIRestTool.d.ts +1 -0
  53. package/build/tools/generate-tests/generateUIRestTool.js +22 -0
  54. package/build/tools/generate-tests/scenarioLint.d.ts +2 -0
  55. package/build/tools/generate-tests/scenarioLint.js +127 -19
  56. package/build/tools/generate-tests/trace-reuse-guard.d.ts +20 -0
  57. package/build/tools/generate-tests/trace-reuse-guard.js +93 -0
  58. package/build/tools/submitReportTool.d.ts +38 -38
  59. package/build/tools/submitReportTool.js +487 -104
  60. package/build/tools/test-management/analyzeChangesTool.d.ts +24 -1
  61. package/build/tools/test-management/analyzeChangesTool.js +75 -12
  62. package/build/tools/test-management/analyzeTestHealthTool.js +7 -7
  63. package/build/tools/test-management/registerTestPlanTool.d.ts +203 -0
  64. package/build/tools/test-management/registerTestPlanTool.js +149 -23
  65. package/build/types/Recommendation.d.ts +34 -5
  66. package/build/types/RepositoryAnalysis.d.ts +133 -114
  67. package/build/types/RepositoryAnalysis.js +1 -1
  68. package/build/types/ReuseOutcome.d.ts +102 -6
  69. package/build/types/ReuseOutcome.js +16 -2
  70. package/build/types/TestRecommendation.js +21 -3
  71. package/build/types/TestTypes.js +14 -8
  72. package/build/types/TestbotReport.d.ts +25 -3
  73. package/build/types/index.d.ts +2 -2
  74. package/build/types/index.js +1 -1
  75. package/build/utils/AnalysisStateManager.d.ts +69 -1
  76. package/build/utils/AnalysisStateManager.js +69 -5
  77. package/build/utils/branchDiff.d.ts +10 -0
  78. package/build/utils/branchDiff.js +28 -0
  79. package/build/utils/changedRoutes.d.ts +29 -0
  80. package/build/utils/changedRoutes.js +87 -0
  81. package/build/utils/featureFlags.d.ts +21 -0
  82. package/build/utils/featureFlags.js +23 -0
  83. package/build/utils/frontendIntegration.js +34 -4
  84. package/build/utils/importerHop.d.ts +2 -8
  85. package/build/utils/importerHop.js +15 -53
  86. package/build/utils/pathMatching.d.ts +38 -0
  87. package/build/utils/pathMatching.js +71 -0
  88. package/build/utils/pathSignatures.d.ts +22 -0
  89. package/build/utils/pathSignatures.js +57 -0
  90. package/build/utils/planMatchKeys.d.ts +16 -3
  91. package/build/utils/planMatchKeys.js +26 -10
  92. package/build/utils/pluralization.d.ts +10 -0
  93. package/build/utils/pluralization.js +18 -0
  94. package/build/utils/pom-catalog-parse.d.ts +52 -0
  95. package/build/utils/pom-catalog-parse.js +141 -0
  96. package/build/utils/pom-scope/selector-extractor.d.ts +12 -0
  97. package/build/utils/pom-scope/selector-extractor.js +34 -8
  98. package/build/utils/pom-verify/verify.d.ts +6 -5
  99. package/build/utils/pom-verify/verify.js +8 -6
  100. package/build/utils/reportLanguage.d.ts +43 -0
  101. package/build/utils/reportLanguage.js +125 -0
  102. package/build/utils/reportVerification.d.ts +74 -4
  103. package/build/utils/reportVerification.js +259 -3
  104. package/build/utils/reuseRouting.d.ts +3 -0
  105. package/build/utils/reuseRouting.js +50 -0
  106. package/build/utils/routeParsers.d.ts +2 -0
  107. package/build/utils/routeParsers.js +65 -8
  108. package/build/utils/scenarioDrafting.d.ts +1 -1
  109. package/build/utils/scenarioDrafting.js +57 -45
  110. package/build/utils/subjectEndpoints.d.ts +19 -0
  111. package/build/utils/subjectEndpoints.js +98 -0
  112. package/build/utils/testFileClassification.d.ts +11 -0
  113. package/build/utils/testFileClassification.js +47 -0
  114. package/build/utils/uiPageEnumerator.d.ts +45 -19
  115. package/build/utils/uiPageEnumerator.js +95 -51
  116. package/build/utils/utils-verify/allow.d.ts +16 -0
  117. package/build/utils/utils-verify/allow.js +68 -0
  118. package/build/utils/utils-verify/call-sites.d.ts +34 -0
  119. package/build/utils/utils-verify/call-sites.js +154 -0
  120. package/build/utils/utils-verify/index.d.ts +7 -0
  121. package/build/utils/utils-verify/index.js +7 -0
  122. package/build/utils/utils-verify/language-spec.d.ts +91 -0
  123. package/build/utils/utils-verify/language-spec.js +210 -0
  124. package/build/utils/utils-verify/locate.d.ts +39 -0
  125. package/build/utils/utils-verify/locate.js +199 -0
  126. package/build/utils/utils-verify/parse.d.ts +34 -0
  127. package/build/utils/utils-verify/parse.js +177 -0
  128. package/build/utils/utils-verify/stage.d.ts +24 -0
  129. package/build/utils/utils-verify/stage.js +107 -0
  130. package/build/utils/utils-verify/verify.d.ts +63 -0
  131. package/build/utils/utils-verify/verify.js +168 -0
  132. package/build/utils/utils.d.ts +3 -1
  133. package/build/utils/utils.js +3 -1
  134. package/build/workspace/workspace.d.ts +32 -32
  135. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +9 -5
  136. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +16 -0
  137. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +2 -0
  138. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +115 -14
  139. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +13 -1
  140. package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
  141. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +2 -0
  142. package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +27 -253
  143. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-DtudTj_v.js → codeMirrorModule-DJMC4zNo.js} +1 -1
  144. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BW82eAUI.js +196 -0
  145. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
  146. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-FNMuBzX1.js → codeMirrorModule-CZfp96qZ.js} +1 -1
  147. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-gpLo02E0.js +809 -0
  148. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.Bq1r1URj.js +2 -0
  149. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  150. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.VEfqi1qN.js +5 -0
  151. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  152. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  153. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  154. package/node_modules/playwright/package.json +1 -1
  155. package/package.json +2 -2
  156. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +0 -422
  157. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +0 -1035
  158. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +0 -2
  159. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +0 -5
@@ -28,16 +28,20 @@ const assertToolSchema = {
28
28
  name: "browser_assert",
29
29
  title: "Assert element state",
30
30
  description: [
31
- "Assert that an element has expected text, value, or is visible.",
31
+ "Assert that an element has expected text, value, or state.",
32
32
  "Use this after key actions to verify the page state is correct.",
33
33
  "The assertion is recorded in the trace for test generation.",
34
- 'Types: "text" checks element text content, "value" checks input field value.'
34
+ 'Types: "text" checks element text content, "value" checks input field value,',
35
+ '"checked" checks checkbox/radio state, "disabled" checks disabled/enabled state,',
36
+ '"visible" checks the element is visible,',
37
+ `"count" counts elements sharing the referenced element's role (or exact text)`,
38
+ "across the page, e.g. ref one option of an open dropdown to assert the number of options."
35
39
  ].join(" "),
36
40
  inputSchema: import_mcpBundle.z.object({
37
- type: import_mcpBundle.z.enum(["text", "value"]).describe('Type of assertion: "text" for text content, "value" for input field value'),
38
- ref: import_mcpBundle.z.string().describe("Element reference from the latest snapshot"),
41
+ type: import_mcpBundle.z.enum(["text", "value", "checked", "disabled", "visible", "count"]).describe(`Type of assertion: "text" for text content, "value" for input field value, "checked" for checkbox/radio state, "disabled" for disabled/enabled state ("false" asserts the element is enabled), "visible" for element visibility, "count" for the number of elements with the referenced element's role`),
42
+ 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).'),
39
43
  element: import_mcpBundle.z.string().describe("Human-readable description of the element being asserted"),
40
- expected: import_mcpBundle.z.string().describe("Expected text or value to assert"),
44
+ 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"). Not used for "visible".'),
41
45
  substring: import_mcpBundle.z.boolean().optional().default(true).describe("For text assertions: match as substring (true) or exact match (false)")
42
46
  }),
43
47
  type: "readOnly"
@@ -370,6 +370,22 @@ async function performClientAction(page, actionInContext) {
370
370
  throw new Error(`assertVisible failed: element is not visible`);
371
371
  return;
372
372
  }
373
+ case "assertDisabled": {
374
+ if (typeof action.disabled !== "boolean")
375
+ throw new Error(`assertDisabled failed: trace line has no boolean "disabled" field`);
376
+ const disabled = await locator.isDisabled(opts);
377
+ if (disabled !== action.disabled)
378
+ throw new Error(`assertDisabled failed: expected disabled=${action.disabled}, got ${disabled}`);
379
+ return;
380
+ }
381
+ case "assertCount": {
382
+ await locator.first().waitFor(opts).catch(() => {
383
+ });
384
+ const count = await locator.count();
385
+ if (count !== action.count)
386
+ throw new Error(`assertCount failed: expected ${action.count} element(s), got ${count}`);
387
+ return;
388
+ }
373
389
  default:
374
390
  return;
375
391
  }
@@ -61,6 +61,8 @@ const ACTION_NAMES = /* @__PURE__ */ new Set([
61
61
  "assertValue",
62
62
  "assertChecked",
63
63
  "assertVisible",
64
+ "assertDisabled",
65
+ "assertCount",
64
66
  "assertSnapshot",
65
67
  "visualSnapshot",
66
68
  "assertTableCell",
@@ -855,6 +855,14 @@ Continue recording with browser_* tools, then call skyramp_export_zip to write t
855
855
  return { toolName: "browser_assert", code: `assertChecked:${a.selector}:${!!a.checked}`, args: { type: "checked", selector: a.selector, checked: !!a.checked } };
856
856
  case "assertVisible":
857
857
  return { toolName: "browser_assert", code: `assertVisible:${a.selector}`, args: { type: "visible", selector: a.selector } };
858
+ case "assertDisabled":
859
+ if (typeof a.disabled !== "boolean")
860
+ return null;
861
+ return { toolName: "browser_assert", code: `assertDisabled:${a.selector}:${a.disabled}`, args: { type: "disabled", selector: a.selector, disabled: a.disabled } };
862
+ case "assertCount":
863
+ if (typeof a.count !== "number" || !Number.isInteger(a.count) || a.count < 0)
864
+ return null;
865
+ return { toolName: "browser_assert", code: `assertCount:${a.selector}:${a.count}`, args: { type: "count", selector: a.selector, count: a.count } };
858
866
  case "visualSnapshot": {
859
867
  const args = this._seedVisualSnapshotArgs(a);
860
868
  return args ? { toolName: "browser_visual_snapshot", code: "", args } : null;
@@ -1260,8 +1268,13 @@ Linked GoJS nodes ${params.fromKey} \u2192 ${params.toKey} (${params.fromPort}\u
1260
1268
  */
1261
1269
  async _handleAssert(params) {
1262
1270
  const timestamp = Date.now();
1263
- if (!params.expected)
1271
+ if (!params.expected && (params.type === "text" || params.type === "value" || params.type === "count"))
1264
1272
  return { content: [{ type: "text", text: '### Error\n"expected" parameter is required.' }], isError: true };
1273
+ if (params.type === "count" && !/^\d+$/.test(params.expected))
1274
+ return { content: [{ type: "text", text: '### Error\nFor "count" assertions, "expected" must be a non-negative integer.' }], isError: true };
1275
+ if ((params.type === "checked" || params.type === "disabled") && params.expected !== void 0 && params.expected !== "true" && params.expected !== "false")
1276
+ return { content: [{ type: "text", text: `### Error
1277
+ For "${params.type}" assertions, "expected" must be "true" or "false".` }], isError: true };
1265
1278
  const hoverResult = await this._browserBackend.callTool("browser_hover", {
1266
1279
  element: params.element,
1267
1280
  ref: params.ref
@@ -1312,30 +1325,105 @@ Could not parse locator: ${locatorExpr}` }], isError: true };
1312
1325
  }
1313
1326
  let passed = false;
1314
1327
  let details = "";
1328
+ let countSelector = "";
1315
1329
  if (params.type === "text") {
1316
1330
  passed = elementText.includes(params.expected) || elementValue.includes(params.expected);
1317
1331
  details = passed ? `Text assertion passed: "${params.element}" contains "${params.expected}".` : `Text assertion FAILED: "${params.element}" has text "${elementText}"${elementValue ? ` / value "${elementValue}"` : ""}, expected "${params.expected}".`;
1318
- } else {
1332
+ } else if (params.type === "value") {
1319
1333
  passed = elementValue === params.expected || elementValue.includes(params.expected);
1320
1334
  details = passed ? `Value assertion passed: "${params.element}" has value "${params.expected}".` : `Value assertion FAILED: "${params.element}" has value "${elementValue}", expected "${params.expected}".`;
1335
+ } else if (params.type === "checked" || params.type === "disabled") {
1336
+ if (!refLine) {
1337
+ passed = false;
1338
+ details = `${params.type === "checked" ? "Checked" : "Disabled"} assertion FAILED: "${params.element}" (ref=${params.ref}) is not in the current snapshot \u2014 re-snapshot and retry with a fresh ref.`;
1339
+ } else if (params.type === "checked") {
1340
+ const expectChecked = params.expected !== "false";
1341
+ const isChecked = refLine.includes("[checked]");
1342
+ const isMixed = refLine.includes("[checked=mixed]");
1343
+ passed = expectChecked ? isChecked : !isChecked && !isMixed;
1344
+ details = passed ? `Checked assertion passed: "${params.element}" is ${expectChecked ? "checked" : "unchecked"}.` : `Checked assertion FAILED: "${params.element}" is ${isMixed ? "mixed" : isChecked ? "checked" : "unchecked"}, expected ${expectChecked ? "checked" : "unchecked"}.`;
1345
+ } else {
1346
+ const expectDisabled = params.expected !== "false";
1347
+ const isDisabled = refLine.includes("[disabled]");
1348
+ passed = expectDisabled === isDisabled;
1349
+ details = passed ? `Disabled assertion passed: "${params.element}" is ${expectDisabled ? "disabled" : "enabled"}.` : `Disabled assertion FAILED: "${params.element}" is ${isDisabled ? "disabled" : "enabled"}, expected ${expectDisabled ? "disabled" : "enabled"}.`;
1350
+ }
1351
+ } else if (params.type === "visible") {
1352
+ passed = !!refLine;
1353
+ details = passed ? `Visibility assertion passed: "${params.element}" is visible.` : `Visibility assertion FAILED: "${params.element}" (ref=${params.ref}) is not in the current snapshot.`;
1354
+ } else {
1355
+ const refRole = (parsed.locator.kind === "role" ? String(parsed.locator.body) : "") || refLine.match(/^\s*-\s*(\w+)/)?.[1] || "";
1356
+ if (!refRole) {
1357
+ return { content: [{ type: "text", text: `### Assertion Failed
1358
+ Could not determine the role of ref=${params.ref} for a count assertion.` }], isError: true };
1359
+ }
1360
+ if (!TraceRecordingBackend.COUNTABLE_ROLES.has(refRole)) {
1361
+ return { content: [{ type: "text", text: `### Assertion Failed
1362
+ Role "${refRole}" is not countable \u2014 reference an exemplar with a semantic role (listitem, option, row, button, link, \u2026).` }], isError: true };
1363
+ }
1364
+ const tab = this._browserBackend.context?.currentTab();
1365
+ if (!tab) {
1366
+ return { content: [{ type: "text", text: "### Assertion Failed\nNo active page." }], isError: true };
1367
+ }
1368
+ countSelector = `internal:role=${refRole}`;
1369
+ let countActual;
1370
+ try {
1371
+ countActual = await tab.page.getByRole(refRole).count();
1372
+ } catch (e) {
1373
+ return { content: [{ type: "text", text: `### Assertion Failed
1374
+ Could not count "${refRole}" elements: ${e.message}` }], isError: true };
1375
+ }
1376
+ passed = countActual === Number(params.expected);
1377
+ details = passed ? `Count assertion passed: ${countActual} "${refRole}" element(s) on the page.` : `Count assertion FAILED: found ${countActual} "${refRole}" element(s), expected ${params.expected}.`;
1321
1378
  }
1322
1379
  if (passed) {
1323
1380
  const recordedExpected = params.type === "text" && params.substring !== false ? (0, import_utils.stableTextLead)(params.expected) : params.expected;
1324
1381
  const selectorText = parsed.locator.kind === "text" ? parsed.locator.body : parsed.locator.next?.kind === "text" ? parsed.locator.next.body : parsed.locator.options?.name ?? null;
1325
- if (selectorText && selectorText === recordedExpected) {
1382
+ const tautological = Boolean((params.type === "text" || params.type === "value") && selectorText && selectorText === recordedExpected);
1383
+ if (tautological) {
1326
1384
  traceDebug(`Skipped tautological assertion: selector already matches "${recordedExpected}"`);
1327
1385
  } else {
1328
- const assertName = params.type === "value" ? "assertValue" : "assertText";
1329
- this._trackedActions.push({
1330
- toolName: "browser_assert",
1331
- // Pass selector/expected as structured args so assertActionToJsonl can
1332
- // decode unambiguously (the colon-encoded code below corrupts values
1333
- // that contain ':', e.g. URLs). The code string is a fallback only.
1334
- args: { type: params.type, ref: params.ref, selector: parsed.selector, expected: recordedExpected, ...params.type === "text" ? { substring: params.substring } : {} },
1335
- code: `${assertName}:${parsed.selector}:${recordedExpected}${params.type === "text" ? ":" + params.substring : ""}`,
1336
- timestamp
1337
- });
1338
- traceDebug(`Assert: ${assertName} with selector ${parsed.selector}`);
1386
+ let assertName;
1387
+ let args;
1388
+ let code;
1389
+ switch (params.type) {
1390
+ case "checked": {
1391
+ const expectChecked = params.expected !== "false";
1392
+ assertName = "assertChecked";
1393
+ args = { type: "checked", ref: params.ref, selector: parsed.selector, checked: expectChecked };
1394
+ code = `assertChecked:${parsed.selector}:${expectChecked}`;
1395
+ break;
1396
+ }
1397
+ case "disabled": {
1398
+ const expectDisabled = params.expected !== "false";
1399
+ assertName = "assertDisabled";
1400
+ args = { type: "disabled", ref: params.ref, selector: parsed.selector, disabled: expectDisabled };
1401
+ code = `assertDisabled:${parsed.selector}:${expectDisabled}`;
1402
+ break;
1403
+ }
1404
+ case "visible":
1405
+ assertName = "assertVisible";
1406
+ args = { type: "visible", ref: params.ref, selector: parsed.selector };
1407
+ code = `assertVisible:${parsed.selector}`;
1408
+ break;
1409
+ case "count":
1410
+ assertName = "assertCount";
1411
+ args = { type: "count", ref: params.ref, selector: countSelector, count: Number(params.expected) };
1412
+ code = `assertCount:${countSelector}:${params.expected}`;
1413
+ break;
1414
+ case "value":
1415
+ assertName = "assertValue";
1416
+ args = { type: "value", ref: params.ref, selector: parsed.selector, expected: recordedExpected };
1417
+ code = `assertValue:${parsed.selector}:${recordedExpected}`;
1418
+ break;
1419
+ default:
1420
+ assertName = "assertText";
1421
+ args = { type: "text", ref: params.ref, selector: parsed.selector, expected: recordedExpected, substring: params.substring };
1422
+ code = `assertText:${parsed.selector}:${recordedExpected}:${params.substring}`;
1423
+ break;
1424
+ }
1425
+ this._trackedActions.push({ toolName: "browser_assert", args, code, timestamp });
1426
+ traceDebug(`Assert: ${assertName} with selector ${args.selector}`);
1339
1427
  }
1340
1428
  }
1341
1429
  return {
@@ -1570,6 +1658,19 @@ Cell row ${params.row}, column ${params.column} has "${actual}", expected "${par
1570
1658
  "menuitem"
1571
1659
  ]);
1572
1660
  }
1661
+ static {
1662
+ // Roles browser_assert type:"count" may generalize page-wide. Superset of
1663
+ // ASSERTABLE_ROLES (kept separate — that set is for name-bearing
1664
+ // single-element selector extraction; widening one must not loosen the
1665
+ // other) plus the collection-item roles that are the natural count targets.
1666
+ this.COUNTABLE_ROLES = /* @__PURE__ */ new Set([
1667
+ ...TraceRecordingBackend.ASSERTABLE_ROLES,
1668
+ "listitem",
1669
+ "article",
1670
+ "gridcell",
1671
+ "treeitem"
1672
+ ]);
1673
+ }
1573
1674
  _extractLocatorForRef(refLine) {
1574
1675
  const testidMatch = refLine.match(/testid="([^"]+)"/);
1575
1676
  if (testidMatch) {
@@ -437,6 +437,10 @@ function assertActionToJsonl(action, pageGuid, timestamp) {
437
437
  expected = args.expected ?? "";
438
438
  } else if (assertType === "checked") {
439
439
  expected = String(args.checked === true || args.expected === "true" || args.expected === true);
440
+ } else if (assertType === "disabled") {
441
+ expected = String(args.disabled === true || args.expected === "true" || args.expected === true);
442
+ } else if (assertType === "count") {
443
+ expected = String(args.count ?? args.expected ?? "");
440
444
  }
441
445
  } else {
442
446
  const parts = code.split(":");
@@ -446,7 +450,7 @@ function assertActionToJsonl(action, pageGuid, timestamp) {
446
450
  substring = parts[parts.length - 1] === "true";
447
451
  expected = parts[parts.length - 2] || "";
448
452
  selector = parts.slice(1, parts.length - 2).join(":");
449
- } else if (assertType === "value" || assertType === "checked") {
453
+ } else if (assertType === "value" || assertType === "checked" || assertType === "disabled" || assertType === "count") {
450
454
  expected = parts[parts.length - 1] || "";
451
455
  selector = parts.slice(1, parts.length - 1).join(":");
452
456
  }
@@ -461,6 +465,14 @@ function assertActionToJsonl(action, pageGuid, timestamp) {
461
465
  return JSON.stringify({ name: "assertValue", selector, value: expected, locator, ...base });
462
466
  case "checked":
463
467
  return JSON.stringify({ name: "assertChecked", selector, checked: expected === "true", locator, ...base });
468
+ case "disabled":
469
+ return JSON.stringify({ name: "assertDisabled", selector, disabled: expected === "true", locator, ...base });
470
+ case "count": {
471
+ const count = Number(expected);
472
+ if (!/^\d+$/.test(expected) || !Number.isInteger(count))
473
+ return null;
474
+ return JSON.stringify({ name: "assertCount", selector, count, locator, ...base });
475
+ }
464
476
  default:
465
477
  return null;
466
478
  }
@@ -128,6 +128,8 @@ const _ACTION_NAMES = /* @__PURE__ */ new Set([
128
128
  "assertValue",
129
129
  "assertChecked",
130
130
  "assertVisible",
131
+ "assertDisabled",
132
+ "assertCount",
131
133
  "assertSnapshot",
132
134
  "visualSnapshot",
133
135
  "assertTableCell",