@react-native-harness/cli 1.0.0-alpha.9 → 1.0.0-canary.1761729829908
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/README.md +23 -4
- package/dist/bundlers/metro.d.ts.map +1 -1
- package/dist/bundlers/metro.js +15 -5
- package/dist/commands/test.d.ts +2 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +23 -19
- package/dist/discovery/index.d.ts +3 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +1 -0
- package/dist/discovery/testDiscovery.d.ts +11 -0
- package/dist/discovery/testDiscovery.d.ts.map +1 -0
- package/dist/discovery/testDiscovery.js +29 -0
- package/dist/errors/errorHandler.d.ts +1 -1
- package/dist/errors/errorHandler.d.ts.map +1 -1
- package/dist/errors/errorHandler.js +103 -101
- package/dist/errors/errors.d.ts +6 -7
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +8 -12
- package/dist/external.d.ts +2 -0
- package/dist/external.d.ts.map +1 -0
- package/dist/external.js +1 -0
- package/dist/index.js +58 -33
- package/dist/jest.d.ts +2 -0
- package/dist/jest.d.ts.map +1 -0
- package/dist/jest.js +7 -0
- package/dist/platforms/android/emulator.d.ts +0 -1
- package/dist/platforms/android/emulator.d.ts.map +1 -1
- package/dist/platforms/android/emulator.js +26 -20
- package/dist/platforms/android/index.js +1 -1
- package/dist/platforms/ios/build.d.ts.map +1 -1
- package/dist/platforms/ios/build.js +5 -1
- package/dist/platforms/ios/device.d.ts +6 -2
- package/dist/platforms/ios/device.d.ts.map +1 -1
- package/dist/platforms/ios/simulator.d.ts.map +1 -1
- package/dist/platforms/ios/simulator.js +8 -3
- package/dist/platforms/platform-registry.d.ts.map +1 -1
- package/dist/platforms/platform-registry.js +3 -1
- package/dist/platforms/vega/build.d.ts +23 -0
- package/dist/platforms/vega/build.d.ts.map +1 -0
- package/dist/platforms/vega/build.js +55 -0
- package/dist/platforms/vega/device.d.ts +57 -0
- package/dist/platforms/vega/device.d.ts.map +1 -0
- package/dist/platforms/vega/device.js +206 -0
- package/dist/platforms/vega/index.d.ts +4 -0
- package/dist/platforms/vega/index.d.ts.map +1 -0
- package/dist/platforms/vega/index.js +75 -0
- package/dist/process.js +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/utils/status-formatter.d.ts +27 -0
- package/dist/utils/status-formatter.d.ts.map +1 -0
- package/dist/utils/status-formatter.js +54 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +15 -0
- package/eslint.config.mjs +1 -0
- package/package.json +25 -9
- package/src/external.ts +0 -0
- package/src/index.ts +65 -44
- package/tsconfig.lib.json +1 -12
- package/src/bundlers/metro.ts +0 -89
- package/src/commands/test.ts +0 -218
- package/src/errors/errorHandler.ts +0 -196
- package/src/errors/errors.ts +0 -119
- package/src/platforms/android/build.ts +0 -49
- package/src/platforms/android/device.ts +0 -48
- package/src/platforms/android/emulator.ts +0 -139
- package/src/platforms/android/index.ts +0 -87
- package/src/platforms/ios/build.ts +0 -68
- package/src/platforms/ios/device.ts +0 -76
- package/src/platforms/ios/index.ts +0 -66
- package/src/platforms/ios/simulator.ts +0 -166
- package/src/platforms/platform-adapter.ts +0 -11
- package/src/platforms/platform-registry.ts +0 -24
- package/src/platforms/web/index.ts +0 -16
- package/src/process.ts +0 -33
- package/src/utils.ts +0 -12
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ConfigLoadError,
|
|
3
|
-
ConfigNotFoundError,
|
|
4
|
-
ConfigValidationError,
|
|
5
|
-
} from '@react-native-harness/config';
|
|
6
|
-
import { AssertionError } from '../utils.js';
|
|
7
|
-
import {
|
|
8
|
-
NoRunnerSpecifiedError,
|
|
9
|
-
RunnerNotFoundError,
|
|
10
|
-
EnvironmentInitializationError,
|
|
11
|
-
TestExecutionError,
|
|
12
|
-
RpcClientError,
|
|
13
|
-
AppNotInstalledError,
|
|
14
|
-
BridgeTimeoutError,
|
|
15
|
-
BundlingFailedError,
|
|
16
|
-
} from './errors.js';
|
|
17
|
-
|
|
18
|
-
export const handleError = (error: unknown): void => {
|
|
19
|
-
if (error instanceof AssertionError) {
|
|
20
|
-
console.error(`\n❌ Assertion Error`);
|
|
21
|
-
console.error(`\nError: ${error.message}`);
|
|
22
|
-
console.error(`\nPlease check your configuration and try again.`);
|
|
23
|
-
} else if (error instanceof ConfigValidationError) {
|
|
24
|
-
console.error(`\n❌ Configuration Error`);
|
|
25
|
-
console.error(`\nFile: ${error.filePath}`);
|
|
26
|
-
console.error(`\nValidation errors:`);
|
|
27
|
-
error.validationErrors.forEach((err) => {
|
|
28
|
-
console.error(` • ${err}`);
|
|
29
|
-
});
|
|
30
|
-
console.error(`\nPlease fix the configuration errors and try again.`);
|
|
31
|
-
} else if (error instanceof ConfigNotFoundError) {
|
|
32
|
-
console.error(`\n❌ Configuration Not Found`);
|
|
33
|
-
console.error(
|
|
34
|
-
`\nCould not find 'rn-harness.config' in '${error.searchPath}' or any parent directories.`
|
|
35
|
-
);
|
|
36
|
-
console.error(`\nSupported file extensions: .js, .mjs, .cjs, .json`);
|
|
37
|
-
console.error(
|
|
38
|
-
`\nPlease create a configuration file or run from a directory that contains one.`
|
|
39
|
-
);
|
|
40
|
-
} else if (error instanceof ConfigLoadError) {
|
|
41
|
-
console.error(`\n❌ Configuration Load Error`);
|
|
42
|
-
console.error(`\nFile: ${error.filePath}`);
|
|
43
|
-
console.error(`Error: ${error.message}`);
|
|
44
|
-
if (error.cause) {
|
|
45
|
-
console.error(`\nCause: ${error.cause.message}`);
|
|
46
|
-
}
|
|
47
|
-
console.error(
|
|
48
|
-
`\nPlease check your configuration file syntax and try again.`
|
|
49
|
-
);
|
|
50
|
-
} else if (error instanceof NoRunnerSpecifiedError) {
|
|
51
|
-
console.error('\n❌ No runner specified');
|
|
52
|
-
console.error(
|
|
53
|
-
'\nPlease specify a runner name or set a defaultRunner in your config.'
|
|
54
|
-
);
|
|
55
|
-
console.error('\nUsage: react-native-harness test [runner-name] [pattern]');
|
|
56
|
-
console.error('\nAvailable runners:');
|
|
57
|
-
error.availableRunners.forEach((r) => {
|
|
58
|
-
console.error(` • ${r.name} (${r.platform})`);
|
|
59
|
-
});
|
|
60
|
-
console.error(
|
|
61
|
-
'\nTo set a default runner, add "defaultRunner" to your config:'
|
|
62
|
-
);
|
|
63
|
-
console.error(' { "defaultRunner": "your-runner-name" }');
|
|
64
|
-
} else if (error instanceof RunnerNotFoundError) {
|
|
65
|
-
console.error(`\n❌ Runner "${error.runnerName}" not found`);
|
|
66
|
-
console.error('\nAvailable runners:');
|
|
67
|
-
error.availableRunners.forEach((r) => {
|
|
68
|
-
console.error(` • ${r.name} (${r.platform})`);
|
|
69
|
-
});
|
|
70
|
-
console.error('\nTo add a new runner, update your rn-harness.config file.');
|
|
71
|
-
} else if (error instanceof EnvironmentInitializationError) {
|
|
72
|
-
console.error(`\n❌ Environment Initialization Error`);
|
|
73
|
-
console.error(`\nRunner: ${error.runnerName} (${error.platform})`);
|
|
74
|
-
console.error(`\nError: ${error.message}`);
|
|
75
|
-
if (error.details) {
|
|
76
|
-
console.error(`\nDetails: ${error.details}`);
|
|
77
|
-
}
|
|
78
|
-
console.error(`\nTroubleshooting steps:`);
|
|
79
|
-
console.error(
|
|
80
|
-
` • Verify that ${error.platform} development environment is properly set up`
|
|
81
|
-
);
|
|
82
|
-
console.error(` • Check that the app is built and ready for testing`);
|
|
83
|
-
console.error(` • Ensure all required dependencies are installed`);
|
|
84
|
-
if (error.platform === 'ios') {
|
|
85
|
-
console.error(` • Verify Xcode and iOS Simulator are working correctly`);
|
|
86
|
-
} else if (error.platform === 'android') {
|
|
87
|
-
console.error(
|
|
88
|
-
` • Verify Android SDK and emulator are working correctly`
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
console.error(
|
|
92
|
-
`\nPlease check your environment configuration and try again.`
|
|
93
|
-
);
|
|
94
|
-
} else if (error instanceof TestExecutionError) {
|
|
95
|
-
console.error(`\n❌ Test Execution Error`);
|
|
96
|
-
console.error(`\nFile: ${error.testFile}`);
|
|
97
|
-
if (error.testSuite) {
|
|
98
|
-
console.error(`\nSuite: ${error.testSuite}`);
|
|
99
|
-
}
|
|
100
|
-
if (error.testName) {
|
|
101
|
-
console.error(`\nTest: ${error.testName}`);
|
|
102
|
-
}
|
|
103
|
-
console.error(`\nError: ${error.message}`);
|
|
104
|
-
console.error(`\nTroubleshooting steps:`);
|
|
105
|
-
console.error(` • Check the test file syntax and logic`);
|
|
106
|
-
console.error(` • Verify all test dependencies are available`);
|
|
107
|
-
console.error(` • Ensure the app is in the expected state for the test`);
|
|
108
|
-
console.error(
|
|
109
|
-
` • Check device/emulator logs for additional error details`
|
|
110
|
-
);
|
|
111
|
-
console.error(`\nPlease check your test file and try again.`);
|
|
112
|
-
} else if (error instanceof RpcClientError) {
|
|
113
|
-
console.error(`\n❌ RPC Client Error`);
|
|
114
|
-
console.error(`\nError: ${error.message}`);
|
|
115
|
-
if (error.bridgePort) {
|
|
116
|
-
console.error(`\nBridge Port: ${error.bridgePort}`);
|
|
117
|
-
}
|
|
118
|
-
if (error.connectionStatus) {
|
|
119
|
-
console.error(`\nConnection Status: ${error.connectionStatus}`);
|
|
120
|
-
}
|
|
121
|
-
console.error(`\nTroubleshooting steps:`);
|
|
122
|
-
console.error(` • Verify the React Native app is running and connected`);
|
|
123
|
-
console.error(` • Check that the bridge port is not blocked by firewall`);
|
|
124
|
-
console.error(
|
|
125
|
-
` • Ensure the app has the React Native Harness runtime integrated`
|
|
126
|
-
);
|
|
127
|
-
console.error(` • Try restarting the app and test harness`);
|
|
128
|
-
console.error(`\nPlease check your bridge connection and try again.`);
|
|
129
|
-
} else if (error instanceof AppNotInstalledError) {
|
|
130
|
-
console.error(`\n❌ App Not Installed`);
|
|
131
|
-
console.error(
|
|
132
|
-
`\nThe app "${error.bundleId}" is not installed on ${
|
|
133
|
-
error.platform === 'ios' ? 'simulator' : 'emulator'
|
|
134
|
-
} "${error.deviceName}".`
|
|
135
|
-
);
|
|
136
|
-
console.error(`\nTo resolve this issue:`);
|
|
137
|
-
if (error.platform === 'ios') {
|
|
138
|
-
console.error(
|
|
139
|
-
` • Build and install the app: npx react-native run-ios --simulator="${error.deviceName}"`
|
|
140
|
-
);
|
|
141
|
-
console.error(
|
|
142
|
-
` • Or install from Xcode: Open ios/*.xcworkspace and run the project`
|
|
143
|
-
);
|
|
144
|
-
} else {
|
|
145
|
-
console.error(
|
|
146
|
-
` • Build and install the app: npx react-native run-android`
|
|
147
|
-
);
|
|
148
|
-
console.error(
|
|
149
|
-
` • Or build manually: ./gradlew assembleDebug && adb install android/app/build/outputs/apk/debug/app-debug.apk`
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
console.error(`\nPlease install the app and try running the tests again.`);
|
|
153
|
-
} else if (error instanceof BundlingFailedError) {
|
|
154
|
-
console.error(`\n❌ Test File Bundling Error`);
|
|
155
|
-
console.error(`\nFile: ${error.modulePath}`);
|
|
156
|
-
console.error(`\nError: ${error.reason}`);
|
|
157
|
-
console.error(`\nTroubleshooting steps:`);
|
|
158
|
-
console.error(` • Check the test file syntax and imports`);
|
|
159
|
-
console.error(` • Verify all imported modules exist and are accessible`);
|
|
160
|
-
console.error(` • Ensure the Metro bundler configuration is correct`);
|
|
161
|
-
console.error(` • Check for any circular dependencies in the test file`);
|
|
162
|
-
console.error(` • Verify that all required packages are installed`);
|
|
163
|
-
console.error(`\nPlease fix the bundling issues and try again.`);
|
|
164
|
-
} else if (error instanceof BridgeTimeoutError) {
|
|
165
|
-
console.error(`\n❌ Bridge Connection Timeout`);
|
|
166
|
-
console.error(
|
|
167
|
-
`\nThe bridge connection timed out after ${error.timeout}ms while waiting for the "${error.runnerName}" (${error.platform}) runner to be ready.`
|
|
168
|
-
);
|
|
169
|
-
console.error(`\nThis usually indicates that:`);
|
|
170
|
-
console.error(
|
|
171
|
-
` • The React Native app failed to load or connect to the bridge`
|
|
172
|
-
);
|
|
173
|
-
console.error(` • The app crashed during startup`);
|
|
174
|
-
console.error(
|
|
175
|
-
` • Network connectivity issues between the app and the test harness`
|
|
176
|
-
);
|
|
177
|
-
console.error(` • The app is taking longer than expected to initialize`);
|
|
178
|
-
console.error(`\nTo resolve this issue:`);
|
|
179
|
-
console.error(
|
|
180
|
-
` • Check that the app is properly installed and can start normally`
|
|
181
|
-
);
|
|
182
|
-
console.error(
|
|
183
|
-
` • Verify that the app has the React Native Harness runtime integrated`
|
|
184
|
-
);
|
|
185
|
-
console.error(` • Check device/emulator logs for any startup errors`);
|
|
186
|
-
console.error(
|
|
187
|
-
` • Ensure the test harness bridge port (3001) is not blocked`
|
|
188
|
-
);
|
|
189
|
-
console.error(
|
|
190
|
-
`\nIf the app needs more time to start, consider increasing the timeout in the configuration.`
|
|
191
|
-
);
|
|
192
|
-
} else {
|
|
193
|
-
console.error(`\n❌ Unexpected Error`);
|
|
194
|
-
console.error(error);
|
|
195
|
-
}
|
|
196
|
-
};
|
package/src/errors/errors.ts
DELETED
|
@@ -1,119 +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'
|
|
101
|
-
) {
|
|
102
|
-
super(
|
|
103
|
-
`App "${bundleId}" is not installed on ${
|
|
104
|
-
platform === 'ios' ? 'simulator' : 'emulator'
|
|
105
|
-
} "${deviceName}"`
|
|
106
|
-
);
|
|
107
|
-
this.name = 'AppNotInstalledError';
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export class BundlingFailedError extends Error {
|
|
112
|
-
constructor(
|
|
113
|
-
public readonly modulePath: string,
|
|
114
|
-
public readonly reason: string
|
|
115
|
-
) {
|
|
116
|
-
super(`Bundling of ${modulePath} failed: ${reason}`);
|
|
117
|
-
this.name = 'BundlingFailedError';
|
|
118
|
-
}
|
|
119
|
-
}
|
|
@@ -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,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
|
-
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(runner.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;
|