@deeeed/metamask-harness 0.33.2 → 0.34.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 +31 -0
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +18 -2
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +83 -23
- package/dist/adapters/mobile/prepare.js +14 -0
- package/dist/adapters/mobile/video-recorder.js +282 -0
- package/dist/adapters.js +225 -35
- package/dist/command-contract.js +1 -0
- package/dist/commands/device-target.js +17 -6
- package/dist/commands/launch/index.js +27 -5
- package/dist/commands/launch/mobile.js +9 -2
- package/dist/commands/run-engine.js +34 -2
- package/dist/devices.js +9 -2
- package/dist/heal-bounds.js +10 -0
- package/dist/mm-harness-cli.js +4 -2
- package/dist/recipe-security.js +1 -0
- package/dist/recording-target.js +23 -5
- package/dist/runner.js +107 -6
- package/dist/runtime-context.js +33 -1
- package/docs/RECIPES.md +41 -0
- package/library/actions/mobile/app/network-control.mjs +135 -0
- package/library/actions/mobile/app/network.mjs +4 -0
- package/library/actions/mobile/perps/capture_performance.mjs +4 -0
- package/library/actions/mobile/perps/performance-capture.mjs +383 -0
- package/library/actions/mobile/perps/perps.mjs +16 -0
- package/library/actions/mobile/platform/bridge.mjs +229 -13
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +27 -6
- package/library/actions/mobile/wallet/select_account.mjs +21 -17
- package/library/manifests/mobile.action-manifest.json +130 -1
- package/library/recipes/mobile/perps/performance.homepage.android-background-reconnect.recipe.json +159 -0
- package/library/recipes/mobile/perps/performance.homepage.android-background-short.recipe.json +157 -0
- package/library/recipes/mobile/perps/performance.homepage.android-cold-disk-cache.recipe.json +147 -0
- package/library/recipes/mobile/perps/performance.homepage.android-cold-no-cache.recipe.json +137 -0
- package/library/recipes/mobile/perps/performance.homepage.android-network-recovery.recipe.json +149 -0
- package/library/recipes/mobile/perps/performance.homepage.ios-background-reconnect.recipe.json +110 -0
- package/library/recipes/mobile/perps/performance.homepage.ios-background-short.recipe.json +108 -0
- package/library/recipes/mobile/perps/performance.homepage.ios-cold-disk-cache.recipe.json +122 -0
- package/library/recipes/mobile/perps/performance.homepage.ios-cold-no-cache.recipe.json +95 -0
- package/package.json +1 -1
- package/scripts/site-contrast.mjs +34 -1
- package/site/architecture.html +12 -2
- package/site/assets/style.css +20 -1
- package/site/cheatsheet.html +7 -6
- package/site/index.html +119 -77
- package/site/perps.html +195 -0
- package/site/recipes.html +23 -1
- package/site/reviewers.html +2 -1
- package/site/tutorials/index.html +2 -1
- package/site/tutorials/v1.html +3 -2
- package/site/tutorials/v2.html +6 -5
- package/site/tutorials/v3.html +43 -2
- package/site/tutorials/v4.html +2 -1
- package/site/tutorials/v5.html +2 -1
- package/site/tutorials/v6.html +2 -1
- package/site/tutorials/v7.html +2 -1
|
@@ -61,6 +61,25 @@ async function waitForTargetStatus(input, timeoutMs = 20000) {
|
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
async function waitForWalletState(input, initialStatus, timeoutMs) {
|
|
65
|
+
const deadline = Date.now() + timeoutMs;
|
|
66
|
+
let last = initialStatus;
|
|
67
|
+
while (true) {
|
|
68
|
+
if (selectedAccount(last, input) || routeName(last, input) === 'Login') return last;
|
|
69
|
+
if (Date.now() >= deadline) break;
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
71
|
+
try {
|
|
72
|
+
last = await status(input);
|
|
73
|
+
} catch {
|
|
74
|
+
// A rotating Hermes target during startup is transient. The target timeout
|
|
75
|
+
// still bounds this loop and the final error reports the last wallet state.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw new Error(
|
|
79
|
+
`No wallet onboarded on this device (route ${routeName(last, input) || 'unknown'}, empty wallet status) — unlock has nothing to unlock.\n Next: mm-harness fixtures set # applies the fixture wallet, then re-run: mm-harness call ensure_unlocked`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
async function waitForUnlocked(input, timeoutMs = 15000) {
|
|
65
84
|
const deadline = Date.now() + timeoutMs;
|
|
66
85
|
let last = null;
|
|
@@ -110,12 +129,14 @@ async function waitForStableUnlocked(input, initialStatus, stableMs = 750) {
|
|
|
110
129
|
}
|
|
111
130
|
|
|
112
131
|
export async function ensureUnlocked(input) {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
const targetTimeoutMs = Number(input.node?.target_timeout_ms ?? 20000);
|
|
133
|
+
const targetDeadline = Date.now() + targetTimeoutMs;
|
|
134
|
+
const targetStatus = await waitForTargetStatus(input, targetTimeoutMs);
|
|
135
|
+
const before = await waitForWalletState(
|
|
136
|
+
input,
|
|
137
|
+
targetStatus,
|
|
138
|
+
Math.max(0, targetDeadline - Date.now()),
|
|
139
|
+
);
|
|
119
140
|
const stableBefore = await waitForStableUnlocked(input, before, Number(input.node?.stable_unlocked_ms ?? 750));
|
|
120
141
|
if (stableBefore) {
|
|
121
142
|
return {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { bridgeCommand,
|
|
1
|
+
import { bridgeCommand, runAdapter } from '../platform/bridge.mjs';
|
|
2
|
+
|
|
3
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
4
|
|
|
3
5
|
runAdapter(async (input) => {
|
|
4
6
|
const requestedAddress = input.node?.address ? String(input.node.address).toLowerCase() : null;
|
|
@@ -23,26 +25,28 @@ runAdapter(async (input) => {
|
|
|
23
25
|
throw new Error(`Requested mobile account was not found in list-accounts: ${JSON.stringify({ address: requestedAddress, id: requestedId, name: requestedName })}`);
|
|
24
26
|
}
|
|
25
27
|
const result = await bridgeCommand(input, ['switch-account', String(match.address)]);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
28
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 5000);
|
|
29
|
+
const deadline = Date.now() + timeoutMs;
|
|
30
|
+
let selected = null;
|
|
31
|
+
do {
|
|
32
|
+
selected = await bridgeCommand(input, ['get-selected-account']);
|
|
33
|
+
if (String(selected?.address ?? selected ?? '').toLowerCase() === String(match.address).toLowerCase()) {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
await sleep(100);
|
|
37
|
+
} while (Date.now() < deadline);
|
|
38
|
+
|
|
39
|
+
if (String(selected?.address ?? selected ?? '').toLowerCase() !== String(match.address).toLowerCase()) {
|
|
40
|
+
throw new Error(`Mobile account selection did not converge to ${match.address} after ${timeoutMs}ms.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
return {
|
|
39
44
|
action: input.action,
|
|
40
45
|
selected: {
|
|
41
|
-
address: result.address,
|
|
42
|
-
id: result.id,
|
|
43
|
-
name: result.name,
|
|
46
|
+
address: selected?.address ?? result.address,
|
|
47
|
+
id: selected?.id ?? result.id,
|
|
48
|
+
name: selected?.name ?? result.name,
|
|
44
49
|
},
|
|
45
|
-
perps,
|
|
46
50
|
proofPath: 'agentic-account-selection',
|
|
47
51
|
};
|
|
48
52
|
});
|
|
@@ -855,7 +855,7 @@
|
|
|
855
855
|
]
|
|
856
856
|
},
|
|
857
857
|
"ui.wait_for": {
|
|
858
|
-
"description": "Wait for a
|
|
858
|
+
"description": "Wait for a React Native testID/test_id or text target. expected=visible requires a measured viewport intersection; expected=present only requires the mounted tree.",
|
|
859
859
|
"schema": {
|
|
860
860
|
"type": "object",
|
|
861
861
|
"properties": {
|
|
@@ -910,6 +910,13 @@
|
|
|
910
910
|
"expected": "absent",
|
|
911
911
|
"intent": "Confirm the requested application state is visible.",
|
|
912
912
|
"next": "done"
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
"action": "ui.wait_for",
|
|
916
|
+
"text": "Limit long",
|
|
917
|
+
"expected": "visible",
|
|
918
|
+
"intent": "Confirm the requested text is actually inside the viewport.",
|
|
919
|
+
"next": "done"
|
|
913
920
|
}
|
|
914
921
|
]
|
|
915
922
|
},
|
|
@@ -1035,6 +1042,9 @@
|
|
|
1035
1042
|
"timeout_ms": {
|
|
1036
1043
|
"type": "integer"
|
|
1037
1044
|
},
|
|
1045
|
+
"runtime_ready_timeout_ms": {
|
|
1046
|
+
"type": "integer"
|
|
1047
|
+
},
|
|
1038
1048
|
"platform": {
|
|
1039
1049
|
"type": "string",
|
|
1040
1050
|
"enum": [
|
|
@@ -1091,6 +1101,39 @@
|
|
|
1091
1101
|
"additionalProperties": false
|
|
1092
1102
|
}
|
|
1093
1103
|
},
|
|
1104
|
+
"app.network": {
|
|
1105
|
+
"description": "Set the selected Android device offline or online for deterministic network-recovery recipes. iOS simulator network mutation is refused until a safe device-scoped mechanism exists.",
|
|
1106
|
+
"examples": [
|
|
1107
|
+
{
|
|
1108
|
+
"action": "app.network",
|
|
1109
|
+
"state": "offline",
|
|
1110
|
+
"settle_ms": 3000,
|
|
1111
|
+
"intent": "Disconnect the selected device before exercising recovery.",
|
|
1112
|
+
"next": "restore-network"
|
|
1113
|
+
}
|
|
1114
|
+
],
|
|
1115
|
+
"schema": {
|
|
1116
|
+
"type": "object",
|
|
1117
|
+
"properties": {
|
|
1118
|
+
"state": {
|
|
1119
|
+
"type": "string",
|
|
1120
|
+
"enum": ["offline", "online"]
|
|
1121
|
+
},
|
|
1122
|
+
"settle_ms": {
|
|
1123
|
+
"type": "integer"
|
|
1124
|
+
},
|
|
1125
|
+
"adb_serial": {
|
|
1126
|
+
"type": "string"
|
|
1127
|
+
},
|
|
1128
|
+
"device": {
|
|
1129
|
+
"type": "string"
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
"required": ["state"],
|
|
1133
|
+
"additionalProperties": false
|
|
1134
|
+
},
|
|
1135
|
+
"execution_capabilities": ["app-mutation"]
|
|
1136
|
+
},
|
|
1094
1137
|
"app.status": {
|
|
1095
1138
|
"description": "Report the adapter's static status — platform, project root, resolved checkout shape, and headless compatibility mode (no live route or account).",
|
|
1096
1139
|
"examples": [
|
|
@@ -1287,6 +1330,11 @@
|
|
|
1287
1330
|
"name": {
|
|
1288
1331
|
"type": "string",
|
|
1289
1332
|
"description": "Display name of the account to select."
|
|
1333
|
+
},
|
|
1334
|
+
"cdp_timeout_ms": {
|
|
1335
|
+
"type": "number",
|
|
1336
|
+
"minimum": 1,
|
|
1337
|
+
"description": "Maximum time in milliseconds for the mobile CDP command."
|
|
1290
1338
|
}
|
|
1291
1339
|
},
|
|
1292
1340
|
"additionalProperties": false
|
|
@@ -2400,6 +2448,87 @@
|
|
|
2400
2448
|
"external-mutation"
|
|
2401
2449
|
]
|
|
2402
2450
|
},
|
|
2451
|
+
"metamask.perps.capture_performance": {
|
|
2452
|
+
"description": "Capture only run-window [HomepagePerf] DevLogger records and a parsed stage/lifecycle summary for Perps on-device validation.",
|
|
2453
|
+
"schema": {
|
|
2454
|
+
"type": "object",
|
|
2455
|
+
"properties": {
|
|
2456
|
+
"phase": {
|
|
2457
|
+
"type": "string",
|
|
2458
|
+
"enum": ["start", "end"]
|
|
2459
|
+
},
|
|
2460
|
+
"source_path": {
|
|
2461
|
+
"type": "string"
|
|
2462
|
+
},
|
|
2463
|
+
"artifact_prefix": {
|
|
2464
|
+
"type": "string"
|
|
2465
|
+
},
|
|
2466
|
+
"require_records": {
|
|
2467
|
+
"type": "boolean"
|
|
2468
|
+
},
|
|
2469
|
+
"required_stages": {
|
|
2470
|
+
"type": "array",
|
|
2471
|
+
"items": {
|
|
2472
|
+
"type": "string"
|
|
2473
|
+
}
|
|
2474
|
+
},
|
|
2475
|
+
"required_lifecycles": {
|
|
2476
|
+
"type": "array",
|
|
2477
|
+
"items": {
|
|
2478
|
+
"type": "string"
|
|
2479
|
+
}
|
|
2480
|
+
},
|
|
2481
|
+
"required_sources": {
|
|
2482
|
+
"type": "array",
|
|
2483
|
+
"items": {
|
|
2484
|
+
"type": "string"
|
|
2485
|
+
}
|
|
2486
|
+
},
|
|
2487
|
+
"required_fresh_content_variants": {
|
|
2488
|
+
"type": "array",
|
|
2489
|
+
"items": {
|
|
2490
|
+
"type": "string"
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
},
|
|
2494
|
+
"required": ["phase"],
|
|
2495
|
+
"additionalProperties": false
|
|
2496
|
+
},
|
|
2497
|
+
"examples": [
|
|
2498
|
+
{
|
|
2499
|
+
"action": "metamask.perps.capture_performance",
|
|
2500
|
+
"phase": "start",
|
|
2501
|
+
"intent": "Start the Perps Homepage performance evidence window",
|
|
2502
|
+
"next": "exercise-flow"
|
|
2503
|
+
},
|
|
2504
|
+
{
|
|
2505
|
+
"action": "metamask.perps.capture_performance",
|
|
2506
|
+
"phase": "end",
|
|
2507
|
+
"required_stages": ["viewport_demand", "next_frame_checkpoint"],
|
|
2508
|
+
"intent": "Preserve the Perps Homepage performance evidence emitted during this run",
|
|
2509
|
+
"next": "done"
|
|
2510
|
+
}
|
|
2511
|
+
],
|
|
2512
|
+
"execution_capabilities": []
|
|
2513
|
+
},
|
|
2514
|
+
"metamask.perps.clear_performance_caches": {
|
|
2515
|
+
"description": "mobile Clear only persisted Perps market/user performance caches in the development app before a cold-no-cache validation run.",
|
|
2516
|
+
"schema": {
|
|
2517
|
+
"type": "object",
|
|
2518
|
+
"properties": {},
|
|
2519
|
+
"additionalProperties": false
|
|
2520
|
+
},
|
|
2521
|
+
"examples": [
|
|
2522
|
+
{
|
|
2523
|
+
"action": "metamask.perps.clear_performance_caches",
|
|
2524
|
+
"intent": "Prepare a deterministic cold process start without Perps cache hydration",
|
|
2525
|
+
"next": "restart-app"
|
|
2526
|
+
}
|
|
2527
|
+
],
|
|
2528
|
+
"execution_capabilities": [
|
|
2529
|
+
"app-mutation"
|
|
2530
|
+
]
|
|
2531
|
+
},
|
|
2403
2532
|
"metamask.perps.start_state": {
|
|
2404
2533
|
"description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Recommended configurable Perps start state that first restores the fixture-backed unlocked wallet, then composes reusable domain operations before the proof window.",
|
|
2405
2534
|
"schema": {
|
package/library/recipes/mobile/perps/performance.homepage.android-background-reconnect.recipe.json
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
|
+
"title": "Android Homepage Perps reconnect visible performance proof",
|
|
4
|
+
"description": "Establishes a visible live order on Wallet Home, backgrounds the physical Android app beyond the 20-second connection grace period, restores it, and validates the reconnect-to-fresh-visible pipeline.",
|
|
5
|
+
"workflow": {
|
|
6
|
+
"entry": "prepare-open-order",
|
|
7
|
+
"nodes": {
|
|
8
|
+
"prepare-open-order": {
|
|
9
|
+
"action": "metamask.perps.start_state",
|
|
10
|
+
"profile": "open_order_testnet",
|
|
11
|
+
"provider": "hyperliquid",
|
|
12
|
+
"network": "testnet",
|
|
13
|
+
"market": "ETH",
|
|
14
|
+
"page": "perps_home",
|
|
15
|
+
"side": "short",
|
|
16
|
+
"orders": {
|
|
17
|
+
"state": "open",
|
|
18
|
+
"mode": "matching",
|
|
19
|
+
"market": "ETH",
|
|
20
|
+
"side": "short",
|
|
21
|
+
"notional": 10,
|
|
22
|
+
"leverage": 2,
|
|
23
|
+
"timeout_ms": 90000
|
|
24
|
+
},
|
|
25
|
+
"positions": false,
|
|
26
|
+
"intent": "Prepare a deterministic live order before the reconnect proof",
|
|
27
|
+
"timeout_ms": 120000,
|
|
28
|
+
"next": "open-wallet-home"
|
|
29
|
+
},
|
|
30
|
+
"open-wallet-home": {
|
|
31
|
+
"action": "ui.navigate",
|
|
32
|
+
"page": "home",
|
|
33
|
+
"intent": "Open Wallet Home before the connection expires",
|
|
34
|
+
"timeout_ms": 30000,
|
|
35
|
+
"next": "wait-content-mounted"
|
|
36
|
+
},
|
|
37
|
+
"wait-content-mounted": {
|
|
38
|
+
"action": "ui.wait_for",
|
|
39
|
+
"test_id": "homepage-perps-positions",
|
|
40
|
+
"expected": "present",
|
|
41
|
+
"intent": "Confirm the data-backed Homepage section is rendered",
|
|
42
|
+
"timeout_ms": 30000,
|
|
43
|
+
"next": "show-content"
|
|
44
|
+
},
|
|
45
|
+
"show-content": {
|
|
46
|
+
"action": "ui.scroll",
|
|
47
|
+
"test_id": "homepage-perps-positions",
|
|
48
|
+
"scroll_into_view": true,
|
|
49
|
+
"animated": true,
|
|
50
|
+
"settle": true,
|
|
51
|
+
"intent": "Make the live order reviewable before disconnecting",
|
|
52
|
+
"timeout_ms": 30000,
|
|
53
|
+
"next": "wait-order-before"
|
|
54
|
+
},
|
|
55
|
+
"wait-order-before": {
|
|
56
|
+
"action": "ui.wait_for",
|
|
57
|
+
"text": "Limit short",
|
|
58
|
+
"expected": "visible",
|
|
59
|
+
"intent": "Prove the same live order is visible before reconnect",
|
|
60
|
+
"timeout_ms": 30000,
|
|
61
|
+
"next": "start-performance-capture"
|
|
62
|
+
},
|
|
63
|
+
"start-performance-capture": {
|
|
64
|
+
"action": "metamask.perps.capture_performance",
|
|
65
|
+
"phase": "start",
|
|
66
|
+
"intent": "Start the evidence window immediately before the long background interval",
|
|
67
|
+
"next": "background-app"
|
|
68
|
+
},
|
|
69
|
+
"background-app": {
|
|
70
|
+
"action": "app.lifecycle",
|
|
71
|
+
"command": "background",
|
|
72
|
+
"settle_ms": 25000,
|
|
73
|
+
"intent": "Keep the app inactive beyond the connection grace period",
|
|
74
|
+
"next": "foreground-app"
|
|
75
|
+
},
|
|
76
|
+
"foreground-app": {
|
|
77
|
+
"action": "app.lifecycle",
|
|
78
|
+
"command": "foreground",
|
|
79
|
+
"settle_ms": 15000,
|
|
80
|
+
"intent": "Restore the installed app and allow stream recovery",
|
|
81
|
+
"next": "wait-content-after"
|
|
82
|
+
},
|
|
83
|
+
"wait-content-after": {
|
|
84
|
+
"action": "ui.wait_for",
|
|
85
|
+
"test_id": "homepage-perps-positions",
|
|
86
|
+
"expected": "present",
|
|
87
|
+
"intent": "Confirm the Homepage Perps state remains rendered after reconnect",
|
|
88
|
+
"timeout_ms": 30000,
|
|
89
|
+
"next": "show-content-after"
|
|
90
|
+
},
|
|
91
|
+
"show-content-after": {
|
|
92
|
+
"action": "ui.scroll",
|
|
93
|
+
"test_id": "homepage-perps-positions",
|
|
94
|
+
"scroll_into_view": true,
|
|
95
|
+
"animated": true,
|
|
96
|
+
"settle": true,
|
|
97
|
+
"intent": "Make the recovered data directly reviewable",
|
|
98
|
+
"timeout_ms": 30000,
|
|
99
|
+
"next": "wait-order-after"
|
|
100
|
+
},
|
|
101
|
+
"wait-order-after": {
|
|
102
|
+
"action": "ui.wait_for",
|
|
103
|
+
"text": "Limit short",
|
|
104
|
+
"expected": "visible",
|
|
105
|
+
"intent": "Prove the live order is displayed after stream recovery",
|
|
106
|
+
"timeout_ms": 30000,
|
|
107
|
+
"next": "capture-after"
|
|
108
|
+
},
|
|
109
|
+
"capture-after": {
|
|
110
|
+
"action": "ui.screenshot",
|
|
111
|
+
"path": "screenshots/homepage-order-after-reconnect.png",
|
|
112
|
+
"intent": "Preserve reviewer-visible reconnect proof",
|
|
113
|
+
"next": "settle-frame"
|
|
114
|
+
},
|
|
115
|
+
"settle-frame": {
|
|
116
|
+
"action": "wait",
|
|
117
|
+
"duration_ms": 2500,
|
|
118
|
+
"intent": "Allow the fresh visible frame checkpoint to finish",
|
|
119
|
+
"next": "finish-performance-capture"
|
|
120
|
+
},
|
|
121
|
+
"finish-performance-capture": {
|
|
122
|
+
"action": "metamask.perps.capture_performance",
|
|
123
|
+
"phase": "end",
|
|
124
|
+
"required_stages": [
|
|
125
|
+
"viewport_demand",
|
|
126
|
+
"socket_received",
|
|
127
|
+
"react_commit",
|
|
128
|
+
"next_frame_checkpoint"
|
|
129
|
+
],
|
|
130
|
+
"required_lifecycles": ["background_reconnect"],
|
|
131
|
+
"required_sources": ["resident_state", "fresh_socket"],
|
|
132
|
+
"required_fresh_content_variants": ["orders"],
|
|
133
|
+
"intent": "Preserve and validate the reconnect measurement cohort",
|
|
134
|
+
"next": "cleanup-order"
|
|
135
|
+
},
|
|
136
|
+
"cleanup-order": {
|
|
137
|
+
"action": "metamask.perps.teardown_state",
|
|
138
|
+
"provider": "hyperliquid",
|
|
139
|
+
"network": "testnet",
|
|
140
|
+
"market": "ETH",
|
|
141
|
+
"page": "home",
|
|
142
|
+
"positions": false,
|
|
143
|
+
"orders": {
|
|
144
|
+
"state": "none",
|
|
145
|
+
"mode": "matching",
|
|
146
|
+
"market": "ETH",
|
|
147
|
+
"timeout_ms": 60000
|
|
148
|
+
},
|
|
149
|
+
"intent": "Cancel the deterministic testnet order after evidence capture",
|
|
150
|
+
"timeout_ms": 90000,
|
|
151
|
+
"next": "done"
|
|
152
|
+
},
|
|
153
|
+
"done": {
|
|
154
|
+
"action": "end",
|
|
155
|
+
"status": "pass"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
package/library/recipes/mobile/perps/performance.homepage.android-background-short.recipe.json
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
|
|
3
|
+
"title": "Android Homepage Perps short-background visible performance proof",
|
|
4
|
+
"description": "Establishes a visible live order on Wallet Home, backgrounds the physical Android app for less than the 20-second reconnect threshold, restores it, and correlates the resumed frame with run-scoped performance evidence.",
|
|
5
|
+
"workflow": {
|
|
6
|
+
"entry": "prepare-open-order",
|
|
7
|
+
"nodes": {
|
|
8
|
+
"prepare-open-order": {
|
|
9
|
+
"action": "metamask.perps.start_state",
|
|
10
|
+
"profile": "open_order_testnet",
|
|
11
|
+
"provider": "hyperliquid",
|
|
12
|
+
"network": "testnet",
|
|
13
|
+
"market": "ETH",
|
|
14
|
+
"page": "perps_home",
|
|
15
|
+
"side": "short",
|
|
16
|
+
"orders": {
|
|
17
|
+
"state": "open",
|
|
18
|
+
"mode": "matching",
|
|
19
|
+
"market": "ETH",
|
|
20
|
+
"side": "short",
|
|
21
|
+
"notional": 10,
|
|
22
|
+
"leverage": 2,
|
|
23
|
+
"timeout_ms": 90000
|
|
24
|
+
},
|
|
25
|
+
"positions": false,
|
|
26
|
+
"intent": "Prepare a deterministic live order before the lifecycle proof",
|
|
27
|
+
"timeout_ms": 120000,
|
|
28
|
+
"next": "open-wallet-home"
|
|
29
|
+
},
|
|
30
|
+
"open-wallet-home": {
|
|
31
|
+
"action": "ui.navigate",
|
|
32
|
+
"page": "home",
|
|
33
|
+
"intent": "Open Wallet Home before the lifecycle transition",
|
|
34
|
+
"timeout_ms": 30000,
|
|
35
|
+
"next": "wait-content-mounted"
|
|
36
|
+
},
|
|
37
|
+
"wait-content-mounted": {
|
|
38
|
+
"action": "ui.wait_for",
|
|
39
|
+
"test_id": "homepage-perps-positions",
|
|
40
|
+
"expected": "present",
|
|
41
|
+
"intent": "Confirm the data-backed Homepage section is rendered",
|
|
42
|
+
"timeout_ms": 30000,
|
|
43
|
+
"next": "show-content"
|
|
44
|
+
},
|
|
45
|
+
"show-content": {
|
|
46
|
+
"action": "ui.scroll",
|
|
47
|
+
"test_id": "homepage-perps-positions",
|
|
48
|
+
"scroll_into_view": true,
|
|
49
|
+
"animated": true,
|
|
50
|
+
"settle": true,
|
|
51
|
+
"intent": "Make the order card available before the app leaves the foreground",
|
|
52
|
+
"timeout_ms": 30000,
|
|
53
|
+
"next": "wait-order-before"
|
|
54
|
+
},
|
|
55
|
+
"wait-order-before": {
|
|
56
|
+
"action": "ui.wait_for",
|
|
57
|
+
"text": "Limit short",
|
|
58
|
+
"expected": "visible",
|
|
59
|
+
"intent": "Prove the same live order is visible before the lifecycle transition",
|
|
60
|
+
"timeout_ms": 30000,
|
|
61
|
+
"next": "start-performance-capture"
|
|
62
|
+
},
|
|
63
|
+
"start-performance-capture": {
|
|
64
|
+
"action": "metamask.perps.capture_performance",
|
|
65
|
+
"phase": "start",
|
|
66
|
+
"intent": "Start the evidence window immediately before backgrounding",
|
|
67
|
+
"next": "background-app"
|
|
68
|
+
},
|
|
69
|
+
"background-app": {
|
|
70
|
+
"action": "app.lifecycle",
|
|
71
|
+
"command": "background",
|
|
72
|
+
"settle_ms": 3000,
|
|
73
|
+
"intent": "Leave the app inactive for less than the reconnect grace period",
|
|
74
|
+
"next": "foreground-app"
|
|
75
|
+
},
|
|
76
|
+
"foreground-app": {
|
|
77
|
+
"action": "app.lifecycle",
|
|
78
|
+
"command": "foreground",
|
|
79
|
+
"settle_ms": 10000,
|
|
80
|
+
"intent": "Restore the same installed app and wallet session",
|
|
81
|
+
"next": "wait-content-after"
|
|
82
|
+
},
|
|
83
|
+
"wait-content-after": {
|
|
84
|
+
"action": "ui.wait_for",
|
|
85
|
+
"test_id": "homepage-perps-positions",
|
|
86
|
+
"expected": "present",
|
|
87
|
+
"intent": "Confirm the Homepage Perps state survived the short background interval",
|
|
88
|
+
"timeout_ms": 30000,
|
|
89
|
+
"next": "show-content-after"
|
|
90
|
+
},
|
|
91
|
+
"show-content-after": {
|
|
92
|
+
"action": "ui.scroll",
|
|
93
|
+
"test_id": "homepage-perps-positions",
|
|
94
|
+
"scroll_into_view": true,
|
|
95
|
+
"animated": true,
|
|
96
|
+
"settle": true,
|
|
97
|
+
"intent": "Make the restored data directly reviewable after foregrounding",
|
|
98
|
+
"timeout_ms": 30000,
|
|
99
|
+
"next": "wait-order-after"
|
|
100
|
+
},
|
|
101
|
+
"wait-order-after": {
|
|
102
|
+
"action": "ui.wait_for",
|
|
103
|
+
"text": "Limit short",
|
|
104
|
+
"expected": "visible",
|
|
105
|
+
"intent": "Prove the live order remains displayed after the short resume",
|
|
106
|
+
"timeout_ms": 30000,
|
|
107
|
+
"next": "capture-after"
|
|
108
|
+
},
|
|
109
|
+
"capture-after": {
|
|
110
|
+
"action": "ui.screenshot",
|
|
111
|
+
"path": "screenshots/homepage-order-after-short-background.png",
|
|
112
|
+
"intent": "Preserve reviewer-visible short-resume proof",
|
|
113
|
+
"next": "settle-frame"
|
|
114
|
+
},
|
|
115
|
+
"settle-frame": {
|
|
116
|
+
"action": "wait",
|
|
117
|
+
"duration_ms": 2500,
|
|
118
|
+
"intent": "Allow the visible frame checkpoint to finish",
|
|
119
|
+
"next": "finish-performance-capture"
|
|
120
|
+
},
|
|
121
|
+
"finish-performance-capture": {
|
|
122
|
+
"action": "metamask.perps.capture_performance",
|
|
123
|
+
"phase": "end",
|
|
124
|
+
"required_stages": [
|
|
125
|
+
"viewport_demand",
|
|
126
|
+
"react_commit",
|
|
127
|
+
"next_frame_checkpoint"
|
|
128
|
+
],
|
|
129
|
+
"required_lifecycles": ["background_short"],
|
|
130
|
+
"required_sources": ["resident_state", "fresh_socket"],
|
|
131
|
+
"intent": "Preserve and validate the short-background measurement cohort",
|
|
132
|
+
"next": "cleanup-order"
|
|
133
|
+
},
|
|
134
|
+
"cleanup-order": {
|
|
135
|
+
"action": "metamask.perps.teardown_state",
|
|
136
|
+
"provider": "hyperliquid",
|
|
137
|
+
"network": "testnet",
|
|
138
|
+
"market": "ETH",
|
|
139
|
+
"page": "home",
|
|
140
|
+
"positions": false,
|
|
141
|
+
"orders": {
|
|
142
|
+
"state": "none",
|
|
143
|
+
"mode": "matching",
|
|
144
|
+
"market": "ETH",
|
|
145
|
+
"timeout_ms": 60000
|
|
146
|
+
},
|
|
147
|
+
"intent": "Cancel the deterministic testnet order after evidence capture",
|
|
148
|
+
"timeout_ms": 90000,
|
|
149
|
+
"next": "done"
|
|
150
|
+
},
|
|
151
|
+
"done": {
|
|
152
|
+
"action": "end",
|
|
153
|
+
"status": "pass"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|