@deeeed/metamask-harness 0.47.4 → 0.49.0

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 (261) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -2
  3. package/adapters/extension/lib/macos-focus.cjs +2 -2
  4. package/adapters/extension/live.sh +6 -8
  5. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +1 -0
  6. package/adapters/shared/ensure-runner-deps.sh +6 -0
  7. package/dist/adapters/extension/runtime-decision.js +18 -1
  8. package/dist/adapters/extension/runtime.js +5 -5
  9. package/dist/adapters/extension/surface.js +1 -0
  10. package/dist/app-lifecycle.js +3 -1
  11. package/dist/command-contract.js +2 -0
  12. package/dist/commands/call.js +1 -8
  13. package/dist/commands/device-target.js +12 -3
  14. package/dist/commands/doctor.js +7 -5
  15. package/dist/commands/fixtures.js +1 -1
  16. package/dist/commands/help.js +17 -5
  17. package/dist/commands/launch/index.js +10 -0
  18. package/dist/commands/manifest.js +3 -1
  19. package/dist/commands/mobile-device-view.js +13 -3
  20. package/dist/commands/parse-args.js +2 -0
  21. package/dist/commands/run-engine.js +29 -4
  22. package/dist/commands/run.js +1 -1
  23. package/dist/commands/runtime-launch.js +10 -1
  24. package/dist/commands/status.js +8 -4
  25. package/dist/devices.js +31 -9
  26. package/dist/execution-provenance.js +8 -7
  27. package/dist/funding-execution-context.js +62 -17
  28. package/dist/heal-bounds.js +3 -3
  29. package/dist/live-adapter-contract.js +4 -0
  30. package/dist/manifest.js +2 -13
  31. package/dist/metamask-action-validation.js +3 -2
  32. package/dist/mm-harness-cli.js +4 -2
  33. package/dist/runner.js +14 -3
  34. package/docs/RECIPES.md +135 -24
  35. package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
  36. package/library/actions/core/perps/_controller.mjs +24 -24
  37. package/library/actions/core/perps/assert_orders.mjs +3 -5
  38. package/library/actions/core/perps/assert_positions.mjs +3 -5
  39. package/library/actions/core/perps/close_orders.mjs +39 -19
  40. package/library/actions/core/perps/close_positions.mjs +53 -12
  41. package/library/actions/core/perps/edit_order.mjs +2 -0
  42. package/library/actions/core/perps/ensure_orders.mjs +3 -4
  43. package/library/actions/core/perps/ensure_positions.mjs +4 -5
  44. package/library/actions/core/perps/place_order.mjs +7 -14
  45. package/library/actions/core/perps/read_account.mjs +2 -0
  46. package/library/actions/core/perps/read_orders.mjs +2 -0
  47. package/library/actions/core/perps/read_positions.mjs +3 -1
  48. package/library/actions/core/perps/read_snapshot.mjs +3 -0
  49. package/library/actions/core/perps/start_state.mjs +3 -14
  50. package/library/actions/core/perps/teardown_state.mjs +3 -14
  51. package/library/actions/core/perps/update_position_tpsl.mjs +13 -19
  52. package/library/actions/core/wallet/list_accounts.mjs +3 -0
  53. package/library/actions/extension/analytics/consent.mjs +2 -0
  54. package/library/actions/extension/analytics/set_consent.mjs +2 -0
  55. package/library/actions/extension/assets/home-token-identity.mjs +31 -0
  56. package/library/actions/extension/assets/import_custom_token.mjs +63 -79
  57. package/library/actions/extension/assets/open_details.mjs +19 -7
  58. package/library/actions/extension/assets/prepare_send.mjs +1 -0
  59. package/library/actions/extension/assets/read_details.mjs +62 -29
  60. package/library/actions/extension/assets/read_visible_state.mjs +5 -1
  61. package/library/actions/extension/assets/review_send.mjs +1 -0
  62. package/library/actions/extension/assets/select_network_scope.mjs +17 -9
  63. package/library/actions/extension/assets/send.mjs +2 -0
  64. package/library/actions/extension/assets/set_custom_gas.mjs +1 -0
  65. package/library/actions/extension/assets/set_token_visibility.mjs +86 -17
  66. package/library/actions/extension/assets/verify_sorting.mjs +63 -14
  67. package/library/actions/extension/deeplink/open.mjs +1 -0
  68. package/library/actions/extension/networks/add_chainlist.mjs +2 -0
  69. package/library/actions/extension/networks/add_custom.mjs +21 -17
  70. package/library/actions/extension/networks/custom_network.mjs +10 -3
  71. package/library/actions/extension/networks/read_visible_state.mjs +2 -0
  72. package/library/actions/extension/networks/remove_custom.mjs +1 -0
  73. package/library/actions/extension/performance/_navigation-memory.mjs +37 -6
  74. package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +5 -2
  75. package/library/actions/extension/performance/measure_detached_dom.mjs +5 -2
  76. package/library/actions/extension/performance/measure_navigation_memory.mjs +5 -2
  77. package/library/actions/extension/perps/assert_orders.mjs +1 -0
  78. package/library/actions/extension/perps/assert_positions.mjs +1 -0
  79. package/library/actions/extension/perps/assert_visible_consistency.mjs +11 -4
  80. package/library/actions/extension/perps/close_orders.mjs +4 -1
  81. package/library/actions/extension/perps/close_positions.mjs +1 -0
  82. package/library/actions/extension/perps/close_visible_position.mjs +160 -11
  83. package/library/actions/extension/perps/compare_provider_market.mjs +2 -0
  84. package/library/actions/extension/perps/edit_margin.mjs +3 -1
  85. package/library/actions/extension/perps/ensure_orders.mjs +1 -0
  86. package/library/actions/extension/perps/ensure_positions.mjs +1 -0
  87. package/library/actions/extension/perps/mutation-receipt.mjs +54 -3
  88. package/library/actions/extension/perps/open_balance_action.mjs +17 -7
  89. package/library/actions/extension/perps/open_position_action.mjs +1 -0
  90. package/library/actions/extension/perps/perps.mjs +550 -69
  91. package/library/actions/extension/perps/place_order.mjs +1 -0
  92. package/library/actions/extension/perps/read_eligibility.mjs +1 -0
  93. package/library/actions/extension/perps/read_funds_confirmation.mjs +10 -0
  94. package/library/actions/extension/perps/read_orders.mjs +1 -0
  95. package/library/actions/extension/perps/read_positions.mjs +1 -0
  96. package/library/actions/extension/perps/read_snapshot.mjs +4 -1
  97. package/library/actions/extension/perps/read_visible_state.mjs +12 -6
  98. package/library/actions/extension/perps/search_markets.mjs +8 -2
  99. package/library/actions/extension/perps/select_activity_filter.mjs +2 -1
  100. package/library/actions/extension/perps/select_market_filter.mjs +75 -45
  101. package/library/actions/extension/perps/set_market_favorite.mjs +7 -3
  102. package/library/actions/extension/perps/state.mjs +2 -0
  103. package/library/actions/extension/perps/update_position_tpsl.mjs +3 -1
  104. package/library/actions/extension/perps/visible-mutation-identity.mjs +6 -2
  105. package/library/actions/extension/platform/cdp.mjs +50 -142
  106. package/library/actions/extension/settings/set_basic_functionality.mjs +1 -0
  107. package/library/actions/extension/swap_bridge/quote-state.mjs +56 -0
  108. package/library/actions/extension/swap_bridge/read_visible_state.mjs +5 -1
  109. package/library/actions/extension/swap_bridge/select_assets.mjs +12 -1
  110. package/library/actions/extension/swap_bridge/set_amount.mjs +11 -8
  111. package/library/actions/extension/swap_bridge/set_max_amount.mjs +18 -18
  112. package/library/actions/extension/swap_bridge/set_slippage.mjs +1 -9
  113. package/library/actions/extension/ui/locators.mjs +1 -0
  114. package/library/actions/extension/ui/navigate.mjs +60 -14
  115. package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -0
  116. package/library/actions/extension/wallet/import.mjs +2 -0
  117. package/library/actions/extension/wallet/list_accounts.mjs +1 -0
  118. package/library/actions/extension/wallet/lock.mjs +4 -1
  119. package/library/actions/extension/wallet/read_state.mjs +1 -0
  120. package/library/actions/extension/wallet/reset.mjs +2 -0
  121. package/library/actions/extension/wallet/secret-input.mjs +2 -0
  122. package/library/actions/extension/wallet/select_account.mjs +2 -0
  123. package/library/actions/extension/wallet/setup.mjs +1 -0
  124. package/library/actions/extension/wallet/state.mjs +2 -0
  125. package/library/actions/extension/wallet/visible-session.mjs +2 -0
  126. package/library/actions/mobile/analytics/consent-settings.mjs +1 -0
  127. package/library/actions/mobile/analytics/set_consent.mjs +1 -0
  128. package/library/actions/mobile/app/network-control.mjs +2 -0
  129. package/library/actions/mobile/app/network.mjs +1 -0
  130. package/library/actions/mobile/app/network_assert.mjs +1 -0
  131. package/library/actions/mobile/app/network_capture.mjs +1 -0
  132. package/library/actions/mobile/assets/import_custom_token.mjs +3 -0
  133. package/library/actions/mobile/assets/open_details.mjs +1 -0
  134. package/library/actions/mobile/assets/read_details.mjs +1 -0
  135. package/library/actions/mobile/assets/read_visible_state.mjs +4 -1
  136. package/library/actions/mobile/assets/set_token_visibility.mjs +15 -9
  137. package/library/actions/mobile/assets/verify_sorting.mjs +90 -7
  138. package/library/actions/mobile/deeplink/open.mjs +1 -0
  139. package/library/actions/mobile/networks/add_custom.mjs +1 -0
  140. package/library/actions/mobile/networks/network-management.mjs +23 -15
  141. package/library/actions/mobile/networks/read_visible_state.mjs +1 -0
  142. package/library/actions/mobile/networks/remove_custom.mjs +7 -2
  143. package/library/actions/mobile/perps/assert_orders.mjs +1 -0
  144. package/library/actions/mobile/perps/assert_positions.mjs +1 -0
  145. package/library/actions/mobile/perps/capture_performance.mjs +1 -0
  146. package/library/actions/mobile/perps/close_orders.mjs +1 -0
  147. package/library/actions/mobile/perps/close_positions.mjs +1 -0
  148. package/library/actions/mobile/perps/ensure_orders.mjs +1 -0
  149. package/library/actions/mobile/perps/ensure_positions.mjs +1 -0
  150. package/library/actions/mobile/perps/measure_homepage_visible.mjs +1 -0
  151. package/library/actions/mobile/perps/performance-capture.mjs +2 -0
  152. package/library/actions/mobile/perps/perps.mjs +8 -1
  153. package/library/actions/mobile/perps/place_order.mjs +1 -0
  154. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +2 -0
  155. package/library/actions/mobile/perps/read-visible-state-loop.mjs +3 -0
  156. package/library/actions/mobile/perps/read_orders.mjs +1 -0
  157. package/library/actions/mobile/perps/read_positions.mjs +1 -0
  158. package/library/actions/mobile/perps/read_visible_state.mjs +1 -0
  159. package/library/actions/mobile/perps/search_markets.mjs +1 -0
  160. package/library/actions/mobile/perps/set_market_favorite.mjs +17 -19
  161. package/library/actions/mobile/platform/bridge.mjs +16 -4
  162. package/library/actions/mobile/platform/native-session-name.mjs +1 -0
  163. package/library/actions/mobile/platform/native-session.mjs +28 -17
  164. package/library/actions/mobile/platform/observe-ui.mjs +4 -2
  165. package/library/actions/mobile/platform/tool-paths.mjs +1 -0
  166. package/library/actions/mobile/swap_bridge/native-session.mjs +1 -0
  167. package/library/actions/mobile/swap_bridge/read_visible_state.mjs +1 -0
  168. package/library/actions/mobile/swap_bridge/select_assets.mjs +2 -0
  169. package/library/actions/mobile/swap_bridge/set_amount.mjs +1 -0
  170. package/library/actions/mobile/swap_bridge/set_max_amount.mjs +1 -0
  171. package/library/actions/mobile/swap_bridge/set_slippage.mjs +1 -0
  172. package/library/actions/mobile/swap_bridge/submit_transaction.mjs +2 -0
  173. package/library/actions/mobile/ui/locators.mjs +1 -0
  174. package/library/actions/mobile/ui/native-navigation.mjs +2 -0
  175. package/library/actions/mobile/ui/navigate.mjs +3 -1
  176. package/library/actions/mobile/wallet/ensure_unlocked.mjs +2 -0
  177. package/library/actions/mobile/wallet/home.mjs +1 -0
  178. package/library/actions/mobile/wallet/import.mjs +2 -0
  179. package/library/actions/mobile/wallet/list_accounts.mjs +1 -0
  180. package/library/actions/mobile/wallet/lock.mjs +1 -0
  181. package/library/actions/mobile/wallet/native-ui.mjs +2 -0
  182. package/library/actions/mobile/wallet/read_state.mjs +2 -0
  183. package/library/actions/mobile/wallet/reset-helper.mjs +1 -0
  184. package/library/actions/mobile/wallet/reset.mjs +1 -0
  185. package/library/actions/mobile/wallet/select_account.mjs +2 -0
  186. package/library/actions/mobile/wallet/setup.mjs +2 -0
  187. package/library/actions/shared/analytics/assert_events.mjs +3 -0
  188. package/library/actions/shared/analytics/consent.mjs +3 -0
  189. package/library/actions/shared/analytics/read_events.mjs +2 -0
  190. package/library/actions/shared/analytics/start_capture.mjs +3 -0
  191. package/library/actions/shared/app/network-artifact.mjs +2 -0
  192. package/library/actions/shared/app/network-assert.mjs +2 -0
  193. package/library/actions/shared/assets/visible-state.mjs +2 -0
  194. package/library/actions/shared/deeplink/url.mjs +3 -0
  195. package/library/actions/shared/networks/visible-state.mjs +2 -0
  196. package/library/actions/shared/perps/visible-state.mjs +16 -3
  197. package/library/actions/shared/swap-bridge/transaction.mjs +3 -0
  198. package/library/actions/shared/swap-bridge/visible-state.mjs +2 -0
  199. package/library/actions/shared/ui/locators.mjs +2 -0
  200. package/library/actions/shared/wallet/import-source.mjs +3 -0
  201. package/library/manifests/core.action-manifest.json +34 -2
  202. package/library/manifests/extension.action-manifest.json +50 -17
  203. package/library/manifests/mobile.action-manifest.json +11 -10
  204. package/library/recipes/core/perps/smoke.recipe.json +23 -0
  205. package/library/recipes/core/perps/snapshot.recipe.json +26 -1
  206. package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +25 -8
  207. package/library/recipes/extension/performance/navigation-memory-suite.recipe.json +219 -0
  208. package/library/recipes/extension/performance/navigation-memory.recipe.json +61 -156
  209. package/library/recipes/extension/wallet/smoke.recipe.json +17 -8
  210. package/library/recipes/mobile/app/lifecycle-smoke.recipe.json +89 -0
  211. package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +31 -6
  212. package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +5 -4
  213. package/library/recipes/mobile/wallet/smoke.recipe.json +17 -8
  214. package/library/recipes/{assets → shared/assets}/release-token-address-import.recipe.json +7 -0
  215. package/library/recipes/{assets → shared/assets}/release-token-details.recipe.json +2 -2
  216. package/library/recipes/shared/assets/release-token-hide-readd.recipe.json +91 -0
  217. package/library/recipes/{assets → shared/assets}/release-token-persistence.recipe.json +3 -0
  218. package/library/recipes/{assets → shared/assets}/release-token-search-import.recipe.json +4 -1
  219. package/library/recipes/{assets → shared/assets}/release-token-sorting.recipe.json +13 -2
  220. package/library/recipes/{swap-bridge → shared/swap-bridge}/release-quote.recipe.json +29 -3
  221. package/package.json +3 -3
  222. package/site/assets/help-recipes.json +220 -27
  223. package/library/recipes/assets/release-token-hide-readd.recipe.json +0 -57
  224. package/library/recipes/core/perps/clean-market-testnet.recipe.json +0 -44
  225. package/library/recipes/core/perps/order-lifecycle.recipe.json +0 -82
  226. package/library/recipes/core/perps/read-markets.recipe.json +0 -36
  227. package/library/recipes/core/perps/trading-lifecycle.recipe.json +0 -80
  228. package/library/recipes/extension/perps/release-activity-filters.recipe.json +0 -51
  229. package/library/recipes/extension/perps/release-funds-flows.recipe.json +0 -76
  230. package/library/recipes/extension/perps/release-live-limit-order.recipe.json +0 -109
  231. package/library/recipes/extension/perps/release-market-filters.recipe.json +0 -97
  232. package/library/recipes/extension/perps/release-order-entry.recipe.json +0 -158
  233. package/library/recipes/extension/perps/release-order-validation.recipe.json +0 -259
  234. package/library/recipes/extension/perps/release-visible-consistency.recipe.json +0 -47
  235. package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +0 -76
  236. package/library/recipes/mobile/app/lifecycle.android-smoke.recipe.json +0 -69
  237. package/library/recipes/mobile/perps/chase-assert-running.recipe.json +0 -110
  238. package/library/recipes/mobile/perps/chase-place.recipe.json +0 -145
  239. package/library/recipes/mobile/perps/chase-terminate.recipe.json +0 -98
  240. package/library/recipes/mobile/perps/performance.recipe.json +0 -722
  241. package/library/recipes/mobile/perps/pro-order-setup.recipe.json +0 -111
  242. package/library/recipes/mobile/perps/pro-order-start-state.recipe.json +0 -53
  243. package/library/recipes/mobile/perps/scale-assert-orders.recipe.json +0 -126
  244. package/library/recipes/mobile/perps/scale-place.recipe.json +0 -186
  245. package/library/recipes/mobile/perps/twap-assert-active.recipe.json +0 -97
  246. package/library/recipes/mobile/perps/twap-place.recipe.json +0 -146
  247. package/library/recipes/mobile/runner/action-validation.recipe.json +0 -336
  248. package/library/recipes/perps/clean-market-testnet.recipe.json +0 -49
  249. package/library/recipes/perps/lifecycle.recipe.json +0 -144
  250. package/library/recipes/perps/release-live-market-order.recipe.json +0 -139
  251. package/library/recipes/perps/release-watchlist.recipe.json +0 -111
  252. package/library/recipes/swap-bridge/release-max-quote.recipe.json +0 -72
  253. /package/library/recipes/{assets → shared/assets}/release-token-list.recipe.json +0 -0
  254. /package/library/recipes/{perps → shared/perps}/release-market-details.recipe.json +0 -0
  255. /package/library/recipes/{perps → shared/perps}/release-market-search.recipe.json +0 -0
  256. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-custom-slippage.recipe.json +0 -0
  257. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-max-swap.recipe.json +0 -0
  258. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-swap.recipe.json +0 -0
  259. /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-surface.recipe.json +0 -0
  260. /package/library/recipes/{wallet → shared/wallet}/import.recipe.json +0 -0
  261. /package/library/recipes/{wallet → shared/wallet}/reset-import.recipe.json +0 -0
@@ -1,3 +1,4 @@
1
+ // Place a Perps order and return visible-control evidence or assert the resulting order or position state.
1
2
  import { runAdapter } from '../platform/cdp.mjs';
2
3
  import { assertOrders, assertPositions, placeOrder } from './perps.mjs';
3
4
  runAdapter(async (input) => {
@@ -1,3 +1,4 @@
1
+ // Fetch MetaMask geolocation and report it alongside the product's current Perps eligibility state.
1
2
  import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
3
 
3
4
  runAdapter(async (input) => {
@@ -1,3 +1,5 @@
1
+ // Read the displayed Perps deposit or withdrawal confirmation and check requested UI expectations.
2
+ // Report preview fields, submit availability, and layout without submitting funds.
1
3
  import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
4
 
3
5
  function expectedList(value, name) {
@@ -117,6 +119,14 @@ export async function readFundsConfirmation(page, node, mode, timeoutMs = 30_000
117
119
  if (observed?.title !== expectedTitle || observed?.amount === null) {
118
120
  throw new Error(`Perps ${mode} confirmation did not finish loading.`);
119
121
  }
122
+ if (node.require_preview_fields === true) {
123
+ const missing = ['transactionFee', 'estimatedTime', 'receive'].filter((field) => !observed[field]);
124
+ if (!observed.payToken && !observed.receiveToken) missing.push('payToken/receiveToken');
125
+ if (observed.loadingVisible) missing.push('loading not settled');
126
+ if (missing.length > 0) {
127
+ throw new Error(`Perps ${mode} preview is incomplete: ${missing.join(', ')}.\nNext: mm-harness logs --full`);
128
+ }
129
+ }
120
130
  if (node.require_no_horizontal_overflow === true && (
121
131
  observed.layout.horizontalOverflow || observed.layout.clippedTestIds.length > 0
122
132
  )) {
@@ -1,3 +1,4 @@
1
+ // Read live Perps orders with optional selection and network checks, returning redacted order fields.
1
2
  import { runAdapter } from '../platform/cdp.mjs';
2
3
  import { readOrders } from './perps.mjs';
3
4
 
@@ -1,3 +1,4 @@
1
+ // Read runtime Perps positions with optional selection and network checks, returning redacted position fields.
1
2
  import { runAdapter } from '../platform/cdp.mjs';
2
3
  import { readPositions } from './perps.mjs';
3
4
  runAdapter(readPositions);
@@ -1,3 +1,5 @@
1
+ // Read bounded, normalized Perps snapshots through background APIs, optionally sampling market changes.
2
+ // History checks can require a matching visible activity card identity.
1
3
  import { pathToFileURL } from 'node:url';
2
4
 
3
5
  import { normalizeMarketSymbol, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
@@ -499,7 +501,8 @@ async function visibleActivityCards(page) {
499
501
  return {
500
502
  testId: String(element.getAttribute('data-testid') || '').slice(0, 500),
501
503
  label: String(element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500),
502
- visible: style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0,
504
+ visible: style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0 &&
505
+ rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth,
503
506
  };
504
507
  })`);
505
508
  }
@@ -1,3 +1,7 @@
1
+ // Capture Perps DOM controls, order-entry values, and layout within configured item and visibility bounds.
2
+ // Normalize the observation and check requested visible-state expectations.
3
+ import { pathToFileURL } from 'node:url';
4
+
1
5
  import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
6
  import {
3
7
  normalizeVisiblePerpsState,
@@ -38,7 +42,7 @@ export async function readVisiblePerpsState(page, node = {}) {
38
42
  const raw = await page.evaluate(`(() => {
39
43
  const includeOffscreen = ${JSON.stringify(options.includeOffscreen)};
40
44
  const maximum = ${JSON.stringify(options.maxItems)};
41
- const relevant = /^(?:perps-|explore-markets-|market-list-view|market-row-|filter-select-button|sort-dropdown-button|search-input|position-card-|order-card-|transaction-card-|bottom-nav-|back-button|direction-|order-type-|amount-|balance-percent-|leverage-|auto-close|submit-order-button|limit-price-|tp-|sl-|custom-amount-|pay-with-|confirm-footer-button|wallet-initiated-header-back-button|transaction-fee-|bridge-(?:fee|time)-|receive-(?:row|value))/u;
45
+ const relevant = /^(?:perps-|parent-selector-perps-(?:market-list|order-entry)|explore-markets-|market-list-view|market-row-|filter-select-button|sort-dropdown-button|search-input|position-card-|order-card-|transaction-card-|bottom-nav-|back-button|direction-|order-type-|amount-|balance-percent-|leverage-|auto-close|submit-order-button|limit-price-|tp-|sl-|custom-amount-|pay-with-|confirm-footer-button|wallet-initiated-header-back-button|transaction-fee-|bridge-(?:fee|time)-|receive-(?:row|value))/u;
42
46
  const items = [];
43
47
  const offscreenItems = [];
44
48
  const clippedTestIds = [];
@@ -115,7 +119,7 @@ export async function readVisiblePerpsState(page, node = {}) {
115
119
  items,
116
120
  offscreenItems,
117
121
  truncated: matched > items.length + offscreenItems.length,
118
- orderEntry: document.querySelector('[data-testid="perps-order-entry-page"]') ? {
122
+ orderEntry: document.querySelector('[data-testid="parent-selector-perps-order-entry"], [data-testid="perps-order-entry-page"]') ? {
119
123
  symbol: field('perps-order-entry-asset-symbol')?.label || null,
120
124
  direction,
121
125
  orderType: field('order-type-limit')?.pressed === 'true' ? 'limit' : 'market',
@@ -150,7 +154,9 @@ export async function readVisiblePerpsState(page, node = {}) {
150
154
  };
151
155
  }
152
156
 
153
- runAdapter(async (input) => withExtensionPage(input, async (page) => ({
154
- action: input.action,
155
- ...await readVisiblePerpsState(page, input.node),
156
- })));
157
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
158
+ runAdapter(async (input) => withExtensionPage(input, async (page) => ({
159
+ action: input.action,
160
+ ...await readVisiblePerpsState(page, input.node),
161
+ })));
162
+ }
@@ -1,10 +1,16 @@
1
+ // Enter a Perps market search query and wait for at least one displayed result row.
1
2
  import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
3
 
3
4
  runAdapter(async (input) => withExtensionPage(input, async (page) => {
4
5
  const query = String(input.node?.query ?? '').trim();
5
6
  const timeoutMs = Number(input.node?.timeout_ms ?? 30_000);
6
7
  if (!query) throw new Error('metamask.perps.search_markets requires a non-empty query.');
7
- await page.setInput(dataTestId('search-input'), query);
8
+ const searchInput = dataTestId('search-input');
9
+ if (!await page.evaluate(`Boolean(document.querySelector(${JSON.stringify(searchInput)}))`)) {
10
+ await page.click(dataTestId('market-list-search-toggle'));
11
+ await page.waitForSelector(searchInput, { timeoutMs });
12
+ }
13
+ await page.setInput(searchInput, query);
8
14
  const deadline = Date.now() + timeoutMs;
9
15
  let marketIds = [];
10
16
  while (Date.now() <= deadline) {
@@ -18,7 +24,7 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
18
24
  .filter(Boolean)
19
25
  .slice(0, 100))()`);
20
26
  if (marketIds.length > 0) break;
21
- await page.evaluate('new Promise((resolve) => setTimeout(resolve, 200))');
27
+ await new Promise((resolve) => setTimeout(resolve, 200));
22
28
  }
23
29
  if (marketIds.length === 0) {
24
30
  throw new Error(`Perps market search returned no visible rows for ${JSON.stringify(query)}.`);
@@ -1,3 +1,4 @@
1
+ // Select a Perps activity filter and wait for its route, label, and loaded cards or empty state.
1
2
  import { pathToFileURL } from 'node:url';
2
3
 
3
4
  import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
@@ -62,7 +63,7 @@ export async function selectActivityFilter(page, input, options = {}) {
62
63
  entries: cards.map((card) => card.label),
63
64
  loadedBoundary: visible(document.querySelector('[data-testid="perps-activity-page-skeleton"]'))
64
65
  ? null
65
- : visible(document.querySelector('[data-testid="perps-activity-page"]'))
66
+ : visible(document.querySelector('[data-testid="perps-activity-page"],[data-testid="parent-selector-perps-activity"]'))
66
67
  ? cards.length > 0 ? 'visible-cards' : 'loaded-empty'
67
68
  : null,
68
69
  empty: String(document.body?.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 2_000),
@@ -1,10 +1,8 @@
1
+ // Select a Perps market category through the dropdown or category rail and inspect the resulting list.
1
2
  import { pathToFileURL } from 'node:url';
2
3
 
3
4
  import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
4
- import {
5
- normalizeVisiblePerpsState,
6
- resolveVisibleStateOptions,
7
- } from '../../shared/perps/visible-state.mjs';
5
+ import { readVisiblePerpsState } from './read_visible_state.mjs';
8
6
 
9
7
  const FILTERS = new Map([
10
8
  ['all', 'all'],
@@ -34,21 +32,7 @@ async function visibleLabel(page, selector) {
34
32
  })()`);
35
33
  }
36
34
 
37
- export async function selectMarketFilter(page, input, pollOptions = {}) {
38
- const filter = String(input.node?.filter ?? '').trim().toLowerCase();
39
- const option = FILTERS.get(filter);
40
- if (!option) {
41
- throw new Error(`metamask.perps.select_market_filter received unsupported filter ${JSON.stringify(filter)}.`);
42
- }
43
- const options = resolveVisibleStateOptions({
44
- surface: 'market-list',
45
- include_offscreen: true,
46
- max_items: input.node?.max_items ?? 100,
47
- minimum_market_count: input.node?.minimum_market_count ?? 1,
48
- maximum_market_count: input.node?.maximum_market_count ?? 200,
49
- });
50
- const timeoutMs = pollOptions.timeoutMs ?? input.node?.timeout_ms ?? 10_000;
51
- const pollIntervalMs = pollOptions.pollIntervalMs ?? 100;
35
+ async function selectDropdownFilter(page, filter, option, timeoutMs, pollIntervalMs) {
52
36
  await page.click(dataTestId('filter-select-button'));
53
37
  const optionSelector = dataTestId(`filter-select-option-${option}`);
54
38
  try {
@@ -76,37 +60,83 @@ export async function selectMarketFilter(page, input, pollOptions = {}) {
76
60
  }
77
61
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
78
62
  }
79
- const raw = await page.evaluate(`(() => {
80
- const maximum = ${JSON.stringify(options.maxItems)};
81
- const items = [];
82
- let matched = 0;
83
- for (const element of document.querySelectorAll('[data-testid]')) {
84
- const testId = element.getAttribute('data-testid');
85
- if (!testId || !/^(?:market-list-view|market-row-|perps-token-logo-|filter-select-button)/u.test(testId)) continue;
86
- const style = getComputedStyle(element);
87
- const rect = element.getBoundingClientRect();
88
- if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) continue;
89
- matched += 1;
90
- if (items.length >= maximum) break;
91
- items.push({
92
- testId,
93
- label: String(element.getAttribute('aria-label') || element.innerText || element.getAttribute('value') || '')
94
- .replace(/\\s+/gu, ' ')
95
- .trim()
96
- .slice(0, 500),
97
- role: element.getAttribute('role') || element.tagName.toLowerCase(),
98
- enabled: !element.disabled && element.getAttribute('aria-disabled') !== 'true',
99
- visible: rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth,
100
- });
63
+ return { selectedLabel: selectedState.label, controlKind: 'dropdown', alreadySelected: false };
64
+ }
65
+
66
+ async function categoryRailState(page) {
67
+ return page.evaluate(`(() => ({
68
+ active: [...document.querySelectorAll('[data-testid^="market-list-categories-pill-"][aria-pressed="true"]')].map((element) => ({
69
+ id: element.getAttribute('data-testid'),
70
+ option: element.getAttribute('data-testid').slice('market-list-categories-pill-'.length),
71
+ label: String(element.innerText || '').trim(),
72
+ })),
73
+ watchlist: document.querySelector('[data-testid="market-list-watchlist-toggle"]')?.getAttribute('aria-pressed') === 'true',
74
+ }))()`);
75
+ }
76
+
77
+ async function selectCategoryRail(page, option, timeoutMs, pollIntervalMs) {
78
+ const deadline = Date.now() + timeoutMs;
79
+ const matches = (state) => !state.watchlist && (option === 'all'
80
+ ? state.active.length === 0
81
+ : state.active.length === 1 && state.active[0].option === option);
82
+ const initial = await categoryRailState(page);
83
+ const alreadySelected = matches(initial);
84
+ if (!alreadySelected) {
85
+ if (option === 'all') {
86
+ if (initial.watchlist) await page.click(dataTestId('market-list-watchlist-toggle'));
87
+ for (const selected of initial.active) await page.click(dataTestId(selected.id));
88
+ } else {
89
+ let selector = dataTestId(`market-list-categories-pill-${option}`);
90
+ if (!(await visibleLabel(page, selector)).visible) {
91
+ const more = dataTestId('market-list-categories-more-button');
92
+ selector = dataTestId(`market-list-categories-more-option-${option}`);
93
+ if (!(await visibleLabel(page, selector)).visible) {
94
+ if (!(await visibleLabel(page, more)).visible) {
95
+ throw new Error(`Perps market filter ${JSON.stringify(option)} is not available in this release.`);
96
+ }
97
+ await page.click(more);
98
+ }
99
+ await page.waitForSelector(selector, { timeoutMs: Math.max(1, deadline - Date.now()) });
100
+ }
101
+ await page.click(selector);
101
102
  }
102
- return { route: location.hash, title: document.title, items, offscreenItems: [], truncated: matched > items.length };
103
- })()`);
103
+ }
104
+ let selected = await categoryRailState(page);
105
+ while (!matches(selected)) {
106
+ if (Date.now() >= deadline) {
107
+ throw new Error(`Perps category ${JSON.stringify(option)} did not become selected: ${JSON.stringify(selected)}.`);
108
+ }
109
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
110
+ selected = await categoryRailState(page);
111
+ }
112
+ return { selectedLabel: selected.active[0]?.label ?? null, controlKind: 'category-rail', alreadySelected };
113
+ }
114
+
115
+ export async function selectMarketFilter(page, input, pollOptions = {}) {
116
+ const filter = String(input.node?.filter ?? '').trim().toLowerCase();
117
+ const option = FILTERS.get(filter);
118
+ if (!option) {
119
+ throw new Error(`metamask.perps.select_market_filter received unsupported filter ${JSON.stringify(filter)}.`);
120
+ }
121
+ const timeoutMs = pollOptions.timeoutMs ?? input.node?.timeout_ms ?? 10_000;
122
+ const pollIntervalMs = pollOptions.pollIntervalMs ?? 100;
123
+ const rail = await visibleLabel(page, dataTestId('market-list-categories'));
124
+ const selection = rail.visible
125
+ ? await selectCategoryRail(page, option, timeoutMs, pollIntervalMs)
126
+ : await selectDropdownFilter(page, filter, option, timeoutMs, pollIntervalMs);
127
+ const state = await readVisiblePerpsState(page, {
128
+ surface: 'market-list',
129
+ include_offscreen: true,
130
+ max_items: input.node?.max_items ?? 100,
131
+ minimum_market_count: input.node?.minimum_market_count ?? 1,
132
+ maximum_market_count: input.node?.maximum_market_count ?? 200,
133
+ });
104
134
  return {
105
135
  action: input.action,
106
136
  filter,
107
137
  option,
108
- selectedLabel: selectedState.label,
109
- ...normalizeVisiblePerpsState(raw, options, 'extension'),
138
+ ...selection,
139
+ ...state,
110
140
  proofPath: 'trusted-pointer-visible-ui',
111
141
  };
112
142
  }
@@ -1,3 +1,4 @@
1
+ // Set the current market's favorite state through its visible control, or require it to be unchanged.
1
2
  import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
3
 
3
4
  runAdapter(async (input) => withExtensionPage(input, async (page) => {
@@ -20,9 +21,15 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
20
21
  const button = document.querySelector(${JSON.stringify(selector)});
21
22
  if (!button) throw new Error('Perps favorite control is not visible.');
22
23
  const label = String(button.getAttribute('aria-label') || button.innerText || '').trim();
24
+ if (!/^(?:add to|remove from) favorites$/iu.test(label)) {
25
+ throw new Error('Perps favorite control has an unsupported label: ' + JSON.stringify(label));
26
+ }
23
27
  return { label, favorite: /^remove from favorites$/iu.test(label) };
24
28
  })()`);
25
29
  const before = await readState();
30
+ if (requireUnchanged && before.favorite !== desired) {
31
+ throw new Error(`Perps favorite state for ${market} did not persist across navigation; no change was made.`);
32
+ }
26
33
  if (before.favorite !== desired) {
27
34
  await page.click(selector);
28
35
  }
@@ -37,9 +44,6 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
37
44
  throw new Error(`Perps favorite state did not become ${desired}.`);
38
45
  }
39
46
  const changed = before.favorite !== after.favorite;
40
- if (requireUnchanged && changed) {
41
- throw new Error(`Perps favorite state for ${market} did not persist across navigation.`);
42
- }
43
47
  return {
44
48
  action: input.action,
45
49
  market,
@@ -1,3 +1,4 @@
1
+ // Build a page expression that reads Perps provider, network, initialization, and eligibility from state hooks.
1
2
  export function readPerpsRuntimeStateExpression() {
2
3
  return `(async () => {
3
4
  const hooks = globalThis.stateHooks || {};
@@ -11,6 +12,7 @@ export function readPerpsRuntimeStateExpression() {
11
12
  return {
12
13
  available: typeof request === 'function',
13
14
  activeProvider: metamask.activeProvider || nestedPerps.activeProvider || 'hyperliquid',
15
+ providerKnown: typeof (metamask.activeProvider ?? nestedPerps.activeProvider) === 'string',
14
16
  networkKnown: typeof metamask.isTestnet === 'boolean' || typeof nestedPerps.isTestnet === 'boolean',
15
17
  isTestnet: Boolean(metamask.isTestnet ?? nestedPerps.isTestnet),
16
18
  initializationState: metamask.initializationState || nestedPerps.initializationState || null,
@@ -1,3 +1,5 @@
1
+ // Submit live take-profit and stop-loss changes through visible controls with mutation receipts.
2
+ // Check the funded position identity and observe the exact requested prices.
1
3
  import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
4
  import {
3
5
  assertTrustedExternalMutationTarget,
@@ -229,7 +231,6 @@ export async function updatePositionTpsl(input) {
229
231
  let dispatchTarget;
230
232
  try {
231
233
  await requireCurrentVisibleFundingBalance(input, page, timeoutMs, fundingBalance.observedBaseUnits);
232
- await requirePerpsTradingContext(input, page);
233
234
  assertTrustedExternalMutationTarget(input, page, identity);
234
235
  const current = await readPosition(page, market);
235
236
  if (current.size !== before.size || current.direction !== before.direction
@@ -246,6 +247,7 @@ export async function updatePositionTpsl(input) {
246
247
  const rect = element.getBoundingClientRect();
247
248
  return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
248
249
  })()`);
250
+ await requirePerpsTradingContext(input, page);
249
251
  assertTrustedExternalMutationTarget(input, page, identity);
250
252
  } catch (error) {
251
253
  throw externalMutationNotDispatched(error instanceof Error ? error.message : String(error));
@@ -1,3 +1,5 @@
1
+ // Parse and match visible Perps position and order identities using USD base-unit tolerances.
2
+ // Also wait for a stable, loaded Perps home view, optionally requiring zero positions and orders.
1
3
  const INTEGER = /^(0|[1-9][0-9]{0,29})$/u;
2
4
 
3
5
  export function parseUsdBaseUnits(value) {
@@ -129,10 +131,12 @@ export async function waitForStableGlobalPerpsHome(page, options = {}) {
129
131
  return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
130
132
  };
131
133
  const visibleCount = (selector) => [...document.querySelectorAll(selector)].filter(visible).length;
132
- const balance = document.querySelector('[data-testid="perps-balance-dropdown-balance"]');
134
+ const balance = document.querySelector('[data-testid="perps-balance-actions-available-value"]')
135
+ ?? document.querySelector('[data-testid="perps-balance-dropdown-balance"]');
133
136
  return {
134
137
  route: String(location.hash),
135
- viewVisible: visible(document.querySelector('[data-testid="perps-view"]')),
138
+ viewVisible: visible(document.querySelector('[data-testid="perps-home-page"]')
139
+ ?? document.querySelector('[data-testid="perps-view"]')),
136
140
  loading: visibleCount('[data-testid$="-skeleton"], [data-testid*="loading"]') > 0,
137
141
  marketCount: visibleCount('[data-testid="perps-explore-markets-row"]'),
138
142
  positionCount: visibleCount('[data-testid^="position-card-"]:not([data-testid^="position-card-roe-"])'),