@deeeed/metamask-harness 0.42.0 → 0.43.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 +17 -0
- package/README.md +5 -0
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +59 -21
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +3 -2
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +2 -0
- package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +194 -25
- package/adapters/mobile/bridge-runtime/lib/config.cjs +14 -4
- package/adapters/mobile/start-console-forwarder.sh +5 -1
- package/dist/adapters/extension/browser-cdp.js +174 -0
- package/dist/adapters/extension/network-observer.js +19 -110
- package/dist/adapters/extension/performance-observer.js +75 -0
- package/dist/adapters/mobile/frame-metrics.js +45 -0
- package/dist/adapters/mobile/performance-observer.js +43 -0
- package/dist/adapters/performance/cdp-trace.js +342 -0
- package/dist/adapters/performance/js-task-metrics.js +35 -0
- package/dist/adapters.js +17 -2
- package/dist/artifact-files.js +92 -0
- package/dist/async.js +19 -0
- package/dist/commands/call.js +23 -2
- package/dist/commands/run-engine.js +18 -0
- package/dist/commands/run.js +20 -1
- package/dist/network-observation.js +59 -47
- package/dist/performance-observation.js +465 -0
- package/docs/PERFORMANCE-CAPTURE.md +33 -0
- package/docs/RECIPES.md +7 -0
- package/library/actions/mobile/platform/bridge.mjs +3 -0
- package/library/manifests/extension.action-manifest.json +85 -0
- package/library/manifests/mobile.action-manifest.json +97 -0
- package/package.json +1 -1
- package/scripts/site-contrast.mjs +43 -27
|
@@ -8,6 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import bridgeErrors from '../../../../adapters/mobile/bridge-runtime/lib/bridge-errors.cjs';
|
|
10
10
|
import brokerModule from '../../../../adapters/mobile/bridge-runtime/lib/cdp-broker.cjs';
|
|
11
|
+
import configModule from '../../../../adapters/mobile/bridge-runtime/lib/config.cjs';
|
|
11
12
|
import { resolveMobileToolPath } from './tool-paths.mjs';
|
|
12
13
|
|
|
13
14
|
const {
|
|
@@ -18,6 +19,7 @@ const {
|
|
|
18
19
|
parseErrorMarker,
|
|
19
20
|
} = bridgeErrors;
|
|
20
21
|
const { brokerSocketPath } = brokerModule;
|
|
22
|
+
const { resolvePort } = configModule;
|
|
21
23
|
|
|
22
24
|
// Re-exported so the TS adapter classifies on the same code constants without a
|
|
23
25
|
// second import path into the cjs bridge-runtime.
|
|
@@ -434,6 +436,7 @@ export async function bridgeCommand(input, args) {
|
|
|
434
436
|
const env = await bridgeEnv(input);
|
|
435
437
|
const brokerSocket = brokerSocketPath(
|
|
436
438
|
path.dirname(resolveBridgeLockPath(input, env)),
|
|
439
|
+
resolvePort(env, input.context.projectRoot),
|
|
437
440
|
);
|
|
438
441
|
if (adapterActionActive && !existsSync(brokerSocket)) {
|
|
439
442
|
env.CDP_BRIDGE_LOCK_OWNER_PID = await acquireActionBridgeLock(input, env);
|
|
@@ -1207,6 +1207,91 @@
|
|
|
1207
1207
|
},
|
|
1208
1208
|
"execution_capabilities": []
|
|
1209
1209
|
},
|
|
1210
|
+
"app.performance_capture": {
|
|
1211
|
+
"description": "Capture bounded CDP renderer frame metrics and separately labelled JavaScript work across Recipe Protocol v1 node boundaries.",
|
|
1212
|
+
"examples": [
|
|
1213
|
+
{
|
|
1214
|
+
"action": "app.performance_capture",
|
|
1215
|
+
"phase": "start",
|
|
1216
|
+
"id": "homepage-scroll",
|
|
1217
|
+
"max_duration_ms": 300000,
|
|
1218
|
+
"intent": "Start the bounded UI smoothness window",
|
|
1219
|
+
"next": "exercise-flow"
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
"action": "app.performance_capture",
|
|
1223
|
+
"phase": "end",
|
|
1224
|
+
"id": "homepage-scroll",
|
|
1225
|
+
"artifact_path": "performance/homepage-scroll.json",
|
|
1226
|
+
"html_path": "performance/homepage-scroll.html",
|
|
1227
|
+
"intent": "End and index the UI smoothness window",
|
|
1228
|
+
"next": "assert-performance"
|
|
1229
|
+
}
|
|
1230
|
+
],
|
|
1231
|
+
"schema": {
|
|
1232
|
+
"type": "object",
|
|
1233
|
+
"properties": {
|
|
1234
|
+
"phase": {
|
|
1235
|
+
"type": "string",
|
|
1236
|
+
"enum": ["start", "end"]
|
|
1237
|
+
},
|
|
1238
|
+
"id": { "type": "string" },
|
|
1239
|
+
"max_duration_ms": {
|
|
1240
|
+
"type": "integer",
|
|
1241
|
+
"minimum": 1,
|
|
1242
|
+
"description": "Maximum accepted at runtime: 3600000 ms."
|
|
1243
|
+
},
|
|
1244
|
+
"artifact_path": { "type": "string" },
|
|
1245
|
+
"html_path": { "type": "string" }
|
|
1246
|
+
},
|
|
1247
|
+
"required": ["phase", "id"],
|
|
1248
|
+
"additionalProperties": false
|
|
1249
|
+
},
|
|
1250
|
+
"execution_capabilities": ["host-read-export"]
|
|
1251
|
+
},
|
|
1252
|
+
"app.performance_assert": {
|
|
1253
|
+
"description": "Assert a previously indexed app.performance_capture summary without hiding unavailable or partial source coverage.",
|
|
1254
|
+
"examples": [
|
|
1255
|
+
{
|
|
1256
|
+
"action": "app.performance_assert",
|
|
1257
|
+
"id": "homepage-scroll",
|
|
1258
|
+
"artifact_path": "performance/homepage-scroll.json",
|
|
1259
|
+
"required_status": ["complete", "partial"],
|
|
1260
|
+
"minimum_frame_count": 1,
|
|
1261
|
+
"require_native_ui_when_supported": true,
|
|
1262
|
+
"required_node_ids": ["scroll-homepage"],
|
|
1263
|
+
"intent": "Assert the indexed UI smoothness summary",
|
|
1264
|
+
"next": "done"
|
|
1265
|
+
}
|
|
1266
|
+
],
|
|
1267
|
+
"schema": {
|
|
1268
|
+
"type": "object",
|
|
1269
|
+
"properties": {
|
|
1270
|
+
"id": { "type": "string" },
|
|
1271
|
+
"artifact_path": { "type": "string" },
|
|
1272
|
+
"required_status": {
|
|
1273
|
+
"type": "array",
|
|
1274
|
+
"items": {
|
|
1275
|
+
"type": "string",
|
|
1276
|
+
"enum": ["complete", "partial", "unavailable"]
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
"minimum_frame_count": {
|
|
1280
|
+
"type": "integer",
|
|
1281
|
+
"minimum": 0,
|
|
1282
|
+
"description": "Minimum MetaMask renderer frame count. JavaScript task count cannot satisfy this assertion."
|
|
1283
|
+
},
|
|
1284
|
+
"require_native_ui_when_supported": { "type": "boolean" },
|
|
1285
|
+
"required_node_ids": {
|
|
1286
|
+
"type": "array",
|
|
1287
|
+
"items": { "type": "string" }
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
"required": ["id"],
|
|
1291
|
+
"additionalProperties": false
|
|
1292
|
+
},
|
|
1293
|
+
"execution_capabilities": ["host-read-export"]
|
|
1294
|
+
},
|
|
1210
1295
|
"app.status": {
|
|
1211
1296
|
"description": "Report the adapter's static status — platform, project root, resolved checkout shape, and headless compatibility mode (no live route or account).",
|
|
1212
1297
|
"examples": [
|
|
@@ -1219,6 +1219,103 @@
|
|
|
1219
1219
|
},
|
|
1220
1220
|
"execution_capabilities": []
|
|
1221
1221
|
},
|
|
1222
|
+
"app.performance_capture": {
|
|
1223
|
+
"description": "Capture bounded CDP native UI frame metrics and separately labelled JavaScript work across Recipe Protocol v1 node boundaries.",
|
|
1224
|
+
"examples": [
|
|
1225
|
+
{
|
|
1226
|
+
"action": "app.performance_capture",
|
|
1227
|
+
"phase": "start",
|
|
1228
|
+
"id": "homepage-scroll",
|
|
1229
|
+
"max_duration_ms": 300000,
|
|
1230
|
+
"intent": "Start the bounded UI smoothness window",
|
|
1231
|
+
"next": "exercise-flow"
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
"action": "app.performance_capture",
|
|
1235
|
+
"phase": "end",
|
|
1236
|
+
"id": "homepage-scroll",
|
|
1237
|
+
"artifact_path": "performance/homepage-scroll.json",
|
|
1238
|
+
"html_path": "performance/homepage-scroll.html",
|
|
1239
|
+
"intent": "End and index the UI smoothness window",
|
|
1240
|
+
"next": "assert-performance"
|
|
1241
|
+
}
|
|
1242
|
+
],
|
|
1243
|
+
"schema": {
|
|
1244
|
+
"type": "object",
|
|
1245
|
+
"properties": {
|
|
1246
|
+
"phase": {
|
|
1247
|
+
"type": "string",
|
|
1248
|
+
"enum": ["start", "end"]
|
|
1249
|
+
},
|
|
1250
|
+
"id": {
|
|
1251
|
+
"type": "string"
|
|
1252
|
+
},
|
|
1253
|
+
"max_duration_ms": {
|
|
1254
|
+
"type": "integer",
|
|
1255
|
+
"minimum": 1,
|
|
1256
|
+
"description": "Maximum accepted at runtime: 3600000 ms."
|
|
1257
|
+
},
|
|
1258
|
+
"artifact_path": {
|
|
1259
|
+
"type": "string"
|
|
1260
|
+
},
|
|
1261
|
+
"html_path": {
|
|
1262
|
+
"type": "string"
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
"required": ["phase", "id"],
|
|
1266
|
+
"additionalProperties": false
|
|
1267
|
+
},
|
|
1268
|
+
"execution_capabilities": ["host-read-export"]
|
|
1269
|
+
},
|
|
1270
|
+
"app.performance_assert": {
|
|
1271
|
+
"description": "Assert a previously indexed app.performance_capture summary without hiding unavailable or partial source coverage.",
|
|
1272
|
+
"examples": [
|
|
1273
|
+
{
|
|
1274
|
+
"action": "app.performance_assert",
|
|
1275
|
+
"id": "homepage-scroll",
|
|
1276
|
+
"artifact_path": "performance/homepage-scroll.json",
|
|
1277
|
+
"required_status": ["complete", "partial"],
|
|
1278
|
+
"minimum_frame_count": 1,
|
|
1279
|
+
"require_native_ui_when_supported": true,
|
|
1280
|
+
"required_node_ids": ["scroll-homepage"],
|
|
1281
|
+
"intent": "Assert the indexed UI smoothness summary",
|
|
1282
|
+
"next": "done"
|
|
1283
|
+
}
|
|
1284
|
+
],
|
|
1285
|
+
"schema": {
|
|
1286
|
+
"type": "object",
|
|
1287
|
+
"properties": {
|
|
1288
|
+
"id": {
|
|
1289
|
+
"type": "string"
|
|
1290
|
+
},
|
|
1291
|
+
"artifact_path": {
|
|
1292
|
+
"type": "string"
|
|
1293
|
+
},
|
|
1294
|
+
"required_status": {
|
|
1295
|
+
"type": "array",
|
|
1296
|
+
"items": {
|
|
1297
|
+
"type": "string",
|
|
1298
|
+
"enum": ["complete", "partial", "unavailable"]
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
"minimum_frame_count": {
|
|
1302
|
+
"type": "integer",
|
|
1303
|
+
"minimum": 0,
|
|
1304
|
+
"description": "Minimum native UI frame count. JavaScript task count cannot satisfy this assertion."
|
|
1305
|
+
},
|
|
1306
|
+
"require_native_ui_when_supported": {
|
|
1307
|
+
"type": "boolean"
|
|
1308
|
+
},
|
|
1309
|
+
"required_node_ids": {
|
|
1310
|
+
"type": "array",
|
|
1311
|
+
"items": { "type": "string" }
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
"required": ["id"],
|
|
1315
|
+
"additionalProperties": false
|
|
1316
|
+
},
|
|
1317
|
+
"execution_capabilities": ["host-read-export"]
|
|
1318
|
+
},
|
|
1222
1319
|
"app.status": {
|
|
1223
1320
|
"description": "Report the adapter's static status — platform, project root, resolved checkout shape, and headless compatibility mode (no live route or account).",
|
|
1224
1321
|
"examples": [
|
package/package.json
CHANGED
|
@@ -272,6 +272,18 @@ function findChrome() {
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
async function startChrome() {
|
|
275
|
+
let lastError;
|
|
276
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
277
|
+
try {
|
|
278
|
+
return await startChromeOnce();
|
|
279
|
+
} catch (error) {
|
|
280
|
+
lastError = error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
throw lastError;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function startChromeOnce() {
|
|
275
287
|
const binary = findChrome();
|
|
276
288
|
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'site-contrast-'));
|
|
277
289
|
const child = spawn(
|
|
@@ -279,6 +291,7 @@ async function startChrome() {
|
|
|
279
291
|
[
|
|
280
292
|
'--headless=new',
|
|
281
293
|
'--disable-gpu',
|
|
294
|
+
'--disable-dev-shm-usage',
|
|
282
295
|
'--no-first-run',
|
|
283
296
|
'--no-default-browser-check',
|
|
284
297
|
'--remote-debugging-port=0',
|
|
@@ -290,35 +303,38 @@ async function startChrome() {
|
|
|
290
303
|
|
|
291
304
|
// Chrome writes the chosen port to DevToolsActivePort once it is listening.
|
|
292
305
|
const portFile = path.join(userDataDir, 'DevToolsActivePort');
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
close: async () => {
|
|
304
|
-
child.kill();
|
|
305
|
-
// Chrome holds the profile dir open until it exits; removing it early
|
|
306
|
-
// fails with ENOTEMPTY. Wait for exit, and never fail the gate on cleanup.
|
|
307
|
-
await new Promise((done) => {
|
|
308
|
-
if (child.exitCode !== null || child.signalCode !== null) return done();
|
|
309
|
-
const timer = setTimeout(done, 5000);
|
|
310
|
-
child.once('exit', () => {
|
|
311
|
-
clearTimeout(timer);
|
|
312
|
-
done();
|
|
313
|
-
});
|
|
306
|
+
const close = async () => {
|
|
307
|
+
child.kill();
|
|
308
|
+
// Chrome holds the profile dir open until it exits; removing it early
|
|
309
|
+
// fails with ENOTEMPTY. Wait for exit, and never fail the gate on cleanup.
|
|
310
|
+
await new Promise((done) => {
|
|
311
|
+
if (child.exitCode !== null || child.signalCode !== null) return done();
|
|
312
|
+
const timer = setTimeout(done, 5000);
|
|
313
|
+
child.once('exit', () => {
|
|
314
|
+
clearTimeout(timer);
|
|
315
|
+
done();
|
|
314
316
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
317
|
+
});
|
|
318
|
+
try {
|
|
319
|
+
fs.rmSync(userDataDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 });
|
|
320
|
+
} catch {
|
|
321
|
+
/* a leftover temp profile is not worth failing a contrast run over */
|
|
322
|
+
}
|
|
321
323
|
};
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const port = await waitFor(() => {
|
|
327
|
+
if (!fs.existsSync(portFile)) return null;
|
|
328
|
+
const line = fs.readFileSync(portFile, 'utf8').split('\n')[0].trim();
|
|
329
|
+
return line ? Number(line) : null;
|
|
330
|
+
}, 15000, 'Chrome did not report a debugging port');
|
|
331
|
+
|
|
332
|
+
const version = await getJson(`http://127.0.0.1:${port}/json/version`);
|
|
333
|
+
return { endpoint: version.webSocketDebuggerUrl, port, close };
|
|
334
|
+
} catch (error) {
|
|
335
|
+
await close();
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
322
338
|
}
|
|
323
339
|
|
|
324
340
|
async function auditPage(browserEndpoint, url) {
|