agent-scenario-loop 0.1.3 → 0.1.4
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/app/profile-session.ts +263 -17
- package/dist/core/artifact-contract.d.ts +6 -4
- package/dist/core/artifact-contract.js +164 -15
- package/dist/core/schema-validator.d.ts +1 -0
- package/dist/core/schema-validator.js +1 -0
- package/dist/runner/android-adb-driver.d.ts +7 -2
- package/dist/runner/android-adb-driver.js +7 -1
- package/dist/runner/android-adb.d.ts +40 -5
- package/dist/runner/android-adb.js +1046 -664
- package/dist/runner/ios-simctl.d.ts +1 -0
- package/dist/runner/ios-simctl.js +1 -0
- package/dist/runner/profile-android.d.ts +11 -1
- package/dist/runner/profile-android.js +230 -16
- package/dist/runner/profile-ios.d.ts +3 -2
- package/dist/runner/profile-ios.js +223 -20
- package/dist/runner/profile-mobile.d.ts +31 -3
- package/dist/runner/profile-mobile.js +793 -20
- package/dist/runner/validate-project.js +3 -0
- package/dist/scripts/consumer-rehearsal.d.ts +119 -0
- package/dist/scripts/consumer-rehearsal.js +757 -0
- package/dist/scripts/downstream-local-package-gate.d.ts +2 -0
- package/dist/scripts/downstream-local-package-gate.js +264 -0
- package/dist/scripts/package-smoke.d.ts +96 -0
- package/dist/scripts/package-smoke.js +2282 -0
- package/dist/scripts/release-readiness.d.ts +2 -0
- package/dist/scripts/release-readiness.js +520 -0
- package/docs/adapters.md +3 -1
- package/docs/api.md +2 -2
- package/docs/authoring.md +34 -2
- package/docs/consumer-rehearsal.md +27 -1
- package/docs/contracts.md +16 -2
- package/docs/live-proofs.md +5 -3
- package/examples/mobile-app/runner-manifests/evidence-provider.json +3 -3
- package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +25 -0
- package/examples/runners/README.md +3 -3
- package/examples/runners/axe-accessibility-provider.json +2 -2
- package/examples/runners/script-accessibility-provider.json +2 -2
- package/examples/runners/script-memory-provider.json +2 -2
- package/examples/runners/script-network-provider.json +2 -2
- package/examples/runners/script-profiler-provider.json +2 -2
- package/package.json +11 -3
- package/schemas/manifest.schema.json +73 -3
- package/schemas/profiler.schema.json +243 -0
- package/schemas/runner-capabilities.schema.json +8 -2
- package/schemas/scenario.schema.json +18 -2
- package/templates/evidence-provider.json +3 -3
- package/templates/scripts/asl-capture-profiler-provider.mjs +20 -0
|
@@ -169,6 +169,7 @@ function parseKeyValueProfileSessionEntry(payload) {
|
|
|
169
169
|
const timestamp = coerceNumber(entry.timestamp);
|
|
170
170
|
const atMs = coerceNumber(entry.atMs);
|
|
171
171
|
const sequence = coerceNumber(entry.sequence);
|
|
172
|
+
const waitMs = coerceNumber(entry.waitMs);
|
|
172
173
|
const waitTimeoutMs = coerceNumber(entry.waitTimeoutMs);
|
|
173
174
|
if (atMs !== null) {
|
|
174
175
|
entry.atMs = atMs;
|
|
@@ -179,6 +180,9 @@ function parseKeyValueProfileSessionEntry(payload) {
|
|
|
179
180
|
if (sequence !== null) {
|
|
180
181
|
entry.sequence = sequence;
|
|
181
182
|
}
|
|
183
|
+
if (waitMs !== null) {
|
|
184
|
+
entry.waitMs = waitMs;
|
|
185
|
+
}
|
|
182
186
|
if (waitTimeoutMs !== null) {
|
|
183
187
|
entry.waitTimeoutMs = waitTimeoutMs;
|
|
184
188
|
}
|
|
@@ -364,10 +368,10 @@ function extractProfileSessionEntries(logText, filters = {}) {
|
|
|
364
368
|
/**
|
|
365
369
|
* Builds timing metrics from app-emitted profile events.
|
|
366
370
|
*
|
|
367
|
-
* @param {{scenario: string, runId: string, events: Record<string, unknown>[], expectedIterations: number, timeoutCount?: number, artifacts?: Record<string, unknown>, cycleEventNames?: Record<string, string> | null, budgets?: Record<string, unknown> | null}} options
|
|
371
|
+
* @param {{scenario: string, runId: string, events: Record<string, unknown>[], expectedIterations: number, timeoutCount?: number, artifacts?: Record<string, unknown>, cycleEventNames?: Record<string, string> | null, milestoneEventsPerIteration?: number, budgets?: Record<string, unknown> | null}} options
|
|
368
372
|
* @returns {Record<string, unknown>}
|
|
369
373
|
*/
|
|
370
|
-
function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterations, timeoutCount = 0, artifacts = {}, cycleEventNames = null, budgets = null, }) {
|
|
374
|
+
function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterations, timeoutCount = 0, artifacts = {}, cycleEventNames = null, milestoneEventsPerIteration = 1, budgets = null, }) {
|
|
371
375
|
const resolvedCycleEventNames = {
|
|
372
376
|
openRequested: cycleEventNames?.openRequested ?? 'surface_open_requested',
|
|
373
377
|
opened: cycleEventNames?.opened ?? 'surface_opened',
|
|
@@ -376,17 +380,35 @@ function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterat
|
|
|
376
380
|
milestone: cycleEventNames?.milestone,
|
|
377
381
|
};
|
|
378
382
|
const usesMilestoneOnlyCycle = typeof resolvedCycleEventNames.milestone === 'string';
|
|
383
|
+
const requiredMilestoneEventsPerIteration = usesMilestoneOnlyCycle &&
|
|
384
|
+
Number.isInteger(milestoneEventsPerIteration) &&
|
|
385
|
+
milestoneEventsPerIteration > 1
|
|
386
|
+
? milestoneEventsPerIteration
|
|
387
|
+
: 1;
|
|
379
388
|
const iterations = new Map();
|
|
389
|
+
let nextImplicitMilestoneIteration = 1;
|
|
390
|
+
let nextImplicitMilestoneCount = 0;
|
|
380
391
|
for (const event of [...events].sort((left, right) => {
|
|
381
392
|
const leftAt = typeof left.atMs === 'number' ? left.atMs : Number.POSITIVE_INFINITY;
|
|
382
393
|
const rightAt = typeof right.atMs === 'number' ? right.atMs : Number.POSITIVE_INFINITY;
|
|
383
394
|
return leftAt - rightAt;
|
|
384
395
|
})) {
|
|
385
|
-
|
|
396
|
+
let eventIteration = typeof event.iteration === 'number'
|
|
386
397
|
? event.iteration
|
|
387
398
|
: expectedIterations === 1
|
|
388
399
|
? 1
|
|
389
400
|
: null;
|
|
401
|
+
if (eventIteration === null &&
|
|
402
|
+
usesMilestoneOnlyCycle &&
|
|
403
|
+
event.event === resolvedCycleEventNames.milestone &&
|
|
404
|
+
nextImplicitMilestoneIteration <= expectedIterations) {
|
|
405
|
+
eventIteration = nextImplicitMilestoneIteration;
|
|
406
|
+
nextImplicitMilestoneCount += 1;
|
|
407
|
+
if (nextImplicitMilestoneCount >= requiredMilestoneEventsPerIteration) {
|
|
408
|
+
nextImplicitMilestoneIteration += 1;
|
|
409
|
+
nextImplicitMilestoneCount = 0;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
390
412
|
if (eventIteration === null) {
|
|
391
413
|
continue;
|
|
392
414
|
}
|
|
@@ -416,8 +438,10 @@ function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterat
|
|
|
416
438
|
event.atMs >= current.closeRequestedAt) {
|
|
417
439
|
current.dismissedAt = event.atMs;
|
|
418
440
|
}
|
|
419
|
-
if (event.event === resolvedCycleEventNames.milestone
|
|
420
|
-
typeof current.
|
|
441
|
+
if (event.event === resolvedCycleEventNames.milestone) {
|
|
442
|
+
current.milestoneCount = typeof current.milestoneCount === 'number'
|
|
443
|
+
? current.milestoneCount + 1
|
|
444
|
+
: 1;
|
|
421
445
|
current.milestoneAt = event.atMs;
|
|
422
446
|
}
|
|
423
447
|
iterations.set(eventIteration, current);
|
|
@@ -445,6 +469,9 @@ function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterat
|
|
|
445
469
|
record.dismissedAt >= record.closeRequestedAt;
|
|
446
470
|
const hasMilestoneDuration = usesMilestoneOnlyCycle &&
|
|
447
471
|
typeof record.milestoneAt === 'number' &&
|
|
472
|
+
(requiredMilestoneEventsPerIteration <= 1 ||
|
|
473
|
+
(typeof record.milestoneCount === 'number' &&
|
|
474
|
+
record.milestoneCount >= requiredMilestoneEventsPerIteration)) &&
|
|
448
475
|
record.milestoneAt >= 0;
|
|
449
476
|
if (hasMilestoneDuration) {
|
|
450
477
|
durationsMs.push(roundMs(record.milestoneAt));
|
|
@@ -482,7 +509,8 @@ function buildMetricsFromProfileEvents({ scenario, runId, events, expectedIterat
|
|
|
482
509
|
incompleteIterations,
|
|
483
510
|
artifacts: sortValue(artifacts),
|
|
484
511
|
};
|
|
485
|
-
const
|
|
512
|
+
const intervalBudgetChecks = evaluateIntervalBudgetChecks({ events, expectedIterations, budgets });
|
|
513
|
+
const budgetEvaluation = evaluateProfileBudgets({ metrics, budgets, extraChecks: intervalBudgetChecks });
|
|
486
514
|
if (budgetEvaluation) {
|
|
487
515
|
metrics.budgetEvaluation = sortValue(budgetEvaluation);
|
|
488
516
|
}
|
|
@@ -507,15 +535,111 @@ function evaluateBudgetCheck({ name, actual, limit }) {
|
|
|
507
535
|
unit: 'ms',
|
|
508
536
|
};
|
|
509
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Collects duration samples for one named interval budget.
|
|
540
|
+
*
|
|
541
|
+
* @param {{events: ProfileEvent[], fromEvent: string, toEvent: string, expectedIterations: number}} options
|
|
542
|
+
* @returns {number[]}
|
|
543
|
+
*/
|
|
544
|
+
function collectIntervalDurations({ events, fromEvent, toEvent, expectedIterations, }) {
|
|
545
|
+
const sortedEvents = [...events].sort((left, right) => {
|
|
546
|
+
const leftAt = typeof left.atMs === 'number' ? left.atMs : Number.POSITIVE_INFINITY;
|
|
547
|
+
const rightAt = typeof right.atMs === 'number' ? right.atMs : Number.POSITIVE_INFINITY;
|
|
548
|
+
return leftAt - rightAt;
|
|
549
|
+
});
|
|
550
|
+
const hasIterationPairs = sortedEvents.some((event) => (typeof event.iteration === 'number' && (event.event === fromEvent || event.event === toEvent)));
|
|
551
|
+
if (hasIterationPairs) {
|
|
552
|
+
const durations = [];
|
|
553
|
+
for (let iteration = 1; iteration <= expectedIterations; iteration += 1) {
|
|
554
|
+
const from = sortedEvents.find((event) => (event.event === fromEvent &&
|
|
555
|
+
event.iteration === iteration &&
|
|
556
|
+
typeof event.atMs === 'number'));
|
|
557
|
+
const to = sortedEvents.find((event) => (event.event === toEvent &&
|
|
558
|
+
event.iteration === iteration &&
|
|
559
|
+
typeof event.atMs === 'number' &&
|
|
560
|
+
typeof from?.atMs === 'number' &&
|
|
561
|
+
event.atMs >= from.atMs));
|
|
562
|
+
if (typeof from?.atMs === 'number' && typeof to?.atMs === 'number') {
|
|
563
|
+
durations.push(roundMs(to.atMs - from.atMs));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return durations;
|
|
567
|
+
}
|
|
568
|
+
const durations = [];
|
|
569
|
+
let pendingFrom = null;
|
|
570
|
+
for (const event of sortedEvents) {
|
|
571
|
+
if (typeof event.atMs !== 'number') {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (event.event === fromEvent && pendingFrom === null) {
|
|
575
|
+
pendingFrom = event.atMs;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (event.event === toEvent && pendingFrom !== null && event.atMs >= pendingFrom) {
|
|
579
|
+
durations.push(roundMs(event.atMs - pendingFrom));
|
|
580
|
+
pendingFrom = null;
|
|
581
|
+
if (durations.length >= expectedIterations) {
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return durations;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Evaluates named interval budgets that should not define scenario health completeness.
|
|
590
|
+
*
|
|
591
|
+
* @param {{events: ProfileEvent[], expectedIterations: number, budgets?: Record<string, unknown> | null}} options
|
|
592
|
+
* @returns {BudgetCheck[]}
|
|
593
|
+
*/
|
|
594
|
+
function evaluateIntervalBudgetChecks({ events, expectedIterations, budgets, }) {
|
|
595
|
+
if (!Array.isArray(budgets?.intervals)) {
|
|
596
|
+
return [];
|
|
597
|
+
}
|
|
598
|
+
return budgets.intervals
|
|
599
|
+
.map((budget) => {
|
|
600
|
+
if (typeof budget.name !== 'string' ||
|
|
601
|
+
typeof budget.metric !== 'string' ||
|
|
602
|
+
typeof budget.limit !== 'number' ||
|
|
603
|
+
typeof budget.fromEvent !== 'string' ||
|
|
604
|
+
typeof budget.toEvent !== 'string') {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
const durations = collectIntervalDurations({
|
|
608
|
+
events,
|
|
609
|
+
fromEvent: budget.fromEvent,
|
|
610
|
+
toEvent: budget.toEvent,
|
|
611
|
+
expectedIterations,
|
|
612
|
+
});
|
|
613
|
+
const actual = budget.metric === 'p50'
|
|
614
|
+
? percentile(durations, 50)
|
|
615
|
+
: budget.metric === 'p95'
|
|
616
|
+
? percentile(durations, 95)
|
|
617
|
+
: null;
|
|
618
|
+
return evaluateBudgetCheck({
|
|
619
|
+
name: budget.name,
|
|
620
|
+
actual,
|
|
621
|
+
limit: budget.limit,
|
|
622
|
+
});
|
|
623
|
+
})
|
|
624
|
+
.filter((check) => Boolean(check));
|
|
625
|
+
}
|
|
510
626
|
/**
|
|
511
627
|
* Evaluates configured profile budgets against generated metrics.
|
|
512
628
|
*
|
|
513
|
-
* @param {{metrics: Record<string, unknown>, budgets?: Record<string, unknown> | null}} options
|
|
629
|
+
* @param {{metrics: Record<string, unknown>, budgets?: Record<string, unknown> | null, extraChecks?: BudgetCheck[]}} options
|
|
514
630
|
* @returns {Record<string, unknown> | null}
|
|
515
631
|
*/
|
|
516
|
-
function evaluateProfileBudgets({ metrics, budgets }) {
|
|
632
|
+
function evaluateProfileBudgets({ metrics, budgets, extraChecks = [], }) {
|
|
517
633
|
if (!budgets?.pass || typeof budgets.pass !== 'object') {
|
|
518
|
-
|
|
634
|
+
if (extraChecks.length === 0) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
metric: budgets?.metric ?? metrics.measurement ?? 'profile budget',
|
|
639
|
+
pass: extraChecks.every((check) => check.pass),
|
|
640
|
+
checks: extraChecks,
|
|
641
|
+
failedChecks: extraChecks.filter((check) => !check.pass).map((check) => check.name),
|
|
642
|
+
};
|
|
519
643
|
}
|
|
520
644
|
const checks = [
|
|
521
645
|
evaluateBudgetCheck({
|
|
@@ -589,7 +713,7 @@ function evaluateProfileBudgets({ metrics, budgets }) {
|
|
|
589
713
|
}
|
|
590
714
|
: null,
|
|
591
715
|
].filter((check) => Boolean(check));
|
|
592
|
-
const allChecks = [...thresholdChecks, ...checks];
|
|
716
|
+
const allChecks = [...thresholdChecks, ...checks, ...extraChecks];
|
|
593
717
|
if (allChecks.length === 0) {
|
|
594
718
|
return null;
|
|
595
719
|
}
|
|
@@ -846,6 +970,7 @@ function buildCommandAcknowledgementTimeline({ entries, startedAt, }) {
|
|
|
846
970
|
...(typeof entry.result === 'string' ? { result: entry.result } : {}),
|
|
847
971
|
...(typeof entry.reason === 'string' ? { reason: entry.reason } : {}),
|
|
848
972
|
...(typeof entry.waitForMilestone === 'string' ? { waitForMilestone: entry.waitForMilestone } : {}),
|
|
973
|
+
...(typeof entry.waitMs === 'number' ? { waitMs: entry.waitMs } : {}),
|
|
849
974
|
...(typeof entry.waitTimeoutMs === 'number' ? { waitTimeoutMs: entry.waitTimeoutMs } : {}),
|
|
850
975
|
};
|
|
851
976
|
return sortValue({
|
|
@@ -1053,6 +1178,7 @@ function normalizeBudgetsForCausalRun(budgets) {
|
|
|
1053
1178
|
*/
|
|
1054
1179
|
function buildCausalRun({ scenario, flowId, runId, platform = 'ios', buildFlavor = 'unknown', interactionDriver, trigger = null, budgets = null, timeline = [], artifacts, manifest, metrics, }) {
|
|
1055
1180
|
const iterationSummary = buildIterationSummary(metrics);
|
|
1181
|
+
const videoPath = typeof artifacts.captures?.video === 'string' ? artifacts.captures.video : null;
|
|
1056
1182
|
return sortValue({
|
|
1057
1183
|
schemaVersion: '1.0.0',
|
|
1058
1184
|
flowId,
|
|
@@ -1084,7 +1210,7 @@ function buildCausalRun({ scenario, flowId, runId, platform = 'ios', buildFlavor
|
|
|
1084
1210
|
summary: artifacts.summary,
|
|
1085
1211
|
metrics: artifacts.metrics,
|
|
1086
1212
|
manifest: artifacts.manifest,
|
|
1087
|
-
video:
|
|
1213
|
+
...(videoPath ? { video: videoPath } : {}),
|
|
1088
1214
|
screenshot: Array.isArray(artifacts.captures?.screenshots)
|
|
1089
1215
|
? artifacts.captures.screenshots[0] ?? null
|
|
1090
1216
|
: null,
|
|
@@ -1217,9 +1343,20 @@ function buildSummaryMarkdown({ manifest, metrics }) {
|
|
|
1217
1343
|
const evidenceAttachments = Array.isArray(manifest.artifacts.evidenceAttachments)
|
|
1218
1344
|
? manifest.artifacts.evidenceAttachments
|
|
1219
1345
|
: [];
|
|
1346
|
+
const diagnostics = Array.isArray(manifest.artifacts.diagnostics)
|
|
1347
|
+
? manifest.artifacts.diagnostics
|
|
1348
|
+
: [];
|
|
1220
1349
|
const evidenceAttachmentLines = evidenceAttachments.length > 0
|
|
1221
1350
|
? evidenceAttachments.map((attachment) => `- ${attachment.channel}/${attachment.kind}: \`${attachment.path}\` (${attachment.sizeBytes} bytes, sha256 ${attachment.sha256})`)
|
|
1222
1351
|
: ['- none'];
|
|
1352
|
+
const diagnosticLines = diagnostics.length > 0
|
|
1353
|
+
? diagnostics.map((diagnostic) => {
|
|
1354
|
+
const label = diagnostic.name ? `${diagnostic.kind}/${diagnostic.name}` : diagnostic.kind;
|
|
1355
|
+
const pathLabel = typeof diagnostic.path === 'string' ? ` \`${diagnostic.path}\`` : '';
|
|
1356
|
+
const reasonLabel = typeof diagnostic.reason === 'string' ? ` - ${diagnostic.reason}` : '';
|
|
1357
|
+
return `- ${label}: ${diagnostic.status}${diagnostic.required ? ' (required)' : ''}${pathLabel}${reasonLabel}`;
|
|
1358
|
+
})
|
|
1359
|
+
: ['- none'];
|
|
1223
1360
|
const attempt = manifest.attempt && typeof manifest.attempt === 'object' ? manifest.attempt : {};
|
|
1224
1361
|
const attemptLines = [
|
|
1225
1362
|
`- Attempt ID: \`${attempt.attemptId ?? manifest.runId}\``,
|
|
@@ -1263,10 +1400,18 @@ function buildSummaryMarkdown({ manifest, metrics }) {
|
|
|
1263
1400
|
`- Manifest: \`${manifest.artifacts.manifest}\``,
|
|
1264
1401
|
`- Scenario: \`${manifest.artifacts.scenario}\``,
|
|
1265
1402
|
`- Metrics: \`${manifest.artifacts.metrics}\``,
|
|
1266
|
-
`- Interaction log:
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
`-
|
|
1403
|
+
`- Interaction log: ${typeof manifest.artifacts.raw.interactionLog === 'string'
|
|
1404
|
+
? `\`${manifest.artifacts.raw.interactionLog}\``
|
|
1405
|
+
: 'none'}`,
|
|
1406
|
+
`- Device log: ${typeof manifest.artifacts.raw.deviceLog === 'string'
|
|
1407
|
+
? `\`${manifest.artifacts.raw.deviceLog}\``
|
|
1408
|
+
: 'none'}`,
|
|
1409
|
+
`- Video: ${typeof manifest.artifacts.captures.video === 'string'
|
|
1410
|
+
? `\`${manifest.artifacts.captures.video}\``
|
|
1411
|
+
: 'none'}`,
|
|
1412
|
+
`- UI tree: ${typeof manifest.artifacts.captures.uiTree === 'string'
|
|
1413
|
+
? `\`${manifest.artifacts.captures.uiTree}\``
|
|
1414
|
+
: 'none'}`,
|
|
1270
1415
|
`- Screenshots: ${screenshots.length > 0
|
|
1271
1416
|
? screenshots.map((item) => `\`${item}\``).join(', ')
|
|
1272
1417
|
: 'none'}`,
|
|
@@ -1278,6 +1423,10 @@ function buildSummaryMarkdown({ manifest, metrics }) {
|
|
|
1278
1423
|
'## Evidence attachments',
|
|
1279
1424
|
'',
|
|
1280
1425
|
...evidenceAttachmentLines,
|
|
1426
|
+
'',
|
|
1427
|
+
'## Diagnostic inventory',
|
|
1428
|
+
'',
|
|
1429
|
+
...diagnosticLines,
|
|
1281
1430
|
];
|
|
1282
1431
|
if (metrics.budgetEvaluation) {
|
|
1283
1432
|
lines.push('', '## Budget', '', `- Metric: ${metrics.budgetEvaluation.metric}`, `- Status: ${metrics.budgetEvaluation.pass ? 'pass' : 'fail'}`);
|
|
@@ -55,6 +55,7 @@ const SCHEMAS = {
|
|
|
55
55
|
liveProofSet: loadSchema('live-proof-set.schema.json'),
|
|
56
56
|
manifest: loadSchema('manifest.schema.json'),
|
|
57
57
|
metrics: loadSchema('metrics.schema.json'),
|
|
58
|
+
profiler: loadSchema('profiler.schema.json'),
|
|
58
59
|
projectValidation: loadSchema('project-validation.schema.json'),
|
|
59
60
|
scenario: loadSchema('scenario.schema.json'),
|
|
60
61
|
runnerCapabilities: loadSchema('runner-capabilities.schema.json'),
|
|
@@ -7,6 +7,7 @@ type AndroidAdbCommandResult = {
|
|
|
7
7
|
rawFileName: string;
|
|
8
8
|
stderr: string;
|
|
9
9
|
stdout: string;
|
|
10
|
+
stdoutBuffer?: Uint8Array;
|
|
10
11
|
};
|
|
11
12
|
type AndroidAdbDriver = {
|
|
12
13
|
assertVisible: (options: AndroidAdbAssertVisibleOptions) => Promise<AndroidAdbCommandResult>;
|
|
@@ -31,12 +32,15 @@ type AndroidAdbDriverOptions = {
|
|
|
31
32
|
deviceSerial: string;
|
|
32
33
|
executor: AndroidAdbCommandExecutor;
|
|
33
34
|
};
|
|
34
|
-
type AndroidAdbCommandExecutor = (command: string, args: string[]
|
|
35
|
+
type AndroidAdbCommandExecutor = (command: string, args: string[], options?: {
|
|
36
|
+
encoding?: 'buffer' | 'utf8';
|
|
37
|
+
}) => Promise<{
|
|
35
38
|
args: string[];
|
|
36
39
|
command: string;
|
|
37
40
|
exitCode: number;
|
|
38
41
|
stderr: string;
|
|
39
42
|
stdout: string;
|
|
43
|
+
stdoutBuffer?: Uint8Array;
|
|
40
44
|
}>;
|
|
41
45
|
type AndroidAdbDeepLinkOptions = {
|
|
42
46
|
packageName?: string | null;
|
|
@@ -110,7 +114,8 @@ declare function quoteAndroidShellArg(value: string): string;
|
|
|
110
114
|
declare function formatAndroidAdbRawOutput(result: {
|
|
111
115
|
stdout: string;
|
|
112
116
|
stderr: string;
|
|
113
|
-
|
|
117
|
+
stdoutBuffer?: Uint8Array;
|
|
118
|
+
}): string | Uint8Array;
|
|
114
119
|
/**
|
|
115
120
|
* Joins command output from a multi-command adb driver action.
|
|
116
121
|
*
|
|
@@ -57,6 +57,7 @@ function buildDriverResult({ action, rawFileName, result, }) {
|
|
|
57
57
|
rawFileName,
|
|
58
58
|
stderr: result.stderr,
|
|
59
59
|
stdout: result.stdout,
|
|
60
|
+
...(result.stdoutBuffer ? { stdoutBuffer: result.stdoutBuffer } : {}),
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
@@ -66,6 +67,9 @@ function buildDriverResult({ action, rawFileName, result, }) {
|
|
|
66
67
|
* @returns {string}
|
|
67
68
|
*/
|
|
68
69
|
function formatAndroidAdbRawOutput(result) {
|
|
70
|
+
if (result.stdoutBuffer && !result.stderr) {
|
|
71
|
+
return result.stdoutBuffer;
|
|
72
|
+
}
|
|
69
73
|
return [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
@@ -373,7 +377,9 @@ function createAndroidAdbDriver({ adbPath, deviceSerial, executor, }) {
|
|
|
373
377
|
};
|
|
374
378
|
},
|
|
375
379
|
async screenshot({ rawFileName = 'adb-screenshot.png', } = {}) {
|
|
376
|
-
const result = await executor(adbPath, ['-s', deviceSerial, 'exec-out', 'screencap', '-p']
|
|
380
|
+
const result = await executor(adbPath, ['-s', deviceSerial, 'exec-out', 'screencap', '-p'], {
|
|
381
|
+
encoding: 'buffer',
|
|
382
|
+
});
|
|
377
383
|
return buildDriverResult({ action: 'screenshot', rawFileName, result });
|
|
378
384
|
},
|
|
379
385
|
async scroll({ durationMs = 300, endX, endY, rawFileName = 'adb-scroll.txt', startX, startY, }) {
|
|
@@ -3,6 +3,7 @@ type CliArgs = {
|
|
|
3
3
|
adb?: string | boolean;
|
|
4
4
|
'capture-logcat'?: string | boolean;
|
|
5
5
|
'clear-logcat'?: string | boolean;
|
|
6
|
+
'command-timeout-ms'?: string | boolean;
|
|
6
7
|
launch?: string | boolean;
|
|
7
8
|
'android-dev-client-url'?: string | boolean;
|
|
8
9
|
'android-dev-client-wait-ms'?: string | boolean;
|
|
@@ -28,8 +29,12 @@ type CommandResult = {
|
|
|
28
29
|
exitCode: number;
|
|
29
30
|
stderr: string;
|
|
30
31
|
stdout: string;
|
|
32
|
+
stdoutBuffer?: Uint8Array;
|
|
31
33
|
};
|
|
32
|
-
type
|
|
34
|
+
type CommandExecutorOptions = {
|
|
35
|
+
encoding?: 'buffer' | 'utf8';
|
|
36
|
+
};
|
|
37
|
+
type CommandExecutor = (command: string, args: string[], options?: CommandExecutorOptions) => Promise<CommandResult>;
|
|
33
38
|
type AndroidDevice = {
|
|
34
39
|
serial: string;
|
|
35
40
|
state: string;
|
|
@@ -40,7 +45,7 @@ type AndroidPreflightResult = {
|
|
|
40
45
|
device: AndroidDevice | null;
|
|
41
46
|
health: Record<string, unknown>;
|
|
42
47
|
metadata: Record<string, unknown>;
|
|
43
|
-
raw: Record<string, string>;
|
|
48
|
+
raw: Record<string, string | Uint8Array>;
|
|
44
49
|
runDir: string;
|
|
45
50
|
verdict: Record<string, unknown>;
|
|
46
51
|
};
|
|
@@ -81,7 +86,9 @@ type AndroidAdbDriverStep = {
|
|
|
81
86
|
type AndroidPreflightOptions = {
|
|
82
87
|
adbPath?: string;
|
|
83
88
|
captureLogcat?: boolean;
|
|
89
|
+
captureWatchdogMs?: number;
|
|
84
90
|
clearLogcat?: boolean;
|
|
91
|
+
commandTimeoutMs?: number;
|
|
85
92
|
deepLinks?: AndroidDeepLinkCommand[];
|
|
86
93
|
delay?: (ms: number) => Promise<void>;
|
|
87
94
|
driverSteps?: AndroidAdbDriverStep[];
|
|
@@ -129,7 +136,16 @@ declare function parsePositiveInteger(value: string | boolean | undefined, fallb
|
|
|
129
136
|
* @param {string[]} args
|
|
130
137
|
* @returns {Promise<CommandResult>}
|
|
131
138
|
*/
|
|
132
|
-
declare function execFileCommand(command: string, args: string[]): Promise<CommandResult>;
|
|
139
|
+
declare function execFileCommand(command: string, args: string[], options?: CommandExecutorOptions): Promise<CommandResult>;
|
|
140
|
+
/**
|
|
141
|
+
* Runs a command with a bounded timeout and captures stdout, stderr, and exit code without throwing.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} command
|
|
144
|
+
* @param {string[]} args
|
|
145
|
+
* @param {number} timeoutMs
|
|
146
|
+
* @returns {Promise<CommandResult>}
|
|
147
|
+
*/
|
|
148
|
+
declare function execFileCommandWithTimeout(command: string, args: string[], timeoutMs?: number, options?: CommandExecutorOptions): Promise<CommandResult>;
|
|
133
149
|
/**
|
|
134
150
|
* Parses `adb devices -l` output into device rows.
|
|
135
151
|
*
|
|
@@ -189,6 +205,25 @@ declare function buildAndroidVerdict({ runId, health }: {
|
|
|
189
205
|
runId: string;
|
|
190
206
|
health: Record<string, unknown>;
|
|
191
207
|
}): Record<string, unknown>;
|
|
208
|
+
type AndroidAdbCaptureWatchdogBudget = {
|
|
209
|
+
ceilingMs: number;
|
|
210
|
+
commandBudgetMs: number;
|
|
211
|
+
commandUnits: number;
|
|
212
|
+
declaredWaitMs: number;
|
|
213
|
+
floorMs: number;
|
|
214
|
+
perCommandOverheadMs: number;
|
|
215
|
+
source: 'derived' | 'override';
|
|
216
|
+
timeoutMs: number;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Derives a whole-capture adb watchdog from declared runner waits and command bounds.
|
|
220
|
+
*
|
|
221
|
+
* @param {AndroidPreflightOptions & {commandTimeoutMs: number}} options
|
|
222
|
+
* @returns {AndroidAdbCaptureWatchdogBudget}
|
|
223
|
+
*/
|
|
224
|
+
declare function deriveAndroidAdbCaptureWatchdogBudget({ captureLogcat, captureWatchdogMs, clearLogcat, commandTimeoutMs, deepLinks, driverSteps, launch, launchWaitMs, packageName, reactNativeDebugHost, startupDeepLinks, storageWrites, waitMs, }: AndroidPreflightOptions & {
|
|
225
|
+
commandTimeoutMs: number;
|
|
226
|
+
}): AndroidAdbCaptureWatchdogBudget;
|
|
192
227
|
/**
|
|
193
228
|
* Builds the driver steps for this adb capture window.
|
|
194
229
|
*
|
|
@@ -243,12 +278,12 @@ declare function runAndroidAdbDriverStep({ capturesDir, driver, driverStep, logc
|
|
|
243
278
|
* @param {AndroidPreflightOptions} options
|
|
244
279
|
* @returns {Promise<AndroidPreflightResult>}
|
|
245
280
|
*/
|
|
246
|
-
declare function runAndroidAdbPreflight({ adbPath, captureLogcat, clearLogcat, deepLinks, delay: wait, driverSteps, executor, launch, launchWaitMs, logcatLines, outputDir, packageName, reactNativeDebugHost, runId, serial, startupDeepLinks, storageWrites, waitMs, }?: AndroidPreflightOptions): Promise<AndroidPreflightResult>;
|
|
281
|
+
declare function runAndroidAdbPreflight({ adbPath, captureLogcat, captureWatchdogMs: captureWatchdogMsOverride, clearLogcat, commandTimeoutMs, deepLinks, delay: wait, driverSteps, executor, launch, launchWaitMs, logcatLines, outputDir, packageName, reactNativeDebugHost, runId, serial, startupDeepLinks, storageWrites, waitMs, }?: AndroidPreflightOptions): Promise<AndroidPreflightResult>;
|
|
247
282
|
/**
|
|
248
283
|
* Runs the android-adb preflight CLI.
|
|
249
284
|
*
|
|
250
285
|
* @returns {Promise<void>}
|
|
251
286
|
*/
|
|
252
287
|
declare function main(): Promise<void>;
|
|
253
|
-
export { ANDROID_DEVICE_EPOCH_MS_PLACEHOLDER, buildAndroidHealth, buildAndroidVerdict, buildReactNativeDebugHostPreferenceCommand, escapeAndroidPreferenceXml, execFileCommand, main, parseAdbDevices, parseArgs, parsePositiveInteger, parseReactNativeDebugHostPort, resolveAndroidAdbDriverSteps, applyAndroidSelectorResolution, buildAndroidSelectorHealthMetadata, needsAndroidSelectorResolution, runAndroidAdbDriverStep, runAndroidAdbPreflight, selectDevice, usage, };
|
|
288
|
+
export { ANDROID_DEVICE_EPOCH_MS_PLACEHOLDER, buildAndroidHealth, buildAndroidVerdict, buildReactNativeDebugHostPreferenceCommand, deriveAndroidAdbCaptureWatchdogBudget, escapeAndroidPreferenceXml, execFileCommand, execFileCommandWithTimeout, main, parseAdbDevices, parseArgs, parsePositiveInteger, parseReactNativeDebugHostPort, resolveAndroidAdbDriverSteps, applyAndroidSelectorResolution, buildAndroidSelectorHealthMetadata, needsAndroidSelectorResolution, runAndroidAdbDriverStep, runAndroidAdbPreflight, selectDevice, usage, };
|
|
254
289
|
export type { AndroidDevice, AndroidAdbDriverStep, AndroidAsyncStorageWrite, AndroidDeepLinkCommand, AndroidPreflightOptions, AndroidPreflightResult, CliArgs, CommandExecutor, CommandResult, };
|