Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"src/metro.ts",
"src/remote-config.ts",
"src/install-source.ts",
"src/android-apps.ts",
"src/android-adb.ts",
"src/android-snapshot-helper.ts",
"src/contracts.ts",
Expand All @@ -17,7 +16,6 @@
"src/bin.ts",
"src/companion-tunnel.ts",
"src/daemon.ts",
"src/daemon-embedding.ts",
"src/utils/update-check-entry.ts",
"test/scripts/metro-prepare-packaged-smoke.mjs",
"test/integration/*.test.ts",
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## 0.15.0

- Breaking: removed the `agent-device/android-apps` public subpath. Use the Android app helpers from `agent-device/android-adb`.
- Breaking: removed the `agent-device/daemon` public subpath. Use `agent-device/contracts` for daemon request/response types.
- Breaking: removed public local ADB bypass/selection helpers such as `spawnAndroidAdbBySerial` and `resolveAndroidAdbProvider`; use `createLocalAndroidAdbProvider(device)` or pass providers directly to the helpers from `agent-device/android-adb`.
- Added Android ADB provider helpers for exec, stream, clipboard, keyboard, app lifecycle, logcat, and port reverse workflows.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Snapshots assign refs like `@e1`, `@e2`, and `@e3` to current-screen elements. R

`agent-device` runs session-aware commands through platform backends: XCTest for iOS and tvOS, ADB plus the Android snapshot helper for Android, a local helper for macOS desktop automation, and AT-SPI for Linux desktop targets. See [Introduction](https://incubator.callstack.com/agent-device/docs/introduction) and [Commands](https://incubator.callstack.com/agent-device/docs/commands) for platform details.

Node consumers can use the typed client and public subpaths for bridge integrations. `agent-device/android-adb` exposes the Android ADB provider contract and reusable helpers for ADB-backed app listing and foreground state. `agent-device/daemon` exposes the supported daemon embedding surface for integrations that intentionally reuse the upstream request router.
Node consumers can use the typed client and public subpaths for bridge integrations. `agent-device/android-adb` exposes the Android ADB provider contract, logcat/clipboard/keyboard/app helpers, and port reverse management.

## Used By

Expand Down
10 changes: 1 addition & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agent-device",
"version": "0.14.3",
"version": "0.15.0",
"description": "Agent-driven CLI for mobile UI automation, network inspection, and performance diagnostics across iOS, Android, tvOS, and macOS.",
"license": "MIT",
"author": "Callstack",
Expand Down Expand Up @@ -45,10 +45,6 @@
"import": "./dist/src/install-source.js",
"types": "./dist/src/install-source.d.ts"
},
"./android-apps": {
"import": "./dist/src/android-apps.js",
"types": "./dist/src/android-apps.d.ts"
},
"./android-adb": {
"import": "./dist/src/android-adb.js",
"types": "./dist/src/android-adb.d.ts"
Expand All @@ -57,10 +53,6 @@
"import": "./dist/src/android-snapshot-helper.js",
"types": "./dist/src/android-snapshot-helper.d.ts"
},
"./daemon": {
"import": "./dist/src/daemon-embedding.js",
"types": "./dist/src/daemon-embedding.d.ts"
},
"./contracts": {
"import": "./dist/src/contracts.js",
"types": "./dist/src/contracts.d.ts"
Expand Down
2 changes: 0 additions & 2 deletions rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@ export default defineConfig({
metro: 'src/metro.ts',
'remote-config': 'src/remote-config.ts',
'install-source': 'src/install-source.ts',
'android-apps': 'src/android-apps.ts',
'android-adb': 'src/android-adb.ts',
'android-snapshot-helper': 'src/android-snapshot-helper.ts',
'daemon-embedding': 'src/daemon-embedding.ts',
contracts: 'src/contracts.ts',
selectors: 'src/selectors.ts',
finders: 'src/finders.ts',
Expand Down
22 changes: 22 additions & 0 deletions src/__tests__/android-adb-public.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';

test('public android-adb entrypoint exposes helpers but not resolver internals', async () => {
const androidAdb = await import('../android-adb.ts');

assert.equal(typeof androidAdb.createLocalAndroidAdbProvider, 'function');
assert.equal(typeof androidAdb.createAndroidPortReverseManager, 'function');
assert.equal(typeof androidAdb.captureAndroidLogcatWithAdb, 'function');
assert.equal(typeof androidAdb.streamAndroidLogcatWithAdb, 'function');
assert.equal(typeof androidAdb.listAndroidAppsWithAdb, 'function');
assert.equal(typeof androidAdb.getAndroidAppStateWithAdb, 'function');
assert.equal(typeof androidAdb.readAndroidClipboardWithAdb, 'function');
assert.equal(typeof androidAdb.dismissAndroidKeyboardWithAdb, 'function');
assert.equal(typeof androidAdb.openAndroidAppWithAdb, 'function');

assert.equal('resolveAndroidAdbProvider' in androidAdb, false);
assert.equal('resolveAndroidAdbExecutor' in androidAdb, false);
assert.equal('createDeviceAdbExecutor' in androidAdb, false);
assert.equal('withAndroidAdbProvider' in androidAdb, false);
assert.equal('spawnAndroidAdbBySerial' in androidAdb, false);
});
73 changes: 0 additions & 73 deletions src/__tests__/android-apps-public.test.ts

This file was deleted.

15 changes: 15 additions & 0 deletions src/__tests__/package-exports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import fs from 'node:fs';
import path from 'node:path';
import { test } from 'vitest';
import assert from 'node:assert/strict';

test('package exports only supported public subpaths', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')) as {
exports: Record<string, unknown>;
};

assert.equal(pkg.exports['./android-apps'], undefined);
assert.equal(pkg.exports['./daemon'], undefined);
assert.equal(pkg.exports['./android-adb'] !== undefined, true);
assert.equal(pkg.exports['./contracts'] !== undefined, true);
});
30 changes: 26 additions & 4 deletions src/android-adb.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
export {
createDeviceAdbExecutor,
resolveAndroidAdbExecutor,
spawnAndroidAdbBySerial,
withAndroidAdbProvider,
createAndroidPortReverseManager,
createLocalAndroidAdbProvider,
type AndroidAdbExecutor,
type AndroidAdbExecutorOptions,
type AndroidAdbProcess,
type AndroidAdbExecutorResult,
type AndroidAdbProvider,
type AndroidAdbSpawner,
type AndroidPortReverseEndpoint,
type AndroidPortReverseMapping,
type AndroidPortReverseOptions,
type AndroidPortReverseProvider,
} from './platforms/android/adb-executor.ts';
export {
getAndroidAppStateWithAdb,
listAndroidAppsWithAdb,
} from './platforms/android/app-helpers.ts';
export {
forceStopAndroidAppWithAdb,
openAndroidAppWithAdb,
resolveAndroidLaunchComponentWithAdb,
type AndroidOpenAppWithAdbOptions,
} from './platforms/android/app-control.ts';
export {
captureAndroidLogcatWithAdb,
streamAndroidLogcatWithAdb,
type AndroidLogcatCaptureOptions,
type AndroidLogcatStreamOptions,
} from './platforms/android/logcat.ts';
export {
dismissAndroidKeyboardWithAdb,
getAndroidKeyboardStatusWithAdb,
readAndroidClipboardWithAdb,
writeAndroidClipboardWithAdb,
type AndroidKeyboardState,
} from './platforms/android/device-input-state.ts';
export type {
AndroidAppListFilter,
AndroidAppListOptions,
Expand Down
6 changes: 0 additions & 6 deletions src/android-apps.ts

This file was deleted.

38 changes: 0 additions & 38 deletions src/daemon-embedding.ts

This file was deleted.

45 changes: 39 additions & 6 deletions src/daemon/__tests__/app-log-android.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import path from 'node:path';
import { PassThrough } from 'node:stream';

vi.mock('node:child_process', () => ({ spawn: vi.fn() }));
vi.mock('../../utils/exec.ts', () => ({
runCmd: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
}));
vi.mock('../../utils/exec.ts', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../utils/exec.ts')>();
return {
...actual,
runCmd: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
};
});
vi.mock('../app-log-stream.ts', async (importOriginal) => {
const actual = await importOriginal<typeof import('../app-log-stream.ts')>();
return { ...actual, sleep: vi.fn(async () => {}) };
Expand All @@ -25,16 +29,18 @@ const mockRunCmd = vi.mocked(runCmd);
type MockChild = EventEmitter & {
stdout: PassThrough;
stderr: PassThrough;
pid: number;
pid?: number;
killed: boolean;
kill: (signal?: NodeJS.Signals) => boolean;
};

function makeMockChild(pid: number): MockChild {
function makeMockChild(pid?: number): MockChild {
const child = new EventEmitter() as MockChild;
child.stdout = new PassThrough();
child.stderr = new PassThrough();
child.pid = pid;
if (pid !== undefined) {
child.pid = pid;
}
child.killed = false;
child.kill = () => {
if (child.killed) return false;
Expand Down Expand Up @@ -91,6 +97,33 @@ test('startAndroidAppLog returns to active state after a successful reattach', a
await appLog.wait;
});

test('startAndroidAppLog reports active for provider streams without host pid', async () => {
const logDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-android-log-'));
const stream = fs.createWriteStream(path.join(logDir, 'app.log'));
const child = makeMockChild();

mockRunCmd.mockReset();
mockRunCmd.mockImplementation(async (_cmd, args) => {
if (args.join(' ') === '-s emulator-5554 shell pidof com.example.app') {
return { stdout: '111\n', stderr: '', exitCode: 0 };
}
return { stdout: '', stderr: '', exitCode: 0 };
});

mockSpawn.mockReset();
mockSpawn.mockImplementation(() => child as unknown as ReturnType<typeof spawn>);

const appLog = await startAndroidAppLog('emulator-5554', 'com.example.app', stream, []);
await vi.waitFor(() => {
expect(mockSpawn).toHaveBeenCalledTimes(1);
});

assert.equal(appLog.getState(), 'active');

await appLog.stop();
await appLog.wait;
});

test('readRecentAndroidLogcatForPackage keeps lines for package-associated prior pids', async () => {
mockRunCmd.mockReset();
mockRunCmd.mockImplementation(async (_cmd, args) => {
Expand Down
Loading
Loading