@deeeed/metamask-harness 0.40.1 → 0.41.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 +22 -0
- package/adapters/manifest.json +25 -1
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +5 -4
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +24 -5
- package/adapters/mobile/coalesce-metro-log.cjs +24 -0
- package/adapters/mobile/launch-metro.cjs +9 -8
- package/adapters/mobile/metro-config.cjs +94 -0
- package/adapters/mobile/metro-log-generation.cjs +106 -0
- package/adapters/mobile/start-metro.sh +30 -8
- package/adapters/mobile/stop-metro.sh +5 -6
- package/adapters/shared/reap-checkout-metros.sh +17 -0
- package/dist/adapters/mobile/runtime-decision.js +0 -21
- package/dist/commands/device-target.js +3 -0
- package/dist/commands/launch/index.js +25 -5
- package/dist/commands/run-engine.js +4 -14
- package/dist/heal-bounds.js +1 -0
- package/dist/live-adapter-contract.js +2 -1
- package/dist/mm-harness-cli.js +1 -1
- package/docs/QA.md +2 -0
- package/docs/RECIPES.md +25 -19
- package/library/actions/mobile/perps/capture_performance.mjs +2 -2
- package/library/actions/mobile/perps/performance-capture.mjs +375 -17
- package/library/actions/mobile/perps/perps.mjs +56 -10
- package/library/actions/mobile/platform/bridge.mjs +59 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +60 -26
- package/library/actions/mobile/wallet/import.mjs +2 -0
- package/library/actions/mobile/wallet/select_account.mjs +21 -2
- package/library/manifests/mobile.action-manifest.json +47 -6
- package/library/recipes/mobile/perps/performance.recipe.json +680 -26
- package/package.json +1 -1
- package/library/recipes/mobile/perps/performance.background-resume.recipe.json +0 -56
- package/library/recipes/mobile/perps/performance.cold-start.recipe.json +0 -56
- package/library/recipes/mobile/perps/performance.homepage.android-background-reconnect.recipe.json +0 -159
- package/library/recipes/mobile/perps/performance.homepage.android-background-short.recipe.json +0 -157
- package/library/recipes/mobile/perps/performance.homepage.android-cold-disk-cache.recipe.json +0 -165
- package/library/recipes/mobile/perps/performance.homepage.android-cold-no-cache.recipe.json +0 -139
- package/library/recipes/mobile/perps/performance.homepage.android-network-recovery.recipe.json +0 -149
- package/library/recipes/mobile/perps/performance.homepage.cold-position-sample.recipe.json +0 -120
- package/library/recipes/mobile/perps/performance.homepage.ios-background-reconnect.recipe.json +0 -111
- package/library/recipes/mobile/perps/performance.homepage.ios-background-short.recipe.json +0 -108
- package/library/recipes/mobile/perps/performance.homepage.ios-cold-disk-cache.recipe.json +0 -122
- package/library/recipes/mobile/perps/performance.homepage.ios-cold-no-cache.recipe.json +0 -95
- package/library/recipes/mobile/perps/performance.warm-start.recipe.json +0 -49
|
@@ -53,6 +53,21 @@ const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
|
|
|
53
53
|
"jsonStream"
|
|
54
54
|
]);
|
|
55
55
|
const DEFAULT_EXTENSION_DAPP_URL = "https://metamask.github.io/test-dapp/";
|
|
56
|
+
function metroLaunchEvidence(adapter, target) {
|
|
57
|
+
if (adapter !== "mobile") return void 0;
|
|
58
|
+
const evidencePath = recipeRuntimePath(target, "metro-generation.json");
|
|
59
|
+
let descriptor;
|
|
60
|
+
try {
|
|
61
|
+
descriptor = fs.openSync(evidencePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
62
|
+
const stat = fs.fstatSync(descriptor);
|
|
63
|
+
if (!stat.isFile() || stat.size > 256 * 1024) return void 0;
|
|
64
|
+
return JSON.parse(fs.readFileSync(descriptor, "utf8"));
|
|
65
|
+
} catch {
|
|
66
|
+
return void 0;
|
|
67
|
+
} finally {
|
|
68
|
+
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
56
71
|
async function handleLaunch(argv) {
|
|
57
72
|
const { options } = parseFlags(argv, LAUNCH_BOOLEANS);
|
|
58
73
|
const json = flag(options, "json");
|
|
@@ -627,14 +642,15 @@ async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget,
|
|
|
627
642
|
platform: mobileTarget ?? (adapter === "extension" ? displayMode : null),
|
|
628
643
|
tier,
|
|
629
644
|
recovered: state.recovered,
|
|
630
|
-
mutations: state.mutations
|
|
645
|
+
mutations: state.mutations,
|
|
646
|
+
metro: metroLaunchEvidence(adapter, target)
|
|
631
647
|
});
|
|
632
648
|
}
|
|
633
649
|
return exitCode;
|
|
634
650
|
}
|
|
635
|
-
return launchPass(jsonOutput, stream, adapter, mobileTarget, tier, displayMode, state);
|
|
651
|
+
return launchPass(jsonOutput, stream, adapter, mobileTarget, tier, displayMode, target, state);
|
|
636
652
|
}
|
|
637
|
-
function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, state) {
|
|
653
|
+
function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, target, state) {
|
|
638
654
|
if (stream.enabled) {
|
|
639
655
|
for (const mutation of state.mutations) stream.mutation(mutation);
|
|
640
656
|
for (const recovery of state.recovered) stream.recovery(recovery);
|
|
@@ -643,7 +659,8 @@ function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, stat
|
|
|
643
659
|
platform: mobileTarget ?? (adapter === "extension" ? displayMode : null),
|
|
644
660
|
tier,
|
|
645
661
|
recovered: state.recovered,
|
|
646
|
-
mutations: state.mutations
|
|
662
|
+
mutations: state.mutations,
|
|
663
|
+
metro: metroLaunchEvidence(adapter, target)
|
|
647
664
|
});
|
|
648
665
|
} else if (json) {
|
|
649
666
|
console.log(
|
|
@@ -658,6 +675,7 @@ function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, stat
|
|
|
658
675
|
phase: "launch",
|
|
659
676
|
recovered: state.recovered,
|
|
660
677
|
mutations: state.mutations,
|
|
678
|
+
metro: metroLaunchEvidence(adapter, target),
|
|
661
679
|
exitCode: EXIT.ok
|
|
662
680
|
},
|
|
663
681
|
null,
|
|
@@ -706,7 +724,8 @@ function launchFail(json, stream, adapter, mobileTarget, tier, state, target, fa
|
|
|
706
724
|
recovered: state.recovered,
|
|
707
725
|
mutations: state.mutations,
|
|
708
726
|
recoverable: failure.recoverable,
|
|
709
|
-
attemptedRecoveries: state.attemptedRecoveries
|
|
727
|
+
attemptedRecoveries: state.attemptedRecoveries,
|
|
728
|
+
metro: metroLaunchEvidence(adapter, target)
|
|
710
729
|
});
|
|
711
730
|
} else if (json) {
|
|
712
731
|
console.log(
|
|
@@ -723,6 +742,7 @@ function launchFail(json, stream, adapter, mobileTarget, tier, state, target, fa
|
|
|
723
742
|
mutations: state.mutations,
|
|
724
743
|
recoverable: failure.recoverable,
|
|
725
744
|
attemptedRecoveries: state.attemptedRecoveries,
|
|
745
|
+
metro: metroLaunchEvidence(adapter, target),
|
|
726
746
|
exitCode: failure.exitCode,
|
|
727
747
|
error: {
|
|
728
748
|
code: failure.code,
|
|
@@ -155,12 +155,11 @@ async function resolveRecipeExecution(adapter, recipe, artifactsDir, projectRoot
|
|
|
155
155
|
projectRoot,
|
|
156
156
|
rootRecipe: recipeDocument
|
|
157
157
|
});
|
|
158
|
-
const suppressHudForLifecycleTiming = adapter === "mobile" && mobileRecipeRequiresUninterruptedLifecycleTiming(recipeDocument);
|
|
159
158
|
const suppressHudForOpaqueMobile = adapter === "mobile" && process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1";
|
|
160
159
|
const runnerOptions = {
|
|
161
160
|
quietStdout: runtimeOptions.stdoutIsMachineContract === true,
|
|
162
161
|
suppressLibraryResolutionLogs: runtimeOptions.suppressLibraryResolutionLogs,
|
|
163
|
-
autoHud:
|
|
162
|
+
autoHud: suppressHudForOpaqueMobile ? false : trust.source && trust.source.trust !== "trusted" ? false : runtimeOptions.autoHud,
|
|
164
163
|
onActionEvent: runtimeOptions.onActionEvent,
|
|
165
164
|
actionSources,
|
|
166
165
|
// A direct engineer invocation is the explicit trust boundary for a
|
|
@@ -213,16 +212,6 @@ function runtimeRecipeDocument(recipe, absoluteRecipePath) {
|
|
|
213
212
|
return void 0;
|
|
214
213
|
}
|
|
215
214
|
}
|
|
216
|
-
function mobileRecipeRequiresUninterruptedLifecycleTiming(recipe) {
|
|
217
|
-
if (!isRecord(recipe)) return false;
|
|
218
|
-
const workflow = isRecord(recipe.workflow) ? recipe.workflow : void 0;
|
|
219
|
-
const nodes = workflow && isRecord(workflow.nodes) ? workflow.nodes : void 0;
|
|
220
|
-
if (!nodes) return false;
|
|
221
|
-
const actions = new Set(
|
|
222
|
-
Object.values(nodes).filter(isRecord).map((node) => node.action).filter((action) => typeof action === "string")
|
|
223
|
-
);
|
|
224
|
-
return actions.has("app.lifecycle") && actions.has("metamask.perps.capture_performance");
|
|
225
|
-
}
|
|
226
215
|
function recipeProcessEnvironment() {
|
|
227
216
|
const env = { ...process.env };
|
|
228
217
|
delete env.MM_HARNESS_CHECKOUT_LOCK_TOKEN;
|
|
@@ -314,7 +303,8 @@ function recipeRunEnv(adapter, runtimeOptions = {}) {
|
|
|
314
303
|
RECIPE_CDP_PORT: runtimeOptions.cdpPort ?? process.env.RECIPE_CDP_PORT,
|
|
315
304
|
FARMSLOT_SLOT_ID: runtimeOptions.slot ?? process.env.FARMSLOT_SLOT_ID,
|
|
316
305
|
SLOT_ID: runtimeOptions.slot ?? process.env.SLOT_ID,
|
|
317
|
-
PLATFORM: process.env.PLATFORM
|
|
306
|
+
PLATFORM: process.env.PLATFORM,
|
|
307
|
+
MM_HARNESS_EXPLICIT_PLATFORM: process.env.MM_HARNESS_EXPLICIT_PLATFORM
|
|
318
308
|
};
|
|
319
309
|
if (adapter !== "mobile") return base;
|
|
320
310
|
return {
|
|
@@ -322,6 +312,7 @@ function recipeRunEnv(adapter, runtimeOptions = {}) {
|
|
|
322
312
|
WATCHER_PORT: process.env.WATCHER_PORT ?? base.CDP_PORT ?? base.RECIPE_CDP_PORT,
|
|
323
313
|
METRO_PORT: process.env.METRO_PORT ?? process.env.WATCHER_PORT ?? base.CDP_PORT ?? base.RECIPE_CDP_PORT,
|
|
324
314
|
IOS_SIMULATOR: process.env.IOS_SIMULATOR,
|
|
315
|
+
SIM_UDID: process.env.SIM_UDID,
|
|
325
316
|
ANDROID_DEVICE: process.env.ANDROID_DEVICE,
|
|
326
317
|
ANDROID_TARGET_DEVICE_NAME: process.env.ANDROID_TARGET_DEVICE_NAME,
|
|
327
318
|
ADB_SERIAL: process.env.ADB_SERIAL,
|
|
@@ -1378,7 +1369,6 @@ export {
|
|
|
1378
1369
|
ensureMobileProofRuntime,
|
|
1379
1370
|
executeWithHealBounds,
|
|
1380
1371
|
listRunnableRecipes,
|
|
1381
|
-
mobileRecipeRequiresUninterruptedLifecycleTiming,
|
|
1382
1372
|
preflightRecipe,
|
|
1383
1373
|
prepareHeal,
|
|
1384
1374
|
prepareRuntimeIfNeeded,
|
package/dist/heal-bounds.js
CHANGED
|
@@ -62,6 +62,7 @@ async function ensureOverlay(adapter, target, heal, state, json) {
|
|
|
62
62
|
}
|
|
63
63
|
function classifyFailure(output) {
|
|
64
64
|
if (/SCREENSHOT_PROTECTED|screenshot is protected by FLAG_SECURE/iu.test(output)) return "screenshot-protected";
|
|
65
|
+
if (/CDP_TIMEOUT|Mobile CDP bridge command timed out|NO_TARGET|WS_CLOSED/iu.test(output)) return "infra";
|
|
65
66
|
if (/wallet|fixture|keyring|not seeded|\bsrp\b|password|onboard/iu.test(output)) return "wallet";
|
|
66
67
|
if (/metro|cdp|chrome|bridge|bundle|packager|port\b|econnrefused|not reachable|watcher|dev client|websocket/iu.test(output)) {
|
|
67
68
|
return "infra";
|
|
@@ -359,7 +359,8 @@ function liveAdapterProcessTimeoutMs(node) {
|
|
|
359
359
|
const actionTimeouts = [
|
|
360
360
|
node.target_timeout_ms,
|
|
361
361
|
node.unlock_timeout_ms,
|
|
362
|
-
node.timeout_ms
|
|
362
|
+
node.timeout_ms,
|
|
363
|
+
node.wait_timeout_ms
|
|
363
364
|
].map(Number).filter((value) => Number.isFinite(value) && value > 0);
|
|
364
365
|
if (actionTimeouts.length > 0) {
|
|
365
366
|
return Math.max(
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -243,7 +243,7 @@ Common overrides:
|
|
|
243
243
|
--json Machine-readable output
|
|
244
244
|
--json-stream Line-flushed JSONL progress + terminal event
|
|
245
245
|
--record-video=full-run Record a video of the run
|
|
246
|
-
--hud <auto|show|hide> Recipe HUD policy (
|
|
246
|
+
--hud <auto|show|hide> Recipe HUD policy (auto shows normal Mobile recipes; hide is explicit)
|
|
247
247
|
|
|
248
248
|
Advanced integrations:
|
|
249
249
|
runtime: --cdp-port, --watcher-port/--metro-port, --runtime-dir, --slot,
|
package/docs/QA.md
CHANGED
|
@@ -124,6 +124,8 @@ mm-harness debug
|
|
|
124
124
|
- [ ] Status matches verified product routes: `Login`/`LockScreen` are locked;
|
|
125
125
|
`WalletView` is unlocked.
|
|
126
126
|
- [ ] App and Metro logs are separate.
|
|
127
|
+
- [ ] A restarted Metro rotates the previous `metro.log`, records its generation and retention actions in launch JSON, and classifies bundle failures only from the current log.
|
|
128
|
+
- [ ] `mm-harness logs --full` preserves errors, completion, and meaningful timestamped bundle progress without repeated same-percentage module counts.
|
|
127
129
|
- [ ] A JS edit rebuilds through Metro and appears after reload without a native
|
|
128
130
|
rebuild; revert restores a clean tree.
|
|
129
131
|
|
package/docs/RECIPES.md
CHANGED
|
@@ -176,30 +176,36 @@ For visual PR proof, prefer a short simulator recording or GIF over still
|
|
|
176
176
|
screenshots when the claim is a UI flow (timing / transitions). Use screenshots
|
|
177
177
|
as supporting stills.
|
|
178
178
|
|
|
179
|
-
### Perps
|
|
179
|
+
### Perps loading lifecycle validation
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
One platform-neutral recipe owns the reusable Perps lifecycle proof. The
|
|
182
|
+
harness adapter supplies Android or iOS behavior; the graph and lifecycle
|
|
183
|
+
parameters remain identical. Run it with an explicit device pin:
|
|
184
184
|
|
|
185
185
|
```bash
|
|
186
|
-
mm-harness run perps.performance
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
mm-harness run perps.performance.homepage.ios-cold-no-cache --device <udid> --heal off --record-video=full-run
|
|
194
|
-
mm-harness run perps.performance.homepage.ios-cold-disk-cache --device <udid> --heal off --record-video=full-run
|
|
186
|
+
mm-harness run perps.performance \
|
|
187
|
+
--device <android-serial-or-ios-udid> \
|
|
188
|
+
account=<fixture-account-name> \
|
|
189
|
+
content_variant=trending \
|
|
190
|
+
market=BTC \
|
|
191
|
+
lifecycle=cold_no_cache \
|
|
192
|
+
--heal off --record-video=full-run
|
|
195
193
|
```
|
|
196
194
|
|
|
197
|
-
The
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
195
|
+
The Mobile recipe HUD is shown by default, including during performance
|
|
196
|
+
validation. Use `--hud hide` only when a specific opaque/release proof requires
|
|
197
|
+
an unobstructed product surface; do not hide it merely to collect timings.
|
|
198
|
+
|
|
199
|
+
Supported lifecycle values are `navigate_return`, `cold_disk_cache`,
|
|
200
|
+
`cold_no_cache`, `background_short`, `background_reconnect`, `account_switch`,
|
|
201
|
+
and `network_switch`. The shared graph uses an existing fixture position and
|
|
202
|
+
never fabricates exchange state inside the measurement. It proves native
|
|
203
|
+
visible content with `ui.wait_for`, captures production `[PerpsLoadProof]` and
|
|
204
|
+
WebSocket milestones, and leaves detailed TTC/DFD to the app's Sentry traces.
|
|
205
|
+
It never relabels Metro, fixture, or process-start duration as TTC.
|
|
206
|
+
The recipe does not label a checkout as main or candidate; pin and record the
|
|
207
|
+
exact checkout commit externally, then use captured source milestones to verify
|
|
208
|
+
which data path actually ran.
|
|
203
209
|
|
|
204
210
|
Rules:
|
|
205
211
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import { capturePerpsPerformance } from './performance-capture.mjs';
|
|
3
3
|
|
|
4
|
-
runAdapter(
|
|
4
|
+
runAdapter(capturePerpsPerformance);
|