@react-native-harness/platform-android 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.
- package/LICENSE +20 -0
- package/README.md +99 -0
- package/dist/adb-id.d.ts +4 -0
- package/dist/adb-id.d.ts.map +1 -0
- package/dist/adb-id.js +24 -0
- package/dist/adb.d.ts +15 -0
- package/dist/adb.d.ts.map +1 -0
- package/dist/adb.js +64 -0
- package/dist/assertions.d.ts +5 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +6 -0
- package/dist/config.d.ts +106 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +39 -0
- package/dist/emulator.d.ts +6 -0
- package/dist/emulator.d.ts.map +1 -0
- package/dist/emulator.js +27 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/factory.d.ts +6 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +48 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/reader.d.ts +6 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +57 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/types.d.ts +381 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +107 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +7 -0
- package/eslint.config.mjs +19 -0
- package/package.json +25 -0
- package/src/adb-id.ts +36 -0
- package/src/adb.ts +99 -0
- package/src/config.ts +60 -0
- package/src/factory.ts +81 -0
- package/src/index.ts +6 -0
- package/src/utils.ts +9 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Callstack
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[![mit licence][license-badge]][license]
|
|
4
|
+
[![npm downloads][npm-downloads-badge]][npm-downloads]
|
|
5
|
+
[![Chat][chat-badge]][chat]
|
|
6
|
+
[![PRs Welcome][prs-welcome-badge]][prs-welcome]
|
|
7
|
+
|
|
8
|
+
Android platform for React Native Harness - enables testing on Android emulators and physical devices.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @react-native-harness/platform-android
|
|
14
|
+
# or
|
|
15
|
+
pnpm add @react-native-harness/platform-android
|
|
16
|
+
# or
|
|
17
|
+
yarn add @react-native-harness/platform-android
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Import the Android platform functions in your `rn-harness.config.mjs`:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import {
|
|
26
|
+
androidPlatform,
|
|
27
|
+
androidEmulator,
|
|
28
|
+
physicalAndroidDevice,
|
|
29
|
+
} from '@react-native-harness/platform-android';
|
|
30
|
+
|
|
31
|
+
const config = {
|
|
32
|
+
runners: [
|
|
33
|
+
androidPlatform({
|
|
34
|
+
name: 'android',
|
|
35
|
+
device: androidEmulator('Pixel_8_API_35'),
|
|
36
|
+
bundleId: 'com.your.app',
|
|
37
|
+
}),
|
|
38
|
+
androidPlatform({
|
|
39
|
+
name: 'physical-device',
|
|
40
|
+
device: physicalAndroidDevice('Motorola', 'Moto G72'),
|
|
41
|
+
bundleId: 'com.your.app',
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
// ... other config
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default config;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
### `androidPlatform(config)`
|
|
53
|
+
|
|
54
|
+
Creates an Android platform runner configuration.
|
|
55
|
+
|
|
56
|
+
**Parameters:**
|
|
57
|
+
|
|
58
|
+
- `config.name` - Unique name for the runner
|
|
59
|
+
- `config.device` - Android device configuration (emulator or physical)
|
|
60
|
+
- `config.bundleId` - Android application bundle ID
|
|
61
|
+
|
|
62
|
+
### `androidEmulator(deviceName)`
|
|
63
|
+
|
|
64
|
+
Creates an Android emulator device configuration.
|
|
65
|
+
|
|
66
|
+
**Parameters:**
|
|
67
|
+
|
|
68
|
+
- `deviceName` - Name of the Android emulator (e.g., 'Pixel_8_API_35')
|
|
69
|
+
|
|
70
|
+
### `physicalAndroidDevice(manufacturer, model)`
|
|
71
|
+
|
|
72
|
+
Creates a physical Android device configuration.
|
|
73
|
+
|
|
74
|
+
**Parameters:**
|
|
75
|
+
|
|
76
|
+
- `manufacturer` - Device manufacturer (e.g., 'Motorola')
|
|
77
|
+
- `model` - Device model (e.g., 'Moto G72')
|
|
78
|
+
|
|
79
|
+
## Requirements
|
|
80
|
+
|
|
81
|
+
- Android SDK installed
|
|
82
|
+
- Android emulator or physical device connected
|
|
83
|
+
- React Native project configured for Android
|
|
84
|
+
|
|
85
|
+
## Made with ❤️ at Callstack
|
|
86
|
+
|
|
87
|
+
`react-native-harness` is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Callstack][callstack-readme-with-love] is a group of React and React Native geeks, contact us at [hello@callstack.com](mailto:hello@callstack.com) if you need any help with these or just want to say hi!
|
|
88
|
+
|
|
89
|
+
Like the project? ⚛️ [Join the team](https://callstack.com/careers/?utm_campaign=Senior_RN&utm_source=github&utm_medium=readme) who does amazing stuff for clients and drives React Native Open Source! 🔥
|
|
90
|
+
|
|
91
|
+
[callstack-readme-with-love]: https://callstack.com/?utm_source=github.com&utm_medium=referral&utm_campaign=react-native-harness&utm_term=readme-with-love
|
|
92
|
+
[license-badge]: https://img.shields.io/npm/l/react-native-harness?style=for-the-badge
|
|
93
|
+
[license]: https://github.com/callstackincubator/react-native-harness/blob/main/LICENSE
|
|
94
|
+
[npm-downloads-badge]: https://img.shields.io/npm/dm/react-native-harness?style=for-the-badge
|
|
95
|
+
[npm-downloads]: https://www.npmjs.com/package/react-native-harness
|
|
96
|
+
[prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
|
|
97
|
+
[prs-welcome]: ./CONTRIBUTING.md
|
|
98
|
+
[chat-badge]: https://img.shields.io/discord/426714625279524876.svg?style=for-the-badge
|
|
99
|
+
[chat]: https://discord.gg/xgGt7KAjxv
|
package/dist/adb-id.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adb-id.d.ts","sourceRoot":"","sources":["../src/adb-id.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,aAAa,EACd,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,OAE/C,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,QAAQ,aAAa,KACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAsBvB,CAAC"}
|
package/dist/adb-id.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as adb from './adb.js';
|
|
2
|
+
import { isAndroidDeviceEmulator, isAndroidDevicePhysical, } from './config.js';
|
|
3
|
+
export const isAdbIdEmulator = (adbId) => {
|
|
4
|
+
return adbId.startsWith('emulator-');
|
|
5
|
+
};
|
|
6
|
+
export const getAdbId = async (device) => {
|
|
7
|
+
const adbIds = await adb.getDeviceIds();
|
|
8
|
+
for (const adbId of adbIds) {
|
|
9
|
+
if (isAndroidDeviceEmulator(device)) {
|
|
10
|
+
const emulatorName = await adb.getEmulatorName(adbId);
|
|
11
|
+
if (emulatorName === device.name) {
|
|
12
|
+
return adbId;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
else if (isAndroidDevicePhysical(device)) {
|
|
16
|
+
const deviceInfo = await adb.getDeviceInfo(adbId);
|
|
17
|
+
if (deviceInfo?.manufacturer === device.manufacturer &&
|
|
18
|
+
deviceInfo?.model === device.model) {
|
|
19
|
+
return adbId;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
};
|
package/dist/adb.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const isAppInstalled: (adbId: string, bundleId: string) => Promise<boolean>;
|
|
2
|
+
export declare const reversePort: (adbId: string, port: number) => Promise<void>;
|
|
3
|
+
export declare const stopApp: (adbId: string, bundleId: string) => Promise<void>;
|
|
4
|
+
export declare const startApp: (adbId: string, bundleId: string, activityName: string) => Promise<void>;
|
|
5
|
+
export declare const getDeviceIds: () => Promise<string[]>;
|
|
6
|
+
export declare const getEmulatorName: (adbId: string) => Promise<string | null>;
|
|
7
|
+
export declare const getShellProperty: (adbId: string, property: string) => Promise<string | null>;
|
|
8
|
+
export type DeviceInfo = {
|
|
9
|
+
manufacturer: string | null;
|
|
10
|
+
model: string | null;
|
|
11
|
+
};
|
|
12
|
+
export declare const getDeviceInfo: (adbId: string) => Promise<DeviceInfo | null>;
|
|
13
|
+
export declare const isBootCompleted: (adbId: string) => Promise<boolean>;
|
|
14
|
+
export declare const stopEmulator: (adbId: string) => Promise<void>;
|
|
15
|
+
//# sourceMappingURL=adb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adb.d.ts","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,GACzB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,OAAO,CAWjB,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,MAAM,MAAM,KACX,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,OAAO,GAClB,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,QAAQ,GACnB,OAAO,MAAM,EACb,UAAU,MAAM,EAChB,cAAc,MAAM,KACnB,OAAO,CAAC,IAAI,CAUd,CAAC;AAEF,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,MAAM,EAAE,CAOrD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAGvB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,MAAM,EACb,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CASvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,OAAO,MAAM,KACZ,OAAO,CAAC,UAAU,GAAG,IAAI,CAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,OAAO,CAGpE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAE9D,CAAC"}
|
package/dist/adb.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { spawn } from '@react-native-harness/tools';
|
|
2
|
+
export const isAppInstalled = async (adbId, bundleId) => {
|
|
3
|
+
const { stdout } = await spawn('adb', [
|
|
4
|
+
'-s',
|
|
5
|
+
adbId,
|
|
6
|
+
'shell',
|
|
7
|
+
'pm',
|
|
8
|
+
'list',
|
|
9
|
+
'packages',
|
|
10
|
+
bundleId,
|
|
11
|
+
]);
|
|
12
|
+
return stdout.trim() !== '';
|
|
13
|
+
};
|
|
14
|
+
export const reversePort = async (adbId, port) => {
|
|
15
|
+
await spawn('adb', ['-s', adbId, 'reverse', `tcp:${port}`, `tcp:${port}`]);
|
|
16
|
+
};
|
|
17
|
+
export const stopApp = async (adbId, bundleId) => {
|
|
18
|
+
await spawn('adb', ['-s', adbId, 'shell', 'am', 'force-stop', bundleId]);
|
|
19
|
+
};
|
|
20
|
+
export const startApp = async (adbId, bundleId, activityName) => {
|
|
21
|
+
await spawn('adb', [
|
|
22
|
+
'-s',
|
|
23
|
+
adbId,
|
|
24
|
+
'shell',
|
|
25
|
+
'am',
|
|
26
|
+
'start',
|
|
27
|
+
'-n',
|
|
28
|
+
`${bundleId}/${activityName}`,
|
|
29
|
+
]);
|
|
30
|
+
};
|
|
31
|
+
export const getDeviceIds = async () => {
|
|
32
|
+
const { stdout } = await spawn('adb', ['devices']);
|
|
33
|
+
return stdout
|
|
34
|
+
.split('\n')
|
|
35
|
+
.slice(1) // Skip header
|
|
36
|
+
.filter((line) => line.trim() !== '')
|
|
37
|
+
.map((line) => line.split('\t')[0]);
|
|
38
|
+
};
|
|
39
|
+
export const getEmulatorName = async (adbId) => {
|
|
40
|
+
const { stdout } = await spawn('adb', ['-s', adbId, 'emu', 'avd', 'name']);
|
|
41
|
+
return stdout.split('\n')[0].trim() || null;
|
|
42
|
+
};
|
|
43
|
+
export const getShellProperty = async (adbId, property) => {
|
|
44
|
+
const { stdout } = await spawn('adb', [
|
|
45
|
+
'-s',
|
|
46
|
+
adbId,
|
|
47
|
+
'shell',
|
|
48
|
+
'getprop',
|
|
49
|
+
property,
|
|
50
|
+
]);
|
|
51
|
+
return stdout.trim() || null;
|
|
52
|
+
};
|
|
53
|
+
export const getDeviceInfo = async (adbId) => {
|
|
54
|
+
const manufacturer = await getShellProperty(adbId, 'ro.product.manufacturer');
|
|
55
|
+
const model = await getShellProperty(adbId, 'ro.product.model');
|
|
56
|
+
return { manufacturer, model };
|
|
57
|
+
};
|
|
58
|
+
export const isBootCompleted = async (adbId) => {
|
|
59
|
+
const bootCompleted = await getShellProperty(adbId, 'sys.boot_completed');
|
|
60
|
+
return bootCompleted === '1';
|
|
61
|
+
};
|
|
62
|
+
export const stopEmulator = async (adbId) => {
|
|
63
|
+
await spawn('adb', ['-s', adbId, 'emu', 'kill']);
|
|
64
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../src/assertions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAE5D,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,IAAI,MAAM,GAAG;IAAE,MAAM,EAAE,sBAAsB,CAAA;CAAE,CAO/D"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const AndroidEmulatorSchema: z.ZodObject<{
|
|
3
|
+
type: z.ZodLiteral<"emulator">;
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
type: "emulator";
|
|
7
|
+
name: string;
|
|
8
|
+
}, {
|
|
9
|
+
type: "emulator";
|
|
10
|
+
name: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const PhysicalAndroidDeviceSchema: z.ZodObject<{
|
|
13
|
+
type: z.ZodLiteral<"physical">;
|
|
14
|
+
manufacturer: z.ZodString;
|
|
15
|
+
model: z.ZodString;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
type: "physical";
|
|
18
|
+
manufacturer: string;
|
|
19
|
+
model: string;
|
|
20
|
+
}, {
|
|
21
|
+
type: "physical";
|
|
22
|
+
manufacturer: string;
|
|
23
|
+
model: string;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const AndroidDeviceSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
26
|
+
type: z.ZodLiteral<"emulator">;
|
|
27
|
+
name: z.ZodString;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
type: "emulator";
|
|
30
|
+
name: string;
|
|
31
|
+
}, {
|
|
32
|
+
type: "emulator";
|
|
33
|
+
name: string;
|
|
34
|
+
}>, z.ZodObject<{
|
|
35
|
+
type: z.ZodLiteral<"physical">;
|
|
36
|
+
manufacturer: z.ZodString;
|
|
37
|
+
model: z.ZodString;
|
|
38
|
+
}, "strip", z.ZodTypeAny, {
|
|
39
|
+
type: "physical";
|
|
40
|
+
manufacturer: string;
|
|
41
|
+
model: string;
|
|
42
|
+
}, {
|
|
43
|
+
type: "physical";
|
|
44
|
+
manufacturer: string;
|
|
45
|
+
model: string;
|
|
46
|
+
}>]>;
|
|
47
|
+
export declare const AndroidPlatformConfigSchema: z.ZodObject<{
|
|
48
|
+
name: z.ZodString;
|
|
49
|
+
device: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
50
|
+
type: z.ZodLiteral<"emulator">;
|
|
51
|
+
name: z.ZodString;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
type: "emulator";
|
|
54
|
+
name: string;
|
|
55
|
+
}, {
|
|
56
|
+
type: "emulator";
|
|
57
|
+
name: string;
|
|
58
|
+
}>, z.ZodObject<{
|
|
59
|
+
type: z.ZodLiteral<"physical">;
|
|
60
|
+
manufacturer: z.ZodString;
|
|
61
|
+
model: z.ZodString;
|
|
62
|
+
}, "strip", z.ZodTypeAny, {
|
|
63
|
+
type: "physical";
|
|
64
|
+
manufacturer: string;
|
|
65
|
+
model: string;
|
|
66
|
+
}, {
|
|
67
|
+
type: "physical";
|
|
68
|
+
manufacturer: string;
|
|
69
|
+
model: string;
|
|
70
|
+
}>]>;
|
|
71
|
+
bundleId: z.ZodString;
|
|
72
|
+
activityName: z.ZodDefault<z.ZodString>;
|
|
73
|
+
}, "strip", z.ZodTypeAny, {
|
|
74
|
+
name: string;
|
|
75
|
+
device: {
|
|
76
|
+
type: "emulator";
|
|
77
|
+
name: string;
|
|
78
|
+
} | {
|
|
79
|
+
type: "physical";
|
|
80
|
+
manufacturer: string;
|
|
81
|
+
model: string;
|
|
82
|
+
};
|
|
83
|
+
bundleId: string;
|
|
84
|
+
activityName: string;
|
|
85
|
+
}, {
|
|
86
|
+
name: string;
|
|
87
|
+
device: {
|
|
88
|
+
type: "emulator";
|
|
89
|
+
name: string;
|
|
90
|
+
} | {
|
|
91
|
+
type: "physical";
|
|
92
|
+
manufacturer: string;
|
|
93
|
+
model: string;
|
|
94
|
+
};
|
|
95
|
+
bundleId: string;
|
|
96
|
+
activityName?: string | undefined;
|
|
97
|
+
}>;
|
|
98
|
+
export type AndroidEmulator = z.infer<typeof AndroidEmulatorSchema>;
|
|
99
|
+
export type PhysicalAndroidDevice = z.infer<typeof PhysicalAndroidDeviceSchema>;
|
|
100
|
+
export type AndroidDevice = z.infer<typeof AndroidDeviceSchema>;
|
|
101
|
+
export type AndroidPlatformConfig = z.infer<typeof AndroidPlatformConfigSchema>;
|
|
102
|
+
export declare const isAndroidDeviceEmulator: (device: AndroidDevice) => device is AndroidEmulator;
|
|
103
|
+
export declare const isAndroidDevicePhysical: (device: AndroidDevice) => device is PhysicalAndroidDevice;
|
|
104
|
+
export declare function assertAndroidDeviceEmulator(device: AndroidDevice): asserts device is AndroidEmulator;
|
|
105
|
+
export declare function assertAndroidDevicePhysical(device: AndroidDevice): asserts device is PhysicalAndroidDevice;
|
|
106
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,qBAAqB;;;;;;;;;EAGhC,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;EAItC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;IAG9B,CAAC;AAEH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQtC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAChF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,KACpB,MAAM,IAAI,eAEZ,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,KACpB,MAAM,IAAI,qBAEZ,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,IAAI,eAAe,CAInC;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,IAAI,qBAAqB,CAIzC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const AndroidEmulatorSchema = z.object({
|
|
3
|
+
type: z.literal('emulator'),
|
|
4
|
+
name: z.string().min(1, 'Name is required'),
|
|
5
|
+
});
|
|
6
|
+
export const PhysicalAndroidDeviceSchema = z.object({
|
|
7
|
+
type: z.literal('physical'),
|
|
8
|
+
manufacturer: z.string().min(1, 'Manufacturer is required'),
|
|
9
|
+
model: z.string().min(1, 'Model is required'),
|
|
10
|
+
});
|
|
11
|
+
export const AndroidDeviceSchema = z.discriminatedUnion('type', [
|
|
12
|
+
AndroidEmulatorSchema,
|
|
13
|
+
PhysicalAndroidDeviceSchema,
|
|
14
|
+
]);
|
|
15
|
+
export const AndroidPlatformConfigSchema = z.object({
|
|
16
|
+
name: z.string().min(1, 'Name is required'),
|
|
17
|
+
device: AndroidDeviceSchema,
|
|
18
|
+
bundleId: z.string().min(1, 'Bundle ID is required'),
|
|
19
|
+
activityName: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1, 'Activity name is required')
|
|
22
|
+
.default('.MainActivity'),
|
|
23
|
+
});
|
|
24
|
+
export const isAndroidDeviceEmulator = (device) => {
|
|
25
|
+
return device.type === 'emulator';
|
|
26
|
+
};
|
|
27
|
+
export const isAndroidDevicePhysical = (device) => {
|
|
28
|
+
return device.type === 'physical';
|
|
29
|
+
};
|
|
30
|
+
export function assertAndroidDeviceEmulator(device) {
|
|
31
|
+
if (!isAndroidDeviceEmulator(device)) {
|
|
32
|
+
throw new Error('Device is not an emulator');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function assertAndroidDevicePhysical(device) {
|
|
36
|
+
if (!isAndroidDevicePhysical(device)) {
|
|
37
|
+
throw new Error('Device is not a physical device');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emulator.d.ts","sourceRoot":"","sources":["../src/emulator.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,SAAS,MAAM,KACd,OAAO,CAAC,eAAe,CA8BzB,CAAC"}
|
package/dist/emulator.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawn } from '@react-native-harness/tools';
|
|
2
|
+
import * as adb from './adb.js';
|
|
3
|
+
export const runEmulator = async (avdName) => {
|
|
4
|
+
const process = spawn('emulator', ['-avd', avdName]);
|
|
5
|
+
await process.nodeChildProcess;
|
|
6
|
+
const adbId = await adb.getEmulatorName(avdName);
|
|
7
|
+
if (!adbId) {
|
|
8
|
+
throw new Error('Emulator not found');
|
|
9
|
+
}
|
|
10
|
+
// Poll for emulator status until it's fully running
|
|
11
|
+
const checkStatus = async () => {
|
|
12
|
+
const status = await adb.isBootCompleted(adbId);
|
|
13
|
+
if (!status) {
|
|
14
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
15
|
+
await checkStatus();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
// Start checking status after a brief delay to allow emulator to start
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
20
|
+
await checkStatus();
|
|
21
|
+
return {
|
|
22
|
+
adbId,
|
|
23
|
+
stop: async () => {
|
|
24
|
+
await adb.stopEmulator(adbId);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class ConfigValidationError extends Error {
|
|
2
|
+
readonly filePath: string;
|
|
3
|
+
readonly validationErrors: string[];
|
|
4
|
+
constructor(filePath: string, validationErrors: string[]);
|
|
5
|
+
}
|
|
6
|
+
export declare class ConfigNotFoundError extends Error {
|
|
7
|
+
readonly searchPath: string;
|
|
8
|
+
constructor(searchPath: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class ConfigLoadError extends Error {
|
|
11
|
+
readonly filePath: string;
|
|
12
|
+
readonly cause?: Error;
|
|
13
|
+
constructor(filePath: string, cause?: Error);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,qBAAsB,SAAQ,KAAK;aAExB,QAAQ,EAAE,MAAM;aAChB,gBAAgB,EAAE,MAAM,EAAE;gBAD1B,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,EAAE;CAKjD;AAED,qBAAa,mBAAoB,SAAQ,KAAK;aACd,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAIjD;AAED,qBAAa,eAAgB,SAAQ,KAAK;aAGV,QAAQ,EAAE,MAAM;IAF5C,SAAyB,KAAK,CAAC,EAAE,KAAK,CAAC;gBAEX,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAK9D"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class ConfigValidationError extends Error {
|
|
2
|
+
filePath;
|
|
3
|
+
validationErrors;
|
|
4
|
+
constructor(filePath, validationErrors) {
|
|
5
|
+
super(`Invalid configuration in ${filePath}`);
|
|
6
|
+
this.filePath = filePath;
|
|
7
|
+
this.validationErrors = validationErrors;
|
|
8
|
+
this.name = 'ConfigValidationError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class ConfigNotFoundError extends Error {
|
|
12
|
+
searchPath;
|
|
13
|
+
constructor(searchPath) {
|
|
14
|
+
super(`Config file not found in ${searchPath} or any parent directories`);
|
|
15
|
+
this.searchPath = searchPath;
|
|
16
|
+
this.name = 'ConfigNotFoundError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class ConfigLoadError extends Error {
|
|
20
|
+
filePath;
|
|
21
|
+
cause;
|
|
22
|
+
constructor(filePath, cause) {
|
|
23
|
+
super(`Failed to load config file ${filePath}`);
|
|
24
|
+
this.filePath = filePath;
|
|
25
|
+
this.name = 'ConfigLoadError';
|
|
26
|
+
this.cause = cause;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { HarnessPlatform } from '@react-native-harness/platforms';
|
|
2
|
+
import { type AndroidEmulator, type AndroidPlatformConfig, type PhysicalAndroidDevice } from './config.js';
|
|
3
|
+
export declare const androidEmulator: (name: string) => AndroidEmulator;
|
|
4
|
+
export declare const physicalAndroidDevice: (manufacturer: string, model: string) => PhysicalAndroidDevice;
|
|
5
|
+
export declare const androidPlatform: (config: AndroidPlatformConfig) => HarnessPlatform;
|
|
6
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,eAAe,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAKrB,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,eAG7C,CAAC;AAEH,eAAO,MAAM,qBAAqB,GAChC,cAAc,MAAM,EACpB,OAAO,MAAM,KACZ,qBAID,CAAC;AAEH,eAAO,MAAM,eAAe,GAC1B,QAAQ,qBAAqB,KAC5B,eAiDD,CAAC"}
|
package/dist/factory.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DeviceNotFoundError, AppNotInstalledError, } from '@react-native-harness/platforms';
|
|
2
|
+
import { AndroidPlatformConfigSchema, } from './config.js';
|
|
3
|
+
import { getAdbId } from './adb-id.js';
|
|
4
|
+
import * as adb from './adb.js';
|
|
5
|
+
import { getDeviceName } from './utils.js';
|
|
6
|
+
export const androidEmulator = (name) => ({
|
|
7
|
+
type: 'emulator',
|
|
8
|
+
name,
|
|
9
|
+
});
|
|
10
|
+
export const physicalAndroidDevice = (manufacturer, model) => ({
|
|
11
|
+
type: 'physical',
|
|
12
|
+
manufacturer: manufacturer.toLowerCase(),
|
|
13
|
+
model: model.toLowerCase(),
|
|
14
|
+
});
|
|
15
|
+
export const androidPlatform = (config) => ({
|
|
16
|
+
name: config.name,
|
|
17
|
+
getInstance: async () => {
|
|
18
|
+
const parsedConfig = AndroidPlatformConfigSchema.parse(config);
|
|
19
|
+
const adbId = await getAdbId(parsedConfig.device);
|
|
20
|
+
if (!adbId) {
|
|
21
|
+
throw new DeviceNotFoundError(getDeviceName(parsedConfig.device));
|
|
22
|
+
}
|
|
23
|
+
const isInstalled = await adb.isAppInstalled(adbId, parsedConfig.bundleId);
|
|
24
|
+
if (!isInstalled) {
|
|
25
|
+
throw new AppNotInstalledError(parsedConfig.bundleId, getDeviceName(parsedConfig.device));
|
|
26
|
+
}
|
|
27
|
+
await Promise.all([
|
|
28
|
+
adb.reversePort(adbId, 8081),
|
|
29
|
+
adb.reversePort(adbId, 8080),
|
|
30
|
+
adb.reversePort(adbId, 3001),
|
|
31
|
+
]);
|
|
32
|
+
return {
|
|
33
|
+
startApp: async () => {
|
|
34
|
+
await adb.startApp(adbId, parsedConfig.bundleId, parsedConfig.activityName);
|
|
35
|
+
},
|
|
36
|
+
restartApp: async () => {
|
|
37
|
+
await adb.stopApp(adbId, parsedConfig.bundleId);
|
|
38
|
+
await adb.startApp(adbId, parsedConfig.bundleId, parsedConfig.activityName);
|
|
39
|
+
},
|
|
40
|
+
stopApp: async () => {
|
|
41
|
+
await adb.stopApp(adbId, parsedConfig.bundleId);
|
|
42
|
+
},
|
|
43
|
+
dispose: async () => {
|
|
44
|
+
await adb.stopApp(adbId, parsedConfig.bundleId);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { androidEmulator, physicalAndroidDevice, androidPlatform, } from './factory.js';
|
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,MAAM,YAAY,CAAC;AAsElD,eAAO,MAAM,SAAS,GACpB,KAAK,MAAM,KACV,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAUjD,CAAC"}
|
package/dist/reader.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ConfigSchema } from './types.js';
|
|
2
|
+
import { ConfigValidationError, ConfigNotFoundError, ConfigLoadError, } from './errors.js';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { ZodError } from 'zod';
|
|
7
|
+
const extensions = ['.js', '.mjs', '.cjs', '.json'];
|
|
8
|
+
const importUp = async (dir, name) => {
|
|
9
|
+
const filePath = path.join(dir, name);
|
|
10
|
+
for (const ext of extensions) {
|
|
11
|
+
const filePathWithExt = `${filePath}${ext}`;
|
|
12
|
+
if (fs.existsSync(filePathWithExt)) {
|
|
13
|
+
let rawConfig;
|
|
14
|
+
try {
|
|
15
|
+
if (ext === '.mjs') {
|
|
16
|
+
rawConfig = await import(filePathWithExt).then((module) => module.default);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
rawConfig = require(filePathWithExt);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new ConfigLoadError(filePathWithExt, error instanceof Error ? error : undefined);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const config = ConfigSchema.parse(rawConfig);
|
|
28
|
+
return { config, filePathWithExt, configDir: dir };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof ZodError) {
|
|
32
|
+
const validationErrors = error.errors.map((err) => {
|
|
33
|
+
const path = err.path.length > 0 ? ` at "${err.path.join('.')}"` : '';
|
|
34
|
+
return `${err.message}${path}`;
|
|
35
|
+
});
|
|
36
|
+
throw new ConfigValidationError(filePathWithExt, validationErrors);
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const parentDir = path.dirname(dir);
|
|
43
|
+
if (parentDir === dir) {
|
|
44
|
+
throw new ConfigNotFoundError(dir);
|
|
45
|
+
}
|
|
46
|
+
return importUp(parentDir, name);
|
|
47
|
+
};
|
|
48
|
+
export const getConfig = async (dir) => {
|
|
49
|
+
const { config, configDir } = await importUp(dir, 'rn-harness.config');
|
|
50
|
+
return {
|
|
51
|
+
config: {
|
|
52
|
+
...config,
|
|
53
|
+
reporter: config.reporter,
|
|
54
|
+
},
|
|
55
|
+
projectRoot: configDir,
|
|
56
|
+
};
|
|
57
|
+
};
|