mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
fix: address camera video PR feedback
This commit is contained in:
@@ -80,6 +80,35 @@ test('apps.open resolves session device identifiers from open response', async (
|
||||
assert.equal(result.device?.ios?.simulatorSetPath, '/tmp/sim-set');
|
||||
});
|
||||
|
||||
test('devices.boot forwards Android emulator camera options', async () => {
|
||||
const setup = createTransport(async () => ({
|
||||
ok: true,
|
||||
data: {
|
||||
platform: 'android',
|
||||
target: 'mobile',
|
||||
device: 'Pixel_9_Pro_XL',
|
||||
id: 'emulator-5554',
|
||||
kind: 'emulator',
|
||||
booted: true,
|
||||
},
|
||||
}));
|
||||
const client = createAgentDeviceClient(setup.config, { transport: setup.transport });
|
||||
|
||||
await client.devices.boot({
|
||||
platform: 'android',
|
||||
device: 'Pixel_9_Pro_XL',
|
||||
cameraFront: '/tmp/front.mp4',
|
||||
cameraBack: 'virtualscene',
|
||||
});
|
||||
|
||||
assert.equal(setup.calls.length, 1);
|
||||
assert.equal(setup.calls[0]?.command, 'boot');
|
||||
assert.equal(setup.calls[0]?.flags?.platform, 'android');
|
||||
assert.equal(setup.calls[0]?.flags?.device, 'Pixel_9_Pro_XL');
|
||||
assert.equal(setup.calls[0]?.flags?.cameraFront, '/tmp/front.mp4');
|
||||
assert.equal(setup.calls[0]?.flags?.cameraBack, 'virtualscene');
|
||||
});
|
||||
|
||||
test('apps.open forwards explicit runtime hints through the daemon request', async () => {
|
||||
const setup = createTransport(async () => ({
|
||||
ok: true,
|
||||
|
||||
@@ -311,6 +311,8 @@ export function buildFlags(options: InternalRequestOptions): CommandFlags {
|
||||
pauseMs: options.pauseMs,
|
||||
pattern: options.pattern,
|
||||
headless: options.headless,
|
||||
cameraFront: options.cameraFront,
|
||||
cameraBack: options.cameraBack,
|
||||
restart: options.restart,
|
||||
replayUpdate: options.replayUpdate,
|
||||
replayBackend: options.replayBackend,
|
||||
|
||||
@@ -438,27 +438,9 @@ export async function ensureAndroidEmulatorBooted(params: {
|
||||
resolvedAvdName,
|
||||
params.serial,
|
||||
);
|
||||
if (existing && (params.cameraFront || params.cameraBack)) {
|
||||
throw new AppError(
|
||||
'INVALID_STATE',
|
||||
'Android emulator camera inputs can only be applied when starting an emulator.',
|
||||
{
|
||||
avdName: resolvedAvdName,
|
||||
serial: existing.id,
|
||||
hint: 'Shut down the emulator first, then run boot again with --camera-front or --camera-back.',
|
||||
},
|
||||
);
|
||||
}
|
||||
assertCameraInputsCanApplyToEmulator(existing, resolvedAvdName, params);
|
||||
if (!existing) {
|
||||
const launchArgs = ['-avd', resolvedAvdName];
|
||||
if (params.headless) {
|
||||
launchArgs.push('-no-window', '-no-audio');
|
||||
}
|
||||
const cameraFront = resolveAndroidEmulatorCameraMode(params.cameraFront, 'front');
|
||||
if (cameraFront) launchArgs.push('-camera-front', cameraFront);
|
||||
const cameraBack = resolveAndroidEmulatorCameraMode(params.cameraBack, 'back');
|
||||
if (cameraBack) launchArgs.push('-camera-back', cameraBack);
|
||||
runCmdDetached('emulator', launchArgs);
|
||||
runCmdDetached('emulator', buildEmulatorLaunchArgs(resolvedAvdName, params));
|
||||
}
|
||||
|
||||
const discovered =
|
||||
@@ -487,6 +469,45 @@ export async function ensureAndroidEmulatorBooted(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function assertCameraInputsCanApplyToEmulator(
|
||||
existing: DeviceInfo | undefined,
|
||||
resolvedAvdName: string,
|
||||
params: {
|
||||
cameraFront?: string;
|
||||
cameraBack?: string;
|
||||
},
|
||||
): void {
|
||||
if (!existing || (!params.cameraFront && !params.cameraBack)) return;
|
||||
throw new AppError(
|
||||
'INVALID_STATE',
|
||||
'Android emulator camera inputs can only be applied when starting an emulator.',
|
||||
{
|
||||
avdName: resolvedAvdName,
|
||||
serial: existing.id,
|
||||
hint: 'Shut down the emulator first, then run boot again with --camera-front or --camera-back.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function buildEmulatorLaunchArgs(
|
||||
resolvedAvdName: string,
|
||||
params: {
|
||||
headless?: boolean;
|
||||
cameraFront?: string;
|
||||
cameraBack?: string;
|
||||
},
|
||||
): string[] {
|
||||
const launchArgs = ['-avd', resolvedAvdName];
|
||||
if (params.headless) {
|
||||
launchArgs.push('-no-window', '-no-audio');
|
||||
}
|
||||
const cameraFront = resolveAndroidEmulatorCameraMode(params.cameraFront, 'front');
|
||||
if (cameraFront) launchArgs.push('-camera-front', cameraFront);
|
||||
const cameraBack = resolveAndroidEmulatorCameraMode(params.cameraBack, 'back');
|
||||
if (cameraBack) launchArgs.push('-camera-back', cameraBack);
|
||||
return launchArgs;
|
||||
}
|
||||
|
||||
function resolveAndroidEmulatorCameraMode(
|
||||
value: string | undefined,
|
||||
camera: 'front' | 'back',
|
||||
|
||||
@@ -257,6 +257,19 @@ Additional CLI-backed methods are exposed on their domain groups with typed opti
|
||||
- `client.recording.record()` and `client.recording.trace()`
|
||||
- `client.settings.update()`
|
||||
|
||||
`client.devices.boot({ platform: 'android', device: 'Pixel_9_Pro_XL', headless: true })` starts an Android emulator without a GUI when it is not already running. To launch with emulator camera inputs, pass `cameraFront` and/or `cameraBack` with `emulated`, `none`, `webcam<N>`, `virtualscene` for the back camera, or a video file path:
|
||||
|
||||
```ts
|
||||
await client.devices.boot({
|
||||
platform: 'android',
|
||||
device: 'Pixel_9_Pro_XL',
|
||||
cameraFront: './front.mp4',
|
||||
cameraBack: 'virtualscene',
|
||||
});
|
||||
```
|
||||
|
||||
Camera inputs are Android-emulator-only and apply only when starting the emulator; shut down a running emulator before changing them.
|
||||
|
||||
`client.observability.perf()` returns daemon-shaped JSON so local and remote transports expose the same metrics payload. Pass `{ area: 'metrics' }` for the broad startup/CPU/memory/frame first pass, or `{ area: 'frames' }` for a focused frame/jank-health payload. On Android and supported Apple targets, `data.metrics.fps.droppedFramePercent` is the primary frame-smoothness value. Android derives it from the current `adb shell dumpsys gfxinfo <package> framestats` window; connected iOS devices derive it from `xcrun xctrace` Animation Hitches for the active app process. Frame samples include `windowStartedAt`, `windowEndedAt`, and `worstWindows` so agents can correlate dropped-frame clusters with logs, network entries, and their own session actions. A successful Android read resets Android frame stats; `open <app>` resets the Android frame window too, so agents can call `perf({ area: 'frames' })`, perform a transition or gesture, then call it again to inspect that focused window. iOS simulator and macOS app sessions report frame health as unavailable rather than inventing FPS or dropped-frame values.
|
||||
|
||||
`client.recording.record({ action: 'start', path, quality: 5 })` starts a smaller 50% resolution video; omit `quality` to keep native/current resolution.
|
||||
|
||||
@@ -38,6 +38,7 @@ agent-device boot
|
||||
agent-device boot --platform ios
|
||||
agent-device boot --platform android
|
||||
agent-device boot --platform android --device Pixel_9_Pro_XL --headless
|
||||
agent-device boot --platform android --device Pixel_9_Pro_XL --camera-back ./back.mp4 --camera-front none
|
||||
agent-device shutdown --platform ios
|
||||
agent-device shutdown --platform android --device Pixel_9_Pro_XL
|
||||
agent-device open [app|url] [url]
|
||||
@@ -62,6 +63,8 @@ agent-device app-switcher
|
||||
- `boot` is mainly needed when starting a new session and `open` fails because no booted simulator/emulator is available.
|
||||
- Android: `boot --platform android --device <avd-name>` launches that emulator in GUI mode when needed.
|
||||
- Android: add `--headless` to launch without opening a GUI window.
|
||||
- Android: add `--camera-front <mode|videoPath>` and/or `--camera-back <mode|videoPath>` when launching an emulator with camera inputs. Supported modes are `emulated`, `none`, `webcam<N>`, and `virtualscene` for the back camera only. File paths are converted to `videofile:<absolute-path>` for the emulator.
|
||||
- Android camera inputs apply only when starting an emulator. Shut down an already-running emulator first, then run `boot` again with the camera flags.
|
||||
- Android: `shutdown --platform android --device <avd-name>` stops a running emulator.
|
||||
- `open [app|url] [url]` already boots/activates the selected target when needed.
|
||||
- `open <url>` deep links are supported on Android and iOS.
|
||||
|
||||
Reference in New Issue
Block a user