@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 +11 -0
- package/README.md +2 -0
- package/library/actions/shared/perps/visible-state.mjs +15 -0
- package/library/manifests/extension.action-manifest.json +1 -1
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/library/recipes/extension/wallet/accept-terms.recipe.json +94 -0
- package/library/recipes/shared/perps/release-market-details.recipe.json +53 -3
- package/library/recipes/shared/perps/release-market-search.recipe.json +56 -4
- package/package.json +1 -1
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": [
|
|
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": "
|
|
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": [
|
|
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": [
|
|
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": "
|
|
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
|
}
|