@deeeed/metamask-harness 0.66.0 → 0.66.1

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,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.66.1 - 2026-09-25
6
+
7
+ ### Fixed
8
+
9
+ - Capture market-detail report evidence from the driven document, excluding adjacent Extension views. Require an explicit market and assert no position before proving Long/Short controls. Clarify window versus document captures in discovery.
10
+
11
+ - Recognize current and legacy Perps Home markers and wait for loading to finish. Navigation reports tutorial gates without dismissing them; dependent navigation and consistency checks require explicit setup.
12
+
5
13
  ## 0.66.0 - 2026-09-25
6
14
 
7
15
  ### Added
package/docs/RECIPES.md CHANGED
@@ -213,7 +213,7 @@ mm-harness run perps.release-market-filters --describe --adapter extension --jso
213
213
  | Deposit and withdraw previews without submission | `perps.release-funds-flows` |
214
214
  | Resting limit placement and exact cancellation | `perps.release-live-order order_type=limit limit_price='"<price>"'` |
215
215
  | Filled market placement and exact position close | `perps.release-live-order order_type=market` |
216
- | Market header and statistics | `perps.release-market-details` |
216
+ | Market header and statistics on an unheld market | `perps.release-market-details market='<unheld symbol>'` |
217
217
  | Market categories | `perps.release-market-filters` |
218
218
  | Market text search | `perps.release-market-search` |
219
219
  | Long/Short and Market/Limit entry controls | `perps.release-order-entry case=configuration` |
@@ -284,7 +284,8 @@ export async function assertVisibleConsistency(page, input, testOptions = {}) {
284
284
 
285
285
  if (options.mode === 'watchlist-stream') {
286
286
  const openHome = testOptions.openHome ?? openPerpsHome;
287
- await openHome(page, options.timeoutMs);
287
+ const home = await openHome(page, options.timeoutMs);
288
+ if (home?.gate) throw new Error('Perps tutorial blocks navigation; run perps.dismiss-tutorial as explicit setup first.');
288
289
  const first = await pollLoaded(
289
290
  () => watchlistRows(page, options.maxItems),
290
291
  (rows) => rows.length > 0,
@@ -343,7 +344,8 @@ export async function assertVisibleConsistency(page, input, testOptions = {}) {
343
344
  { action: 'metamask.perps.select_activity_filter', node: { filter } },
344
345
  { timeoutMs },
345
346
  ));
346
- await openHome(page, options.timeoutMs);
347
+ const home = await openHome(page, options.timeoutMs);
348
+ if (home?.gate) throw new Error('Perps tutorial blocks navigation; run perps.dismiss-tutorial as explicit setup first.');
347
349
  const recent = await pollLoaded(
348
350
  () => recentActivityState(page, options.maxItems),
349
351
  (state) => state.ids.length >= options.minimumRecentActivityCount &&
@@ -1,5 +1,5 @@
1
1
  // Opens named wallet screens through UI controls or history, and accepts direct URL/hash navigation.
2
- // Named navigation may dismiss tutorials or drawers and add a missing network before selecting its filter.
2
+ // Named navigation reports tutorial gates, may dismiss drawers, and may add a missing network before selecting its filter.
3
3
  import { pathToFileURL } from 'node:url';
4
4
 
5
5
  import {
@@ -81,12 +81,12 @@ async function goBack(page, timeoutMs) {
81
81
 
82
82
  async function hasVisibleSelector(page, selector) {
83
83
  return page.evaluate(`(() => {
84
- const element = document.querySelector(${JSON.stringify(selector)});
85
- if (!element) return false;
86
- if (element.closest('dialog')?.open === false) return false;
87
- const style = getComputedStyle(element);
88
- const rect = element.getBoundingClientRect();
89
- return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
84
+ return [...document.querySelectorAll(${JSON.stringify(selector)})].some((element) => {
85
+ if (element.closest('dialog')?.open === false) return false;
86
+ const style = getComputedStyle(element);
87
+ const rect = element.getBoundingClientRect();
88
+ return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
89
+ });
90
90
  })()`);
91
91
  }
92
92
 
@@ -199,18 +199,25 @@ export async function openHome(page, timeoutMs) {
199
199
  export async function openPerpsHome(page, timeoutMs) {
200
200
  const bottomNav = dataTestId('bottom-nav-perps');
201
201
  const legacyTab = dataTestId('account-overview__perps-tab');
202
- const perpsHome = dataTestId('perps-home-page');
202
+ const perpsHome = ['perps-home-page', 'parent-selector-perps-tab', 'perps-view'].map(dataTestId).join(',');
203
+ const perpsLoading = dataTestId('perps-view-loading');
203
204
  const tutorialSkip = dataTestId('perps-tutorial-skip-button');
204
205
  const steps = [];
205
206
  const deadline = Date.now() + timeoutMs;
206
207
  let pendingActivation;
207
208
  let perpsActivation;
209
+ let perpsControl;
208
210
  let perpsHomeVisibleAt;
209
211
 
210
212
  while (Date.now() < deadline) {
211
213
  if (await hasVisibleSelector(page, tutorialSkip)) {
212
- await activateSemanticControl(page, tutorialSkip);
213
- steps.push(tutorialSkip);
214
+ return {
215
+ gate: 'perps-tutorial', gateSelector: tutorialSkip, method: 'visible-ui',
216
+ ...(perpsActivation ? { navigation: perpsActivation, selector: perpsControl } : {}),
217
+ href: await currentHref(page), steps,
218
+ };
219
+ }
220
+ if (await hasVisibleSelector(page, perpsLoading)) {
214
221
  perpsHomeVisibleAt = undefined;
215
222
  await pauseForUi(page);
216
223
  continue;
@@ -226,7 +233,7 @@ export async function openPerpsHome(page, timeoutMs) {
226
233
  continue;
227
234
  }
228
235
  return {
229
- ...(perpsActivation ? { navigation: perpsActivation, selector: bottomNav } : {}),
236
+ ...(perpsActivation ? { navigation: perpsActivation, selector: perpsControl } : {}),
230
237
  method: 'visible-ui',
231
238
  href: await currentHref(page),
232
239
  steps,
@@ -236,23 +243,20 @@ export async function openPerpsHome(page, timeoutMs) {
236
243
  if (await hasVisibleSelector(page, bottomNav)) {
237
244
  if (!perpsActivation) {
238
245
  perpsActivation = await activateSemanticControl(page, bottomNav);
246
+ perpsControl = bottomNav;
239
247
  steps.push(bottomNav);
240
248
  }
241
249
  await pauseForUi(page);
242
250
  continue;
243
251
  }
244
252
  if (await hasVisibleSelector(page, legacyTab)) {
245
- const navigation = await activateSemanticControl(page, legacyTab);
246
- await page.waitForSelector(dataTestId('perps-view'), {
247
- timeoutMs: Math.max(1, deadline - Date.now()),
248
- });
249
- return {
250
- navigation,
251
- method: 'visible-ui',
252
- href: await currentHref(page),
253
- selector: legacyTab,
254
- steps,
255
- };
253
+ if (!perpsActivation) {
254
+ perpsActivation = await activateSemanticControl(page, legacyTab);
255
+ perpsControl = legacyTab;
256
+ steps.push(legacyTab);
257
+ }
258
+ await pauseForUi(page);
259
+ continue;
256
260
  }
257
261
 
258
262
  let backControl;
@@ -313,6 +317,7 @@ export async function openPerpsMarket(page, market, timeoutMs) {
313
317
  };
314
318
  }
315
319
  const home = await openPerpsHome(page, Math.max(1, deadline - Date.now()));
320
+ if (home.gate) throw new Error('Perps tutorial blocks navigation; run perps.dismiss-tutorial as explicit setup first.');
316
321
  const selectors = [
317
322
  dataTestId(`perps-watchlist-${normalizedMarket}`),
318
323
  dataTestId(`explore-markets-${normalizedMarket.replaceAll(':', '-')}`),
@@ -349,6 +354,7 @@ export async function openPerpsActivity(page, timeoutMs) {
349
354
  }
350
355
  const deadline = Date.now() + timeoutMs;
351
356
  const home = await openPerpsHome(page, Math.max(1, deadline - Date.now()));
357
+ if (home.gate) throw new Error('Perps tutorial blocks navigation; run perps.dismiss-tutorial as explicit setup first.');
352
358
  const activity = dataTestId('perps-recent-activity-see-all');
353
359
  await page.waitForSelector(activity, { timeoutMs: Math.max(1, deadline - Date.now()) });
354
360
  const navigation = await activateSemanticControl(page, activity);
@@ -383,33 +389,17 @@ export async function openPerpsMarketList(page, timeoutMs) {
383
389
  };
384
390
  }
385
391
  const home = await openPerpsHome(page, Math.max(1, deadline - Date.now()));
392
+ if (home.gate) throw new Error('Perps tutorial blocks navigation; run perps.dismiss-tutorial as explicit setup first.');
386
393
  const explore = dataTestId('perps-explore-markets-row');
387
394
  const tutorialSkip = dataTestId('perps-tutorial-skip-button');
388
395
  if (!(await waitForFirstVisible(page, [explore], deadline))) {
389
396
  throw new Error('Perps Explore Markets did not become visible before timeout.');
390
397
  }
391
- let navigation = await activateSemanticControl(page, explore);
398
+ const navigation = await activateSemanticControl(page, explore);
392
399
  const destination = await waitForFirstVisible(page, [marketList, tutorialSkip], deadline);
393
400
  const steps = [...home.steps, explore];
394
401
  if (destination === tutorialSkip) {
395
- await activateSemanticControl(page, tutorialSkip);
396
- steps.push(tutorialSkip);
397
- let exploreReady = false;
398
- while (Date.now() < deadline) {
399
- if (!(await hasVisibleSelector(page, tutorialSkip)) && await hasVisibleSelector(page, explore)) {
400
- exploreReady = true;
401
- break;
402
- }
403
- await pauseForUi(page);
404
- }
405
- if (!exploreReady) {
406
- throw new Error('Perps Explore Markets did not become visible after dismissing the tutorial.');
407
- }
408
- navigation = await activateSemanticControl(page, explore);
409
- steps.push(explore);
410
- if (!(await waitForFirstVisible(page, [marketList], deadline))) {
411
- throw new Error('Perps market list did not become visible after dismissing the tutorial.');
412
- }
402
+ throw new Error('Perps tutorial blocks navigation; run perps.dismiss-tutorial as explicit setup first.');
413
403
  } else if (destination !== marketList) {
414
404
  throw new Error('Perps market list did not become visible before timeout.');
415
405
  }
@@ -495,7 +495,7 @@
495
495
  ]
496
496
  },
497
497
  "ui.navigate": {
498
- "description": "Navigate by semantic page intent when available; the adapter uses the current visible UI or a verified route. Raw url/hash/path values must be verified against the current app first.",
498
+ "description": "Navigate by semantic page intent when available; the adapter uses the current visible UI or a verified route. Raw url/hash/path values must be verified against the current app first. Perps Home returns gate=perps-tutorial and gateSelector without dismissing a visible tutorial. Complete explicit tutorial setup before dependent market or activity navigation.",
499
499
  "schema": {
500
500
  "type": "object",
501
501
  "properties": {
@@ -1132,7 +1132,7 @@
1132
1132
  ]
1133
1133
  },
1134
1134
  "ui.screenshot": {
1135
- "description": "Capture the current native browser window on macOS, with bounded Chrome viewport capture as fallback. Always inspect the resulting PNG; artifact metadata records the provider. Failed real capture does not produce a reconstructed DOM image.",
1135
+ "description": "Capture the current native browser window on macOS, with bounded Chrome viewport capture as fallback. A native window can include a separate side panel or a tab other than the driven page. For report evidence of the driven document, use ui.capture_surface. Always inspect the resulting PNG and artifact metadata; failed real capture does not produce a reconstructed DOM image.",
1136
1136
  "examples": [
1137
1137
  {
1138
1138
  "action": "ui.screenshot",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
3
  "title": "Perps release market details",
4
- "description": "Opens one live Perps market through visible UI and proves its header price, change, statistics, and Long/Short controls; it does not assert chart data.",
4
+ "description": "Opens one live Perps market through visible UI and proves its header price, change, statistics, and Long/Short controls; it does not assert chart data. Requires no existing position in the chosen market: held markets show Modify/Close instead. Verify the fixture with a supported read before selecting market; never close a position to satisfy this recipe.",
5
5
  "paramsSchema": {
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
+ "required": ["market"],
8
9
  "properties": {
9
10
  "market": {
10
11
  "type": "string",
11
- "default": "ETH",
12
- "description": "Perps market symbol to open and verify."
12
+ "description": "Perps market symbol with no existing position, so Long/Short controls are expected."
13
13
  },
14
14
  "timeout_ms": {
15
15
  "type": "integer",
@@ -45,6 +45,13 @@
45
45
  ],
46
46
  "max_items": 100,
47
47
  "intent": "Prove the requested market appears in a populated live market list",
48
+ "next": "require-flat-market"
49
+ },
50
+ "require-flat-market": {
51
+ "action": "metamask.perps.assert_positions",
52
+ "market": "{{params.market}}",
53
+ "state": "none",
54
+ "intent": "Require the flat-market fixture before proving Long and Short controls; never change existing positions",
48
55
  "next": "open-market"
49
56
  },
50
57
  "open-market": {
@@ -70,7 +77,7 @@
70
77
  "next": "verify-directions"
71
78
  },
72
79
  "capture-market": {
73
- "action": "ui.screenshot",
80
+ "action": "ui.capture_surface",
74
81
  "path": "screenshots/perps-market-details.png",
75
82
  "label": "Perps release market details",
76
83
  "intent": "Preserve reviewer-visible proof of the selected live market",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.66.0",
3
+ "version": "0.66.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"