@deeeed/metamask-harness 0.64.0 → 0.65.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.65.0 - 2026-09-25
6
+
7
+ ### Added
8
+
9
+ - Explicit Extension `wallet.accept-terms` setup recipe with caller-authorized consent and enabled-state checks.
10
+ - Structured visible market-detail identity and price observations, with null for missing or ambiguous fields.
11
+
12
+ ### Fixed
13
+
14
+ - Give bundled market-detail and search recipes portable runtime assertions so composed QA cases can validate their existing behavioral contract.
15
+
5
16
  ## 0.64.0 - 2026-09-25
6
17
 
7
18
  ### Added
package/README.md CHANGED
@@ -226,6 +226,8 @@ mm-harness run --list
226
226
  mm-harness run perps.smoke --describe
227
227
  mm-harness run wallet.smoke --describe
228
228
  mm-harness run wallet.import method=auto
229
+ # When a visible Terms of Use gate follows import and acceptance is authorized:
230
+ mm-harness run wallet.accept-terms accept=true
229
231
 
230
232
  # Use visible client UI to delete and import the wallet again.
231
233
  mm-harness run wallet.reset-import
@@ -232,11 +232,25 @@ export function normalizeVisiblePerpsState(raw, options, platform) {
232
232
  ...(item.label ? { label: item.label } : {}),
233
233
  visible: item.visible,
234
234
  }));
235
+ const detailField = (pattern) => {
236
+ const matches = items.filter((item) => item.visible && pattern.test(item.testId));
237
+ const values = [...new Set(matches.map((item) => item.label).filter(Boolean))];
238
+ return values.length === 1 ? values[0] : null;
239
+ };
240
+ const detailPair = detailField(/^(?:perps-market-detail-pair|perps-(?:pro-)?market-header-subtitle)$/u);
241
+ const detailSymbol = detailPair?.match(/^(.+?)-(?:USDC|USD|PERP)(?:\s+perp)?$/iu)?.[1]?.toUpperCase() ?? null;
235
242
  const state = {
236
243
  source: 'visible-ui',
237
244
  platform,
238
245
  surface,
239
246
  favorite,
247
+ marketDetails: surface === 'market-details' ? {
248
+ symbol: detailSymbol,
249
+ pair: detailPair,
250
+ name: detailField(/^(?:perps-market-detail-name|perps-(?:pro-)?market-header-title)$/u),
251
+ price: detailField(/^perps-(?:market-detail-(?:header-)?price|market-header-price|pro-market-price)$/u),
252
+ priceChange: detailField(/^perps-(?:market-detail-(?:header-)?change|market-header-price-change|pro-market-price-change)$/u),
253
+ } : null,
240
254
  ...(route ? { route } : {}),
241
255
  ...(text(raw.title) ? { title: text(raw.title) } : {}),
242
256
  balance: compactEntries(
@@ -244,6 +258,7 @@ export function normalizeVisiblePerpsState(raw, options, platform) {
244
258
  1,
245
259
  )[0] ?? null,
246
260
  markets: marketItems,
261
+ marketCount: marketItems.length,
247
262
  positions: compactEntries(
248
263
  selected(unique, (item) => /^(?:position-card-(?!roe-)|perps-position-row-|perps-position-size-value$|perps-pro-market-position-row-|perps-home-position-card-)/u.test(item.testId), options.maxItems),
249
264
  options.maxItems,
@@ -2809,7 +2809,7 @@
2809
2809
  ]
2810
2810
  },
2811
2811
  "metamask.perps.read_visible_state": {
2812
- "description": "Read and assert bounded visible Perps UI state without product background hooks. favorite is boolean only when the requested market pair and one favorite control are both visible; otherwise null, never an assumed false.",
2812
+ "description": "Read and assert bounded visible Perps UI state without product background hooks. marketDetails exposes visible symbol, pair, name, price and priceChange as text, or null when absent/ambiguous. marketCount counts the bounded observed markets array, not an unbounded total. favorite is boolean only when the requested market pair and one favorite control are both visible; otherwise null, never an assumed false.",
2813
2813
  "schema": {
2814
2814
  "type": "object",
2815
2815
  "properties": {
@@ -2178,7 +2178,7 @@
2178
2178
  "execution_capabilities": ["app-mutation"]
2179
2179
  },
2180
2180
  "metamask.perps.read_visible_state": {
2181
- "description": "Read and assert bounded visible Perps UI state through native accessibility. favorite is boolean only when the requested market pair and one favorite control are both observed as visible; otherwise null, never an assumed false.",
2181
+ "description": "Read and assert bounded visible Perps UI state through native accessibility. favorite is boolean only when the requested market pair and one favorite control are both observed as visible; otherwise null, never an assumed false. marketDetails exposes visible symbol, pair, name, price and priceChange as text, or null when absent/ambiguous. marketCount counts the bounded observed markets array, not an unbounded total.",
2182
2182
  "schema": {
2183
2183
  "type": "object",
2184
2184
  "properties": {
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
+ "title": "Accept the visible wallet Terms of Use",
4
+ "description": "Setup-only Extension journey for a visible Terms of Use gate. Requires explicit caller authority to accept the displayed terms. Scrolls through the agreement, checks consent and waits until Agree is enabled, then proves the gate is absent. Does not import, unlock or change network/trading state; call after wallet.import when this gate is present.",
5
+ "paramsSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "accept"
10
+ ],
11
+ "properties": {
12
+ "accept": {
13
+ "type": "boolean",
14
+ "enum": [
15
+ true
16
+ ],
17
+ "description": "The caller authorizes accepting the displayed terms for this fixture."
18
+ },
19
+ "timeout_ms": {
20
+ "type": "integer",
21
+ "minimum": 1000,
22
+ "default": 15000
23
+ }
24
+ }
25
+ },
26
+ "proofTargets": [
27
+ {
28
+ "id": "terms-dismissed",
29
+ "claim": "The visible Terms of Use gate is accepted through its controls and no longer covers the wallet."
30
+ }
31
+ ],
32
+ "workflow": {
33
+ "entry": "require-terms",
34
+ "nodes": {
35
+ "require-terms": {
36
+ "action": "ui.wait_for",
37
+ "test_id": "terms-of-use-agree-button",
38
+ "expected": "visible",
39
+ "timeout_ms": "{{params.timeout_ms}}",
40
+ "intent": "Require the agreement to be visible before changing consent",
41
+ "next": "scroll-terms"
42
+ },
43
+ "scroll-terms": {
44
+ "action": "ui.press",
45
+ "test_id": "terms-of-use-scroll-button",
46
+ "intent": "Reach the end of the displayed agreement",
47
+ "next": "wait-checkbox"
48
+ },
49
+ "wait-checkbox": {
50
+ "action": "ui.wait_for",
51
+ "selector": "[data-testid=\"terms-of-use-checkbox\"]:not([disabled])",
52
+ "expected": "visible",
53
+ "timeout_ms": "{{params.timeout_ms}}",
54
+ "intent": "The displayed agreement allows the caller to record consent",
55
+ "next": "check-consent"
56
+ },
57
+ "check-consent": {
58
+ "action": "ui.press",
59
+ "test_id": "terms-of-use-checkbox",
60
+ "intent": "Record the caller-authorized consent through the checkbox",
61
+ "next": "wait-agree"
62
+ },
63
+ "wait-agree": {
64
+ "action": "ui.wait_for",
65
+ "selector": "[data-testid=\"terms-of-use-agree-button\"]:not([disabled])",
66
+ "expected": "visible",
67
+ "timeout_ms": "{{params.timeout_ms}}",
68
+ "intent": "The completed agreement review enables its confirmation control",
69
+ "next": "agree"
70
+ },
71
+ "agree": {
72
+ "action": "ui.press",
73
+ "test_id": "terms-of-use-agree-button",
74
+ "intent": "Accept the displayed terms for the authorized fixture",
75
+ "next": "verify-dismissed"
76
+ },
77
+ "verify-dismissed": {
78
+ "action": "ui.wait_for",
79
+ "test_id": "terms-of-use-agree-button",
80
+ "expected": "absent",
81
+ "timeout_ms": "{{params.timeout_ms}}",
82
+ "proves": [
83
+ "terms-dismissed"
84
+ ],
85
+ "intent": "Prove the agreement no longer covers wallet controls",
86
+ "next": "done"
87
+ },
88
+ "done": {
89
+ "action": "end",
90
+ "status": "pass"
91
+ }
92
+ }
93
+ }
94
+ }
@@ -40,7 +40,9 @@
40
40
  "surface": "market-list",
41
41
  "market": "{{params.market}}",
42
42
  "minimum_market_count": 2,
43
- "require_features": ["market-list"],
43
+ "require_features": [
44
+ "market-list"
45
+ ],
44
46
  "max_items": 100,
45
47
  "intent": "Prove the requested market appears in a populated live market list",
46
48
  "next": "open-market"
@@ -65,7 +67,7 @@
65
67
  ],
66
68
  "max_items": 100,
67
69
  "intent": "Prove the selected market, live pricing, statistics, and trade directions are visible",
68
- "next": "capture-market"
70
+ "next": "verify-directions"
69
71
  },
70
72
  "capture-market": {
71
73
  "action": "ui.screenshot",
@@ -77,7 +79,55 @@
77
79
  "done": {
78
80
  "action": "end",
79
81
  "status": "pass"
82
+ },
83
+ "verify-price": {
84
+ "action": "assert_output",
85
+ "source": "assert-market",
86
+ "assert": {
87
+ "path": "$.features.price",
88
+ "operator": "eq",
89
+ "value": true
90
+ },
91
+ "proves": [
92
+ "market-details"
93
+ ],
94
+ "intent": "The requested market displays a price quote",
95
+ "next": "capture-market"
96
+ },
97
+ "verify-stats": {
98
+ "action": "assert_output",
99
+ "source": "assert-market",
100
+ "assert": {
101
+ "path": "$.features.stats",
102
+ "operator": "eq",
103
+ "value": true
104
+ },
105
+ "proves": [
106
+ "market-details"
107
+ ],
108
+ "intent": "The requested market displays its statistics",
109
+ "next": "verify-price"
110
+ },
111
+ "verify-directions": {
112
+ "action": "assert_output",
113
+ "source": "assert-market",
114
+ "assert": {
115
+ "path": "$.features.long-short",
116
+ "operator": "eq",
117
+ "value": true
118
+ },
119
+ "proves": [
120
+ "market-details"
121
+ ],
122
+ "intent": "Both trading directions are available for the requested market",
123
+ "next": "verify-stats"
80
124
  }
81
125
  }
82
- }
126
+ },
127
+ "proofTargets": [
128
+ {
129
+ "id": "market-details",
130
+ "claim": "The requested market shows its live price, statistics and both trade directions."
131
+ }
132
+ ]
83
133
  }
@@ -45,7 +45,9 @@
45
45
  "action": "metamask.perps.read_visible_state",
46
46
  "surface": "market-list",
47
47
  "minimum_market_count": "{{params.minimum_unfiltered_results}}",
48
- "require_features": ["market-list"],
48
+ "require_features": [
49
+ "market-list"
50
+ ],
49
51
  "max_items": 100,
50
52
  "intent": "Prove the live market list is populated before filtering",
51
53
  "next": "search"
@@ -62,10 +64,12 @@
62
64
  "market": "{{params.expected_market}}",
63
65
  "minimum_market_count": 1,
64
66
  "maximum_market_count": "{{params.maximum_results}}",
65
- "require_features": ["market-list"],
67
+ "require_features": [
68
+ "market-list"
69
+ ],
66
70
  "max_items": 100,
67
71
  "intent": "Prove the search reduced the list and retained the requested market",
68
- "next": "capture"
72
+ "next": "verify-result"
69
73
  },
70
74
  "capture": {
71
75
  "action": "ui.screenshot",
@@ -77,7 +81,55 @@
77
81
  "done": {
78
82
  "action": "end",
79
83
  "status": "pass"
84
+ },
85
+ "verify-result": {
86
+ "action": "assert_output",
87
+ "source": "assert-filtered",
88
+ "assert": {
89
+ "path": "$.markets",
90
+ "operator": "length_lte",
91
+ "value": "{{params.maximum_results}}"
92
+ },
93
+ "proves": [
94
+ "filtered-market"
95
+ ],
96
+ "intent": "Assert the observed filtered result count stays within the requested bounds",
97
+ "next": "verify-nonempty"
98
+ },
99
+ "verify-nonempty": {
100
+ "action": "assert_output",
101
+ "source": "assert-filtered",
102
+ "assert": {
103
+ "path": "$.markets",
104
+ "operator": "length_gte",
105
+ "value": 1
106
+ },
107
+ "proves": [
108
+ "filtered-market"
109
+ ],
110
+ "intent": "The filtered market list contains at least one result",
111
+ "next": "verify-reduction"
112
+ },
113
+ "verify-reduction": {
114
+ "action": "assert_output",
115
+ "source": "assert-filtered",
116
+ "assert": {
117
+ "path": "$.markets",
118
+ "operator": "length_lt",
119
+ "value": "{{outputs.assert-unfiltered.marketCount}}"
120
+ },
121
+ "proves": [
122
+ "filtered-market"
123
+ ],
124
+ "intent": "The query reduces the observed market list from its unfiltered count",
125
+ "next": "capture"
80
126
  }
81
127
  }
82
- }
128
+ },
129
+ "proofTargets": [
130
+ {
131
+ "id": "filtered-market",
132
+ "claim": "The entered query reduces the live market list and retains the expected market."
133
+ }
134
+ ]
83
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"