@deeeed/metamask-harness 0.37.0 → 0.38.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 (113) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/adapters/extension/artifact-runtime-state.cjs +13 -3
  3. package/adapters/extension/inject.mjs +1 -0
  4. package/adapters/extension/launch-browser.cjs +2 -0
  5. package/adapters/extension/live.sh +1 -1
  6. package/adapters/extension/readiness.mjs +1 -1
  7. package/adapters/manifest.json +8 -0
  8. package/dist/adapters/extension/runtime-decision.js +4 -1
  9. package/dist/adapters/extension/runtime.js +42 -3
  10. package/dist/adapters/mobile/provision.js +38 -3
  11. package/dist/adapters.js +68 -3
  12. package/dist/app-lifecycle.js +4 -3
  13. package/dist/commands/call.js +4 -1
  14. package/dist/commands/launch/extension.js +21 -5
  15. package/dist/commands/launch/index.js +3 -0
  16. package/dist/commands/run-engine.js +40 -29
  17. package/dist/commands/run.js +1 -1
  18. package/dist/recipe-security.js +40 -0
  19. package/dist/runner.js +3 -0
  20. package/docs/RELEASE-QA-CAPABILITY-MAP.md +116 -141
  21. package/library/actions/extension/assets/finish_send.mjs +128 -0
  22. package/library/actions/extension/assets/import_custom_token.mjs +227 -0
  23. package/library/actions/extension/assets/prepare_send.mjs +53 -0
  24. package/library/actions/extension/assets/read_details.mjs +57 -0
  25. package/library/actions/extension/assets/read_visible_state.mjs +50 -0
  26. package/library/actions/extension/assets/review_send.mjs +23 -0
  27. package/library/actions/extension/assets/select_network_scope.mjs +30 -0
  28. package/library/actions/extension/assets/send.mjs +104 -0
  29. package/library/actions/extension/assets/set_custom_gas.mjs +56 -0
  30. package/library/actions/extension/assets/set_token_visibility.mjs +129 -0
  31. package/library/actions/extension/assets/verify_sorting.mjs +89 -0
  32. package/library/actions/extension/networks/add_chainlist.mjs +94 -0
  33. package/library/actions/extension/networks/add_custom.mjs +123 -0
  34. package/library/actions/extension/networks/custom_network.mjs +103 -0
  35. package/library/actions/extension/networks/read_visible_state.mjs +50 -0
  36. package/library/actions/extension/networks/remove_custom.mjs +68 -0
  37. package/library/actions/extension/perps/close_orders.mjs +1 -0
  38. package/library/actions/extension/perps/close_visible_position.mjs +133 -0
  39. package/library/actions/extension/perps/compare_provider_market.mjs +109 -0
  40. package/library/actions/extension/perps/edit_margin.mjs +82 -0
  41. package/library/actions/extension/perps/open_balance_action.mjs +58 -0
  42. package/library/actions/extension/perps/open_position_action.mjs +80 -0
  43. package/library/actions/extension/perps/perps.mjs +194 -2
  44. package/library/actions/extension/perps/place_order.mjs +5 -2
  45. package/library/actions/extension/perps/read_eligibility.mjs +34 -0
  46. package/library/actions/extension/perps/read_funds_confirmation.mjs +69 -0
  47. package/library/actions/extension/perps/read_visible_state.mjs +98 -0
  48. package/library/actions/extension/perps/select_activity_filter.mjs +44 -0
  49. package/library/actions/extension/perps/select_market_filter.mjs +71 -0
  50. package/library/actions/extension/perps/set_market_favorite.mjs +47 -0
  51. package/library/actions/extension/perps/update_position_tpsl.mjs +106 -0
  52. package/library/actions/extension/platform/cdp.mjs +104 -1
  53. package/library/actions/extension/settings/set_basic_functionality.mjs +58 -0
  54. package/library/actions/extension/swap_bridge/read_visible_state.mjs +71 -0
  55. package/library/actions/extension/swap_bridge/select_assets.mjs +103 -0
  56. package/library/actions/extension/swap_bridge/set_amount.mjs +36 -0
  57. package/library/actions/extension/swap_bridge/set_max_amount.mjs +58 -0
  58. package/library/actions/extension/swap_bridge/set_slippage.mjs +121 -0
  59. package/library/actions/extension/swap_bridge/submit_transaction.mjs +363 -0
  60. package/library/actions/extension/ui/activate_and_observe.mjs +96 -0
  61. package/library/actions/extension/ui/navigate.mjs +264 -8
  62. package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -6
  63. package/library/actions/extension/wallet/lock.mjs +35 -0
  64. package/library/actions/extension/wallet/select_account.mjs +71 -7
  65. package/library/actions/mobile/assets/read_visible_state.mjs +37 -0
  66. package/library/actions/mobile/perps/read_visible_state.mjs +31 -0
  67. package/library/actions/mobile/platform/native-session-name.mjs +5 -0
  68. package/library/actions/mobile/platform/observe-ui.mjs +5 -3
  69. package/library/actions/mobile/swap_bridge/native-session.mjs +45 -0
  70. package/library/actions/mobile/swap_bridge/read_visible_state.mjs +31 -0
  71. package/library/actions/mobile/swap_bridge/select_assets.mjs +94 -0
  72. package/library/actions/mobile/swap_bridge/set_amount.mjs +73 -0
  73. package/library/actions/mobile/ui/native-navigation.mjs +45 -5
  74. package/library/actions/mobile/ui/navigate.mjs +3 -1
  75. package/library/actions/mobile/wallet/ensure_unlocked.mjs +17 -0
  76. package/library/actions/mobile/wallet/native-ui.mjs +21 -2
  77. package/library/actions/shared/assets/visible-state.mjs +176 -0
  78. package/library/actions/shared/networks/visible-state.mjs +70 -0
  79. package/library/actions/shared/perps/visible-state.mjs +354 -0
  80. package/library/actions/shared/swap-bridge/transaction.mjs +54 -0
  81. package/library/actions/shared/swap-bridge/visible-state.mjs +97 -0
  82. package/library/manifests/extension.action-manifest.json +1255 -189
  83. package/library/manifests/mobile.action-manifest.json +219 -1
  84. package/library/recipes/assets/release-custom-gas-send.recipe.json +88 -0
  85. package/library/recipes/assets/release-custom-network-token-persistence.recipe.json +106 -0
  86. package/library/recipes/assets/release-native-max-cancel.recipe.json +70 -0
  87. package/library/recipes/assets/release-native-max-send.recipe.json +78 -0
  88. package/library/recipes/assets/release-own-account-send.recipe.json +80 -0
  89. package/library/recipes/assets/release-token-address-import.recipe.json +47 -0
  90. package/library/recipes/assets/release-token-details.recipe.json +48 -0
  91. package/library/recipes/assets/release-token-hide-readd.recipe.json +55 -0
  92. package/library/recipes/assets/release-token-list.recipe.json +75 -0
  93. package/library/recipes/assets/release-token-persistence.recipe.json +73 -0
  94. package/library/recipes/assets/release-token-search-import.recipe.json +40 -0
  95. package/library/recipes/assets/release-token-sorting.recipe.json +29 -0
  96. package/library/recipes/extension/networks/release-balance-stability.recipe.json +51 -0
  97. package/library/recipes/extension/perps/release-live-limit-order.recipe.json +104 -0
  98. package/library/recipes/extension/perps/release-order-entry.recipe.json +155 -0
  99. package/library/recipes/extension/perps/release-order-validation.recipe.json +220 -0
  100. package/library/recipes/perps/release-activity-filters.recipe.json +51 -0
  101. package/library/recipes/perps/release-funds-flows.recipe.json +76 -0
  102. package/library/recipes/perps/release-live-market-order.recipe.json +122 -0
  103. package/library/recipes/perps/release-market-details.recipe.json +83 -0
  104. package/library/recipes/perps/release-market-search.recipe.json +79 -0
  105. package/library/recipes/perps/release-watchlist.recipe.json +99 -0
  106. package/library/recipes/swap-bridge/release-custom-slippage.recipe.json +90 -0
  107. package/library/recipes/swap-bridge/release-live-max-swap.recipe.json +99 -0
  108. package/library/recipes/swap-bridge/release-live-swap.recipe.json +101 -0
  109. package/library/recipes/swap-bridge/release-max-quote.recipe.json +72 -0
  110. package/library/recipes/swap-bridge/release-quote.recipe.json +72 -0
  111. package/library/recipes/swap-bridge/release-surface.recipe.json +43 -0
  112. package/package.json +6 -5
  113. package/scripts/validate-release-report.mjs +319 -0
@@ -3,10 +3,19 @@ import { lstat, readFile, readdir, realpath } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
5
5
  "ui.locators",
6
+ "ui.activate_and_observe",
6
7
  "metamask.wallet.fixture_status",
7
8
  "metamask.wallet.validate_import",
8
9
  "metamask.wallet.list_accounts",
9
10
  "metamask.wallet.read_state",
11
+ "metamask.assets.read_visible_state",
12
+ "metamask.assets.read_details",
13
+ "metamask.networks.read_visible_state",
14
+ "metamask.perps.read_visible_state",
15
+ "metamask.perps.compare_provider_market",
16
+ "metamask.perps.read_eligibility",
17
+ "metamask.perps.read_funds_confirmation",
18
+ "metamask.swap_bridge.read_visible_state",
10
19
  "metamask.perps.read_positions",
11
20
  "metamask.perps.read_orders",
12
21
  "metamask.perps.read_account",
@@ -19,15 +28,37 @@ const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
19
28
  "metamask.analytics.assert_events"
20
29
  ]);
21
30
  const APP_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
31
+ "ui.activate_and_observe",
32
+ "metamask.assets.verify_sorting",
33
+ "metamask.assets.set_token_visibility",
34
+ "metamask.assets.import_custom_token",
35
+ "metamask.assets.prepare_send",
36
+ "metamask.assets.review_send",
37
+ "metamask.assets.finish_send",
38
+ "metamask.assets.set_custom_gas",
39
+ "metamask.assets.select_network_scope",
40
+ "metamask.wallet.lock",
41
+ "metamask.networks.add_custom",
42
+ "metamask.networks.add_chainlist",
43
+ "metamask.networks.remove_custom",
22
44
  "metamask.wallet.setup",
23
45
  "metamask.wallet.import",
24
46
  "metamask.wallet.reset",
25
47
  "metamask.wallet.ensure_unlocked",
26
48
  "metamask.wallet.select_account",
49
+ "metamask.swap_bridge.select_assets",
50
+ "metamask.swap_bridge.set_amount",
51
+ "metamask.swap_bridge.set_max_amount",
52
+ "metamask.swap_bridge.set_slippage",
53
+ "metamask.swap_bridge.submit_transaction",
54
+ "metamask.settings.set_basic_functionality",
27
55
  "metamask.deeplink.open",
28
56
  "metamask.perps.close_positions",
29
57
  "metamask.perps.close_orders",
30
58
  "metamask.perps.place_order",
59
+ "metamask.perps.close_visible_position",
60
+ "metamask.perps.open_position_action",
61
+ "metamask.perps.edit_margin",
31
62
  "metamask.perps.edit_order",
32
63
  "metamask.perps.update_position_tpsl",
33
64
  "metamask.perps.ensure_positions",
@@ -35,15 +66,24 @@ const APP_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
35
66
  "metamask.perps.clear_performance_caches",
36
67
  "metamask.perps.measure_homepage_visible",
37
68
  "metamask.perps.prepare_local_snapshot_endpoint",
69
+ "metamask.perps.set_market_favorite",
70
+ "metamask.perps.select_activity_filter",
71
+ "metamask.perps.select_market_filter",
72
+ "metamask.perps.open_balance_action",
38
73
  "metamask.perps.start_state",
39
74
  "metamask.perps.teardown_state",
40
75
  // Consent changes wallet state and is therefore an app mutation.
41
76
  "metamask.analytics.set_consent"
42
77
  ]);
43
78
  const EXTERNAL_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
79
+ "ui.activate_and_observe",
80
+ "metamask.assets.finish_send",
81
+ "metamask.swap_bridge.submit_transaction",
44
82
  "metamask.perps.close_positions",
45
83
  "metamask.perps.close_orders",
46
84
  "metamask.perps.place_order",
85
+ "metamask.perps.close_visible_position",
86
+ "metamask.perps.edit_margin",
47
87
  "metamask.perps.edit_order",
48
88
  "metamask.perps.update_position_tpsl",
49
89
  "metamask.perps.ensure_positions",
package/dist/runner.js CHANGED
@@ -246,6 +246,9 @@ function mobileSourceAwareLifecycleAdapters(adapter, lifecycle, readinessProbe =
246
246
  return lifecycle.map((entry) => ({
247
247
  ...entry,
248
248
  async execute(node, context) {
249
+ if (context.env?.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1") {
250
+ return entry.execute(node, context);
251
+ }
249
252
  const command = node.command ?? node.event ?? node.state;
250
253
  const reloadsSource = command === "launch" || command === "foreground" || command === "restart";
251
254
  const checksIosContinuity = command === "foreground" && !isAndroidLifecycle(node, context.env ?? {});
@@ -1,150 +1,125 @@
1
1
  # Release QA capability map
2
2
 
3
- This document maps the Extension release checklists to durable recipe
4
- capabilities. The baseline is `mm-harness` 0.36.0. It is a capability map, not
5
- a claim that the listed release cases currently pass.
6
-
7
- Source baseline: QA repository commit
3
+ This document maps the current mm-harness release-validation surface to the
4
+ MetaMask Extension QA checklists at commit
8
5
  [`77591c5`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/tree/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension).
9
- The relevant sources are the Perps
10
- [`checklist`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/perps-rc/perps-rc-regression.md)
11
- and
12
- [`runbook`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/perps-rc/runbook.md),
13
- Assets
14
- [`checklist`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/assets-rc/assets-rc-regression.md)
15
- and
16
- [`runbook`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/assets-rc/runbook.md),
17
- Swap and Bridge
18
- [`checklist`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/swap-bridge-rc/swap-bridge-rc-regression.md)
19
- and
20
- [`runbook`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/swap-bridge-rc/runbook.md),
21
- and Networks
22
- [`checklist`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/networks-regression-rc/networks-regression-rc.md)
23
- and
24
- [`runbook`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/networks-regression-rc/runbook.md).
25
-
26
- Ratings:
27
-
28
- - **AVAILABLE**: a typed capability supplies the required setup, operation, or
29
- machine-checkable observation.
30
- - **PARTIAL**: generic UI operations or a narrower domain action can perform
31
- part of the case, but deterministic setup or a domain assertion is absent.
32
- - **MISSING**: no durable recipe capability covers the case family.
33
- - **TARGET**: being implemented in the current change; not present in 0.36.0.
34
-
35
- ## Cross-cutting capabilities
36
-
37
- | Requirement | 0.36.0 capability | Status |
38
- | --- | --- | --- |
39
- | Discover the typed surface | `mm-harness actions --adapter extension --json` exposes the schemas in the [Extension action manifest](../library/manifests/extension.action-manifest.json), so a plan can reject unsupported domain operations before launch. | **AVAILABLE** |
40
- | Validate and launch the downloaded official RC artifact | Network discovery and download remain in the release-validation workflow, following the QA [`download-build.sh`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/assets-rc/scripts/download-build.sh) contract. | **TARGET**: verify a local Extension ZIP or Android APK against its trusted digest and declared product identity, then run it without using a checkout build. Extension adds safe extraction, cached tree integrity, and isolated launch. Android adds explicit-device install and launch; release recipes use native accessibility without a development bridge, Metro, or HUD. Downloaded iOS artifacts remain unsupported. |
41
- | Start from an imported wallet or clean onboarding | [`metamask.wallet.import`](../library/actions/extension/wallet/import.mjs), [`metamask.wallet.reset`](../library/actions/extension/wallet/reset.mjs), and the shared [`import`](../library/recipes/wallet/import.recipe.json) and [`reset-import`](../library/recipes/wallet/reset-import.recipe.json) recipes can use visible UI and parameterized credential sources. | **AVAILABLE** |
42
- | Drive and observe UI | The [Extension action manifest](../library/manifests/extension.action-manifest.json) exposes typed navigation, press, input, key, scroll, wait, screenshot, surface capture, locators, and CDP target selection. | **AVAILABLE** for mechanics; domain meaning remains **PARTIAL**. |
43
- | Record evidence and a terminal verdict | Recipe traces, action results, screenshots, and artifact indexing exist. There is no release-plan completion gate equivalent to the QA [`validate-run-report.sh`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/assets-rc/scripts/validate-run-report.sh), including canonical case-ID coverage and explicit remaining cases. | **PARTIAL** |
44
- | Parameterized environment control | Wallet import choices and Extension analytics consent are typed. Domain fixtures, service doubles, geo eligibility, network catalogs, and remote stream scenarios are not general release-plan inputs. | **MISSING** |
45
-
46
- ## Perps: 64 Critical cases
47
-
48
- The existing Perps surface can prepare, read, assert, and close selected
49
- positions and open orders, and place a basic order. See the
50
- [`Extension manifest`](../library/manifests/extension.action-manifest.json) and
51
- [`smoke recipe`](../library/recipes/extension/perps/smoke.recipe.json). It does
52
- not reproduce the checklist's seeded account snapshot: balance, long and short
53
- positions, limit orders, take-profit/stop-loss state, activity, watchlist, and
54
- controlled provider responses and streams.
55
-
56
- | Case family | Required start state | Required operations and assertions | 0.36.0 coverage |
6
+ It describes reusable capabilities, not a release verdict. A release verdict
7
+ must come from the case inventory, terminal traces, and completion gate for that
8
+ specific artifact.
9
+
10
+ The referenced plans contain 135 cases:
11
+
12
+ - Perps: [64 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/perps-rc/perps-rc-regression.md)
13
+ - Assets: [22 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/assets-rc/assets-rc-regression.md)
14
+ - Swap and Bridge: [39 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/swap-bridge-rc/swap-bridge-rc-regression.md)
15
+ - Networks: [10 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/networks-regression-rc/networks-regression-rc.md)
16
+
17
+ ## Artifact boundary
18
+
19
+ Artifact discovery and download belong to the release-validation workflow. The
20
+ workflow supplies mm-harness with a local artifact, expected product identity,
21
+ and trusted SHA-256. mm-harness never discovers or downloads a release.
22
+
23
+ For Extension, `runtime-launch` verifies the ZIP digest and manifest version,
24
+ extracts it safely, records archive and tree provenance, copies the exact tree
25
+ to an isolated runtime directory, resets only the selected Chrome profile, and
26
+ launches without building or patching the checkout.
27
+
28
+ For Android, `runtime-launch` requires one physical-device serial, verifies the
29
+ local APK digest, package ID, version name, and version code, installs it, and
30
+ binds subsequent recipes to the installed APK digest and selected device. The
31
+ release path does not use Metro, a development bridge, or the HUD.
32
+
33
+ ```sh
34
+ mm-harness runtime-launch \
35
+ --adapter extension \
36
+ --target /path/to/metamask-extension \
37
+ --cdp-port 9222 \
38
+ --artifact-file /path/to/metamask-chrome-X.Y.Z.zip \
39
+ --artifact-version X.Y.Z \
40
+ --artifact-sha256 <trusted-digest>
41
+
42
+ mm-harness runtime-launch \
43
+ --adapter mobile \
44
+ --platform android \
45
+ --target /path/to/metamask-mobile \
46
+ --device <physical-device-serial> \
47
+ --artifact-file /path/to/app.apk \
48
+ --artifact-version X.Y.Z \
49
+ --artifact-build-id <version-code> \
50
+ --artifact-package-id io.metamask \
51
+ --artifact-sha256 <trusted-digest>
52
+ ```
53
+
54
+ ## Completion gate
55
+
56
+ [`scripts/validate-release-report.mjs`](../scripts/validate-release-report.mjs)
57
+ compares a canonical case inventory with terminal verdicts. It rejects missing
58
+ or unknown IDs, duplicate IDs, non-terminal statuses, absent evidence, evidence
59
+ outside the declared root, incomplete run directories, and any non-empty
60
+ `remainingCases`. A passing gate writes `metadata.json`,
61
+ `report-quality.json`, and `report.md`.
62
+
63
+ Supported verdicts are `PASS`, `FAIL`, `BLOCKED_FUNDS`, `BLOCKED_COST`, and
64
+ `BLOCKED_ENVIRONMENT`. Product failures must remain failures; infrastructure,
65
+ funding, and cost limits must not be reported as passes.
66
+
67
+ ## Capability matrix
68
+
69
+ `AVAILABLE` means a typed action or bundled recipe covers the reusable
70
+ operation and produces machine-readable evidence. `PARTIAL` means the generic
71
+ operation exists but the complete checklist family still depends on task-local
72
+ composition, live funds, or deterministic external state. `MISSING` means the
73
+ current manifests do not expose the required durable capability.
74
+
75
+ ### Perps
76
+
77
+ | Case family | Required state and behavior | Current capability | Status |
57
78
  | --- | --- | --- | --- |
58
- | Feature, navigation, session (4) | Feature/eligibility combinations and accounts with different Perps state | Toggle gates, lock/unlock, switch accounts, navigate back; assert gating, account isolation, and route recovery | **PARTIAL**: wallet/account and generic navigation exist; deterministic gates and Perps session assertions are missing. |
59
- | Perps home (8) | Seeded balance, positions, orders, watchlist, and history | Open cards and balance actions, mutate watchlist; assert all seeded summaries and recent activity | **PARTIAL**: position/order reads and assertions exist; account, market, watchlist, and activity observations are missing. |
60
- | Market list (4) | Controlled catalog, categories, and price updates | Search, filter, sort, open a market; assert catalog and live values | **PARTIAL**: UI mechanics only; no market catalog or price assertion. |
61
- | Market details (6) | Selected market data, candles, position, and market-specific order | Navigate and change chart range; assert header, stats, chart states, action mode, and selected-market orders | **PARTIAL**: route and selected order/position state only; market and chart observations are missing. |
62
- | Order entry (12) | Known balance, constraints, price, leverage limits, and order outcomes | Edit side, amount, percent, leverage, limit price, TP/SL; submit market/limit orders; assert validation, preview, fill, resting order, and duplicate protection | **PARTIAL**: basic placement and open-order convergence exist; rich entry controls, limit/TP-SL inputs, validation, and outcome fields are missing. |
63
- | Position management (11) | Seeded isolated position, margin, TP/SL, and resting order | Add/reduce exposure, add margin, edit TP/SL, cancel order; assert size, margin, liquidation price, TP/SL, and removal | **PARTIAL**: close positions/orders exists; modify, margin, TP/SL, and rich field assertions are missing. |
64
- | Deposit (3) | Funded wallet and known Perps balance | Enter amount/Max and confirm; assert validation, pending/success, and resulting balances/activity | **MISSING** |
65
- | Withdraw (4) | Known withdrawable balance | Enter amount/percent/Max and confirm; assert validation, destination, balance, and activity | **MISSING** |
66
- | Activity and history (6) | Seeded trades, orders, funding, deposits, and withdrawals | Filter and open details; assert type-specific rows and agreement with home | **MISSING** |
67
- | Streaming and resilience (4) | Controllable initial responses, updates, reconnects, and order events | Refresh, disconnect/reconnect, close/reopen; assert price, position, order, activity, and loading-state reconciliation | **MISSING** |
68
- | Geo/compliance (1) | Ineligible trade state | Attempt a trade; assert blocking UI and no submitted order | **MISSING** |
69
- | Responsive presentation (1) | Popup-width Perps views with populated content | Traverse core views and capture surfaces; assert no clipping or overlap | **PARTIAL**: screenshots and generic UI assertions exist; no reusable Perps presentation assertion. |
70
-
71
- The aggregate Extension Perps snapshot is a **TARGET**. It exposes account,
72
- market, position, open-order, activity, and watchlist observations in one typed,
73
- read-only result when the Extension background test hook is present. An
74
- untouched production artifact does not expose that hook, so official-artifact
75
- Perps assertions still require a product-supported observation boundary. The
76
- snapshot also does not create the required start state.
77
-
78
- ## Assets: 22 Critical cases
79
-
80
- | Case family | Required start state | Required operations and assertions | 0.36.0 coverage |
79
+ | Navigation, home, markets, and details | Eligible account, live catalog, market summaries, chart, statistics, and route recovery | Visible-state reads, market navigation/search/filter, provider comparison, eligibility reads, and market-detail recipes | **AVAILABLE** for visible live flows |
80
+ | Watchlist and activity | Existing favorites and typed activity rows | Favorite mutation, activity-filter selection, and release recipes | **AVAILABLE** |
81
+ | Order entry | Funded Perps balance, market/limit inputs, validation, preview, fill, and duplicate protection | Parameterized order placement plus visible order-entry and validation recipes | **PARTIAL**: live state and provider outcomes remain release-dependent |
82
+ | Position management | Open position, close, margin, and TP/SL changes | Visible position actions, close, edit-margin, and TP/SL actions | **PARTIAL**: exact outcome depends on live account state |
83
+ | Deposit and withdrawal | Wallet funds, Perps balance, confirmations, and resulting activity | Balance actions and confirmation reads with a funds-flow recipe | **PARTIAL**: funds and provider availability are external |
84
+ | Snapshot assertions | Positions, orders, markets, history, and watchlist | Bounded redacted snapshot when the background hook exists; visible-state reads on untouched releases | **AVAILABLE** with runtime-specific observation paths |
85
+ | Deterministic seeded provider state | Balance, positions, limit orders, TP/SL, history, and controlled REST/stream behavior | Existing setup fixtures do not reproduce the complete QA provider model inside an untouched release artifact | **MISSING** |
86
+
87
+ ### Assets
88
+
89
+ | Case family | Required state and behavior | Current capability | Status |
81
90
  | --- | --- | --- | --- |
82
- | Token list, prices, details, sorting (4) | Imported account with known live holdings across networks | Open and sort token list/details; assert holdings, fiat, change, chart, and market data | **PARTIAL**: wallet import and UI mechanics only; no asset reads or assertions. |
83
- | Token import, hide, persistence, custom network (5) | Known ERC-20 contracts and custom network configuration | Search/import/hide/re-add/restart/remove network; assert visibility and persistence | **MISSING**: no token-list/import or custom-network domain actions. |
84
- | Real-time and aggregate balances (2) | Known multi-chain balances and an externally triggered update | Observe stream and aggregate; assert prompt update and total | **MISSING** |
85
- | EVM send and activity (3) | Two owned accounts with native/token funds and gas | Send token, native Max, and custom-gas transaction; assert confirmation, balances, gas behavior, and activity | **MISSING** |
86
- | NFTs (3) | Owned indexed NFT plus owned and unowned contracts | Detect/import/filter/open media; assert ownership rejection, metadata, image, and network filter | **MISSING** |
87
- | DeFi positions (2) | Account with known live protocol positions | Open list/details; assert protocol, supplied/borrowed assets, and USD totals | **MISSING** |
88
- | Solana assets and send (2) | Imported Solana account with SOL and SPL balances | Inspect assets and send SPL; assert fiat, confirmation, balances, and activity | **MISSING** |
89
- | Bitcoin balance (1) | Imported Bitcoin account with known balance | Open asset details; assert balance, fiat, and chart | **MISSING** |
90
-
91
- ## Swap and Bridge: 39 cases (25 Critical, 14 Non-Critical)
92
-
93
- These cases intentionally form a sequential, live-funds journey. A successful
94
- step changes the next step's network and balances, so generic navigation alone
95
- cannot establish or prove the required state transitions.
96
-
97
- | Case family | Required start state | Required operations and assertions | 0.36.0 coverage |
91
+ | Token list, prices, details, and sorting | Funded account with known holdings and price data | Visible token-list/details reads, network scope, sorting assertions, and bundled recipes | **AVAILABLE** for EVM token UI |
92
+ | Token search/import/hide/persistence | Known contracts and a persistent profile | Custom-token import, visibility changes, restart-safe recipes, and custom-network composition | **AVAILABLE** for Extension EVM tokens |
93
+ | Native/token send and gas | Funded owned accounts and live gas | Prepare, review, custom-gas, finish, and Max/cancel/send recipes | **PARTIAL**: terminal submission is gated by funds and cost policy |
94
+ | Aggregate Android holdings | Imported wallet with multi-chain assets | Native-accessibility token-list read on an untouched physical-device release | **AVAILABLE** |
95
+ | Balance streaming and aggregation arithmetic | Triggered inbound update and expected cross-network totals | Visible snapshots exist; deterministic inbound trigger and tolerance assertions are not bundled | **PARTIAL** |
96
+ | NFTs, DeFi, Solana send, and Bitcoin details | Indexed owned positions/assets and chain-specific transaction flows | Generic visible UI only | **MISSING** |
97
+
98
+ ### Swap and Bridge
99
+
100
+ | Case family | Required state and behavior | Current capability | Status |
98
101
  | --- | --- | --- | --- |
99
- | Random ERC-20 round trip on Ethereum (2 Critical) | Funded imported account and live token/quote services | Select eligible random token, swap amount then Max; assert quote, confirmation, balance deltas, and activity | **MISSING** |
100
- | WETH round trip (2 Critical) | Funded Ethereum account | Swap ETH to WETH and Max back; assert quotes, wrapping result, balance deltas, and activity | **MISSING** |
101
- | Gasless round trip (2 Critical) | Smart Transactions enabled and funded ETH/USDC balances | Execute full-balance gasless swaps; assert route, no stranded input, balances, and activity | **MISSING** |
102
- | Ethereum to native Solana (2 Critical) | Funded Ethereum source and derived Solana destination | Bridge Max and await completion; assert destination asset is native SOL, balances, and activity | **MISSING** |
103
- | Half/Max swaps across destination networks (12 Critical, 8 Non-Critical) | Balance delivered by the preceding bridge on each network | Pick supported random token, swap about half and Max back; assert quote/route, confirmation, balance deltas, and activity | **MISSING** |
104
- | Sequential onward bridges (5 Critical, 4 Non-Critical) | Source balance produced by the preceding swap | Bridge Max to the specified next network and await completion; assert source/destination assets, balances, and activity | **MISSING** |
105
- | Quote-only networks (2 Non-Critical) | Selected network and funded source asset | Request but do not submit a quote; assert supported routes, fees, and destination | **MISSING** |
102
+ | Surface, asset, network, and amount selection | Supported source/destination pair | Exact asset/network selection, amount/Max input, and visible-state reads on Extension; exact native selection and amount input on Android | **AVAILABLE** |
103
+ | Quote and slippage | Live quote services and supported route | Surface, quote, Max-quote, and custom-slippage recipes | **AVAILABLE** |
104
+ | Submit and confirmation | Sufficient source balance and gas | Typed submit action with explicit confirmation policy | **PARTIAL**: live funds and cost policy decide whether submission is allowed |
105
+ | Sequential multi-network journey | Balances delivered by preceding swaps and bridges | Individual steps are composable, but the complete live-funds journey remains task-local and resumable by its case inventory | **PARTIAL** |
106
+ | Activity, balance deltas, and completed bridge tracking | Confirmed transactions and indexed activity | Visible-state evidence exists; reusable terminal activity and arithmetic assertions are incomplete | **PARTIAL** |
106
107
 
107
- ## Networks: 10 cases (7 Critical, 3 not marked Critical)
108
+ ### Networks
108
109
 
109
- | Case family | Required start state | Required operations and assertions | 0.36.0 coverage |
110
+ | Case family | Required state and behavior | Current capability | Status |
110
111
  | --- | --- | --- | --- |
111
- | Custom-network asset rendering (4) | HyperEVM, INK, and XDC configurations plus known token contracts/balances | Add/select networks and tokens; assert decimals, render stability, logos, and token rows | **MISSING** |
112
- | Fiat and price APIs (2) | Custom networks with supported native assets and conversion responses | Refresh/select network; assert conversion rates and fiat values | **MISSING** |
113
- | Send Max and activity (2) | Funded HyperEVM/INK accounts and recipient | Send native Max; assert review amount, gas handling, completion, balance, and activity | **MISSING** |
114
- | Bridge label, tracking, details (2) | Funded Monad account and a completed bridge route | Submit/open bridge activity; assert label, status tracking, route, fees, and details | **MISSING** |
115
-
116
- ## Prioritized delivery order
117
-
118
- 1. **Downloaded official artifact validation and launch — current target.**
119
- Accept a local ZIP, trusted digest, and requested version from the
120
- release-validation workflow; validate the manifest and extracted tree,
121
- reuse a validated cache, and launch without modifying the source checkout
122
- or original artifact. Prove onboarding, visible wallet import, normal popup
123
- operation, and side-panel operation.
124
- 2. **Aggregate Extension Perps snapshot current target.** Add a typed,
125
- read-only result for account, markets, positions, open orders, activity, and
126
- watchlist, with selectors suitable for machine assertions. It is usable in
127
- source-built runtimes that expose the background test hook; an untouched
128
- production artifact must fail explicitly until it has a supported read-only
129
- observation boundary.
130
- 3. **Deterministic Perps start state.** Add setup-time, trace-visible provider
131
- doubles and seeded balance, positions, limit orders, TP/SL, activity, and
132
- watchlist. Keep setup outside the proof window and never mutate product
133
- state to manufacture an assertion.
134
- 4. **Complete Perps operations and assertions.** Add parameterized feature and
135
- eligibility control, watchlist, rich order entry, position changes, margin,
136
- TP/SL, deposits, withdrawals, activity filters, and stream recovery. Extend
137
- shared Mobile/Extension vocabulary where both clients support the behavior.
138
- 5. **Release-plan completion gate.** Validate canonical case IDs, verdict
139
- counts, required evidence, and explicit remaining cases before reporting a
140
- terminal result.
141
- 6. **Assets and Networks domain surfaces.** Add shared network selection and
142
- custom-network state first, then typed assets, balances, token import,
143
- transaction/activity, NFT, DeFi, and non-EVM observations.
144
- 7. **Swap and Bridge domain surface.** Add quote, route, submit, status,
145
- balance-delta, and activity actions with explicit live-funds safeguards and
146
- resumable sequential state.
147
-
148
- Completing the two current targets does **not** provide full Perps parity. The
149
- 64-case plan still depends on deterministic domain state, controlled provider
150
- behavior, richer mutations and assertions, and a canonical completion gate.
112
+ | Add, select, and remove custom networks | Known RPC configuration and chain identity | Typed custom-network and Chainlist add, visible-state read, select, and remove actions | **AVAILABLE** on Extension |
113
+ | Custom-network tokens and persistence | Known token contract and persistent profile | Network actions compose with Assets import/visibility/persistence recipes | **AVAILABLE** on Extension |
114
+ | Balance stability and fiat visibility | Funded custom network and live price APIs | Visible-state reads and a bounded balance-stability recipe | **PARTIAL**: external price availability remains uncontrolled |
115
+ | Send Max and activity | Funded custom network and recipient | Assets send actions cover the transaction path | **PARTIAL**: exact activity and balance-delta assertions remain task-local |
116
+ | Bridge tracking and details | Completed supported bridge route | Swap/Bridge composition exists; reusable terminal tracking assertions are incomplete | **PARTIAL** |
117
+ | Android multi-network visibility | Imported wallet with holdings on multiple chains | Native Assets read reports aggregate visible holdings without Metro or a development bridge | **AVAILABLE** |
118
+
119
+ ## Platform scope
120
+
121
+ The 135-case source inventory targets Extension. It must not be relabeled as an
122
+ Android checklist. Android release validation uses a separate case inventory
123
+ for the product behaviors exercised on a physical device, while reusing the
124
+ same action vocabulary and report gate where the clients expose equivalent
125
+ flows. Simulator-only evidence is not accepted for Android release claims.
@@ -0,0 +1,128 @@
1
+ import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
+ import { activityItem, bodyState, selectors, tokenRow, transactionState, visible, waitFor } from './send.mjs';
3
+
4
+ runAdapter(async (input) => withExtensionPage(input, async (page) => {
5
+ const decision = String(input.node?.decision || '').trim();
6
+ const symbol = String(input.node?.symbol || '').trim().toUpperCase();
7
+ const amount = String(input.node?.amount || '').trim();
8
+ const resumeSubmitted = input.node?.resume_submitted === true;
9
+ const resumedHash = String(input.node?.transaction_hash || '').trim();
10
+ const timeoutMs = Number(input.node?.timeout_ms ?? 120_000);
11
+ if (!['confirm', 'cancel'].includes(decision) || !symbol || !amount) {
12
+ throw new Error('metamask.assets.finish_send requires decision=confirm|cancel, symbol, and amount.');
13
+ }
14
+ if (decision === 'confirm' && input.node?.confirm_live !== true) {
15
+ throw new Error('metamask.assets.finish_send decision=confirm requires confirm_live=true.');
16
+ }
17
+ const control = decision === 'confirm' ? selectors.confirm : selectors.cancel;
18
+ let state = await bodyState(page);
19
+ let submittedTransaction = null;
20
+ let submittedNow = false;
21
+ if (await visible(page, control)) {
22
+ const beforeTransactions = decision === 'confirm' ? await transactionState(page) : null;
23
+ if (decision === 'confirm' && !beforeTransactions?.available) {
24
+ throw new Error('Transaction state is unavailable; refusing to submit without proof binding.');
25
+ }
26
+ const beforeById = new Map((beforeTransactions?.transactions ?? []).map((transaction) => [transaction.id, transaction]));
27
+ await page.click(control);
28
+ submittedNow = decision === 'confirm';
29
+ state = await waitFor(page, async () => {
30
+ const current = await bodyState(page);
31
+ return current.route === '' || current.route === '#/' ? current : null;
32
+ }, 'Wallet did not return after send decision.', timeoutMs);
33
+ if (decision === 'confirm') {
34
+ submittedTransaction = await waitFor(page, async () => {
35
+ const current = await transactionState(page);
36
+ const changed = current.transactions.filter((transaction) => {
37
+ const before = beforeById.get(transaction.id);
38
+ if (!before) return true;
39
+ const wasAwaitingApproval = ['unapproved', 'approved'].includes(String(before.status).toLowerCase());
40
+ return wasAwaitingApproval && (before.status !== transaction.status || before.hash !== transaction.hash);
41
+ });
42
+ if (changed.length > 1) {
43
+ throw new Error(`Send submission changed multiple transaction records: ${changed.map((transaction) => transaction.id).join(', ')}.`);
44
+ }
45
+ return changed[0] ?? null;
46
+ }, 'Send confirmation did not change one transaction record.', timeoutMs);
47
+ }
48
+ } else if (decision === 'cancel') {
49
+ throw new Error('Send cancel control was not visible.');
50
+ } else if (!resumeSubmitted) {
51
+ throw new Error('Send confirm control was not visible. Next: rerun with resume_submitted=true only if this exact transaction was already submitted.');
52
+ } else if (!resumedHash) {
53
+ throw new Error('metamask.assets.finish_send resume_submitted=true requires transaction_hash.');
54
+ } else if (!/^0x[0-9a-f]{64}$/iu.test(resumedHash)) {
55
+ throw new Error('metamask.assets.finish_send transaction_hash must be a full EVM transaction hash.');
56
+ }
57
+ if (decision === 'cancel') {
58
+ return { action: input.action, decision, cancelled: true, proofPath: 'trusted-pointer-visible-ui' };
59
+ }
60
+ submittedTransaction = await waitFor(page, async () => {
61
+ const current = await transactionState(page);
62
+ const transaction = submittedNow
63
+ ? current.transactions.find((entry) => entry.id === submittedTransaction?.id)
64
+ : current.transactions.find((entry) => entry.hash?.toLowerCase() === resumedHash.toLowerCase());
65
+ if (!transaction) return null;
66
+ if (['failed', 'rejected', 'dropped'].includes(String(transaction.status).toLowerCase())) {
67
+ throw new Error(`Send transaction ${transaction.id} reached ${transaction.status}.`);
68
+ }
69
+ return String(transaction.status).toLowerCase() === 'confirmed' && /^0x[0-9a-f]{64}$/iu.test(transaction.hash || '')
70
+ ? transaction
71
+ : null;
72
+ }, `Sent ${amount} ${symbol} did not produce one confirmed transaction record.`, timeoutMs);
73
+ let tokenAfter = null;
74
+ if (!state.route.includes('/tx/')) {
75
+ if (state.route.includes('/activity')) {
76
+ await page.click(selectors.home);
77
+ }
78
+ tokenAfter = await waitFor(page, () => tokenRow(page, symbol), `${symbol} did not reappear after send.`, timeoutMs);
79
+ await waitFor(page, async () => {
80
+ const current = await bodyState(page);
81
+ if (current.route.includes('/activity') || current.route.includes('/tx/')) return current;
82
+ await page.click(selectors.activity);
83
+ return null;
84
+ }, 'Activity did not open after send.', timeoutMs);
85
+ }
86
+ let activity = null;
87
+ if (!state.route.includes('/tx/')) {
88
+ activity = await waitFor(page, () => activityItem(page, symbol, amount), `Sent ${amount} ${symbol} did not appear in Activity.`, timeoutMs);
89
+ await waitFor(page, async () => {
90
+ const current = await bodyState(page);
91
+ if (current.route.includes('/tx/')) return current;
92
+ const currentActivity = await activityItem(page, symbol, amount);
93
+ if (currentActivity) {
94
+ activity = currentActivity;
95
+ await page.click(currentActivity.selector);
96
+ }
97
+ return null;
98
+ }, `Sent ${amount} ${symbol} transaction details did not open.`, timeoutMs);
99
+ }
100
+ const details = await waitFor(page, async () => {
101
+ const current = await bodyState(page);
102
+ if (!current.route.includes('/tx/') || !/\bConfirmed\b/u.test(current.text)) return null;
103
+ return current;
104
+ }, `Sent ${amount} ${symbol} did not reach Confirmed.`, timeoutMs);
105
+ const explorerUrl = await page.evaluate(`document.querySelector('[data-testid="transaction-details-block-explorer"]')?.getAttribute('data-explorer-url') || null`);
106
+ const transactionHash = details.route.match(/\/(0x[0-9a-f]{64})(?:\?|$)/iu)?.[1]
107
+ ?? details.text.match(/\b0x[0-9a-f]{64}\b/iu)?.[0]
108
+ ?? String(explorerUrl || '').match(/\/(0x[0-9a-f]{64})(?:[/?#]|$)/iu)?.[1]
109
+ ?? null;
110
+ if (!transactionHash) throw new Error('Confirmed transaction details did not expose a full transaction hash.');
111
+ if (transactionHash.toLowerCase() !== submittedTransaction.hash.toLowerCase()) {
112
+ throw new Error(`Confirmed Activity details opened ${transactionHash}, expected ${submittedTransaction.hash}.`);
113
+ }
114
+ return {
115
+ action: input.action,
116
+ decision,
117
+ symbol,
118
+ amount,
119
+ activity,
120
+ tokenAfter,
121
+ details,
122
+ explorerUrl,
123
+ transactionHash,
124
+ transactionId: submittedTransaction.id,
125
+ resumed: !submittedNow,
126
+ proofPath: 'trusted-pointer-visible-ui',
127
+ };
128
+ }));