@deeeed/metamask-harness 0.34.2 → 0.34.3
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/dist/adapters.js +5 -2
- package/dist/recipe-security.js +1 -0
- package/library/actions/core/perps/_controller.mjs +75 -23
- package/library/actions/core/perps/read_account.mjs +6 -12
- package/library/actions/core/perps/read_snapshot.mjs +66 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +372 -0
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +18 -0
- package/library/actions/extension/performance/measure_detached_dom.mjs +18 -0
- package/library/actions/extension/performance/measure_navigation_memory.mjs +18 -0
- package/library/actions/mobile/app/network-control.mjs +24 -12
- package/library/manifests/core.action-manifest.json +55 -0
- package/library/manifests/extension.action-manifest.json +100 -0
- package/library/recipes/core/perps/snapshot.recipe.json +25 -0
- package/library/recipes/extension/performance/navigation-memory.recipe.json +175 -0
- package/package.json +1 -1
- package/scripts/site-contrast.mjs +39 -3
- package/site/architecture.html +22 -16
- package/site/assets/progress.mjs +1 -1
- package/site/assets/style.css +113 -1
- package/site/cheatsheet.html +14 -12
- package/site/how-it-works.html +693 -0
- package/site/index.html +70 -640
- package/site/perps.html +7 -6
- package/site/recipes.html +23 -17
- package/site/reviewers.html +7 -6
- package/site/tutorials/index.html +7 -6
- package/site/tutorials/v1.html +12 -11
- package/site/tutorials/v2.html +7 -6
- package/site/tutorials/v3.html +15 -11
- package/site/tutorials/v4.html +6 -5
- package/site/tutorials/v5.html +6 -5
- package/site/tutorials/v6.html +6 -5
- package/site/tutorials/v7.html +6 -5
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import {
|
|
3
|
+
compareIdleNavigation,
|
|
4
|
+
idleControlBound,
|
|
5
|
+
navigationMemoryOptions,
|
|
6
|
+
} from './_navigation-memory.mjs';
|
|
7
|
+
|
|
8
|
+
runAdapter((input) =>
|
|
9
|
+
withExtensionPage(input, async (page) => ({
|
|
10
|
+
action: input.action,
|
|
11
|
+
status: 'pass',
|
|
12
|
+
...(await compareIdleNavigation(
|
|
13
|
+
page,
|
|
14
|
+
navigationMemoryOptions(input.node, 'rounds', 8),
|
|
15
|
+
idleControlBound(input.node),
|
|
16
|
+
)),
|
|
17
|
+
})),
|
|
18
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import {
|
|
3
|
+
detachedDomBound,
|
|
4
|
+
measureDetachedDom,
|
|
5
|
+
navigationMemoryOptions,
|
|
6
|
+
} from './_navigation-memory.mjs';
|
|
7
|
+
|
|
8
|
+
runAdapter((input) =>
|
|
9
|
+
withExtensionPage(input, async (page) => ({
|
|
10
|
+
action: input.action,
|
|
11
|
+
status: 'pass',
|
|
12
|
+
...(await measureDetachedDom(
|
|
13
|
+
page,
|
|
14
|
+
navigationMemoryOptions(input.node, 'cycles', 5),
|
|
15
|
+
detachedDomBound(input.node),
|
|
16
|
+
)),
|
|
17
|
+
})),
|
|
18
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import {
|
|
3
|
+
measureNavigationSlope,
|
|
4
|
+
navigationMemoryOptions,
|
|
5
|
+
navigationSlopeBounds,
|
|
6
|
+
} from './_navigation-memory.mjs';
|
|
7
|
+
|
|
8
|
+
runAdapter((input) =>
|
|
9
|
+
withExtensionPage(input, async (page) => ({
|
|
10
|
+
action: input.action,
|
|
11
|
+
status: 'pass',
|
|
12
|
+
...(await measureNavigationSlope(
|
|
13
|
+
page,
|
|
14
|
+
navigationMemoryOptions(input.node),
|
|
15
|
+
navigationSlopeBounds(input.node),
|
|
16
|
+
)),
|
|
17
|
+
})),
|
|
18
|
+
);
|
|
@@ -42,6 +42,12 @@ async function defaultRun(file, args) {
|
|
|
42
42
|
return execFileAsync(file, args, { timeout: 15_000, encoding: 'utf8' });
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function isAppFocused(output, appId) {
|
|
46
|
+
return String(output ?? '')
|
|
47
|
+
.split('\n')
|
|
48
|
+
.some((line) => line.includes('mCurrentFocus=') && line.includes(appId));
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
export async function setMobileNetworkState(input, deps = {}) {
|
|
46
52
|
const state = String(input.node?.state ?? '').toLowerCase();
|
|
47
53
|
if (state !== 'offline' && state !== 'online') {
|
|
@@ -87,19 +93,26 @@ export async function setMobileNetworkState(input, deps = {}) {
|
|
|
87
93
|
`app.network requested ${state}, but Android airplane_mode_on was ${JSON.stringify(observed)} instead of ${expected}.`,
|
|
88
94
|
);
|
|
89
95
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await run(adb, [
|
|
96
|
+
const focusBefore = await run(adb, [
|
|
93
97
|
'-s',
|
|
94
98
|
serial,
|
|
95
99
|
'shell',
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
appId,
|
|
99
|
-
'-c',
|
|
100
|
-
'android.intent.category.LAUNCHER',
|
|
101
|
-
'1',
|
|
100
|
+
'dumpsys',
|
|
101
|
+
'window',
|
|
102
102
|
]);
|
|
103
|
+
if (!isAppFocused(focusBefore.stdout, appId)) {
|
|
104
|
+
await run(adb, [
|
|
105
|
+
'-s',
|
|
106
|
+
serial,
|
|
107
|
+
'shell',
|
|
108
|
+
'monkey',
|
|
109
|
+
'-p',
|
|
110
|
+
appId,
|
|
111
|
+
'-c',
|
|
112
|
+
'android.intent.category.LAUNCHER',
|
|
113
|
+
'1',
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
103
116
|
|
|
104
117
|
const fallbackSettleMs = state === 'offline' ? 3000 : 10000;
|
|
105
118
|
const settleMs = Number(input.node?.settle_ms ?? fallbackSettleMs);
|
|
@@ -113,10 +126,9 @@ export async function setMobileNetworkState(input, deps = {}) {
|
|
|
113
126
|
serial,
|
|
114
127
|
'shell',
|
|
115
128
|
'dumpsys',
|
|
116
|
-
'
|
|
117
|
-
'activities',
|
|
129
|
+
'window',
|
|
118
130
|
]);
|
|
119
|
-
if (!
|
|
131
|
+
if (!isAppFocused(foreground.stdout, appId)) {
|
|
120
132
|
throw new Error(
|
|
121
133
|
`app.network changed Android networking but ${appId} was not foregrounded afterward.`,
|
|
122
134
|
);
|
|
@@ -728,6 +728,61 @@
|
|
|
728
728
|
],
|
|
729
729
|
"execution_capabilities": []
|
|
730
730
|
},
|
|
731
|
+
"metamask.perps.read_snapshot": {
|
|
732
|
+
"description": "Read global market and account snapshots through the public PerpsController API used by wallet clients.",
|
|
733
|
+
"schema": {
|
|
734
|
+
"type": "object",
|
|
735
|
+
"properties": {
|
|
736
|
+
"scope": {
|
|
737
|
+
"type": "string",
|
|
738
|
+
"enum": [
|
|
739
|
+
"markets",
|
|
740
|
+
"account",
|
|
741
|
+
"all"
|
|
742
|
+
],
|
|
743
|
+
"default": "all",
|
|
744
|
+
"description": "Select the public controller snapshot calls to execute."
|
|
745
|
+
},
|
|
746
|
+
"account": {
|
|
747
|
+
"type": "string",
|
|
748
|
+
"description": "Wallet fixture account name or EVM address; defaults to dev1."
|
|
749
|
+
},
|
|
750
|
+
"account_name": {
|
|
751
|
+
"type": "string",
|
|
752
|
+
"description": "Alias for a wallet fixture account name."
|
|
753
|
+
},
|
|
754
|
+
"network": {
|
|
755
|
+
"type": "string",
|
|
756
|
+
"enum": [
|
|
757
|
+
"testnet",
|
|
758
|
+
"mainnet"
|
|
759
|
+
],
|
|
760
|
+
"default": "testnet"
|
|
761
|
+
},
|
|
762
|
+
"terminal_market_data_url": {
|
|
763
|
+
"type": "string",
|
|
764
|
+
"description": "Optional full URL for the legacy Terminal market-data endpoint."
|
|
765
|
+
},
|
|
766
|
+
"terminal_global_snapshot_url": {
|
|
767
|
+
"type": "string",
|
|
768
|
+
"description": "Optional full URL for the Terminal schema-v2 global snapshot endpoint."
|
|
769
|
+
},
|
|
770
|
+
"timeout_ms": {
|
|
771
|
+
"type": "number"
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
"additionalProperties": false
|
|
775
|
+
},
|
|
776
|
+
"examples": [
|
|
777
|
+
{
|
|
778
|
+
"action": "metamask.perps.read_snapshot",
|
|
779
|
+
"scope": "all",
|
|
780
|
+
"intent": "Read coherent Perps snapshots through the public controller API",
|
|
781
|
+
"next": "done"
|
|
782
|
+
}
|
|
783
|
+
],
|
|
784
|
+
"execution_capabilities": []
|
|
785
|
+
},
|
|
731
786
|
"metamask.perps.place_order": {
|
|
732
787
|
"description": "core Place a real Perps order on HyperLiquid testnet by driving the headless perps controller placeOrder() through the full signing/provider path. Supports market (default), resting limit orders (order_type=limit with price/offset_pct), and resting trigger placements (stop_market | stop_limit | take_profit_market | take_profit_limit with trigger_price/trigger_offset_pct), plus reduce_only and attached/partial TP/SL (take_profit_price/take_profit_size, stop_loss_price/stop_loss_size, tpsl_linkage). Venue is selected with network (testnet default; mainnet signs with REAL funds and also requires CORE_PERPS_ALLOW_MAINNET_WRITES=1). Plain limit orders accept time_in_force GTC | ALO (post-only).",
|
|
733
788
|
"schema": {
|
|
@@ -1221,6 +1221,106 @@
|
|
|
1221
1221
|
],
|
|
1222
1222
|
"execution_capabilities": []
|
|
1223
1223
|
},
|
|
1224
|
+
"metamask.performance.measure_navigation_memory": {
|
|
1225
|
+
"description": "Measure retained DOM nodes and listeners across repeated Extension navigation after forced garbage collection, using least-squares slopes and separate declared bounds.",
|
|
1226
|
+
"schema": {
|
|
1227
|
+
"type": "object",
|
|
1228
|
+
"properties": {
|
|
1229
|
+
"screen_hash": { "type": "string" },
|
|
1230
|
+
"screen_test_id": { "type": "string" },
|
|
1231
|
+
"return_hash": { "type": "string", "default": "#/" },
|
|
1232
|
+
"cycles": { "type": "integer", "minimum": 2, "default": 8 },
|
|
1233
|
+
"max_nodes_per_cycle": { "type": "number", "default": 2 },
|
|
1234
|
+
"max_listeners_per_cycle": { "type": "number", "default": 4 },
|
|
1235
|
+
"settle_ms": { "type": "integer", "minimum": 0, "default": 1500 },
|
|
1236
|
+
"gc_settle_ms": { "type": "integer", "minimum": 0, "default": 300 },
|
|
1237
|
+
"screen_timeout_ms": { "type": "integer", "minimum": 1, "default": 15000 },
|
|
1238
|
+
"timeout_ms": { "type": "integer", "minimum": 1, "default": 400000 }
|
|
1239
|
+
},
|
|
1240
|
+
"required": ["screen_hash", "screen_test_id"],
|
|
1241
|
+
"additionalProperties": false
|
|
1242
|
+
},
|
|
1243
|
+
"examples": [
|
|
1244
|
+
{
|
|
1245
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
1246
|
+
"screen_hash": "#/settings",
|
|
1247
|
+
"screen_test_id": "settings-tab-bar-grouped",
|
|
1248
|
+
"return_hash": "#/",
|
|
1249
|
+
"cycles": 8,
|
|
1250
|
+
"max_nodes_per_cycle": 2,
|
|
1251
|
+
"max_listeners_per_cycle": 4,
|
|
1252
|
+
"intent": "Measure post-GC retained-memory slopes across repeated navigation.",
|
|
1253
|
+
"next": "done"
|
|
1254
|
+
}
|
|
1255
|
+
],
|
|
1256
|
+
"execution_capabilities": []
|
|
1257
|
+
},
|
|
1258
|
+
"metamask.performance.measure_detached_dom": {
|
|
1259
|
+
"description": "Compare retained detached-DOM counts before and after repeated Extension navigation, with forced garbage collection before each heap snapshot.",
|
|
1260
|
+
"schema": {
|
|
1261
|
+
"type": "object",
|
|
1262
|
+
"properties": {
|
|
1263
|
+
"screen_hash": { "type": "string" },
|
|
1264
|
+
"screen_test_id": { "type": "string" },
|
|
1265
|
+
"return_hash": { "type": "string", "default": "#/" },
|
|
1266
|
+
"cycles": { "type": "integer", "minimum": 2, "default": 5 },
|
|
1267
|
+
"max_detached_growth": { "type": "number", "default": 50 },
|
|
1268
|
+
"settle_ms": { "type": "integer", "minimum": 0, "default": 1500 },
|
|
1269
|
+
"gc_settle_ms": { "type": "integer", "minimum": 0, "default": 300 },
|
|
1270
|
+
"screen_timeout_ms": { "type": "integer", "minimum": 1, "default": 15000 },
|
|
1271
|
+
"timeout_ms": { "type": "integer", "minimum": 1, "default": 400000 }
|
|
1272
|
+
},
|
|
1273
|
+
"required": ["screen_hash", "screen_test_id"],
|
|
1274
|
+
"additionalProperties": false
|
|
1275
|
+
},
|
|
1276
|
+
"examples": [
|
|
1277
|
+
{
|
|
1278
|
+
"action": "metamask.performance.measure_detached_dom",
|
|
1279
|
+
"screen_hash": "#/settings",
|
|
1280
|
+
"screen_test_id": "settings-tab-bar-grouped",
|
|
1281
|
+
"return_hash": "#/",
|
|
1282
|
+
"cycles": 5,
|
|
1283
|
+
"max_detached_growth": 50,
|
|
1284
|
+
"intent": "Measure detached DOM retained after navigation and forced garbage collection.",
|
|
1285
|
+
"next": "done"
|
|
1286
|
+
}
|
|
1287
|
+
],
|
|
1288
|
+
"execution_capabilities": []
|
|
1289
|
+
},
|
|
1290
|
+
"metamask.performance.compare_idle_navigation_memory": {
|
|
1291
|
+
"description": "Compare equal-duration idle and navigating Extension arms to isolate navigation-attributable listener growth after forced garbage collection.",
|
|
1292
|
+
"schema": {
|
|
1293
|
+
"type": "object",
|
|
1294
|
+
"properties": {
|
|
1295
|
+
"screen_hash": { "type": "string" },
|
|
1296
|
+
"screen_test_id": { "type": "string" },
|
|
1297
|
+
"return_hash": { "type": "string", "default": "#/" },
|
|
1298
|
+
"rounds": { "type": "integer", "minimum": 2, "default": 8 },
|
|
1299
|
+
"round_duration_ms": { "type": "integer", "minimum": 1, "default": 10000 },
|
|
1300
|
+
"max_attributable_listeners_per_cycle": { "type": "number", "default": 2 },
|
|
1301
|
+
"settle_ms": { "type": "integer", "minimum": 0, "default": 1500 },
|
|
1302
|
+
"gc_settle_ms": { "type": "integer", "minimum": 0, "default": 300 },
|
|
1303
|
+
"screen_timeout_ms": { "type": "integer", "minimum": 1, "default": 15000 },
|
|
1304
|
+
"timeout_ms": { "type": "integer", "minimum": 1, "default": 400000 }
|
|
1305
|
+
},
|
|
1306
|
+
"required": ["screen_hash", "screen_test_id"],
|
|
1307
|
+
"additionalProperties": false
|
|
1308
|
+
},
|
|
1309
|
+
"examples": [
|
|
1310
|
+
{
|
|
1311
|
+
"action": "metamask.performance.compare_idle_navigation_memory",
|
|
1312
|
+
"screen_hash": "#/settings",
|
|
1313
|
+
"screen_test_id": "settings-tab-bar-grouped",
|
|
1314
|
+
"return_hash": "#/",
|
|
1315
|
+
"rounds": 8,
|
|
1316
|
+
"round_duration_ms": 10000,
|
|
1317
|
+
"max_attributable_listeners_per_cycle": 2,
|
|
1318
|
+
"intent": "Separate navigation-attributable listener growth from equal-duration idle growth.",
|
|
1319
|
+
"next": "done"
|
|
1320
|
+
}
|
|
1321
|
+
],
|
|
1322
|
+
"execution_capabilities": []
|
|
1323
|
+
},
|
|
1224
1324
|
"metamask.perps.read_positions": {
|
|
1225
1325
|
"description": "extension Read live Perps positions; without a selector, return all live positions.",
|
|
1226
1326
|
"schema": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
|
+
"title": "MetaMask core Perps snapshots",
|
|
4
|
+
"description": "Reads global market and account snapshots through the public PerpsController API used by wallet clients. Configure CORE_PERPS_TERMINAL_GLOBAL_SNAPSHOT_URL to exercise a Terminal schema-v2 endpoint.",
|
|
5
|
+
"workflow": {
|
|
6
|
+
"entry": "status",
|
|
7
|
+
"nodes": {
|
|
8
|
+
"status": {
|
|
9
|
+
"action": "app.status",
|
|
10
|
+
"next": "read_snapshot",
|
|
11
|
+
"intent": "Resolve the Core checkout and report headless compatibility"
|
|
12
|
+
},
|
|
13
|
+
"read_snapshot": {
|
|
14
|
+
"action": "metamask.perps.read_snapshot",
|
|
15
|
+
"scope": "all",
|
|
16
|
+
"next": "done",
|
|
17
|
+
"intent": "Read coherent market and account snapshots through public controller methods"
|
|
18
|
+
},
|
|
19
|
+
"done": {
|
|
20
|
+
"action": "end",
|
|
21
|
+
"status": "pass"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
|
+
"title": "Extension navigation retained-memory audit",
|
|
4
|
+
"description": "Measures post-GC retained nodes and listeners across eight representative Extension screens, verifies detached DOM does not grow, and separates navigation-attributable listener growth from an equal-duration idle control.",
|
|
5
|
+
"proofTargets": [
|
|
6
|
+
{
|
|
7
|
+
"id": "screen-slopes",
|
|
8
|
+
"claim": "Eight representative Extension screens remain within separate post-GC retained-node and listener growth bounds during navigation churn."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "detached-dom",
|
|
12
|
+
"claim": "Detached DOM retained after forced garbage collection does not grow beyond the declared bound."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "idle-control",
|
|
16
|
+
"claim": "Navigation-attributable listener growth remains within its declared bound after subtracting an equal-duration idle control."
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"workflow": {
|
|
20
|
+
"entry": "require-cdp",
|
|
21
|
+
"nodes": {
|
|
22
|
+
"require-cdp": {
|
|
23
|
+
"action": "cdp.target",
|
|
24
|
+
"required": true,
|
|
25
|
+
"require_reachable": true,
|
|
26
|
+
"intent": "Confirm the live Extension CDP target is reachable before measuring retained memory.",
|
|
27
|
+
"next": "ensure-unlocked"
|
|
28
|
+
},
|
|
29
|
+
"ensure-unlocked": {
|
|
30
|
+
"action": "metamask.wallet.ensure_unlocked",
|
|
31
|
+
"intent": "Ensure the representative screens are reachable from an unlocked wallet.",
|
|
32
|
+
"next": "market-detail"
|
|
33
|
+
},
|
|
34
|
+
"market-detail": {
|
|
35
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
36
|
+
"screen_hash": "#/perps/market/ETH",
|
|
37
|
+
"screen_test_id": "perps-market-detail-page",
|
|
38
|
+
"return_hash": "#/",
|
|
39
|
+
"cycles": 8,
|
|
40
|
+
"timeout_ms": 400000,
|
|
41
|
+
"max_nodes_per_cycle": 2,
|
|
42
|
+
"max_listeners_per_cycle": 4,
|
|
43
|
+
"intent": "Measure the post-GC navigation slope for Perps market detail.",
|
|
44
|
+
"proves": ["screen-slopes"],
|
|
45
|
+
"next": "order-entry"
|
|
46
|
+
},
|
|
47
|
+
"order-entry": {
|
|
48
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
49
|
+
"screen_hash": "#/perps/trade/ETH?direction=long&mode=new",
|
|
50
|
+
"screen_test_id": "perps-order-entry-page",
|
|
51
|
+
"return_hash": "#/",
|
|
52
|
+
"cycles": 6,
|
|
53
|
+
"timeout_ms": 400000,
|
|
54
|
+
"max_nodes_per_cycle": 2,
|
|
55
|
+
"max_listeners_per_cycle": 4,
|
|
56
|
+
"intent": "Measure the post-GC navigation slope for Perps order entry.",
|
|
57
|
+
"proves": ["screen-slopes"],
|
|
58
|
+
"next": "perps-home"
|
|
59
|
+
},
|
|
60
|
+
"perps-home": {
|
|
61
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
62
|
+
"screen_hash": "#/perps-home",
|
|
63
|
+
"screen_test_id": "perps-home-page",
|
|
64
|
+
"return_hash": "#/",
|
|
65
|
+
"cycles": 6,
|
|
66
|
+
"timeout_ms": 400000,
|
|
67
|
+
"max_nodes_per_cycle": 2,
|
|
68
|
+
"max_listeners_per_cycle": 4,
|
|
69
|
+
"intent": "Measure the post-GC navigation slope for Perps home.",
|
|
70
|
+
"proves": ["screen-slopes"],
|
|
71
|
+
"next": "market-list"
|
|
72
|
+
},
|
|
73
|
+
"market-list": {
|
|
74
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
75
|
+
"screen_hash": "#/perps/market-list",
|
|
76
|
+
"screen_test_id": "market-list-view",
|
|
77
|
+
"return_hash": "#/",
|
|
78
|
+
"cycles": 6,
|
|
79
|
+
"timeout_ms": 400000,
|
|
80
|
+
"max_nodes_per_cycle": 2,
|
|
81
|
+
"max_listeners_per_cycle": 4,
|
|
82
|
+
"intent": "Measure the post-GC navigation slope for the Perps market list.",
|
|
83
|
+
"proves": ["screen-slopes"],
|
|
84
|
+
"next": "perps-activity"
|
|
85
|
+
},
|
|
86
|
+
"perps-activity": {
|
|
87
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
88
|
+
"screen_hash": "#/perps/activity",
|
|
89
|
+
"screen_test_id": "perps-activity-page",
|
|
90
|
+
"return_hash": "#/",
|
|
91
|
+
"cycles": 6,
|
|
92
|
+
"timeout_ms": 400000,
|
|
93
|
+
"max_nodes_per_cycle": 2,
|
|
94
|
+
"max_listeners_per_cycle": 4,
|
|
95
|
+
"intent": "Measure the post-GC navigation slope for Perps activity.",
|
|
96
|
+
"proves": ["screen-slopes"],
|
|
97
|
+
"next": "asset-details"
|
|
98
|
+
},
|
|
99
|
+
"asset-details": {
|
|
100
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
101
|
+
"screen_hash": "#/asset/0x1/",
|
|
102
|
+
"screen_test_id": "asset-price-chart",
|
|
103
|
+
"return_hash": "#/",
|
|
104
|
+
"cycles": 6,
|
|
105
|
+
"timeout_ms": 400000,
|
|
106
|
+
"max_nodes_per_cycle": 2,
|
|
107
|
+
"max_listeners_per_cycle": 4,
|
|
108
|
+
"intent": "Measure the post-GC navigation slope for asset details.",
|
|
109
|
+
"proves": ["screen-slopes"],
|
|
110
|
+
"next": "swaps-bridge"
|
|
111
|
+
},
|
|
112
|
+
"swaps-bridge": {
|
|
113
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
114
|
+
"screen_hash": "#/cross-chain/swaps/prepare-bridge-page",
|
|
115
|
+
"screen_test_id": "bridge-source-button",
|
|
116
|
+
"return_hash": "#/",
|
|
117
|
+
"cycles": 6,
|
|
118
|
+
"timeout_ms": 400000,
|
|
119
|
+
"max_nodes_per_cycle": 2,
|
|
120
|
+
"max_listeners_per_cycle": 4,
|
|
121
|
+
"intent": "Measure the post-GC navigation slope for Swaps and Bridge.",
|
|
122
|
+
"proves": ["screen-slopes"],
|
|
123
|
+
"next": "settings-control"
|
|
124
|
+
},
|
|
125
|
+
"settings-control": {
|
|
126
|
+
"action": "metamask.performance.measure_navigation_memory",
|
|
127
|
+
"screen_hash": "#/settings",
|
|
128
|
+
"screen_test_id": "settings-tab-bar-grouped",
|
|
129
|
+
"return_hash": "#/",
|
|
130
|
+
"cycles": 6,
|
|
131
|
+
"timeout_ms": 400000,
|
|
132
|
+
"max_nodes_per_cycle": 2,
|
|
133
|
+
"max_listeners_per_cycle": 4,
|
|
134
|
+
"intent": "Measure a non-Perps control screen with the same retained-memory gate.",
|
|
135
|
+
"proves": ["screen-slopes"],
|
|
136
|
+
"next": "detached-market-detail"
|
|
137
|
+
},
|
|
138
|
+
"detached-market-detail": {
|
|
139
|
+
"action": "metamask.performance.measure_detached_dom",
|
|
140
|
+
"screen_hash": "#/perps/market/ETH",
|
|
141
|
+
"screen_test_id": "perps-market-detail-page",
|
|
142
|
+
"return_hash": "#/",
|
|
143
|
+
"cycles": 5,
|
|
144
|
+
"timeout_ms": 400000,
|
|
145
|
+
"max_detached_growth": 50,
|
|
146
|
+
"intent": "Compare retained detached DOM before and after market-detail navigation churn.",
|
|
147
|
+
"proves": ["detached-dom"],
|
|
148
|
+
"next": "idle-control-market-detail"
|
|
149
|
+
},
|
|
150
|
+
"idle-control-market-detail": {
|
|
151
|
+
"action": "metamask.performance.compare_idle_navigation_memory",
|
|
152
|
+
"screen_hash": "#/perps/market/ETH",
|
|
153
|
+
"screen_test_id": "perps-market-detail-page",
|
|
154
|
+
"return_hash": "#/",
|
|
155
|
+
"rounds": 8,
|
|
156
|
+
"round_duration_ms": 10000,
|
|
157
|
+
"timeout_ms": 400000,
|
|
158
|
+
"max_attributable_listeners_per_cycle": 2,
|
|
159
|
+
"intent": "Subtract equal-duration idle growth from the market-detail navigation slope.",
|
|
160
|
+
"proves": ["idle-control"],
|
|
161
|
+
"next": "return-home"
|
|
162
|
+
},
|
|
163
|
+
"return-home": {
|
|
164
|
+
"action": "ui.navigate",
|
|
165
|
+
"page": "home",
|
|
166
|
+
"intent": "Leave the Extension on a known screen after the audit.",
|
|
167
|
+
"next": "done"
|
|
168
|
+
},
|
|
169
|
+
"done": {
|
|
170
|
+
"action": "end",
|
|
171
|
+
"status": "pass"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
package/package.json
CHANGED
|
@@ -351,11 +351,33 @@ async function checkBehavior(browserEndpoint, port, pages) {
|
|
|
351
351
|
return window.__siteCopied;
|
|
352
352
|
})()
|
|
353
353
|
`);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
354
|
+
// Both halves of the onboarding have to survive a copy: set up the harness
|
|
355
|
+
// and the skills, then drive real work through one.
|
|
356
|
+
const expected = ['npm i -g @deeeed/metamask-harness@latest', 'yarn skills', '/mms-recipe-cook'];
|
|
357
|
+
const missing = expected.filter((line) => !copied?.includes(line));
|
|
358
|
+
if (missing.length) {
|
|
359
|
+
throw new Error(`copy prompt omitted ${missing.map((m) => `"${m}"`).join(', ')}`);
|
|
357
360
|
}
|
|
358
361
|
|
|
362
|
+
// The landing page is the lobby: one prompt, one way onward, nothing else.
|
|
363
|
+
const lobby = await page.evaluate(String.raw`
|
|
364
|
+
(() => ({
|
|
365
|
+
nav: document.querySelectorAll('.nav a').length,
|
|
366
|
+
steps: document.querySelectorAll('.step[data-step]').length,
|
|
367
|
+
depth: document.querySelector('a[href="how-it-works.html"]') !== null,
|
|
368
|
+
anchor: document.getElementById('prompt') !== null,
|
|
369
|
+
}))()
|
|
370
|
+
`);
|
|
371
|
+
if (lobby.nav || lobby.steps || !lobby.depth || !lobby.anchor) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`landing page is not a lobby: ${lobby.nav} nav links, ${lobby.steps} steps, ` +
|
|
374
|
+
`depth link ${lobby.depth}, prompt anchor ${lobby.anchor}`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// The walkthrough and its saved progress live one click deeper.
|
|
380
|
+
await withPage(browserEndpoint, `${base}how-it-works.html`, async (page) => {
|
|
359
381
|
const progress = await page.evaluate(String.raw`
|
|
360
382
|
(() => {
|
|
361
383
|
const box = document.querySelector('.step-check');
|
|
@@ -426,6 +448,20 @@ async function checkBehavior(browserEndpoint, port, pages) {
|
|
|
426
448
|
if (broken.length) {
|
|
427
449
|
throw new Error(`${relative} has broken internal links: ${broken.join(', ')}`);
|
|
428
450
|
}
|
|
451
|
+
// Every page past the lobby owes the reader one click back to the prompt.
|
|
452
|
+
if (relative !== 'index.html') {
|
|
453
|
+
const back = await page.evaluate(String.raw`
|
|
454
|
+
(() => {
|
|
455
|
+
const link = document.querySelector('.nav a.nav-cta');
|
|
456
|
+
if (!link) return null;
|
|
457
|
+
const url = new URL(link.getAttribute('href'), location.href);
|
|
458
|
+
return url.pathname + url.hash;
|
|
459
|
+
})()
|
|
460
|
+
`);
|
|
461
|
+
if (back !== '/index.html#prompt') {
|
|
462
|
+
throw new Error(`${relative} has no nav link back to the setup prompt (found ${back})`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
429
465
|
for (const width of [834, 390]) {
|
|
430
466
|
await page.send('Emulation.setDeviceMetricsOverride', {
|
|
431
467
|
width,
|
package/site/architecture.html
CHANGED
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
<div class="wrap topbar-inner">
|
|
15
15
|
<a class="brand" href="index.html">
|
|
16
16
|
<span class="brand-mark" aria-hidden="true"></span>
|
|
17
|
-
<span class="brand-name">
|
|
17
|
+
<span class="brand-name">recipes</span>
|
|
18
18
|
</a>
|
|
19
19
|
<nav class="nav" aria-label="Main">
|
|
20
|
-
<a href="index.html">
|
|
21
|
-
<a href="
|
|
20
|
+
<a class="nav-cta" href="index.html#prompt">Quick start</a>
|
|
21
|
+
<a href="how-it-works.html">How it works</a>
|
|
22
|
+
<a href="recipes.html">Recipe anatomy</a>
|
|
22
23
|
<a href="perps.html">Perps</a>
|
|
23
24
|
<a href="cheatsheet.html">Cheatsheet</a>
|
|
24
25
|
<a href="architecture.html" aria-current="page">Architecture</a>
|
|
@@ -30,11 +31,12 @@
|
|
|
30
31
|
|
|
31
32
|
<main>
|
|
32
33
|
<section class="wrap hero" style="padding-bottom:1rem">
|
|
33
|
-
<span class="eyebrow">The stack</span>
|
|
34
|
+
<span class="eyebrow">The stack behind a recipe</span>
|
|
34
35
|
<h1>How the pieces fit together</h1>
|
|
35
36
|
<p class="lede">
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
The recipe is the unit of trust; the rest of the stack runs it. Four moving parts, two of which
|
|
38
|
+
people routinely confuse. What each layer is, where it lives, which workflow you are in, and the
|
|
39
|
+
mistakes that cost newcomers their first afternoon.
|
|
38
40
|
</p>
|
|
39
41
|
</section>
|
|
40
42
|
|
|
@@ -45,8 +47,9 @@
|
|
|
45
47
|
<span class="k">A recipe proves a task with actions.</span> Recipes are JSON graphs of typed
|
|
46
48
|
actions; <code>mm-harness</code> executes them against a real app and produces an evidence bundle
|
|
47
49
|
a reviewer can trust. <strong>Skills</strong> teach an agent a workflow — author a recipe, review
|
|
48
|
-
a PR, validate a release
|
|
49
|
-
|
|
50
|
+
a PR, validate a release — and are the layer you work through; they call the harness, which runs
|
|
51
|
+
the recipe. <strong>Recipe libraries</strong> hold each team's recipes and domain actions.
|
|
52
|
+
Compose in that order, against the product repo under test.
|
|
50
53
|
</p>
|
|
51
54
|
</div>
|
|
52
55
|
</section>
|
|
@@ -67,9 +70,12 @@
|
|
|
67
70
|
release — turning "figure it out" into a procedure with gates you can steer.
|
|
68
71
|
</p>
|
|
69
72
|
<p>
|
|
70
|
-
Skills
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
Skills come from the public <code>MetaMask/skills</code> repo plus the internal
|
|
74
|
+
<code>Consensys/skills</code> overlay, which needs access to that org; private skills override
|
|
75
|
+
public ones on a name collision. Clone each, point <code>METAMASK_SKILLS_DIR</code> and
|
|
76
|
+
<code>CONSENSYS_SKILLS_DIR</code> at them, then install per checkout with
|
|
77
|
+
<code>yarn skills</code>. <strong>You do not need skills to run a recipe</strong>, only to have
|
|
78
|
+
an agent follow a proven workflow instead of improvising one.
|
|
73
79
|
<a href="tutorials/v3.html">V3 walks the install.</a>
|
|
74
80
|
</p>
|
|
75
81
|
</div>
|
|
@@ -129,8 +135,8 @@
|
|
|
129
135
|
</p>
|
|
130
136
|
<p style="margin-bottom:0">
|
|
131
137
|
They differ, and the harness says so rather than pretending otherwise: Core is headless with
|
|
132
|
-
nothing to launch and no UI actions;
|
|
133
|
-
|
|
138
|
+
nothing to launch and no UI actions; Extension and iOS can record full-run video; Android
|
|
139
|
+
replay video is not implemented yet, so use screenshot evidence there.
|
|
134
140
|
</p>
|
|
135
141
|
</div>
|
|
136
142
|
</div>
|
|
@@ -466,7 +472,7 @@
|
|
|
466
472
|
|
|
467
473
|
<hr class="sep">
|
|
468
474
|
<div class="btn-row">
|
|
469
|
-
<a class="btn btn-primary" href="
|
|
475
|
+
<a class="btn btn-primary" href="how-it-works.html#steps">Do the walkthrough →</a>
|
|
470
476
|
<a class="btn btn-ghost" href="reviewers.html">Read an evidence bundle</a>
|
|
471
477
|
</div>
|
|
472
478
|
</section>
|
|
@@ -474,8 +480,8 @@
|
|
|
474
480
|
|
|
475
481
|
<footer class="footer">
|
|
476
482
|
<div class="wrap">
|
|
477
|
-
<p>Internal getting-started guide for
|
|
478
|
-
<p>
|
|
483
|
+
<p>Internal getting-started guide for proving MetaMask changes with recipes. Not official MetaMask product documentation.</p>
|
|
484
|
+
<p>Sample output moves between releases; trust your terminal over this page.</p>
|
|
479
485
|
</div>
|
|
480
486
|
</footer>
|
|
481
487
|
|
package/site/assets/progress.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* buttons, platform filters, and the layer diagram. No dependencies, no
|
|
4
4
|
* network, no tracking. Progress lives in localStorage under
|
|
5
5
|
* `mmh.progress.<page>`, where <page> comes from body[data-progress-page].
|
|
6
|
-
* Pages that share a namespace share their state (
|
|
6
|
+
* Pages that share a namespace share their state (the How it works walkthrough and the V1
|
|
7
7
|
* tutorial are deliberately the same checklist).
|
|
8
8
|
*/
|
|
9
9
|
(function () {
|