@deeeed/metamask-harness 0.34.0 → 0.34.2

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 (33) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/adapters/manifest.json +0 -8
  3. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +236 -3
  4. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +22 -1
  5. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +22 -15
  6. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +18 -0
  7. package/adapters/mobile/launch-metro.cjs +6 -4
  8. package/adapters/mobile/open-device.sh +170 -19
  9. package/adapters/mobile/start-metro.sh +165 -93
  10. package/adapters/mobile/stop-metro.sh +1 -0
  11. package/dist/adapters/mobile/prepare.js +1 -4
  12. package/dist/adapters/mobile/video-recorder.js +12 -10
  13. package/dist/adapters.js +134 -6
  14. package/dist/commands/launch/index.js +9 -0
  15. package/dist/commands/launch/mobile.js +17 -3
  16. package/dist/live-adapter-contract.js +10 -3
  17. package/dist/recipe-security.js +2 -0
  18. package/dist/runner.js +53 -8
  19. package/dist/runtime-context.js +1 -0
  20. package/library/actions/mobile/perps/measure_homepage_visible.mjs +4 -0
  21. package/library/actions/mobile/perps/performance-capture.mjs +579 -111
  22. package/library/actions/mobile/perps/perps.mjs +221 -38
  23. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +58 -0
  24. package/library/actions/mobile/platform/bridge.mjs +102 -8
  25. package/library/actions/mobile/wallet/ensure_unlocked.mjs +35 -8
  26. package/library/manifests/mobile.action-manifest.json +243 -511
  27. package/library/recipes/mobile/perps/performance.homepage.android-cold-disk-cache.recipe.json +20 -2
  28. package/library/recipes/mobile/perps/performance.homepage.android-cold-no-cache.recipe.json +2 -0
  29. package/library/recipes/mobile/perps/performance.homepage.cold-position-sample.recipe.json +120 -0
  30. package/library/recipes/mobile/perps/performance.homepage.ios-background-reconnect.recipe.json +7 -6
  31. package/library/recipes/mobile/perps/performance.homepage.ios-cold-no-cache.recipe.json +5 -5
  32. package/package.json +1 -1
  33. package/adapters/mobile/metro-config.cjs +0 -93
@@ -57,6 +57,8 @@
57
57
  },
58
58
  "unlock-after-restart": {
59
59
  "action": "metamask.wallet.ensure_unlocked",
60
+ "target_timeout_ms": 90000,
61
+ "unlock_timeout_ms": 90000,
60
62
  "intent": "Restore the fixture wallet after the cold process launch",
61
63
  "next": "open-wallet-home-after"
62
64
  },
@@ -108,17 +110,33 @@
108
110
  "finish-performance-capture": {
109
111
  "action": "metamask.perps.capture_performance",
110
112
  "phase": "end",
113
+ "artifact_prefix": "logs/homepage-order-cold-disk-cache",
111
114
  "required_stages": [
112
115
  "disk_cache_hydrated",
113
116
  "viewport_demand",
114
117
  "socket_received",
118
+ "subscriber_delivery",
115
119
  "react_commit",
116
120
  "next_frame_checkpoint"
117
121
  ],
118
122
  "required_lifecycles": ["cold_disk_cache"],
119
- "required_sources": ["disk_cache", "fresh_socket"],
123
+ "required_sources": ["fresh_socket"],
120
124
  "required_fresh_content_variants": ["orders"],
121
- "intent": "Preserve and validate the cold disk-cache measurement cohort",
125
+ "first_fresh_delivery_requirements": [
126
+ {
127
+ "stream": "positions",
128
+ "lifecycle": "cold_disk_cache",
129
+ "subscriber_throttle_ms": 5000,
130
+ "max_socket_to_subscriber_ms": 1000
131
+ },
132
+ {
133
+ "stream": "orders",
134
+ "lifecycle": "cold_disk_cache",
135
+ "subscriber_throttle_ms": 5000,
136
+ "max_socket_to_subscriber_ms": 1000
137
+ }
138
+ ],
139
+ "intent": "Require cache hydration, fresh visible order content, and immediate first-fresh positions/orders delivery",
122
140
  "next": "cleanup-order"
123
141
  },
124
142
  "cleanup-order": {
@@ -48,6 +48,8 @@
48
48
  },
49
49
  "unlock-wallet": {
50
50
  "action": "metamask.wallet.ensure_unlocked",
51
+ "target_timeout_ms": 90000,
52
+ "unlock_timeout_ms": 90000,
51
53
  "intent": "Unlock the fixture wallet after the cold process launch",
52
54
  "next": "open-wallet-home"
53
55
  },
@@ -0,0 +1,120 @@
1
+ {
2
+ "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
+ "title": "Homepage Perps cold no-cache position sample",
4
+ "description": "Clears only persisted Perps performance caches, reproduces a process-cold Homepage position, and measures production-like Perps timing from the first usable Wallet Home frame with the device performance clock. Metro, splash, bridge readiness, unlock, and recipe orchestration are excluded from the Perps metric.",
5
+ "paramsSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "account": {
10
+ "type": "string",
11
+ "description": "Existing fixture account that owns the position."
12
+ },
13
+ "market": {
14
+ "type": "string",
15
+ "default": "BTC",
16
+ "description": "Exact visible position market symbol."
17
+ },
18
+ "sample_id": {
19
+ "type": "string",
20
+ "default": "sample",
21
+ "description": "Artifact-local sample identifier."
22
+ },
23
+ "snapshot_port": {
24
+ "type": "number",
25
+ "default": 3000,
26
+ "description": "Host port for the explicitly configured local Terminal global snapshot endpoint."
27
+ }
28
+ },
29
+ "required": ["account"]
30
+ },
31
+ "workflow": {
32
+ "entry": "ensure-wallet",
33
+ "nodes": {
34
+ "ensure-wallet": {
35
+ "action": "metamask.wallet.ensure_unlocked",
36
+ "target_timeout_ms": 90000,
37
+ "unlock_timeout_ms": 180000,
38
+ "intent": "Reuse the existing unlocked fixture wallet without replacing it",
39
+ "next": "select-account"
40
+ },
41
+ "select-account": {
42
+ "action": "metamask.wallet.select_account",
43
+ "name": "{{params.account}}",
44
+ "cdp_timeout_ms": 30000,
45
+ "intent": "Pin the device-owned fixture account before the measured cold restart",
46
+ "next": "assert-position"
47
+ },
48
+ "assert-position": {
49
+ "action": "metamask.perps.assert_positions",
50
+ "market": "{{params.market}}",
51
+ "state": "open",
52
+ "timeout_ms": 30000,
53
+ "intent": "Prove the real position exists before process termination",
54
+ "next": "open-wallet-home"
55
+ },
56
+ "open-wallet-home": {
57
+ "action": "ui.navigate",
58
+ "page": "home",
59
+ "timeout_ms": 30000,
60
+ "intent": "Persist Wallet Home as the route restored by the cold launch",
61
+ "next": "clear-perps-caches"
62
+ },
63
+ "clear-perps-caches": {
64
+ "action": "metamask.perps.clear_performance_caches",
65
+ "intent": "Remove only persisted Perps market and user snapshots before the measured cold launch",
66
+ "next": "prepare-local-snapshot-endpoint"
67
+ },
68
+ "prepare-local-snapshot-endpoint": {
69
+ "action": "metamask.perps.prepare_local_snapshot_endpoint",
70
+ "port": "{{params.snapshot_port}}",
71
+ "intent": "Make the configured local Terminal v2 endpoint reachable on physical Android without an external command node",
72
+ "next": "cold-restart"
73
+ },
74
+ "cold-restart": {
75
+ "action": "app.lifecycle",
76
+ "command": "restart",
77
+ "settle_ms": 0,
78
+ "runtime_ready_timeout_ms": 90000,
79
+ "timeout_ms": 120000,
80
+ "intent": "Create a process-cold app state without rebuilding or resetting wallet state; this node is setup and is not part of the Perps metric",
81
+ "next": "measure-visible-perps"
82
+ },
83
+ "measure-visible-perps": {
84
+ "action": "metamask.perps.measure_homepage_visible",
85
+ "target_timeout_ms": 90000,
86
+ "unlock_timeout_ms": 180000,
87
+ "scroll_test_id": "wallet-screen",
88
+ "from_test_id": "wallet-screen",
89
+ "offset": 600,
90
+ "poll_interval_ms": 1000,
91
+ "visible_event_stage": "first_visible_recorded",
92
+ "visible_event_grace_ms": 1500,
93
+ "animated": true,
94
+ "target_test_id": "homepage-section-title-perps",
95
+ "required_present_test_id": "perps-position-row-{{params.market}}",
96
+ "timeout_ms": 30000,
97
+ "intent": "Confirm the restored wallet is usable and immediately measure the direct-CDP transition to visible Perps position content without returning control to the recipe runner",
98
+ "next": "wait-position"
99
+ },
100
+ "wait-position": {
101
+ "action": "ui.wait_for",
102
+ "test_id": "perps-position-row-{{params.market}}",
103
+ "expected": "present",
104
+ "timeout_ms": 60000,
105
+ "intent": "Prove the real market position row remains rendered after the measured Perps transition",
106
+ "next": "capture-position"
107
+ },
108
+ "capture-position": {
109
+ "action": "ui.screenshot",
110
+ "path": "screenshots/cold-position-{{params.sample_id}}.png",
111
+ "intent": "Preserve native proof that meaningful Perps content is visibly rendered after the fixed viewport demand",
112
+ "next": "done"
113
+ },
114
+ "done": {
115
+ "action": "end",
116
+ "status": "pass"
117
+ }
118
+ }
119
+ }
120
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
3
3
  "title": "iOS Homepage Perps long-background reconnect proof",
4
- "description": "Keeps the existing no-order simulator fixture, backgrounds longer than the connection grace period, then validates resident-to-fresh trending content at the visible Homepage endpoint.",
4
+ "description": "Keeps the existing simulator wallet and position, backgrounds longer than the connection grace period, then validates resident-to-fresh position content at the visible Homepage endpoint.",
5
5
  "workflow": {
6
6
  "entry": "ensure-wallet",
7
7
  "nodes": {
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "wait-perps-before": {
21
21
  "action": "ui.wait_for",
22
- "test_id": "homepage-section-title-perps",
22
+ "test_id": "homepage-perps-positions",
23
23
  "expected": "present",
24
24
  "intent": "Require the Perps section before backgrounding",
25
25
  "timeout_ms": 30000,
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "show-perps-before": {
29
29
  "action": "ui.scroll",
30
- "test_id": "homepage-section-title-perps",
30
+ "test_id": "homepage-perps-positions",
31
31
  "scroll_into_view": true,
32
32
  "animated": true,
33
33
  "settle": true,
@@ -53,12 +53,13 @@
53
53
  "command": "foreground",
54
54
  "settle_ms": 10000,
55
55
  "runtime_ready_timeout_ms": 60000,
56
+ "require_process_continuity": true,
56
57
  "intent": "Restore the same process and require a reconnect",
57
58
  "next": "wait-perps-after"
58
59
  },
59
60
  "wait-perps-after": {
60
61
  "action": "ui.wait_for",
61
- "test_id": "homepage-section-title-perps",
62
+ "test_id": "homepage-perps-positions",
62
63
  "expected": "present",
63
64
  "intent": "Confirm the Perps section remains available after reconnect",
64
65
  "timeout_ms": 30000,
@@ -66,7 +67,7 @@
66
67
  },
67
68
  "show-perps-after": {
68
69
  "action": "ui.scroll",
69
- "test_id": "homepage-section-title-perps",
70
+ "test_id": "homepage-perps-positions",
70
71
  "scroll_into_view": true,
71
72
  "animated": true,
72
73
  "settle": true,
@@ -97,7 +98,7 @@
97
98
  ],
98
99
  "required_lifecycles": ["background_reconnect"],
99
100
  "required_sources": ["resident_state", "fresh_socket"],
100
- "required_fresh_content_variants": ["trending"],
101
+ "required_fresh_content_variants": ["positions"],
101
102
  "intent": "Require the long-background visible reconnect cohort",
102
103
  "next": "done"
103
104
  },
@@ -43,15 +43,15 @@
43
43
  },
44
44
  "wait-perps-section": {
45
45
  "action": "ui.wait_for",
46
- "test_id": "homepage-section-title-perps",
46
+ "test_id": "homepage-perps-positions",
47
47
  "expected": "present",
48
- "intent": "Require the Perps section after cold launch",
48
+ "intent": "Require the selected account's Perps position after cold launch",
49
49
  "timeout_ms": 30000,
50
50
  "next": "show-perps-section"
51
51
  },
52
52
  "show-perps-section": {
53
53
  "action": "ui.scroll",
54
- "test_id": "homepage-section-title-perps",
54
+ "test_id": "homepage-perps-positions",
55
55
  "scroll_into_view": true,
56
56
  "animated": true,
57
57
  "settle": true,
@@ -82,8 +82,8 @@
82
82
  ],
83
83
  "required_lifecycles": ["cold_no_cache"],
84
84
  "required_sources": ["fresh_socket"],
85
- "required_fresh_content_variants": ["trending"],
86
- "intent": "Require cold-no-cache lifecycle and fresh visible trending content",
85
+ "required_fresh_content_variants": ["positions"],
86
+ "intent": "Require cold-no-cache lifecycle and the fresh visible position",
87
87
  "next": "done"
88
88
  },
89
89
  "done": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.34.0",
3
+ "version": "0.34.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -1,93 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const { createRequire } = require('node:module');
5
- const path = require('node:path');
6
- const { pathToFileURL } = require('node:url');
7
-
8
- function escapeRegExp(value) {
9
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
- }
11
-
12
- function appendBlockList(blockList, pattern) {
13
- if (Array.isArray(blockList)) return [...blockList, pattern];
14
- return blockList instanceof RegExp ? [blockList, pattern] : [pattern];
15
- }
16
-
17
- function blockListFlags(blockList) {
18
- const first = Array.isArray(blockList) ? blockList[0] : blockList;
19
- return first instanceof RegExp ? first.flags : '';
20
- }
21
-
22
- function resolveExpoConfigLoader(projectRoot) {
23
- const requireFromProject = createRequire(path.join(projectRoot, 'package.json'));
24
- let requireUtilsPath;
25
- try {
26
- requireUtilsPath = requireFromProject.resolve('@expo/require-utils');
27
- } catch (error) {
28
- if (error?.code !== 'MODULE_NOT_FOUND') throw error;
29
- return null;
30
- }
31
- const { loadModuleSync } = requireFromProject(requireUtilsPath);
32
- return typeof loadModuleSync === 'function' ? loadModuleSync : null;
33
- }
34
-
35
- async function loadProductConfig(configPath, baseConfig, projectRoot) {
36
- let loaded;
37
- const expoConfigLoader = resolveExpoConfigLoader(projectRoot);
38
- if (expoConfigLoader) {
39
- loaded = expoConfigLoader(configPath);
40
- } else {
41
- try {
42
- loaded = require(configPath);
43
- } catch (error) {
44
- if (error?.code !== 'ERR_REQUIRE_ESM') throw error;
45
- loaded = await import(pathToFileURL(configPath).href);
46
- }
47
- }
48
-
49
- const moduleValue = await loaded;
50
- const exported =
51
- moduleValue?.__esModule ||
52
- moduleValue?.[Symbol.toStringTag] === 'Module'
53
- ? moduleValue.default
54
- : moduleValue;
55
- const config = await exported;
56
- return typeof config === 'function' ? config(baseConfig) : config;
57
- }
58
-
59
- module.exports = async function harnessMetroConfig(baseConfig) {
60
- const projectRoot = path.resolve(
61
- process.env.MM_HARNESS_METRO_PROJECT_ROOT || process.cwd(),
62
- );
63
- const productConfigPath = path.resolve(
64
- process.env.MM_HARNESS_METRO_BASE_CONFIG ||
65
- path.join(projectRoot, 'metro.config.js'),
66
- );
67
- let productConfig = baseConfig;
68
- if (fs.existsSync(productConfigPath)) {
69
- productConfig = await loadProductConfig(
70
- productConfigPath,
71
- baseConfig,
72
- projectRoot,
73
- );
74
- }
75
-
76
- const tempRoot = path.join(projectRoot, 'temp');
77
- const existingBlockList =
78
- productConfig.resolver?.blockList ?? baseConfig.resolver?.blockList;
79
- const tempPattern = new RegExp(
80
- `^${escapeRegExp(tempRoot)}(?:${escapeRegExp(path.sep)}|$)`,
81
- blockListFlags(existingBlockList),
82
- );
83
- return {
84
- ...productConfig,
85
- resolver: {
86
- ...productConfig.resolver,
87
- blockList: appendBlockList(
88
- existingBlockList,
89
- tempPattern,
90
- ),
91
- },
92
- };
93
- };