@deeeed/metamask-harness 0.64.0 → 0.66.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 +24 -0
- package/README.md +2 -0
- package/library/actions/extension/perps/read_visible_state.mjs +28 -1
- package/library/actions/extension/perps/select_market_filter.mjs +2 -2
- package/library/actions/shared/perps/visible-state.mjs +35 -3
- package/library/manifests/extension.action-manifest.json +2 -1
- package/library/manifests/mobile.action-manifest.json +2 -1
- package/library/recipes/extension/perps/smoke.recipe.json +52 -3
- 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 +57 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.66.0 - 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Observe Top Movers pills and visible counts separately from ordinary market rows.
|
|
10
|
+
- Expose market-filter selection matches from observed controls for composable proof assertions.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Give Extension Perps smoke its own runtime assertions and refuse a visible tutorial gate.
|
|
15
|
+
- Refuse absence claims on truncated Perps observations and exclude pills clipped by the viewport or scroll containers from Top Movers visible counts.
|
|
16
|
+
- Capture the driven market-search page through the existing surface-capture action.
|
|
17
|
+
|
|
18
|
+
## 0.65.0 - 2026-09-25
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Explicit Extension `wallet.accept-terms` setup recipe with caller-authorized consent and enabled-state checks.
|
|
23
|
+
- Structured visible market-detail identity and price observations, with null for missing or ambiguous fields.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Give bundled market-detail and search recipes portable runtime assertions so composed QA cases can validate their existing behavioral contract.
|
|
28
|
+
|
|
5
29
|
## 0.64.0 - 2026-09-25
|
|
6
30
|
|
|
7
31
|
### 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
|
|
@@ -12,6 +12,12 @@ function pointInside(point, left, top, right, bottom) {
|
|
|
12
12
|
return point && point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export function topMoverFitsVisibleBounds(rect, bounds) {
|
|
16
|
+
return rect.width > 0 && rect.height > 0 && bounds.every((clip) =>
|
|
17
|
+
rect.left >= clip.left - 1 && rect.right <= clip.right + 1 &&
|
|
18
|
+
rect.top >= clip.top - 1 && rect.bottom <= clip.bottom + 1);
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
export function classifyActionableControlLayout(controls) {
|
|
16
22
|
const bounded = Array.isArray(controls) ? controls.slice(0, 200) : [];
|
|
17
23
|
return {
|
|
@@ -42,6 +48,26 @@ export async function readVisiblePerpsState(page, node = {}) {
|
|
|
42
48
|
const raw = await page.evaluate(`(() => {
|
|
43
49
|
const includeOffscreen = ${JSON.stringify(options.includeOffscreen)};
|
|
44
50
|
const maximum = ${JSON.stringify(options.maxItems)};
|
|
51
|
+
const topMoverFitsVisibleBounds = ${topMoverFitsVisibleBounds.toString()};
|
|
52
|
+
const topMoverVisible = (element, rect) => {
|
|
53
|
+
const bounds = [{ left: 0, top: 0, right: innerWidth, bottom: innerHeight }];
|
|
54
|
+
for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
55
|
+
const style = getComputedStyle(ancestor);
|
|
56
|
+
const clipsX = /^(hidden|clip|scroll|auto|overlay)$/.test(style.overflowX);
|
|
57
|
+
const clipsY = /^(hidden|clip|scroll|auto|overlay)$/.test(style.overflowY);
|
|
58
|
+
if (!clipsX && !clipsY) continue;
|
|
59
|
+
const box = ancestor.getBoundingClientRect();
|
|
60
|
+
const left = box.left + ancestor.clientLeft;
|
|
61
|
+
const top = box.top + ancestor.clientTop;
|
|
62
|
+
bounds.push({
|
|
63
|
+
left: clipsX ? left : -Infinity,
|
|
64
|
+
right: clipsX ? left + ancestor.clientWidth : Infinity,
|
|
65
|
+
top: clipsY ? top : -Infinity,
|
|
66
|
+
bottom: clipsY ? top + ancestor.clientHeight : Infinity,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return topMoverFitsVisibleBounds(rect, bounds);
|
|
70
|
+
};
|
|
45
71
|
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;
|
|
46
72
|
const items = [];
|
|
47
73
|
const offscreenItems = [];
|
|
@@ -70,7 +96,8 @@ export async function readVisiblePerpsState(page, node = {}) {
|
|
|
70
96
|
const rendered = style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
71
97
|
if (!rendered) continue;
|
|
72
98
|
matched += 1;
|
|
73
|
-
const visible = rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth
|
|
99
|
+
const visible = rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth &&
|
|
100
|
+
(!testId.startsWith('perps-top-movers-pill-') || topMoverVisible(element, rect));
|
|
74
101
|
if (rect.bottom > 0 && rect.top < innerHeight && (rect.left < -1 || rect.right > innerWidth + 1)) {
|
|
75
102
|
clippedTestIds.push(testId);
|
|
76
103
|
}
|
|
@@ -60,7 +60,7 @@ async function selectDropdownFilter(page, filter, option, timeoutMs, pollInterva
|
|
|
60
60
|
}
|
|
61
61
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
62
62
|
}
|
|
63
|
-
return { selectedLabel: selectedState.label, controlKind: 'dropdown', alreadySelected: false };
|
|
63
|
+
return { selectedLabel: selectedState.label, selectionMatches: selectedState.visible && selectedState.label === optionState.label, controlKind: 'dropdown', alreadySelected: false };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async function categoryRailState(page) {
|
|
@@ -109,7 +109,7 @@ async function selectCategoryRail(page, option, timeoutMs, pollIntervalMs) {
|
|
|
109
109
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
110
110
|
selected = await categoryRailState(page);
|
|
111
111
|
}
|
|
112
|
-
return { selectedLabel: selected.active[0]?.label ?? null, controlKind: 'category-rail', alreadySelected };
|
|
112
|
+
return { selectedLabel: selected.active[0]?.label ?? null, selectionMatches: matches(selected), controlKind: 'category-rail', alreadySelected };
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export async function selectMarketFilter(page, input, pollOptions = {}) {
|
|
@@ -232,11 +232,34 @@ 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;
|
|
242
|
+
const moverList = items.find((item) => item.testId === 'perps-top-movers-list');
|
|
243
|
+
const moverPills = items.filter((item) => item.testId.startsWith('perps-top-movers-pill-'))
|
|
244
|
+
.map((item) => ({
|
|
245
|
+
market: marketFromTestId(item.testId.replace('perps-top-movers-pill-', 'explore-markets-')),
|
|
246
|
+
testId: item.testId,
|
|
247
|
+
label: item.label ?? null,
|
|
248
|
+
visible: item.visible,
|
|
249
|
+
}))
|
|
250
|
+
.filter((item) => item.market);
|
|
235
251
|
const state = {
|
|
236
252
|
source: 'visible-ui',
|
|
237
253
|
platform,
|
|
238
254
|
surface,
|
|
239
255
|
favorite,
|
|
256
|
+
marketDetails: surface === 'market-details' ? {
|
|
257
|
+
symbol: detailSymbol,
|
|
258
|
+
pair: detailPair,
|
|
259
|
+
name: detailField(/^(?:perps-market-detail-name|perps-(?:pro-)?market-header-title)$/u),
|
|
260
|
+
price: detailField(/^perps-(?:market-detail-(?:header-)?price|market-header-price|pro-market-price)$/u),
|
|
261
|
+
priceChange: detailField(/^perps-(?:market-detail-(?:header-)?change|market-header-price-change|pro-market-price-change)$/u),
|
|
262
|
+
} : null,
|
|
240
263
|
...(route ? { route } : {}),
|
|
241
264
|
...(text(raw.title) ? { title: text(raw.title) } : {}),
|
|
242
265
|
balance: compactEntries(
|
|
@@ -244,6 +267,13 @@ export function normalizeVisiblePerpsState(raw, options, platform) {
|
|
|
244
267
|
1,
|
|
245
268
|
)[0] ?? null,
|
|
246
269
|
markets: marketItems,
|
|
270
|
+
marketCount: marketItems.length,
|
|
271
|
+
topMovers: moverList ? {
|
|
272
|
+
visible: moverList.visible,
|
|
273
|
+
pills: moverPills,
|
|
274
|
+
count: moverPills.length,
|
|
275
|
+
visibleCount: moverPills.filter((item) => item.visible).length,
|
|
276
|
+
} : null,
|
|
247
277
|
positions: compactEntries(
|
|
248
278
|
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
279
|
options.maxItems,
|
|
@@ -306,11 +336,14 @@ export function normalizeVisiblePerpsState(raw, options, platform) {
|
|
|
306
336
|
: [],
|
|
307
337
|
} : null,
|
|
308
338
|
};
|
|
309
|
-
assertVisibleState(state, options);
|
|
339
|
+
assertVisibleState(state, options, testIds);
|
|
310
340
|
return state;
|
|
311
341
|
}
|
|
312
342
|
|
|
313
|
-
function assertVisibleState(state, options) {
|
|
343
|
+
function assertVisibleState(state, options, observed) {
|
|
344
|
+
if (options.forbidTestIds.length > 0 && state.truncated) {
|
|
345
|
+
throw new Error('Cannot prove forbidden Perps controls are absent from a truncated observation.');
|
|
346
|
+
}
|
|
314
347
|
if (options.requireNoHorizontalOverflow && (
|
|
315
348
|
!state.layout || state.layout.horizontalOverflow || state.layout.clippedTestIds.length > 0
|
|
316
349
|
)) {
|
|
@@ -346,7 +379,6 @@ function assertVisibleState(state, options) {
|
|
|
346
379
|
if (state.activity.length < options.minimumActivityCount) {
|
|
347
380
|
throw new Error(`Expected at least ${options.minimumActivityCount} visible Perps activity entries, but observed ${state.activity.length}.`);
|
|
348
381
|
}
|
|
349
|
-
const observed = new Set(state.observedTestIds);
|
|
350
382
|
const missing = options.requireTestIds.filter((testId) => !observed.has(testId));
|
|
351
383
|
if (missing.length > 0) {
|
|
352
384
|
throw new Error(`Missing required visible Perps test IDs: ${missing.join(', ')}.`);
|
|
@@ -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. topMovers separately reports its bounded pills, count and visibleCount when that list is observed; otherwise null. 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": {
|
|
@@ -2919,6 +2919,7 @@
|
|
|
2919
2919
|
"default": false
|
|
2920
2920
|
},
|
|
2921
2921
|
"forbid_test_ids": {
|
|
2922
|
+
"description": "Requires a complete observation; truncated results fail rather than assuming these controls are absent.",
|
|
2922
2923
|
"type": "array",
|
|
2923
2924
|
"items": {
|
|
2924
2925
|
"type": "string"
|
|
@@ -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. topMovers separately reports its bounded pills, count and visibleCount when that list is observed; otherwise null.",
|
|
2182
2182
|
"schema": {
|
|
2183
2183
|
"type": "object",
|
|
2184
2184
|
"properties": {
|
|
@@ -2227,6 +2227,7 @@
|
|
|
2227
2227
|
"default": 0
|
|
2228
2228
|
},
|
|
2229
2229
|
"forbid_test_ids": {
|
|
2230
|
+
"description": "Requires a complete observation; truncated results fail rather than assuming these controls are absent.",
|
|
2230
2231
|
"type": "array",
|
|
2231
2232
|
"items": { "type": "string" },
|
|
2232
2233
|
"maxItems": 100
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
3
|
"title": "MetaMask Extension Perps smoke",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Read-only Extension Perps smoke: verifies CDP, unlocks if needed, opens Perps, proves visible home with market data, and reads positions/orders. Requires approved wallet setup and completed onboarding gates; a visible tutorial is setup, not passing smoke.",
|
|
5
5
|
"workflow": {
|
|
6
6
|
"entry": "status",
|
|
7
7
|
"nodes": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"open-perps": {
|
|
27
27
|
"action": "ui.navigate",
|
|
28
28
|
"page": "perps",
|
|
29
|
-
"next": "read-
|
|
29
|
+
"next": "read-home",
|
|
30
30
|
"intent": "Show the Perps home surface"
|
|
31
31
|
},
|
|
32
32
|
"read-positions": {
|
|
@@ -42,7 +42,56 @@
|
|
|
42
42
|
"done": {
|
|
43
43
|
"action": "end",
|
|
44
44
|
"status": "pass"
|
|
45
|
+
},
|
|
46
|
+
"read-home": {
|
|
47
|
+
"action": "metamask.perps.read_visible_state",
|
|
48
|
+
"surface": "home",
|
|
49
|
+
"minimum_market_count": 1,
|
|
50
|
+
"require_features": [
|
|
51
|
+
"balance",
|
|
52
|
+
"market-list"
|
|
53
|
+
],
|
|
54
|
+
"intent": "Observe a populated Perps home after setup",
|
|
55
|
+
"next": "assert-home",
|
|
56
|
+
"forbid_test_ids": [
|
|
57
|
+
"perps-tutorial-skip-button"
|
|
58
|
+
],
|
|
59
|
+
"max_items": 200
|
|
60
|
+
},
|
|
61
|
+
"assert-home": {
|
|
62
|
+
"action": "assert_output",
|
|
63
|
+
"source": "read-home",
|
|
64
|
+
"assert": {
|
|
65
|
+
"path": "$.surface",
|
|
66
|
+
"operator": "eq",
|
|
67
|
+
"value": "home"
|
|
68
|
+
},
|
|
69
|
+
"proves": [
|
|
70
|
+
"perps-home"
|
|
71
|
+
],
|
|
72
|
+
"intent": "The observed surface is Perps home",
|
|
73
|
+
"next": "assert-markets"
|
|
74
|
+
},
|
|
75
|
+
"assert-markets": {
|
|
76
|
+
"action": "assert_output",
|
|
77
|
+
"source": "read-home",
|
|
78
|
+
"assert": {
|
|
79
|
+
"path": "$.marketCount",
|
|
80
|
+
"operator": "gte",
|
|
81
|
+
"value": 1
|
|
82
|
+
},
|
|
83
|
+
"proves": [
|
|
84
|
+
"perps-home"
|
|
85
|
+
],
|
|
86
|
+
"intent": "Perps home contains market data rather than only a loading shell",
|
|
87
|
+
"next": "read-positions"
|
|
45
88
|
}
|
|
46
89
|
}
|
|
47
|
-
}
|
|
90
|
+
},
|
|
91
|
+
"proofTargets": [
|
|
92
|
+
{
|
|
93
|
+
"id": "perps-home",
|
|
94
|
+
"claim": "The unlocked Perps home has visible market data and no observed tutorial gate."
|
|
95
|
+
}
|
|
96
|
+
]
|
|
48
97
|
}
|
|
@@ -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,13 +64,15 @@
|
|
|
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
|
-
"action": "ui.
|
|
75
|
+
"action": "ui.capture_surface",
|
|
72
76
|
"path": "screenshots/perps-market-search.png",
|
|
73
77
|
"label": "Perps release market search",
|
|
74
78
|
"intent": "Preserve reviewer-visible proof of the filtered market list",
|
|
@@ -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
|
}
|