agent-scenario-loop 0.1.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/LICENSE +21 -0
- package/README.md +119 -0
- package/app/profile-session.ts +812 -0
- package/core/config-template.json +41 -0
- package/dist/core/agent-summary.d.ts +15 -0
- package/dist/core/agent-summary.js +177 -0
- package/dist/core/artifact-contract.d.ts +151 -0
- package/dist/core/artifact-contract.js +897 -0
- package/dist/core/artifact-layout.d.ts +56 -0
- package/dist/core/artifact-layout.js +61 -0
- package/dist/core/artifact-writer.d.ts +44 -0
- package/dist/core/artifact-writer.js +55 -0
- package/dist/core/comparison.d.ts +133 -0
- package/dist/core/comparison.js +294 -0
- package/dist/core/evidence-interpreter.d.ts +28 -0
- package/dist/core/evidence-interpreter.js +69 -0
- package/dist/core/execution-plan.d.ts +44 -0
- package/dist/core/execution-plan.js +95 -0
- package/dist/core/planner.d.ts +132 -0
- package/dist/core/planner.js +812 -0
- package/dist/core/ports.d.ts +198 -0
- package/dist/core/ports.js +146 -0
- package/dist/core/run-index.d.ts +62 -0
- package/dist/core/run-index.js +143 -0
- package/dist/core/schema-validator.d.ts +86 -0
- package/dist/core/schema-validator.js +407 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +27 -0
- package/dist/runner/agent-device-driver.d.ts +126 -0
- package/dist/runner/agent-device-driver.js +168 -0
- package/dist/runner/agent-device.d.ts +295 -0
- package/dist/runner/agent-device.js +1271 -0
- package/dist/runner/android-adb-driver.d.ts +175 -0
- package/dist/runner/android-adb-driver.js +399 -0
- package/dist/runner/android-adb.d.ts +254 -0
- package/dist/runner/android-adb.js +1618 -0
- package/dist/runner/argent-driver.d.ts +183 -0
- package/dist/runner/argent-driver.js +297 -0
- package/dist/runner/argent.d.ts +349 -0
- package/dist/runner/argent.js +1211 -0
- package/dist/runner/check-plan.d.ts +45 -0
- package/dist/runner/check-plan.js +210 -0
- package/dist/runner/cli.d.ts +20 -0
- package/dist/runner/cli.js +23 -0
- package/dist/runner/compare-latest.d.ts +99 -0
- package/dist/runner/compare-latest.js +233 -0
- package/dist/runner/compare.d.ts +58 -0
- package/dist/runner/compare.js +157 -0
- package/dist/runner/demo-loop.d.ts +45 -0
- package/dist/runner/demo-loop.js +170 -0
- package/dist/runner/example-android-live.d.ts +137 -0
- package/dist/runner/example-android-live.js +454 -0
- package/dist/runner/example-ios-live.d.ts +137 -0
- package/dist/runner/example-ios-live.js +471 -0
- package/dist/runner/host-doctor.d.ts +131 -0
- package/dist/runner/host-doctor.js +628 -0
- package/dist/runner/init-project.d.ts +88 -0
- package/dist/runner/init-project.js +263 -0
- package/dist/runner/ios-simctl-driver.d.ts +69 -0
- package/dist/runner/ios-simctl-driver.js +97 -0
- package/dist/runner/ios-simctl.d.ts +254 -0
- package/dist/runner/ios-simctl.js +1415 -0
- package/dist/runner/live-android.d.ts +137 -0
- package/dist/runner/live-android.js +539 -0
- package/dist/runner/live-comparison.d.ts +67 -0
- package/dist/runner/live-comparison.js +147 -0
- package/dist/runner/live-ios.d.ts +137 -0
- package/dist/runner/live-ios.js +460 -0
- package/dist/runner/live-proof-summary.d.ts +263 -0
- package/dist/runner/live-proof-summary.js +465 -0
- package/dist/runner/live-proof.d.ts +467 -0
- package/dist/runner/live-proof.js +920 -0
- package/dist/runner/local-env.d.ts +64 -0
- package/dist/runner/local-env.js +155 -0
- package/dist/runner/profile-android.d.ts +82 -0
- package/dist/runner/profile-android.js +671 -0
- package/dist/runner/profile-ios.d.ts +108 -0
- package/dist/runner/profile-ios.js +532 -0
- package/dist/runner/profile-mobile.d.ts +254 -0
- package/dist/runner/profile-mobile.js +1307 -0
- package/dist/runner/validate-project.d.ts +273 -0
- package/dist/runner/validate-project.js +1501 -0
- package/docs/adapters.md +145 -0
- package/docs/api.md +94 -0
- package/docs/authoring.md +196 -0
- package/docs/concepts.md +136 -0
- package/docs/consumer-rehearsal.md +115 -0
- package/docs/contracts.md +267 -0
- package/docs/live-proofs.md +270 -0
- package/docs/principles.md +46 -0
- package/examples/event-logs/app-startup-baseline.log +4 -0
- package/examples/event-logs/app-startup-current.log +4 -0
- package/examples/minimal-app/README.md +70 -0
- package/examples/mobile-app/README.md +302 -0
- package/examples/mobile-app/app.json +22 -0
- package/examples/mobile-app/asl/package-scripts.json +32 -0
- package/examples/mobile-app/asl.config.json +37 -0
- package/examples/mobile-app/event-logs/android-app-startup.log +4 -0
- package/examples/mobile-app/event-logs/android-open-close-cycle.log +12 -0
- package/examples/mobile-app/event-logs/android-scroll-settle.log +12 -0
- package/examples/mobile-app/event-logs/app-startup.log +4 -0
- package/examples/mobile-app/event-logs/open-close-cycle.log +12 -0
- package/examples/mobile-app/event-logs/scroll-settle.log +12 -0
- package/examples/mobile-app/index.ts +20 -0
- package/examples/mobile-app/metro.config.js +20 -0
- package/examples/mobile-app/package.json +62 -0
- package/examples/mobile-app/patches/expo-modules-jsi@56.0.10.patch +19 -0
- package/examples/mobile-app/plugins/with-ios-build-compat.js +271 -0
- package/examples/mobile-app/pnpm-lock.yaml +4440 -0
- package/examples/mobile-app/runner-manifests/evidence-provider.json +79 -0
- package/examples/mobile-app/runner-manifests/primary-runner.json +19 -0
- package/examples/mobile-app/scenarios/android/app-startup-video.json +73 -0
- package/examples/mobile-app/scenarios/android/app-startup.json +44 -0
- package/examples/mobile-app/scenarios/android/open-close-cycle.json +54 -0
- package/examples/mobile-app/scenarios/android/scroll-settle.json +49 -0
- package/examples/mobile-app/scenarios/ios/app-startup.json +44 -0
- package/examples/mobile-app/scenarios/ios/open-close-cycle.json +54 -0
- package/examples/mobile-app/scenarios/ios/scroll-settle.json +49 -0
- package/examples/mobile-app/scenarios/mobile/app-startup.json +91 -0
- package/examples/mobile-app/scenarios/mobile/open-close-cycle.json +160 -0
- package/examples/mobile-app/scenarios/mobile/scroll-settle.json +148 -0
- package/examples/mobile-app/scripts/asl-capture-accessibility-provider.mjs +112 -0
- package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +127 -0
- package/examples/mobile-app/src/devtools/profile-session.ts +7 -0
- package/examples/mobile-app/src/example-screen.tsx +322 -0
- package/examples/mobile-app/tsconfig.json +16 -0
- package/examples/mobile-app/tsconfig.typecheck.json +13 -0
- package/examples/runners/README.md +44 -0
- package/examples/runners/adb-android.json +25 -0
- package/examples/runners/agent-device-android.json +27 -0
- package/examples/runners/agent-device-ios.json +27 -0
- package/examples/runners/argent-android.json +32 -0
- package/examples/runners/argent-ios.json +32 -0
- package/examples/runners/argent-react-profiler-provider.json +15 -0
- package/examples/runners/axe-accessibility-provider.json +24 -0
- package/examples/runners/manual-log-ingest.json +9 -0
- package/examples/runners/rozenite-profiler-provider.json +9 -0
- package/examples/runners/script-accessibility-provider.json +24 -0
- package/examples/runners/script-memory-provider.json +24 -0
- package/examples/runners/script-network-provider.json +24 -0
- package/examples/runners/script-profiler-provider.json +30 -0
- package/examples/runners/xcodebuildmcp-ios.json +29 -0
- package/examples/scenarios/ios/app-startup.json +28 -0
- package/examples/scenarios/ios/open-close-cycle.json +35 -0
- package/examples/scenarios/mobile/app-startup.json +72 -0
- package/examples/scenarios/mobile/media-open-close.json +141 -0
- package/examples/scenarios/mobile/open-close-cycle.json +135 -0
- package/examples/scenarios/mobile/scroll-settle.json +106 -0
- package/package.json +240 -0
- package/schemas/budget-verdict.schema.json +115 -0
- package/schemas/causal-run.schema.json +279 -0
- package/schemas/comparison.schema.json +196 -0
- package/schemas/health.schema.json +108 -0
- package/schemas/live-proof-set.schema.json +195 -0
- package/schemas/live-proof.schema.json +413 -0
- package/schemas/manifest.schema.json +204 -0
- package/schemas/metrics.schema.json +137 -0
- package/schemas/project-validation.schema.json +343 -0
- package/schemas/runner-capabilities.schema.json +217 -0
- package/schemas/scenario.schema.json +400 -0
- package/schemas/verdict.schema.json +88 -0
- package/templates/evidence-provider.json +83 -0
- package/templates/gitignore-snippet +9 -0
- package/templates/integration-readme.md +125 -0
- package/templates/mobile-scenario.json +133 -0
- package/templates/package-scripts.json +32 -0
- package/templates/primary-runner.json +19 -0
- package/templates/project.config.json +37 -0
- package/templates/scripts/asl-capture-accessibility-provider.mjs +112 -0
- package/templates/scripts/asl-capture-profiler-provider.mjs +127 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
declare const buildProfileHealth: any, buildProfileVerdict: any, buildVerdictBudgetChecks: any, parseArgs: any, usage: any;
|
|
3
|
+
type IosProfileOptions = {
|
|
4
|
+
agentDeviceExecutor?: import('./agent-device').CommandExecutor;
|
|
5
|
+
comparisonLane?: string;
|
|
6
|
+
delay?: (ms: number) => Promise<void>;
|
|
7
|
+
executor?: import('./ios-simctl').CommandExecutor;
|
|
8
|
+
};
|
|
9
|
+
type IosSimctlProfileCommand = {
|
|
10
|
+
command: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
waitMs?: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the simctl capture output directory for a profile run.
|
|
16
|
+
*
|
|
17
|
+
* @param {{args: import('./profile-mobile').CliArgs, runId: string}} options
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
declare function resolveSimctlCaptureOutputDir({ args, runId, }: {
|
|
21
|
+
args: import('./profile-mobile').CliArgs;
|
|
22
|
+
runId: string;
|
|
23
|
+
}): string;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the iOS bundle id from explicit CLI input or project config.
|
|
26
|
+
*
|
|
27
|
+
* @param {{args: import('./profile-mobile').CliArgs, config: Record<string, unknown>}} options
|
|
28
|
+
* @returns {string | null}
|
|
29
|
+
*/
|
|
30
|
+
declare function resolveIosBundleId({ args, config, }: {
|
|
31
|
+
args: import('./profile-mobile').CliArgs;
|
|
32
|
+
config: Record<string, any>;
|
|
33
|
+
}): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Resolves optional sibling iOS bundle ids that make simulator targeting ambiguous.
|
|
36
|
+
*
|
|
37
|
+
* @param {Record<string, unknown>} config
|
|
38
|
+
* @returns {string[]}
|
|
39
|
+
*/
|
|
40
|
+
declare function resolveIosConflictingBundleIds(config: Record<string, any>): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Builds a profile-session deep link for the example app or another configured app.
|
|
43
|
+
*
|
|
44
|
+
* @param {{config: Record<string, unknown>, action: 'start' | 'command', scenario: string, runId: string, command?: string}} options
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
declare function buildProfileSessionUrl({ action, command, config, runId, scenario, }: {
|
|
48
|
+
action: 'start' | 'command';
|
|
49
|
+
command?: string;
|
|
50
|
+
config: Record<string, any>;
|
|
51
|
+
runId: string;
|
|
52
|
+
scenario: string;
|
|
53
|
+
}): string;
|
|
54
|
+
/**
|
|
55
|
+
* Derives a storage-backed profile capture window from scenario waits and cycles.
|
|
56
|
+
*
|
|
57
|
+
* @param {Record<string, unknown>} scenario
|
|
58
|
+
* @returns {number}
|
|
59
|
+
*/
|
|
60
|
+
declare function deriveProfileSessionCaptureWaitMs(scenario: Record<string, any>): number;
|
|
61
|
+
/**
|
|
62
|
+
* Resolves the iOS capture wait, keeping explicit CLI waits authoritative.
|
|
63
|
+
*
|
|
64
|
+
* @param {{args: import('./profile-mobile').CliArgs, scenario: Record<string, unknown>, profileSessionEnabled: boolean}} options
|
|
65
|
+
* @returns {number}
|
|
66
|
+
*/
|
|
67
|
+
declare function resolveProfileSessionCaptureWaitMs({ args, profileSessionEnabled, scenario, }: {
|
|
68
|
+
args: import('./profile-mobile').CliArgs;
|
|
69
|
+
profileSessionEnabled: boolean;
|
|
70
|
+
scenario: Record<string, any>;
|
|
71
|
+
}): number;
|
|
72
|
+
/**
|
|
73
|
+
* Expands scenario-declared iOS commands for a simctl capture profile session.
|
|
74
|
+
*
|
|
75
|
+
* @param {Record<string, unknown>} scenario
|
|
76
|
+
* @returns {IosSimctlProfileCommand[]}
|
|
77
|
+
*/
|
|
78
|
+
declare function resolveIosSimctlProfileCommands(scenario: Record<string, any>): IosSimctlProfileCommand[];
|
|
79
|
+
/**
|
|
80
|
+
* Returns true when the scenario asks iOS simctl capture to preserve a screenshot.
|
|
81
|
+
*
|
|
82
|
+
* @param {Record<string, unknown>} scenario
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
declare function requiresIosSimctlScreenshot(scenario: Record<string, any>): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Summarizes failed simctl capture checks for CLI errors.
|
|
88
|
+
*
|
|
89
|
+
* @param {Record<string, unknown>} health
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
declare function summarizeFailedIosChecks(health: Record<string, unknown>): string;
|
|
93
|
+
/**
|
|
94
|
+
* Runs the iOS log-ingest profile artifact pipeline.
|
|
95
|
+
*
|
|
96
|
+
* @param {import('./profile-mobile').CliArgs} args
|
|
97
|
+
* @param {IosProfileOptions} [options]
|
|
98
|
+
* @returns {Promise<import('./profile-mobile').ProfileRunResult>}
|
|
99
|
+
*/
|
|
100
|
+
declare function runProfileIos(args: import('./profile-mobile').CliArgs, options?: IosProfileOptions): Promise<import('./profile-mobile').ProfileRunResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Runs the profile-ios CLI.
|
|
103
|
+
*
|
|
104
|
+
* @returns {Promise<void>}
|
|
105
|
+
*/
|
|
106
|
+
declare function main(): Promise<void>;
|
|
107
|
+
export { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, buildProfileSessionUrl, deriveProfileSessionCaptureWaitMs, main, parseArgs, resolveIosBundleId, resolveIosConflictingBundleIds, resolveIosSimctlProfileCommands, resolveProfileSessionCaptureWaitMs, resolveSimctlCaptureOutputDir, requiresIosSimctlScreenshot, runProfileIos, summarizeFailedIosChecks, usage, };
|
|
108
|
+
export type { CliArgs, ProfileRunResult, } from './profile-mobile';
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.usage = exports.parseArgs = exports.buildVerdictBudgetChecks = exports.buildProfileVerdict = exports.buildProfileHealth = void 0;
|
|
5
|
+
exports.buildProfileSessionUrl = buildProfileSessionUrl;
|
|
6
|
+
exports.deriveProfileSessionCaptureWaitMs = deriveProfileSessionCaptureWaitMs;
|
|
7
|
+
exports.main = main;
|
|
8
|
+
exports.resolveIosBundleId = resolveIosBundleId;
|
|
9
|
+
exports.resolveIosConflictingBundleIds = resolveIosConflictingBundleIds;
|
|
10
|
+
exports.resolveIosSimctlProfileCommands = resolveIosSimctlProfileCommands;
|
|
11
|
+
exports.resolveProfileSessionCaptureWaitMs = resolveProfileSessionCaptureWaitMs;
|
|
12
|
+
exports.resolveSimctlCaptureOutputDir = resolveSimctlCaptureOutputDir;
|
|
13
|
+
exports.requiresIosSimctlScreenshot = requiresIosSimctlScreenshot;
|
|
14
|
+
exports.runProfileIos = runProfileIos;
|
|
15
|
+
exports.summarizeFailedIosChecks = summarizeFailedIosChecks;
|
|
16
|
+
const crypto = require('node:crypto');
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { hasHelpFlag } = require('./cli');
|
|
20
|
+
const { buildScenarioExecutionPlan } = require('../core/execution-plan');
|
|
21
|
+
const { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, readScalarArg, runProfileCli, runProfileMobile, usage, } = require('./profile-mobile');
|
|
22
|
+
exports.buildProfileHealth = buildProfileHealth;
|
|
23
|
+
exports.buildProfileVerdict = buildProfileVerdict;
|
|
24
|
+
exports.buildVerdictBudgetChecks = buildVerdictBudgetChecks;
|
|
25
|
+
exports.parseArgs = parseArgs;
|
|
26
|
+
exports.usage = usage;
|
|
27
|
+
const { runIosSimctlCapture } = require('./ios-simctl');
|
|
28
|
+
const { runAgentDeviceCapture } = require('./agent-device');
|
|
29
|
+
const { loadAslLocalEnv, readStringArgOrEnv } = require('./local-env');
|
|
30
|
+
const PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS = 1000;
|
|
31
|
+
const PROFILE_SESSION_CAPTURE_MAX_MS = 30000;
|
|
32
|
+
const DEFAULT_IOS_PROFILE_SESSION_STORAGE_KEY = 'agent-scenario-loop.profile-session.1';
|
|
33
|
+
const DEFAULT_IOS_PROFILE_COMMAND_STORAGE_KEY = 'agent-scenario-loop.profile-commands.1';
|
|
34
|
+
const DEFAULT_IOS_PROFILE_EVENT_STORAGE_KEY = 'agent-scenario-loop.profile-events.1';
|
|
35
|
+
const DEFAULT_IOS_PROFILE_SIGNAL_STORAGE_KEY = 'agent-scenario-loop.profile-signals.1';
|
|
36
|
+
const DEFAULT_IOS_PROFILE_SESSION_ENTRIES_STORAGE_KEY = 'agent-scenario-loop.profile-session-entries.1';
|
|
37
|
+
/**
|
|
38
|
+
* Reads and parses a JSON object from disk.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} filePath
|
|
41
|
+
* @returns {Record<string, unknown>}
|
|
42
|
+
*/
|
|
43
|
+
function readJson(filePath) {
|
|
44
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Checks whether a boolean-style CLI flag is enabled.
|
|
48
|
+
*
|
|
49
|
+
* @param {string | boolean | undefined} value
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
function isEnabled(value) {
|
|
53
|
+
return value === true || value === 'true';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reads a positive integer from unknown scenario adapter metadata.
|
|
57
|
+
*
|
|
58
|
+
* @param {unknown} value
|
|
59
|
+
* @param {number} fallback
|
|
60
|
+
* @returns {number}
|
|
61
|
+
*/
|
|
62
|
+
function readPositiveInteger(value, fallback) {
|
|
63
|
+
const parsed = typeof value === 'string' ? Number(value) : value;
|
|
64
|
+
return typeof parsed === 'number' && Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Reads the number of scenario iterations that can emit app-owned truth events.
|
|
68
|
+
*
|
|
69
|
+
* @param {Record<string, unknown>} scenario
|
|
70
|
+
* @returns {number}
|
|
71
|
+
*/
|
|
72
|
+
function readScenarioIterationCount(scenario) {
|
|
73
|
+
return readPositiveInteger(scenario.defaultIterations, readPositiveInteger(scenario.cycles?.iterations, 1));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates a short run id when simctl capture must share the id with profile artifacts.
|
|
77
|
+
*
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
function createRunId() {
|
|
81
|
+
return crypto.randomBytes(6).toString('hex');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the simctl capture output directory for a profile run.
|
|
85
|
+
*
|
|
86
|
+
* @param {{args: import('./profile-mobile').CliArgs, runId: string}} options
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
function resolveSimctlCaptureOutputDir({ args, runId, }) {
|
|
90
|
+
if (typeof args['simctl-out'] === 'string') {
|
|
91
|
+
return path.resolve(args['simctl-out']);
|
|
92
|
+
}
|
|
93
|
+
if (typeof args.out === 'string') {
|
|
94
|
+
return path.resolve(args.out, '_ios-simctl-captures', runId);
|
|
95
|
+
}
|
|
96
|
+
return path.resolve('artifacts/ios-simctl-captures', runId);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolves the agent-device capture output directory for a profile run.
|
|
100
|
+
*
|
|
101
|
+
* @param {{args: import('./profile-mobile').CliArgs, runId: string}} options
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
function resolveAgentDeviceCaptureOutputDir({ args, runId, }) {
|
|
105
|
+
if (typeof args['agent-device-out'] === 'string') {
|
|
106
|
+
return path.resolve(args['agent-device-out']);
|
|
107
|
+
}
|
|
108
|
+
if (typeof args.out === 'string') {
|
|
109
|
+
return path.resolve(args.out, '_agent-device-captures', runId);
|
|
110
|
+
}
|
|
111
|
+
return path.resolve('artifacts/agent-device-captures', runId);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolves the iOS bundle id from explicit CLI input or project config.
|
|
115
|
+
*
|
|
116
|
+
* @param {{args: import('./profile-mobile').CliArgs, config: Record<string, unknown>}} options
|
|
117
|
+
* @returns {string | null}
|
|
118
|
+
*/
|
|
119
|
+
function resolveIosBundleId({ args, config, }) {
|
|
120
|
+
if (typeof args.bundle === 'string') {
|
|
121
|
+
return args.bundle;
|
|
122
|
+
}
|
|
123
|
+
return typeof config.app?.iosBundleId === 'string' ? config.app.iosBundleId : null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolves optional sibling iOS bundle ids that make simulator targeting ambiguous.
|
|
127
|
+
*
|
|
128
|
+
* @param {Record<string, unknown>} config
|
|
129
|
+
* @returns {string[]}
|
|
130
|
+
*/
|
|
131
|
+
function resolveIosConflictingBundleIds(config) {
|
|
132
|
+
const configured = config.app?.iosConflictingBundleIds;
|
|
133
|
+
return Array.isArray(configured)
|
|
134
|
+
? configured.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
135
|
+
: [];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Builds a profile-session deep link for the example app or another configured app.
|
|
139
|
+
*
|
|
140
|
+
* @param {{config: Record<string, unknown>, action: 'start' | 'command', scenario: string, runId: string, command?: string}} options
|
|
141
|
+
* @returns {string}
|
|
142
|
+
*/
|
|
143
|
+
function buildProfileSessionUrl({ action, command, config, runId, scenario, }) {
|
|
144
|
+
const scheme = typeof config.app?.profileSessionScheme === 'string'
|
|
145
|
+
? config.app.profileSessionScheme
|
|
146
|
+
: typeof config.app?.scheme === 'string'
|
|
147
|
+
? config.app.scheme
|
|
148
|
+
: 'app';
|
|
149
|
+
const params = new URLSearchParams({ runId, scenario });
|
|
150
|
+
if (action === 'command' && command) {
|
|
151
|
+
params.set('command', command);
|
|
152
|
+
}
|
|
153
|
+
return `${scheme}://profile-session/${action}?${params.toString()}`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Reads iOS-specific wait metadata from a normalized execution step.
|
|
157
|
+
*
|
|
158
|
+
* @param {import('../core/execution-plan').ScenarioExecutionStep} step
|
|
159
|
+
* @returns {number}
|
|
160
|
+
*/
|
|
161
|
+
function readStepWaitMs(step) {
|
|
162
|
+
const iosSimctlOptions = step.adapterOptions?.iosSimctl;
|
|
163
|
+
if (iosSimctlOptions && typeof iosSimctlOptions === 'object' && !Array.isArray(iosSimctlOptions)) {
|
|
164
|
+
const waitMs = iosSimctlOptions.waitMs;
|
|
165
|
+
if (typeof waitMs === 'number' && Number.isInteger(waitMs) && waitMs > 0) {
|
|
166
|
+
return waitMs;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return readPositiveInteger(step.timeoutMs, 0);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Derives a storage-backed profile capture window from scenario waits and cycles.
|
|
173
|
+
*
|
|
174
|
+
* @param {Record<string, unknown>} scenario
|
|
175
|
+
* @returns {number}
|
|
176
|
+
*/
|
|
177
|
+
function deriveProfileSessionCaptureWaitMs(scenario) {
|
|
178
|
+
const executionPlan = buildScenarioExecutionPlan(scenario);
|
|
179
|
+
const iterations = readScenarioIterationCount(scenario);
|
|
180
|
+
const perIterationWaitMs = executionPlan.steps.reduce((total, step) => {
|
|
181
|
+
if (step.kind === 'command') {
|
|
182
|
+
return total + readStepWaitMs(step);
|
|
183
|
+
}
|
|
184
|
+
if (step.portMethod === 'waitForTruthEvent') {
|
|
185
|
+
return total + readPositiveInteger(step.timeoutMs, 0);
|
|
186
|
+
}
|
|
187
|
+
return total;
|
|
188
|
+
}, 0);
|
|
189
|
+
const derivedWaitMs = PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS + (perIterationWaitMs * iterations);
|
|
190
|
+
return Math.min(Math.max(derivedWaitMs, PROFILE_SESSION_CAPTURE_BOOTSTRAP_MS), PROFILE_SESSION_CAPTURE_MAX_MS);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Resolves the iOS capture wait, keeping explicit CLI waits authoritative.
|
|
194
|
+
*
|
|
195
|
+
* @param {{args: import('./profile-mobile').CliArgs, scenario: Record<string, unknown>, profileSessionEnabled: boolean}} options
|
|
196
|
+
* @returns {number}
|
|
197
|
+
*/
|
|
198
|
+
function resolveProfileSessionCaptureWaitMs({ args, profileSessionEnabled, scenario, }) {
|
|
199
|
+
const explicitWaitMs = readScalarArg(args['wait-ms']);
|
|
200
|
+
if (explicitWaitMs !== undefined) {
|
|
201
|
+
return readPositiveInteger(explicitWaitMs, 0);
|
|
202
|
+
}
|
|
203
|
+
return profileSessionEnabled ? deriveProfileSessionCaptureWaitMs(scenario) : 0;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Expands portable scenario command steps into iOS profile-session commands.
|
|
207
|
+
*
|
|
208
|
+
* @param {Record<string, unknown>} scenario
|
|
209
|
+
* @returns {IosSimctlProfileCommand[]}
|
|
210
|
+
*/
|
|
211
|
+
function resolveExecutionPlanProfileCommands(scenario) {
|
|
212
|
+
const executionPlan = buildScenarioExecutionPlan(scenario);
|
|
213
|
+
const repeat = readPositiveInteger(scenario.defaultIterations, readPositiveInteger(scenario.cycles?.iterations, 1));
|
|
214
|
+
const commands = executionPlan.steps
|
|
215
|
+
.filter((step) => step.portMethod === 'executeStep' && typeof step.command === 'string')
|
|
216
|
+
.map((step) => ({
|
|
217
|
+
command: step.command,
|
|
218
|
+
label: step.id,
|
|
219
|
+
waitMs: readStepWaitMs(step),
|
|
220
|
+
}));
|
|
221
|
+
return Array.from({ length: repeat }).flatMap(() => commands);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Expands scenario-declared iOS commands for a simctl capture profile session.
|
|
225
|
+
*
|
|
226
|
+
* @param {Record<string, unknown>} scenario
|
|
227
|
+
* @returns {IosSimctlProfileCommand[]}
|
|
228
|
+
*/
|
|
229
|
+
function resolveIosSimctlProfileCommands(scenario) {
|
|
230
|
+
const iosSimctlOptions = scenario.adapterOptions?.iosSimctl;
|
|
231
|
+
if (!iosSimctlOptions || !Array.isArray(iosSimctlOptions.commands)) {
|
|
232
|
+
return resolveExecutionPlanProfileCommands(scenario);
|
|
233
|
+
}
|
|
234
|
+
const repeat = readPositiveInteger(iosSimctlOptions.repeat, readPositiveInteger(scenario.defaultIterations, 1));
|
|
235
|
+
const commands = [];
|
|
236
|
+
for (let iteration = 0; iteration < repeat; iteration += 1) {
|
|
237
|
+
for (const command of iosSimctlOptions.commands) {
|
|
238
|
+
if (!command || typeof command.command !== 'string') {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
commands.push({
|
|
242
|
+
command: command.command,
|
|
243
|
+
...(typeof command.label === 'string' ? { label: command.label } : {}),
|
|
244
|
+
waitMs: readPositiveInteger(command.waitMs, 0),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return commands;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Returns true when the scenario asks iOS simctl capture to preserve a screenshot.
|
|
252
|
+
*
|
|
253
|
+
* @param {Record<string, unknown>} scenario
|
|
254
|
+
* @returns {boolean}
|
|
255
|
+
*/
|
|
256
|
+
function requiresIosSimctlScreenshot(scenario) {
|
|
257
|
+
const executionPlan = buildScenarioExecutionPlan(scenario);
|
|
258
|
+
return executionPlan.steps.some((step) => step.driverAction === 'screenshot' || step.artifact === 'screenshot');
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Appends one repeatable profile capture argument without losing caller-provided values.
|
|
262
|
+
*
|
|
263
|
+
* @param {{args: import('./profile-mobile').CliArgs, value: string}} options
|
|
264
|
+
* @returns {string | boolean | Array<string | boolean>}
|
|
265
|
+
*/
|
|
266
|
+
function appendCaptureArg({ args, value, }) {
|
|
267
|
+
const existing = args.capture;
|
|
268
|
+
return existing === undefined ? value : Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Summarizes failed simctl capture checks for CLI errors.
|
|
272
|
+
*
|
|
273
|
+
* @param {Record<string, unknown>} health
|
|
274
|
+
* @returns {string}
|
|
275
|
+
*/
|
|
276
|
+
function summarizeFailedIosChecks(health) {
|
|
277
|
+
const checks = Array.isArray(health.checks) ? health.checks : [];
|
|
278
|
+
const failedChecks = checks
|
|
279
|
+
.filter((check) => check?.status === 'failed')
|
|
280
|
+
.map((check) => (typeof check.message === 'string'
|
|
281
|
+
? check.message
|
|
282
|
+
: typeof check.code === 'string'
|
|
283
|
+
? check.code
|
|
284
|
+
: 'unknown failure'));
|
|
285
|
+
return failedChecks.length > 0 ? ` Failed checks: ${failedChecks.join(' ')}` : '';
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Summarizes failed agent-device checks for CLI errors.
|
|
289
|
+
*
|
|
290
|
+
* @param {Record<string, unknown>} health
|
|
291
|
+
* @returns {string}
|
|
292
|
+
*/
|
|
293
|
+
function summarizeFailedAgentDeviceChecks(health) {
|
|
294
|
+
const checks = Array.isArray(health.checks) ? health.checks : [];
|
|
295
|
+
const failedChecks = checks
|
|
296
|
+
.filter((check) => check?.status === 'failed')
|
|
297
|
+
.map((check) => (typeof check.message === 'string'
|
|
298
|
+
? check.message
|
|
299
|
+
: typeof check.code === 'string'
|
|
300
|
+
? check.code
|
|
301
|
+
: 'unknown failure'));
|
|
302
|
+
return failedChecks.length > 0 ? ` Failed checks: ${failedChecks.join(' ')}` : '';
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Appends screenshots from an agent-device capture as profile capture inputs.
|
|
306
|
+
*
|
|
307
|
+
* @param {{args: import('./profile-mobile').CliArgs, capture: import('./agent-device').AgentDeviceCaptureResult}} options
|
|
308
|
+
* @returns {import('./profile-mobile').CliArgs}
|
|
309
|
+
*/
|
|
310
|
+
function appendAgentDeviceCaptureArgs({ args, capture, }) {
|
|
311
|
+
let captureArg = args.capture;
|
|
312
|
+
for (const screenshot of capture.captures.screenshots) {
|
|
313
|
+
captureArg = appendCaptureArg({
|
|
314
|
+
args: captureArg === undefined ? {} : { capture: captureArg },
|
|
315
|
+
value: `screenshot:${path.join(capture.runDir, screenshot)}`,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return captureArg === undefined ? args : { ...args, capture: captureArg };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Runs the iOS log-ingest profile artifact pipeline.
|
|
322
|
+
*
|
|
323
|
+
* @param {import('./profile-mobile').CliArgs} args
|
|
324
|
+
* @param {IosProfileOptions} [options]
|
|
325
|
+
* @returns {Promise<import('./profile-mobile').ProfileRunResult>}
|
|
326
|
+
*/
|
|
327
|
+
async function runProfileIos(args, options = {}) {
|
|
328
|
+
if (!isEnabled(args['simctl-capture']) && !isEnabled(args['agent-device-capture'])) {
|
|
329
|
+
return runProfileMobile(args, {
|
|
330
|
+
...(options.comparisonLane ? { comparisonLane: options.comparisonLane } : {}),
|
|
331
|
+
defaultDriver: 'ios-simctl',
|
|
332
|
+
...(typeof args['simctl-artifacts'] === 'string' ? { interactionDriver: 'ios-simctl' } : {}),
|
|
333
|
+
platform: 'ios',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (typeof args.config !== 'string' || typeof args.scenario !== 'string') {
|
|
337
|
+
throw new Error('Both --config and --scenario are required.');
|
|
338
|
+
}
|
|
339
|
+
const config = readJson(path.resolve(args.config));
|
|
340
|
+
const scenario = readJson(path.resolve(args.scenario));
|
|
341
|
+
const runId = typeof args['run-id'] === 'string' ? args['run-id'] : createRunId();
|
|
342
|
+
const profileSessionEnabled = isEnabled(args['profile-session']);
|
|
343
|
+
const profileSessionStorageEnabled = isEnabled(args['profile-session-storage']);
|
|
344
|
+
const profileSessionStorageKey = readStringArgOrEnv(args['ios-profile-session-storage-key'], [
|
|
345
|
+
'ASL_IOS_PROFILE_SESSION_STORAGE_KEY',
|
|
346
|
+
'ASL_EXAMPLE_IOS_PROFILE_SESSION_STORAGE_KEY',
|
|
347
|
+
]) ?? DEFAULT_IOS_PROFILE_SESSION_STORAGE_KEY;
|
|
348
|
+
const profileCommandStorageKey = readStringArgOrEnv(args['ios-profile-command-storage-key'], [
|
|
349
|
+
'ASL_IOS_PROFILE_COMMAND_STORAGE_KEY',
|
|
350
|
+
'ASL_EXAMPLE_IOS_PROFILE_COMMAND_STORAGE_KEY',
|
|
351
|
+
]) ?? DEFAULT_IOS_PROFILE_COMMAND_STORAGE_KEY;
|
|
352
|
+
const profileEventStorageKey = readStringArgOrEnv(args['ios-profile-event-storage-key'], [
|
|
353
|
+
'ASL_IOS_PROFILE_EVENT_STORAGE_KEY',
|
|
354
|
+
'ASL_EXAMPLE_IOS_PROFILE_EVENT_STORAGE_KEY',
|
|
355
|
+
]) ?? DEFAULT_IOS_PROFILE_EVENT_STORAGE_KEY;
|
|
356
|
+
const profileSignalStorageKey = readStringArgOrEnv(args['ios-profile-signal-storage-key'], [
|
|
357
|
+
'ASL_IOS_PROFILE_SIGNAL_STORAGE_KEY',
|
|
358
|
+
'ASL_EXAMPLE_IOS_PROFILE_SIGNAL_STORAGE_KEY',
|
|
359
|
+
]) ?? DEFAULT_IOS_PROFILE_SIGNAL_STORAGE_KEY;
|
|
360
|
+
const profileSessionEntriesStorageKey = readStringArgOrEnv(args['ios-profile-session-entries-storage-key'], [
|
|
361
|
+
'ASL_IOS_PROFILE_SESSION_ENTRIES_STORAGE_KEY',
|
|
362
|
+
'ASL_EXAMPLE_IOS_PROFILE_SESSION_ENTRIES_STORAGE_KEY',
|
|
363
|
+
]) ?? DEFAULT_IOS_PROFILE_SESSION_ENTRIES_STORAGE_KEY;
|
|
364
|
+
const iosDevClientUrl = readStringArgOrEnv(args['ios-dev-client-url'], [
|
|
365
|
+
'ASL_IOS_DEV_CLIENT_URL',
|
|
366
|
+
'ASL_EXAMPLE_IOS_DEV_CLIENT_URL',
|
|
367
|
+
]);
|
|
368
|
+
const iosDevClientWaitMs = readPositiveInteger(readStringArgOrEnv(args['ios-dev-client-wait-ms'], [
|
|
369
|
+
'ASL_IOS_DEV_CLIENT_WAIT_MS',
|
|
370
|
+
'ASL_EXAMPLE_IOS_DEV_CLIENT_WAIT_MS',
|
|
371
|
+
]), 1000);
|
|
372
|
+
const scenarioName = typeof scenario.name === 'string' ? scenario.name : path.basename(args.scenario, '.json');
|
|
373
|
+
const profileSessionCommands = profileSessionEnabled ? resolveIosSimctlProfileCommands(scenario) : [];
|
|
374
|
+
const iosDevClientDeepLinks = iosDevClientUrl
|
|
375
|
+
? [
|
|
376
|
+
{
|
|
377
|
+
label: 'ios-dev-client-url',
|
|
378
|
+
url: iosDevClientUrl,
|
|
379
|
+
waitMs: iosDevClientWaitMs,
|
|
380
|
+
},
|
|
381
|
+
]
|
|
382
|
+
: [];
|
|
383
|
+
const profileSessionDeepLinks = profileSessionEnabled && !profileSessionStorageEnabled
|
|
384
|
+
? [
|
|
385
|
+
{
|
|
386
|
+
label: 'profile-session-start',
|
|
387
|
+
url: buildProfileSessionUrl({
|
|
388
|
+
action: 'start',
|
|
389
|
+
config,
|
|
390
|
+
runId,
|
|
391
|
+
scenario: scenarioName,
|
|
392
|
+
}),
|
|
393
|
+
waitMs: readPositiveInteger(readScalarArg(args['command-wait-ms']), 250),
|
|
394
|
+
},
|
|
395
|
+
...profileSessionCommands.map((profileCommand, index) => ({
|
|
396
|
+
label: profileCommand.label ?? `profile-command-${index + 1}`,
|
|
397
|
+
url: buildProfileSessionUrl({
|
|
398
|
+
action: 'command',
|
|
399
|
+
command: profileCommand.command,
|
|
400
|
+
config,
|
|
401
|
+
runId,
|
|
402
|
+
scenario: scenarioName,
|
|
403
|
+
}),
|
|
404
|
+
waitMs: profileCommand.waitMs,
|
|
405
|
+
})),
|
|
406
|
+
]
|
|
407
|
+
: [];
|
|
408
|
+
const deepLinks = [...iosDevClientDeepLinks, ...profileSessionDeepLinks];
|
|
409
|
+
const shouldLaunchWithSimctl = isEnabled(args.launch) && !(profileSessionStorageEnabled && iosDevClientUrl);
|
|
410
|
+
const simctlCapture = isEnabled(args['simctl-capture'])
|
|
411
|
+
? await runIosSimctlCapture({
|
|
412
|
+
bundleId: resolveIosBundleId({ args, config }),
|
|
413
|
+
collectProfileStorage: profileSessionStorageEnabled,
|
|
414
|
+
conflictingBundleIds: resolveIosConflictingBundleIds(config),
|
|
415
|
+
deepLinks,
|
|
416
|
+
...(options.delay ? { delay: options.delay } : {}),
|
|
417
|
+
...(typeof args.device === 'string' ? { device: args.device } : {}),
|
|
418
|
+
...(options.executor ? { executor: options.executor } : {}),
|
|
419
|
+
launch: shouldLaunchWithSimctl,
|
|
420
|
+
...(typeof args['log-last'] === 'string' ? { logLast: args['log-last'] } : {}),
|
|
421
|
+
outputDir: resolveSimctlCaptureOutputDir({ args, runId }),
|
|
422
|
+
...(profileSessionStorageEnabled
|
|
423
|
+
? {
|
|
424
|
+
profileStorageKeys: {
|
|
425
|
+
command: profileCommandStorageKey,
|
|
426
|
+
event: profileEventStorageKey,
|
|
427
|
+
session: profileSessionStorageKey,
|
|
428
|
+
sessionEntries: profileSessionEntriesStorageKey,
|
|
429
|
+
signal: profileSignalStorageKey,
|
|
430
|
+
},
|
|
431
|
+
profileSessionStorage: {
|
|
432
|
+
commands: profileSessionCommands.map((profileCommand, index) => ({
|
|
433
|
+
command: profileCommand.command,
|
|
434
|
+
id: `ios-storage-command-${index + 1}`,
|
|
435
|
+
...(typeof profileCommand.label === 'string' ? { label: profileCommand.label } : {}),
|
|
436
|
+
})),
|
|
437
|
+
runId,
|
|
438
|
+
scenario: scenarioName,
|
|
439
|
+
},
|
|
440
|
+
terminateBeforeLaunch: true,
|
|
441
|
+
}
|
|
442
|
+
: {
|
|
443
|
+
terminateBeforeLaunch: isEnabled(args['terminate-before-launch']),
|
|
444
|
+
}),
|
|
445
|
+
runId,
|
|
446
|
+
screenshot: isEnabled(args.screenshot) || requiresIosSimctlScreenshot(scenario),
|
|
447
|
+
waitMs: resolveProfileSessionCaptureWaitMs({
|
|
448
|
+
args,
|
|
449
|
+
profileSessionEnabled,
|
|
450
|
+
scenario,
|
|
451
|
+
}),
|
|
452
|
+
...(typeof args.xcrun === 'string' ? { xcrunPath: args.xcrun } : {}),
|
|
453
|
+
})
|
|
454
|
+
: null;
|
|
455
|
+
if (simctlCapture && simctlCapture.health.healthStatus !== 'passed') {
|
|
456
|
+
throw new Error(`iOS simctl capture failed; inspect ${simctlCapture.runDir}/agent-summary.md.${summarizeFailedIosChecks(simctlCapture.health)}`);
|
|
457
|
+
}
|
|
458
|
+
const agentDeviceCapture = isEnabled(args['agent-device-capture'])
|
|
459
|
+
? await runAgentDeviceCapture({
|
|
460
|
+
...(typeof args['agent-device'] === 'string' ? { agentDevicePath: args['agent-device'] } : {}),
|
|
461
|
+
app: typeof args['agent-device-app'] === 'string'
|
|
462
|
+
? args['agent-device-app']
|
|
463
|
+
: resolveIosBundleId({ args, config }),
|
|
464
|
+
...(options.agentDeviceExecutor ? { executor: options.agentDeviceExecutor } : {}),
|
|
465
|
+
...(typeof args['agent-device-device'] === 'string' ? { device: args['agent-device-device'] } : {}),
|
|
466
|
+
...(typeof args['agent-device-session'] === 'string' ? { session: args['agent-device-session'] } : {}),
|
|
467
|
+
...(typeof args['agent-device-session-mode'] === 'string'
|
|
468
|
+
? { sessionMode: args['agent-device-session-mode'] }
|
|
469
|
+
: {}),
|
|
470
|
+
...(typeof args.device === 'string' ? { udid: args.device } : {}),
|
|
471
|
+
open: isEnabled(args['agent-device-open']),
|
|
472
|
+
outputDir: resolveAgentDeviceCaptureOutputDir({ args, runId }),
|
|
473
|
+
platform: 'ios',
|
|
474
|
+
runId,
|
|
475
|
+
scenario,
|
|
476
|
+
waitMs: readPositiveInteger(readScalarArg(args['agent-device-wait-ms']), 0),
|
|
477
|
+
})
|
|
478
|
+
: null;
|
|
479
|
+
if (agentDeviceCapture && agentDeviceCapture.health.healthStatus !== 'passed') {
|
|
480
|
+
throw new Error(`agent-device capture failed; inspect ${agentDeviceCapture.runDir}/agent-summary.md.${summarizeFailedAgentDeviceChecks(agentDeviceCapture.health)}`);
|
|
481
|
+
}
|
|
482
|
+
const baseProfileArgs = {
|
|
483
|
+
...args,
|
|
484
|
+
'run-id': runId,
|
|
485
|
+
...(simctlCapture ? { 'simctl-artifacts': simctlCapture.runDir } : {}),
|
|
486
|
+
...(simctlCapture?.captures.screenshot
|
|
487
|
+
? { capture: appendCaptureArg({
|
|
488
|
+
args,
|
|
489
|
+
value: `screenshot:${path.join(simctlCapture.runDir, simctlCapture.captures.screenshot)}`,
|
|
490
|
+
}) }
|
|
491
|
+
: {}),
|
|
492
|
+
};
|
|
493
|
+
if (simctlCapture) {
|
|
494
|
+
delete baseProfileArgs.events;
|
|
495
|
+
}
|
|
496
|
+
const profileArgs = agentDeviceCapture
|
|
497
|
+
? appendAgentDeviceCaptureArgs({ args: baseProfileArgs, capture: agentDeviceCapture })
|
|
498
|
+
: baseProfileArgs;
|
|
499
|
+
return runProfileMobile(profileArgs, {
|
|
500
|
+
...(options.comparisonLane ? { comparisonLane: options.comparisonLane } : {}),
|
|
501
|
+
defaultDriver: 'ios-simctl',
|
|
502
|
+
interactionDriver: agentDeviceCapture ? 'agent-device' : 'ios-simctl',
|
|
503
|
+
platform: 'ios',
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Runs the profile-ios CLI.
|
|
508
|
+
*
|
|
509
|
+
* @returns {Promise<void>}
|
|
510
|
+
*/
|
|
511
|
+
async function main() {
|
|
512
|
+
const argv = process.argv.slice(2);
|
|
513
|
+
if (hasHelpFlag(argv)) {
|
|
514
|
+
usage({ binaryName: 'asl-profile-ios', output: process.stdout, platform: 'ios' });
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
loadAslLocalEnv();
|
|
518
|
+
const args = parseArgs(argv);
|
|
519
|
+
if (typeof args.config !== 'string' || typeof args.scenario !== 'string') {
|
|
520
|
+
usage({ binaryName: 'asl-profile-ios', platform: 'ios' });
|
|
521
|
+
process.exitCode = 1;
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const result = await runProfileIos(args);
|
|
525
|
+
process.stdout.write(`${result.runDir}\n`);
|
|
526
|
+
}
|
|
527
|
+
if (require.main === module) {
|
|
528
|
+
main().catch((error) => {
|
|
529
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
530
|
+
process.exitCode = 1;
|
|
531
|
+
});
|
|
532
|
+
}
|