@react-native-harness/cli 1.0.0-alpha.15 → 1.0.0-alpha.18

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.
Files changed (43) hide show
  1. package/dist/commands/test.d.ts.map +1 -1
  2. package/dist/commands/test.js +4 -2
  3. package/dist/errors/errorHandler.d.ts +1 -1
  4. package/dist/errors/errorHandler.d.ts.map +1 -1
  5. package/dist/errors/errorHandler.js +101 -123
  6. package/dist/errors/errors.d.ts +2 -7
  7. package/dist/errors/errors.d.ts.map +1 -1
  8. package/dist/errors/errors.js +0 -17
  9. package/dist/external.d.ts +2 -0
  10. package/dist/external.d.ts.map +1 -0
  11. package/dist/external.js +1 -0
  12. package/dist/index.js +58 -49
  13. package/dist/jest.d.ts +2 -0
  14. package/dist/jest.d.ts.map +1 -0
  15. package/dist/jest.js +7 -0
  16. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  17. package/package.json +25 -8
  18. package/src/external.ts +0 -0
  19. package/src/index.ts +65 -72
  20. package/tsconfig.json +0 -3
  21. package/tsconfig.lib.json +1 -12
  22. package/src/bundlers/metro.ts +0 -102
  23. package/src/commands/test.ts +0 -228
  24. package/src/discovery/index.ts +0 -2
  25. package/src/discovery/testDiscovery.ts +0 -50
  26. package/src/errors/errorHandler.ts +0 -226
  27. package/src/errors/errors.ts +0 -131
  28. package/src/platforms/android/build.ts +0 -49
  29. package/src/platforms/android/device.ts +0 -48
  30. package/src/platforms/android/emulator.ts +0 -137
  31. package/src/platforms/android/index.ts +0 -87
  32. package/src/platforms/ios/build.ts +0 -71
  33. package/src/platforms/ios/device.ts +0 -79
  34. package/src/platforms/ios/index.ts +0 -66
  35. package/src/platforms/ios/simulator.ts +0 -171
  36. package/src/platforms/platform-adapter.ts +0 -11
  37. package/src/platforms/platform-registry.ts +0 -26
  38. package/src/platforms/vega/build.ts +0 -85
  39. package/src/platforms/vega/device.ts +0 -258
  40. package/src/platforms/vega/index.ts +0 -107
  41. package/src/platforms/web/index.ts +0 -16
  42. package/src/process.ts +0 -33
  43. package/src/utils.ts +0 -29
@@ -1,131 +0,0 @@
1
- import { TestRunnerConfig } from '@react-native-harness/config';
2
-
3
- export class NoRunnerSpecifiedError extends Error {
4
- constructor(availableRunners: TestRunnerConfig[]) {
5
- super('No runner specified');
6
- this.name = 'NoRunnerSpecifiedError';
7
- this.availableRunners = availableRunners;
8
- }
9
- availableRunners: TestRunnerConfig[];
10
- }
11
-
12
- export class RunnerNotFoundError extends Error {
13
- constructor(runnerName: string, availableRunners: TestRunnerConfig[]) {
14
- super(`Runner "${runnerName}" not found`);
15
- this.name = 'RunnerNotFoundError';
16
- this.runnerName = runnerName;
17
- this.availableRunners = availableRunners;
18
- }
19
- runnerName: string;
20
- availableRunners: TestRunnerConfig[];
21
- }
22
-
23
- export class EnvironmentInitializationError extends Error {
24
- constructor(
25
- message: string,
26
- runnerName: string,
27
- platform: string,
28
- details?: string
29
- ) {
30
- super(
31
- `Failed to initialize environment for "${runnerName}" (${platform}): ${message}`
32
- );
33
- this.name = 'EnvironmentInitializationError';
34
- this.runnerName = runnerName;
35
- this.platform = platform;
36
- this.details = details;
37
- }
38
- runnerName: string;
39
- platform: string;
40
- details?: string;
41
- }
42
-
43
- export class TestExecutionError extends Error {
44
- constructor(
45
- testFile: string,
46
- error: unknown,
47
- testSuite?: string,
48
- testName?: string
49
- ) {
50
- const errorMessage = error instanceof Error ? error.message : String(error);
51
- const suiteInfo = testSuite ? ` in suite "${testSuite}"` : '';
52
- const testInfo = testName ? ` test "${testName}"` : '';
53
-
54
- super(
55
- `Test execution failed for ${testFile}${suiteInfo}${testInfo}: ${errorMessage}`
56
- );
57
- this.name = 'TestExecutionError';
58
- this.testFile = testFile;
59
- this.testSuite = testSuite;
60
- this.testName = testName;
61
- this.originalError = error;
62
- }
63
- testFile: string;
64
- testSuite?: string;
65
- testName?: string;
66
- originalError: unknown;
67
- }
68
-
69
- export class RpcClientError extends Error {
70
- constructor(message: string, bridgePort?: number, connectionStatus?: string) {
71
- const portInfo = bridgePort ? ` (port ${bridgePort})` : '';
72
- const statusInfo = connectionStatus ? ` - Status: ${connectionStatus}` : '';
73
-
74
- super(`RPC client error${portInfo}: ${message}${statusInfo}`);
75
- this.name = 'RpcClientError';
76
- this.bridgePort = bridgePort;
77
- this.connectionStatus = connectionStatus;
78
- }
79
- bridgePort?: number;
80
- connectionStatus?: string;
81
- }
82
-
83
- export class BridgeTimeoutError extends Error {
84
- constructor(
85
- public readonly timeout: number,
86
- public readonly runnerName: string,
87
- public readonly platform: string
88
- ) {
89
- super(
90
- `Bridge connection timed out after ${timeout}ms while waiting for "${runnerName}" (${platform}) runner to be ready`
91
- );
92
- this.name = 'BridgeTimeoutError';
93
- }
94
- }
95
-
96
- export class AppNotInstalledError extends Error {
97
- constructor(
98
- public readonly deviceName: string,
99
- public readonly bundleId: string,
100
- public readonly platform: 'ios' | 'android' | 'vega'
101
- ) {
102
- const deviceType =
103
- platform === 'ios'
104
- ? 'simulator'
105
- : platform === 'android'
106
- ? 'emulator'
107
- : 'virtual device';
108
-
109
- super(
110
- `App "${bundleId}" is not installed on ${deviceType} "${deviceName}"`
111
- );
112
- this.name = 'AppNotInstalledError';
113
- }
114
- }
115
-
116
- export class BundlingFailedError extends Error {
117
- constructor(
118
- public readonly modulePath: string,
119
- public readonly reason: string
120
- ) {
121
- super(`Bundling of ${modulePath} failed: ${reason}`);
122
- this.name = 'BundlingFailedError';
123
- }
124
- }
125
-
126
- export class MetroPortUnavailableError extends Error {
127
- constructor(public readonly port: number) {
128
- super(`Metro port ${port} is not available`);
129
- this.name = 'MetroPortUnavailableError';
130
- }
131
- }
@@ -1,49 +0,0 @@
1
- import path from 'node:path';
2
- import { spawn } from '@react-native-harness/tools';
3
-
4
- export const buildAndroidApp = async (): Promise<void> => {
5
- await spawn('react-native', ['build-android', '--tasks', 'assembleDebug']);
6
- };
7
-
8
- export const installApp = async (deviceId: string): Promise<void> => {
9
- await spawn('adb', [
10
- '-s',
11
- deviceId,
12
- 'install',
13
- '-r',
14
- path.join(
15
- process.cwd(),
16
- 'android',
17
- 'app',
18
- 'build',
19
- 'outputs',
20
- 'apk',
21
- 'debug',
22
- 'app-debug.apk'
23
- ),
24
- ]);
25
- };
26
-
27
- export const killApp = async (
28
- deviceId: string,
29
- bundleId: string
30
- ): Promise<void> => {
31
- await spawn('adb', ['-s', deviceId, 'shell', 'am', 'force-stop', bundleId]);
32
- };
33
-
34
- export const runApp = async (
35
- deviceId: string,
36
- bundleId: string,
37
- activityName: string
38
- ): Promise<void> => {
39
- await killApp(deviceId, bundleId);
40
- await spawn('adb', [
41
- '-s',
42
- deviceId,
43
- 'shell',
44
- 'am',
45
- 'start',
46
- '-n',
47
- `${bundleId}/${activityName}`,
48
- ]);
49
- };
@@ -1,48 +0,0 @@
1
- import { spawn } from '@react-native-harness/tools';
2
-
3
- export const killApp = async (
4
- deviceId: string,
5
- bundleId: string
6
- ): Promise<void> => {
7
- await spawn('adb', ['-s', deviceId, 'shell', 'am', 'force-stop', bundleId]);
8
- };
9
-
10
- export const runApp = async (
11
- deviceId: string,
12
- bundleId: string
13
- ): Promise<void> => {
14
- await killApp(deviceId, bundleId);
15
- await spawn('adb', [
16
- '-s',
17
- deviceId,
18
- 'shell',
19
- 'am',
20
- 'start',
21
- '-n',
22
- `${bundleId}/.MainActivity`,
23
- ]);
24
- };
25
-
26
- export const isAppInstalled = async (
27
- deviceId: string,
28
- bundleId: string
29
- ): Promise<boolean> => {
30
- try {
31
- const { stdout } = await spawn('adb', [
32
- '-s',
33
- deviceId,
34
- 'shell',
35
- 'pm',
36
- 'list',
37
- 'packages',
38
- bundleId,
39
- ]);
40
- return stdout.trim() !== '';
41
- } catch {
42
- return false;
43
- }
44
- };
45
-
46
- export const reversePort = async (port: number): Promise<void> => {
47
- await spawn('adb', ['reverse', `tcp:${port}`, `tcp:${port}`]);
48
- };
@@ -1,137 +0,0 @@
1
- import { spawn } from '@react-native-harness/tools';
2
- import { ChildProcess } from 'node:child_process';
3
-
4
- export type AndroidEmulatorStatus = 'running' | 'loading' | 'stopped';
5
-
6
- export const getEmulatorNameFromId = async (
7
- emulatorId: string
8
- ): Promise<string | null> => {
9
- try {
10
- const { stdout } = await spawn('adb', [
11
- '-s',
12
- emulatorId,
13
- 'emu',
14
- 'avd',
15
- 'name',
16
- ]);
17
- const avdName = stdout.split('\n')[0].trim();
18
- return avdName || null;
19
- } catch {
20
- return null;
21
- }
22
- };
23
-
24
- export const getEmulatorDeviceId = async (
25
- avdName: string
26
- ): Promise<string | null> => {
27
- try {
28
- const { stdout } = await spawn('adb', ['devices']);
29
- const lines = stdout.split('\n');
30
-
31
- for (const line of lines) {
32
- const parts = line.trim().split('\t');
33
- if (parts.length === 2 && parts[0].startsWith('emulator-')) {
34
- const emulatorId = parts[0].trim();
35
- const name = await getEmulatorNameFromId(emulatorId);
36
- if (name === avdName) {
37
- return emulatorId;
38
- }
39
- }
40
- }
41
- return null;
42
- } catch {
43
- return null;
44
- }
45
- };
46
-
47
- export const getEmulatorStatus = async (
48
- avdName: string
49
- ): Promise<AndroidEmulatorStatus> => {
50
- const emulatorId = await getEmulatorDeviceId(avdName);
51
- if (!emulatorId) {
52
- return 'stopped';
53
- }
54
-
55
- try {
56
- // Check if device is fully booted by checking boot completion
57
- const { stdout } = await spawn('adb', [
58
- '-s',
59
- emulatorId,
60
- 'shell',
61
- 'getprop',
62
- 'sys.boot_completed',
63
- ]);
64
- const bootCompleted = stdout.trim() === '1';
65
- return bootCompleted ? 'running' : 'loading';
66
- } catch {
67
- return 'loading';
68
- }
69
- };
70
-
71
- export const runEmulator = async (name: string): Promise<ChildProcess> => {
72
- // Start the emulator
73
- const process = spawn('emulator', ['-avd', name]);
74
- const nodeChildProcess = await process.nodeChildProcess;
75
-
76
- // Poll for emulator status until it's fully running
77
- const checkStatus = async (): Promise<void> => {
78
- const status = await getEmulatorStatus(name);
79
-
80
- if (status === 'running') {
81
- return;
82
- } else if (status === 'loading') {
83
- // Check again in 2 seconds
84
- await new Promise((resolve) => setTimeout(resolve, 2000));
85
- await checkStatus();
86
- } else {
87
- // Still stopped, check again in 1 second
88
- await new Promise((resolve) => setTimeout(resolve, 1000));
89
- await checkStatus();
90
- }
91
- };
92
-
93
- // Start checking status after a brief delay to allow emulator to start
94
- await new Promise((resolve) => setTimeout(resolve, 3000));
95
- await checkStatus();
96
-
97
- return nodeChildProcess;
98
- };
99
-
100
- export const stopEmulator = async (avdName: string): Promise<void> => {
101
- // First, get the emulator device ID
102
- const emulatorId = await getEmulatorDeviceId(avdName);
103
- if (!emulatorId) {
104
- return; // No emulator running, nothing to stop
105
- }
106
-
107
- await stopEmulatorById(emulatorId);
108
- };
109
-
110
- const stopEmulatorById = async (emulatorId: string): Promise<void> => {
111
- // Stop the emulator using the found ID
112
- await spawn('adb', ['-s', emulatorId, 'emu', 'kill']);
113
- };
114
-
115
- export const isAppInstalled = async (
116
- emulatorId: string,
117
- bundleId: string
118
- ): Promise<boolean> => {
119
- try {
120
- const { stdout } = await spawn('adb', [
121
- '-s',
122
- emulatorId,
123
- 'shell',
124
- 'pm',
125
- 'list',
126
- 'packages',
127
- bundleId,
128
- ]);
129
- return stdout.trim() !== '';
130
- } catch {
131
- return false;
132
- }
133
- };
134
-
135
- export const reversePort = async (port: number): Promise<void> => {
136
- await spawn('adb', ['reverse', `tcp:${port}`, `tcp:${port}`]);
137
- };
@@ -1,87 +0,0 @@
1
- import { type ChildProcess } from 'node:child_process';
2
- import {
3
- assertAndroidRunnerConfig,
4
- TestRunnerConfig,
5
- } from '@react-native-harness/config';
6
- import { logger } from '@react-native-harness/tools';
7
-
8
- import { type PlatformAdapter } from '../platform-adapter.js';
9
- import {
10
- runEmulator,
11
- getEmulatorDeviceId,
12
- reversePort,
13
- isAppInstalled,
14
- getEmulatorStatus,
15
- } from './emulator.js';
16
- import { runApp, killApp } from './build.js';
17
- import { killWithAwait } from '../../process.js';
18
- import { runMetro } from '../../bundlers/metro.js';
19
- import { AppNotInstalledError } from '../../errors/errors.js';
20
-
21
- const androidPlatformAdapter: PlatformAdapter = {
22
- name: 'android',
23
- getEnvironment: async (runner: TestRunnerConfig) => {
24
- assertAndroidRunnerConfig(runner);
25
-
26
- let emulator: ChildProcess | null = null;
27
- const emulatorStatus = await getEmulatorStatus(runner.deviceId);
28
- logger.debug(`Emulator status: ${emulatorStatus}`);
29
-
30
- const metroPromise = runMetro();
31
-
32
- if (emulatorStatus === 'stopped') {
33
- logger.debug(`Emulator ${runner.deviceId} is stopped, starting it`);
34
- emulator = await runEmulator(runner.deviceId);
35
- }
36
-
37
- const deviceId = await getEmulatorDeviceId(runner.deviceId);
38
- logger.debug(`Device ID: ${deviceId}`);
39
-
40
- if (!deviceId) {
41
- throw new Error('Emulator not found');
42
- }
43
-
44
- await Promise.all([
45
- reversePort(8081),
46
- reversePort(8080),
47
- reversePort(3001),
48
- ]);
49
- logger.debug('Ports reversed');
50
-
51
- const isInstalled = await isAppInstalled(deviceId, runner.bundleId);
52
- logger.debug(`App is installed: ${isInstalled}`);
53
-
54
- if (!isInstalled) {
55
- throw new AppNotInstalledError(
56
- runner.deviceId,
57
- runner.bundleId,
58
- 'android'
59
- );
60
- }
61
-
62
- logger.debug('Waiting for Metro to start');
63
- const metro = await metroPromise;
64
- logger.debug('Metro started');
65
-
66
- logger.debug('Running app');
67
- await runApp(deviceId, runner.bundleId, runner.activityName);
68
- logger.debug('App running');
69
-
70
- return {
71
- restart: async () => {
72
- await runApp(deviceId, runner.bundleId, runner.activityName);
73
- },
74
- dispose: async () => {
75
- await killApp(deviceId, runner.bundleId);
76
-
77
- if (emulator) {
78
- await killWithAwait(emulator);
79
- }
80
-
81
- await killWithAwait(metro);
82
- },
83
- };
84
- },
85
- };
86
-
87
- export default androidPlatformAdapter;
@@ -1,71 +0,0 @@
1
- import { spawn, spawnAndForget } from '@react-native-harness/tools';
2
-
3
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
- export const listDevices = async (): Promise<any> => {
5
- const { stdout } = await spawn('xcrun', [
6
- 'simctl',
7
- 'list',
8
- 'devices',
9
- '--json',
10
- ]);
11
- return JSON.parse(stdout);
12
- };
13
-
14
- export const getDeviceByName = async (
15
- simulatorName: string,
16
- systemVersion: string
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- ): Promise<any | null> => {
19
- const devices = await listDevices();
20
- const expectedRuntimeId = `com.apple.CoreSimulator.SimRuntime.iOS-${systemVersion.replace(
21
- /\./,
22
- '-'
23
- )}`;
24
-
25
- const runtime = devices.devices[expectedRuntimeId];
26
-
27
- if (!runtime) {
28
- return null;
29
- }
30
-
31
- const runtimeDevices = devices.devices[runtime];
32
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
- const device = runtimeDevices.find((d: any) => d.name === simulatorName);
34
-
35
- if (device) {
36
- return device;
37
- }
38
-
39
- return null;
40
- };
41
-
42
- export const listApps = async (udid: string): Promise<string[]> => {
43
- const { stdout: plistOutput } = await spawn('xcrun', [
44
- 'simctl',
45
- 'listapps',
46
- udid,
47
- ]);
48
- const { stdout: jsonOutput } = await spawn(
49
- 'plutil',
50
- ['-convert', 'json', '-o', '-', '-'],
51
- { stdin: { string: plistOutput } }
52
- );
53
- return Object.keys(JSON.parse(jsonOutput));
54
- };
55
-
56
- export const isAppInstalled = async (
57
- udid: string,
58
- bundleId: string
59
- ): Promise<boolean> => {
60
- const appList = await listApps(udid);
61
- return appList.includes(bundleId);
62
- };
63
-
64
- export const runApp = async (udid: string, appName: string): Promise<void> => {
65
- await killApp(udid, appName);
66
- await spawn('xcrun', ['simctl', 'launch', udid, appName]);
67
- };
68
-
69
- export const killApp = async (udid: string, appName: string): Promise<void> => {
70
- await spawnAndForget('xcrun', ['simctl', 'terminate', udid, appName]);
71
- };
@@ -1,79 +0,0 @@
1
- import { spawn, spawnAndForget } from '@react-native-harness/tools';
2
-
3
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
- type Device = any;
5
-
6
- export const listDevices = async (): Promise<{ devices: Device[] }> => {
7
- const { stdout } = await spawn('xcrun', [
8
- 'simctl',
9
- 'list',
10
- 'devices',
11
- '--json',
12
- ]);
13
- return JSON.parse(stdout);
14
- };
15
-
16
- export const getDeviceByName = async (
17
- simulatorName: string
18
- ): Promise<Device | null> => {
19
- const devices = await listDevices();
20
-
21
- for (const runtime in devices.devices) {
22
- const runtimeDevices = devices.devices[runtime];
23
- for (const device of runtimeDevices) {
24
- if (device.name === simulatorName && device.isAvailable) {
25
- return device;
26
- }
27
- }
28
- }
29
-
30
- return null;
31
- };
32
-
33
- export const listApps = async (udid: string): Promise<string[]> => {
34
- const { stdout: plistOutput } = await spawn('xcrun', [
35
- 'simctl',
36
- 'listapps',
37
- udid,
38
- ]);
39
- const { stdout: jsonOutput } = await spawn(
40
- 'plutil',
41
- ['-convert', 'json', '-o', '-', '-'],
42
- { stdin: { string: plistOutput } }
43
- );
44
- return Object.keys(JSON.parse(jsonOutput));
45
- };
46
-
47
- export const isAppInstalled = async (
48
- simulatorName: string,
49
- bundleId: string
50
- ): Promise<boolean> => {
51
- const device = await getDeviceByName(simulatorName);
52
-
53
- if (!device) {
54
- throw new Error(`Simulator ${simulatorName} not found`);
55
- }
56
-
57
- const appList = await listApps(device.udid);
58
- return appList.includes(bundleId);
59
- };
60
-
61
- export const runApp = async (
62
- simulatorName: string,
63
- appName: string
64
- ): Promise<void> => {
65
- await killApp(simulatorName, appName);
66
- await spawn('xcrun', ['simctl', 'launch', simulatorName, appName]);
67
- };
68
-
69
- export const killApp = async (
70
- simulatorName: string,
71
- appName: string
72
- ): Promise<void> => {
73
- await spawnAndForget('xcrun', [
74
- 'simctl',
75
- 'terminate',
76
- simulatorName,
77
- appName,
78
- ]);
79
- };
@@ -1,66 +0,0 @@
1
- import {
2
- assertIOSRunnerConfig,
3
- TestRunnerConfig,
4
- } from '@react-native-harness/config';
5
- import { type PlatformAdapter } from '../platform-adapter.js';
6
- import {
7
- getSimulatorDeviceId,
8
- getSimulatorStatus,
9
- runSimulator,
10
- stopSimulator,
11
- } from './simulator.js';
12
- import { isAppInstalled, runApp, killApp } from './build.js';
13
- import { killWithAwait } from '../../process.js';
14
- import { runMetro } from '../../bundlers/metro.js';
15
- import { AppNotInstalledError } from '../../errors/errors.js';
16
- import { assert } from '../../utils.js';
17
-
18
- const iosPlatformAdapter: PlatformAdapter = {
19
- name: 'ios',
20
- getEnvironment: async (runner: TestRunnerConfig) => {
21
- assertIOSRunnerConfig(runner);
22
- // TODO: system version is also important as there may be two emulators with the same name
23
- // but different system versions
24
-
25
- let shouldStopSimulator = false;
26
- const udid = await getSimulatorDeviceId(
27
- runner.deviceId,
28
- runner.systemVersion
29
- );
30
-
31
- assert(!!udid, 'Simulator not found');
32
-
33
- const simulatorStatus = await getSimulatorStatus(udid);
34
- const metroPromise = runMetro();
35
-
36
- if (simulatorStatus === 'stopped') {
37
- await runSimulator(udid);
38
- shouldStopSimulator = true;
39
- }
40
-
41
- const isInstalled = await isAppInstalled(udid, runner.bundleId);
42
-
43
- if (!isInstalled) {
44
- throw new AppNotInstalledError(runner.deviceId, runner.bundleId, 'ios');
45
- }
46
-
47
- const metro = await metroPromise;
48
- await runApp(udid, runner.bundleId);
49
-
50
- return {
51
- restart: async () => {
52
- await runApp(udid, runner.bundleId);
53
- },
54
- dispose: async () => {
55
- await killApp(udid, runner.bundleId);
56
- if (shouldStopSimulator) {
57
- await stopSimulator(udid);
58
- }
59
-
60
- await killWithAwait(metro);
61
- },
62
- };
63
- },
64
- };
65
-
66
- export default iosPlatformAdapter;