react-native-harness 1.0.0-alpha.1 → 1.0.0-alpha.11

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 (111) hide show
  1. package/README.md +2 -2
  2. package/bin.js +1 -1
  3. package/dist/babel.d.ts +2 -0
  4. package/dist/babel.d.ts.map +1 -0
  5. package/dist/babel.js +1 -0
  6. package/dist/index.d.ts +1 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -37
  9. package/dist/metro.d.ts +2 -0
  10. package/dist/metro.d.ts.map +1 -0
  11. package/dist/metro.js +1 -0
  12. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  13. package/eslint.config.mjs +1 -1
  14. package/package.json +31 -12
  15. package/src/babel.ts +1 -0
  16. package/src/index.ts +1 -52
  17. package/src/metro.ts +1 -0
  18. package/tsconfig.json +9 -3
  19. package/tsconfig.lib.json +8 -14
  20. package/types/index.d.ts +1 -0
  21. package/dist/bundlers/metro.d.ts +0 -5
  22. package/dist/bundlers/metro.d.ts.map +0 -1
  23. package/dist/bundlers/metro.js +0 -52
  24. package/dist/bundlers/webpack.d.ts +0 -2
  25. package/dist/bundlers/webpack.d.ts.map +0 -1
  26. package/dist/bundlers/webpack.js +0 -49
  27. package/dist/commands/test.d.ts +0 -2
  28. package/dist/commands/test.d.ts.map +0 -1
  29. package/dist/commands/test.js +0 -111
  30. package/dist/errors/appNotInstalledError.d.ts +0 -7
  31. package/dist/errors/appNotInstalledError.d.ts.map +0 -1
  32. package/dist/errors/appNotInstalledError.js +0 -12
  33. package/dist/errors/bridgeTimeoutError.d.ts +0 -7
  34. package/dist/errors/bridgeTimeoutError.d.ts.map +0 -1
  35. package/dist/errors/bridgeTimeoutError.js +0 -12
  36. package/dist/errors/errorHandler.d.ts +0 -2
  37. package/dist/errors/errorHandler.d.ts.map +0 -1
  38. package/dist/errors/errorHandler.js +0 -138
  39. package/dist/errors/errors.d.ts +0 -41
  40. package/dist/errors/errors.d.ts.map +0 -1
  41. package/dist/errors/errors.js +0 -83
  42. package/dist/platforms/android/build.d.ts +0 -5
  43. package/dist/platforms/android/build.d.ts.map +0 -1
  44. package/dist/platforms/android/build.js +0 -29
  45. package/dist/platforms/android/device.d.ts +0 -5
  46. package/dist/platforms/android/device.d.ts.map +0 -1
  47. package/dist/platforms/android/device.js +0 -36
  48. package/dist/platforms/android/emulator.d.ts +0 -11
  49. package/dist/platforms/android/emulator.d.ts.map +0 -1
  50. package/dist/platforms/android/emulator.js +0 -110
  51. package/dist/platforms/android/index.d.ts +0 -4
  52. package/dist/platforms/android/index.d.ts.map +0 -1
  53. package/dist/platforms/android/index.js +0 -56
  54. package/dist/platforms/ios/build.d.ts +0 -7
  55. package/dist/platforms/ios/build.d.ts.map +0 -1
  56. package/dist/platforms/ios/build.js +0 -51
  57. package/dist/platforms/ios/device.d.ts +0 -7
  58. package/dist/platforms/ios/device.d.ts.map +0 -1
  59. package/dist/platforms/ios/device.js +0 -51
  60. package/dist/platforms/ios/index.d.ts +0 -4
  61. package/dist/platforms/ios/index.d.ts.map +0 -1
  62. package/dist/platforms/ios/index.js +0 -38
  63. package/dist/platforms/ios/simulator.d.ts +0 -11
  64. package/dist/platforms/ios/simulator.d.ts.map +0 -1
  65. package/dist/platforms/ios/simulator.js +0 -133
  66. package/dist/platforms/platform-adapter.d.ts +0 -10
  67. package/dist/platforms/platform-adapter.d.ts.map +0 -1
  68. package/dist/platforms/platform-adapter.js +0 -1
  69. package/dist/platforms/platform-registry.d.ts +0 -3
  70. package/dist/platforms/platform-registry.d.ts.map +0 -1
  71. package/dist/platforms/platform-registry.js +0 -19
  72. package/dist/platforms/web/index.d.ts +0 -4
  73. package/dist/platforms/web/index.d.ts.map +0 -1
  74. package/dist/platforms/web/index.js +0 -73
  75. package/dist/process.d.ts +0 -3
  76. package/dist/process.d.ts.map +0 -1
  77. package/dist/process.js +0 -28
  78. package/dist/reporters/default-reporter.d.ts +0 -3
  79. package/dist/reporters/default-reporter.d.ts.map +0 -1
  80. package/dist/reporters/default-reporter.js +0 -116
  81. package/dist/reporters/junit-reporter.d.ts +0 -3
  82. package/dist/reporters/junit-reporter.d.ts.map +0 -1
  83. package/dist/reporters/junit-reporter.js +0 -119
  84. package/dist/reporters/live-reporter.d.ts +0 -20
  85. package/dist/reporters/live-reporter.d.ts.map +0 -1
  86. package/dist/reporters/live-reporter.js +0 -176
  87. package/dist/src/reporters/default-reporter.js +0 -135
  88. package/dist/test-reporter-demo.js +0 -95
  89. package/dist/utils.d.ts +0 -5
  90. package/dist/utils.d.ts.map +0 -1
  91. package/dist/utils.js +0 -11
  92. package/src/bundlers/metro.ts +0 -79
  93. package/src/commands/test.ts +0 -189
  94. package/src/errors/errorHandler.ts +0 -184
  95. package/src/errors/errors.ts +0 -109
  96. package/src/platforms/android/build.ts +0 -48
  97. package/src/platforms/android/device.ts +0 -48
  98. package/src/platforms/android/emulator.ts +0 -139
  99. package/src/platforms/android/index.ts +0 -87
  100. package/src/platforms/ios/build.ts +0 -76
  101. package/src/platforms/ios/device.ts +0 -76
  102. package/src/platforms/ios/index.ts +0 -55
  103. package/src/platforms/ios/simulator.ts +0 -177
  104. package/src/platforms/platform-adapter.ts +0 -11
  105. package/src/platforms/platform-registry.ts +0 -24
  106. package/src/platforms/web/index.ts +0 -95
  107. package/src/process.ts +0 -33
  108. package/src/reporters/default-reporter.ts +0 -149
  109. package/src/reporters/junit-reporter.ts +0 -179
  110. package/src/utils.ts +0 -12
  111. package/tsconfig.tsbuildinfo +0 -1
@@ -1,48 +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
- ): Promise<void> => {
38
- await killApp(deviceId, bundleId);
39
- await spawn('adb', [
40
- '-s',
41
- deviceId,
42
- 'shell',
43
- 'am',
44
- 'start',
45
- '-n',
46
- `${bundleId}/.MainActivity`,
47
- ]);
48
- };
@@ -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,139 +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', ['-s', emulatorId, 'emu', 'avd', 'name']);
11
- const avdName = stdout.split('\n')[0].trim();
12
- return avdName || null;
13
- } catch {
14
- return null;
15
- }
16
- };
17
-
18
- export const getEmulatorDeviceId = async (
19
- avdName: string
20
- ): Promise<string | null> => {
21
- try {
22
- const { stdout } = await spawn('adb', ['devices']);
23
- const lines = stdout.split('\n');
24
-
25
- for (const line of lines) {
26
- const parts = line.trim().split('\t');
27
- if (parts.length === 2 && parts[0].startsWith('emulator-')) {
28
- const emulatorId = parts[0].trim();
29
- const name = await getEmulatorNameFromId(emulatorId);
30
- if (name === avdName) {
31
- return emulatorId;
32
- }
33
- }
34
- }
35
- return null;
36
- } catch {
37
- return null;
38
- }
39
- };
40
-
41
- export const getEmulatorStatus = async (
42
- avdName: string
43
- ): Promise<AndroidEmulatorStatus> => {
44
- const emulatorId = await getEmulatorDeviceId(avdName);
45
- if (!emulatorId) {
46
- return 'stopped';
47
- }
48
-
49
- try {
50
- // Check if device is fully booted by checking boot completion
51
- const { stdout } = await spawn('adb', ['-s', emulatorId, 'shell', 'getprop', 'sys.boot_completed']);
52
- const bootCompleted = stdout.trim() === '1';
53
- return bootCompleted ? 'running' : 'loading';
54
- } catch {
55
- return 'loading';
56
- }
57
- };
58
-
59
- export const runEmulator = async (name: string): Promise<ChildProcess> => {
60
- // Start the emulator
61
- const process = spawn('emulator', ['-avd', name]);
62
- const nodeChildProcess = await process.nodeChildProcess;
63
-
64
- // Poll for emulator status until it's fully running
65
- const checkStatus = async (): Promise<void> => {
66
- const status = await getEmulatorStatus(name);
67
-
68
- if (status === 'running') {
69
- return;
70
- } else if (status === 'loading') {
71
- // Check again in 2 seconds
72
- await new Promise(resolve => setTimeout(resolve, 2000));
73
- await checkStatus();
74
- } else {
75
- // Still stopped, check again in 1 second
76
- await new Promise(resolve => setTimeout(resolve, 1000));
77
- await checkStatus();
78
- }
79
- };
80
-
81
- // Start checking status after a brief delay to allow emulator to start
82
- await new Promise(resolve => setTimeout(resolve, 3000));
83
- await checkStatus();
84
-
85
- return nodeChildProcess;
86
- };
87
-
88
- export const stopEmulator = async (avdName: string): Promise<void> => {
89
- // First, get the emulator device ID
90
- const emulatorId = await getEmulatorDeviceId(avdName);
91
- if (!emulatorId) {
92
- return; // No emulator running, nothing to stop
93
- }
94
-
95
- await stopEmulatorById(emulatorId);
96
- };
97
-
98
- const stopEmulatorById = async (emulatorId: string): Promise<void> => {
99
- // Stop the emulator using the found ID
100
- await spawn('adb', ['-s', emulatorId, 'emu', 'kill']);
101
- };
102
-
103
- export const isAppInstalled = async (
104
- emulatorId: string,
105
- bundleId: string
106
- ): Promise<boolean> => {
107
- try {
108
- const { stdout } = await spawn('adb', ['-s', emulatorId, 'shell', 'pm', 'list', 'packages', bundleId]);
109
- return stdout.trim() !== '';
110
- } catch {
111
- return false;
112
- }
113
- };
114
-
115
- export const reversePort = async (port: number): Promise<void> => {
116
- await spawn('adb', ['reverse', `tcp:${port}`, `tcp:${port}`]);
117
- };
118
-
119
- export const getEmulatorScreenshot = async (
120
- emulatorId: string,
121
- name: string = `${emulatorId}-${new Date()
122
- .toISOString()
123
- .replace(/:/g, '-')
124
- .replace(/\//g, '-')}.png`
125
- ): Promise<string> => {
126
- // Use screencap to save directly to device, then pull the file
127
- const devicePath = '/sdcard/screenshot.png';
128
-
129
- // Take screenshot and save to device
130
- await spawn('adb', ['-s', emulatorId, 'shell', 'screencap', '-p', devicePath]);
131
-
132
- // Pull the file from device to local
133
- await spawn('adb', ['-s', emulatorId, 'pull', devicePath, name]);
134
-
135
- // Clean up the file on device
136
- await spawn('adb', ['-s', emulatorId, 'shell', 'rm', devicePath]);
137
-
138
- return name;
139
- };
@@ -1,87 +0,0 @@
1
- import { type ChildProcess } from 'node:child_process';
2
- import {
3
- assertNativeRunnerConfig,
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
- assertNativeRunnerConfig(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);
68
- logger.debug('App running');
69
-
70
- return {
71
- restart: async () => {
72
- await runApp(runner.deviceId, runner.bundleId);
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,76 +0,0 @@
1
- import { spawn, spawnAndForget } from '@react-native-harness/tools';
2
-
3
- export const listDevices = async (): Promise<any> => {
4
- const { stdout } = await spawn('xcrun', [
5
- 'simctl',
6
- 'list',
7
- 'devices',
8
- '--json',
9
- ]);
10
- return JSON.parse(stdout);
11
- };
12
-
13
- export const getDeviceByName = async (
14
- simulatorName: string
15
- ): Promise<any | null> => {
16
- const devices = await listDevices();
17
-
18
- for (const runtime in devices.devices) {
19
- const runtimeDevices = devices.devices[runtime];
20
- for (const device of runtimeDevices) {
21
- if (device.name === simulatorName && device.isAvailable) {
22
- return device;
23
- }
24
- }
25
- }
26
-
27
- return null;
28
- };
29
-
30
- export const listApps = async (udid: string): Promise<string[]> => {
31
- const { stdout: plistOutput } = await spawn('xcrun', [
32
- 'simctl',
33
- 'listapps',
34
- udid,
35
- ]);
36
- const { stdout: jsonOutput } = await spawn(
37
- 'plutil',
38
- ['-convert', 'json', '-o', '-', '-'],
39
- { stdin: { string: plistOutput } }
40
- );
41
- return Object.keys(JSON.parse(jsonOutput));
42
- };
43
-
44
- export const isAppInstalled = async (
45
- simulatorName: string,
46
- bundleId: string
47
- ): Promise<boolean> => {
48
- const device = await getDeviceByName(simulatorName);
49
-
50
- if (!device) {
51
- throw new Error(`Simulator ${simulatorName} not found`);
52
- }
53
-
54
- const appList = await listApps(device.udid);
55
- return appList.includes(bundleId);
56
- };
57
-
58
- export const runApp = async (
59
- simulatorName: string,
60
- appName: string
61
- ): Promise<void> => {
62
- await killApp(simulatorName, appName);
63
- await spawn('xcrun', ['simctl', 'launch', simulatorName, appName]);
64
- };
65
-
66
- export const killApp = async (
67
- simulatorName: string,
68
- appName: string
69
- ): Promise<void> => {
70
- await spawnAndForget('xcrun', [
71
- 'simctl',
72
- 'terminate',
73
- simulatorName,
74
- appName,
75
- ]);
76
- };
@@ -1,76 +0,0 @@
1
- import { spawn, spawnAndForget } from '@react-native-harness/tools';
2
-
3
- export const listDevices = async (): Promise<any> => {
4
- const { stdout } = await spawn('xcrun', [
5
- 'simctl',
6
- 'list',
7
- 'devices',
8
- '--json',
9
- ]);
10
- return JSON.parse(stdout);
11
- };
12
-
13
- export const getDeviceByName = async (
14
- simulatorName: string
15
- ): Promise<any | null> => {
16
- const devices = await listDevices();
17
-
18
- for (const runtime in devices.devices) {
19
- const runtimeDevices = devices.devices[runtime];
20
- for (const device of runtimeDevices) {
21
- if (device.name === simulatorName && device.isAvailable) {
22
- return device;
23
- }
24
- }
25
- }
26
-
27
- return null;
28
- };
29
-
30
- export const listApps = async (udid: string): Promise<string[]> => {
31
- const { stdout: plistOutput } = await spawn('xcrun', [
32
- 'simctl',
33
- 'listapps',
34
- udid,
35
- ]);
36
- const { stdout: jsonOutput } = await spawn(
37
- 'plutil',
38
- ['-convert', 'json', '-o', '-', '-'],
39
- { stdin: { string: plistOutput } }
40
- );
41
- return Object.keys(JSON.parse(jsonOutput));
42
- };
43
-
44
- export const isAppInstalled = async (
45
- simulatorName: string,
46
- bundleId: string
47
- ): Promise<boolean> => {
48
- const device = await getDeviceByName(simulatorName);
49
-
50
- if (!device) {
51
- throw new Error(`Simulator ${simulatorName} not found`);
52
- }
53
-
54
- const appList = await listApps(device.udid);
55
- return appList.includes(bundleId);
56
- };
57
-
58
- export const runApp = async (
59
- simulatorName: string,
60
- appName: string
61
- ): Promise<void> => {
62
- await killApp(simulatorName, appName);
63
- await spawn('xcrun', ['simctl', 'launch', simulatorName, appName]);
64
- };
65
-
66
- export const killApp = async (
67
- simulatorName: string,
68
- appName: string
69
- ): Promise<void> => {
70
- await spawnAndForget('xcrun', [
71
- 'simctl',
72
- 'terminate',
73
- simulatorName,
74
- appName,
75
- ]);
76
- };
@@ -1,55 +0,0 @@
1
- import {
2
- assertNativeRunnerConfig,
3
- TestRunnerConfig,
4
- } from '@react-native-harness/config';
5
- import { type PlatformAdapter } from '../platform-adapter.js';
6
- import {
7
- getSimulatorStatus,
8
- runSimulator,
9
- stopSimulator,
10
- } from './simulator.js';
11
- import { isAppInstalled, runApp, killApp } from './build.js';
12
- import { killWithAwait } from '../../process.js';
13
- import { runMetro } from '../../bundlers/metro.js';
14
- import { AppNotInstalledError } from '../../errors/errors.js';
15
-
16
- const iosPlatformAdapter: PlatformAdapter = {
17
- name: 'ios',
18
- getEnvironment: async (runner: TestRunnerConfig) => {
19
- assertNativeRunnerConfig(runner);
20
-
21
- let shouldStopSimulator = false;
22
- const simulatorStatus = await getSimulatorStatus(runner.deviceId);
23
- const metroPromise = runMetro();
24
-
25
- if (simulatorStatus === 'stopped') {
26
- await runSimulator(runner.deviceId);
27
- shouldStopSimulator = true;
28
- }
29
-
30
- const isInstalled = await isAppInstalled(runner.deviceId, runner.bundleId);
31
-
32
- if (!isInstalled) {
33
- throw new AppNotInstalledError(runner.deviceId, runner.bundleId, 'ios');
34
- }
35
-
36
- const metro = await metroPromise;
37
- await runApp(runner.deviceId, runner.bundleId);
38
-
39
- return {
40
- restart: async () => {
41
- await runApp(runner.deviceId, runner.bundleId);
42
- },
43
- dispose: async () => {
44
- await killApp(runner.deviceId, runner.bundleId);
45
- if (shouldStopSimulator) {
46
- await stopSimulator(runner.deviceId);
47
- }
48
-
49
- await killWithAwait(metro);
50
- },
51
- };
52
- },
53
- };
54
-
55
- export default iosPlatformAdapter;
@@ -1,177 +0,0 @@
1
- import { spawn } from '@react-native-harness/tools';
2
-
3
- export type IOSSimulatorStatus = 'stopped' | 'loading' | 'running';
4
-
5
- export const getSimulatorDeviceId = async (
6
- simulatorName: string
7
- ): Promise<string | null> => {
8
- try {
9
- const { stdout } = await spawn('xcrun', [
10
- 'simctl',
11
- 'list',
12
- 'devices',
13
- '--json',
14
- ]);
15
- const devices = JSON.parse(stdout);
16
-
17
- // Find the device across all iOS versions
18
- for (const runtime in devices.devices) {
19
- if (runtime.includes('iOS')) {
20
- const runtimeDevices = devices.devices[runtime];
21
- const device = runtimeDevices.find(
22
- (d: any) => d.name === simulatorName
23
- );
24
-
25
- if (device) {
26
- return device.udid;
27
- }
28
- }
29
- }
30
-
31
- return null;
32
- } catch {
33
- return null;
34
- }
35
- };
36
-
37
- export const getAvailableSimulators = async (): Promise<
38
- Array<{ name: string; udid: string; runtime: string }>
39
- > => {
40
- try {
41
- const { stdout } = await spawn('xcrun', [
42
- 'simctl',
43
- 'list',
44
- 'devices',
45
- '--json',
46
- ]);
47
- const devices = JSON.parse(stdout);
48
- const simulators: Array<{
49
- name: string;
50
- udid: string;
51
- runtime: string;
52
- }> = [];
53
-
54
- for (const runtime in devices.devices) {
55
- if (runtime.includes('iOS')) {
56
- const runtimeDevices = devices.devices[runtime];
57
- runtimeDevices.forEach((device: any) => {
58
- if (device.isAvailable) {
59
- simulators.push({
60
- name: device.name,
61
- udid: device.udid,
62
- runtime: runtime,
63
- });
64
- }
65
- });
66
- }
67
- }
68
-
69
- return simulators;
70
- } catch {
71
- return [];
72
- }
73
- };
74
-
75
- export const getSimulatorStatus = async (
76
- simulatorName: string
77
- ): Promise<IOSSimulatorStatus> => {
78
- try {
79
- const { stdout } = await spawn('xcrun', [
80
- 'simctl',
81
- 'list',
82
- 'devices',
83
- '--json',
84
- ]);
85
- const devices = JSON.parse(stdout);
86
-
87
- for (const runtime in devices.devices) {
88
- if (runtime.includes('iOS')) {
89
- const runtimeDevices = devices.devices[runtime];
90
- const device = runtimeDevices.find(
91
- (d: any) => d.name === simulatorName
92
- );
93
-
94
- if (device) {
95
- switch (device.state) {
96
- case 'Booted':
97
- return 'running';
98
- case 'Booting':
99
- return 'loading';
100
- default:
101
- return 'stopped';
102
- }
103
- }
104
- }
105
- }
106
-
107
- return 'stopped';
108
- } catch {
109
- return 'stopped';
110
- }
111
- };
112
-
113
- export const runSimulator = async (name: string): Promise<void> => {
114
- const deviceId = await getSimulatorDeviceId(name);
115
-
116
- if (!deviceId) {
117
- throw new Error('Simulator not found');
118
- }
119
-
120
- try {
121
- await spawn('xcrun', ['simctl', 'boot', deviceId]);
122
- } catch (bootError: any) {
123
- // Ignore if simulator is already booted
124
- if (
125
- !bootError.stderr?.includes(
126
- 'Unable to boot device in current state: Booted'
127
- )
128
- ) {
129
- throw bootError;
130
- }
131
- }
132
-
133
- await spawn('open', ['-a', 'Simulator']);
134
-
135
- let attempts = 0;
136
-
137
- while (true) {
138
- attempts++;
139
-
140
- const status = await getSimulatorStatus(name);
141
-
142
- if (status === 'running') {
143
- break;
144
- }
145
-
146
- if (attempts > 10) {
147
- throw new Error('Simulator not running');
148
- }
149
-
150
- await new Promise((resolve) => setTimeout(resolve, 1000));
151
- }
152
- };
153
-
154
- export const stopSimulator = async (name: string): Promise<void> => {
155
- const simulatorId = await getSimulatorDeviceId(name);
156
-
157
- if (!simulatorId) {
158
- return; // Already stopped
159
- }
160
-
161
- await stopSimulatorById(simulatorId);
162
- };
163
-
164
- const stopSimulatorById = async (simulatorId: string): Promise<void> => {
165
- try {
166
- await spawn('xcrun', ['simctl', 'shutdown', simulatorId]);
167
- } catch (shutdownError: any) {
168
- // Ignore if simulator is already shut down
169
- if (
170
- !shutdownError.stderr?.includes(
171
- 'Unable to shutdown device in current state: Shutdown'
172
- )
173
- ) {
174
- throw shutdownError;
175
- }
176
- }
177
- };