@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/src/adb-id.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as adb from './adb.js';
|
|
2
|
+
import {
|
|
3
|
+
isAndroidDeviceEmulator,
|
|
4
|
+
isAndroidDevicePhysical,
|
|
5
|
+
AndroidDevice,
|
|
6
|
+
} from './config.js';
|
|
7
|
+
|
|
8
|
+
export const isAdbIdEmulator = (adbId: string): boolean => {
|
|
9
|
+
return adbId.startsWith('emulator-');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const getAdbId = async (
|
|
13
|
+
device: AndroidDevice
|
|
14
|
+
): Promise<string | null> => {
|
|
15
|
+
const adbIds = await adb.getDeviceIds();
|
|
16
|
+
|
|
17
|
+
for (const adbId of adbIds) {
|
|
18
|
+
if (isAndroidDeviceEmulator(device)) {
|
|
19
|
+
const emulatorName = await adb.getEmulatorName(adbId);
|
|
20
|
+
|
|
21
|
+
if (emulatorName === device.name) {
|
|
22
|
+
return adbId;
|
|
23
|
+
}
|
|
24
|
+
} else if (isAndroidDevicePhysical(device)) {
|
|
25
|
+
const deviceInfo = await adb.getDeviceInfo(adbId);
|
|
26
|
+
if (
|
|
27
|
+
deviceInfo?.manufacturer === device.manufacturer &&
|
|
28
|
+
deviceInfo?.model === device.model
|
|
29
|
+
) {
|
|
30
|
+
return adbId;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
};
|
package/src/adb.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { spawn } from '@react-native-harness/tools';
|
|
2
|
+
|
|
3
|
+
export const isAppInstalled = async (
|
|
4
|
+
adbId: string,
|
|
5
|
+
bundleId: string
|
|
6
|
+
): Promise<boolean> => {
|
|
7
|
+
const { stdout } = await spawn('adb', [
|
|
8
|
+
'-s',
|
|
9
|
+
adbId,
|
|
10
|
+
'shell',
|
|
11
|
+
'pm',
|
|
12
|
+
'list',
|
|
13
|
+
'packages',
|
|
14
|
+
bundleId,
|
|
15
|
+
]);
|
|
16
|
+
return stdout.trim() !== '';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const reversePort = async (
|
|
20
|
+
adbId: string,
|
|
21
|
+
port: number
|
|
22
|
+
): Promise<void> => {
|
|
23
|
+
await spawn('adb', ['-s', adbId, 'reverse', `tcp:${port}`, `tcp:${port}`]);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const stopApp = async (
|
|
27
|
+
adbId: string,
|
|
28
|
+
bundleId: string
|
|
29
|
+
): Promise<void> => {
|
|
30
|
+
await spawn('adb', ['-s', adbId, 'shell', 'am', 'force-stop', bundleId]);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const startApp = async (
|
|
34
|
+
adbId: string,
|
|
35
|
+
bundleId: string,
|
|
36
|
+
activityName: string
|
|
37
|
+
): Promise<void> => {
|
|
38
|
+
await spawn('adb', [
|
|
39
|
+
'-s',
|
|
40
|
+
adbId,
|
|
41
|
+
'shell',
|
|
42
|
+
'am',
|
|
43
|
+
'start',
|
|
44
|
+
'-n',
|
|
45
|
+
`${bundleId}/${activityName}`,
|
|
46
|
+
]);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const getDeviceIds = async (): Promise<string[]> => {
|
|
50
|
+
const { stdout } = await spawn('adb', ['devices']);
|
|
51
|
+
return stdout
|
|
52
|
+
.split('\n')
|
|
53
|
+
.slice(1) // Skip header
|
|
54
|
+
.filter((line) => line.trim() !== '')
|
|
55
|
+
.map((line) => line.split('\t')[0]);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const getEmulatorName = async (
|
|
59
|
+
adbId: string
|
|
60
|
+
): Promise<string | null> => {
|
|
61
|
+
const { stdout } = await spawn('adb', ['-s', adbId, 'emu', 'avd', 'name']);
|
|
62
|
+
return stdout.split('\n')[0].trim() || null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const getShellProperty = async (
|
|
66
|
+
adbId: string,
|
|
67
|
+
property: string
|
|
68
|
+
): Promise<string | null> => {
|
|
69
|
+
const { stdout } = await spawn('adb', [
|
|
70
|
+
'-s',
|
|
71
|
+
adbId,
|
|
72
|
+
'shell',
|
|
73
|
+
'getprop',
|
|
74
|
+
property,
|
|
75
|
+
]);
|
|
76
|
+
return stdout.trim() || null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type DeviceInfo = {
|
|
80
|
+
manufacturer: string | null;
|
|
81
|
+
model: string | null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const getDeviceInfo = async (
|
|
85
|
+
adbId: string
|
|
86
|
+
): Promise<DeviceInfo | null> => {
|
|
87
|
+
const manufacturer = await getShellProperty(adbId, 'ro.product.manufacturer');
|
|
88
|
+
const model = await getShellProperty(adbId, 'ro.product.model');
|
|
89
|
+
return { manufacturer, model };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const isBootCompleted = async (adbId: string): Promise<boolean> => {
|
|
93
|
+
const bootCompleted = await getShellProperty(adbId, 'sys.boot_completed');
|
|
94
|
+
return bootCompleted === '1';
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const stopEmulator = async (adbId: string): Promise<void> => {
|
|
98
|
+
await spawn('adb', ['-s', adbId, 'emu', 'kill']);
|
|
99
|
+
};
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const AndroidEmulatorSchema = z.object({
|
|
4
|
+
type: z.literal('emulator'),
|
|
5
|
+
name: z.string().min(1, 'Name is required'),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const PhysicalAndroidDeviceSchema = z.object({
|
|
9
|
+
type: z.literal('physical'),
|
|
10
|
+
manufacturer: z.string().min(1, 'Manufacturer is required'),
|
|
11
|
+
model: z.string().min(1, 'Model is required'),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const AndroidDeviceSchema = z.discriminatedUnion('type', [
|
|
15
|
+
AndroidEmulatorSchema,
|
|
16
|
+
PhysicalAndroidDeviceSchema,
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export const AndroidPlatformConfigSchema = z.object({
|
|
20
|
+
name: z.string().min(1, 'Name is required'),
|
|
21
|
+
device: AndroidDeviceSchema,
|
|
22
|
+
bundleId: z.string().min(1, 'Bundle ID is required'),
|
|
23
|
+
activityName: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1, 'Activity name is required')
|
|
26
|
+
.default('.MainActivity'),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type AndroidEmulator = z.infer<typeof AndroidEmulatorSchema>;
|
|
30
|
+
export type PhysicalAndroidDevice = z.infer<typeof PhysicalAndroidDeviceSchema>;
|
|
31
|
+
export type AndroidDevice = z.infer<typeof AndroidDeviceSchema>;
|
|
32
|
+
export type AndroidPlatformConfig = z.infer<typeof AndroidPlatformConfigSchema>;
|
|
33
|
+
|
|
34
|
+
export const isAndroidDeviceEmulator = (
|
|
35
|
+
device: AndroidDevice
|
|
36
|
+
): device is AndroidEmulator => {
|
|
37
|
+
return device.type === 'emulator';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const isAndroidDevicePhysical = (
|
|
41
|
+
device: AndroidDevice
|
|
42
|
+
): device is PhysicalAndroidDevice => {
|
|
43
|
+
return device.type === 'physical';
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function assertAndroidDeviceEmulator(
|
|
47
|
+
device: AndroidDevice
|
|
48
|
+
): asserts device is AndroidEmulator {
|
|
49
|
+
if (!isAndroidDeviceEmulator(device)) {
|
|
50
|
+
throw new Error('Device is not an emulator');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function assertAndroidDevicePhysical(
|
|
55
|
+
device: AndroidDevice
|
|
56
|
+
): asserts device is PhysicalAndroidDevice {
|
|
57
|
+
if (!isAndroidDevicePhysical(device)) {
|
|
58
|
+
throw new Error('Device is not a physical device');
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/factory.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeviceNotFoundError,
|
|
3
|
+
AppNotInstalledError,
|
|
4
|
+
HarnessPlatform,
|
|
5
|
+
} from '@react-native-harness/platforms';
|
|
6
|
+
import {
|
|
7
|
+
AndroidPlatformConfigSchema,
|
|
8
|
+
type AndroidEmulator,
|
|
9
|
+
type AndroidPlatformConfig,
|
|
10
|
+
type PhysicalAndroidDevice,
|
|
11
|
+
} from './config.js';
|
|
12
|
+
import { getAdbId } from './adb-id.js';
|
|
13
|
+
import * as adb from './adb.js';
|
|
14
|
+
import { getDeviceName } from './utils.js';
|
|
15
|
+
|
|
16
|
+
export const androidEmulator = (name: string): AndroidEmulator => ({
|
|
17
|
+
type: 'emulator',
|
|
18
|
+
name,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const physicalAndroidDevice = (
|
|
22
|
+
manufacturer: string,
|
|
23
|
+
model: string
|
|
24
|
+
): PhysicalAndroidDevice => ({
|
|
25
|
+
type: 'physical',
|
|
26
|
+
manufacturer: manufacturer.toLowerCase(),
|
|
27
|
+
model: model.toLowerCase(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const androidPlatform = (
|
|
31
|
+
config: AndroidPlatformConfig
|
|
32
|
+
): HarnessPlatform => ({
|
|
33
|
+
name: config.name,
|
|
34
|
+
getInstance: async () => {
|
|
35
|
+
const parsedConfig = AndroidPlatformConfigSchema.parse(config);
|
|
36
|
+
const adbId = await getAdbId(parsedConfig.device);
|
|
37
|
+
|
|
38
|
+
if (!adbId) {
|
|
39
|
+
throw new DeviceNotFoundError(getDeviceName(parsedConfig.device));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isInstalled = await adb.isAppInstalled(adbId, parsedConfig.bundleId);
|
|
43
|
+
|
|
44
|
+
if (!isInstalled) {
|
|
45
|
+
throw new AppNotInstalledError(
|
|
46
|
+
parsedConfig.bundleId,
|
|
47
|
+
getDeviceName(parsedConfig.device)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await Promise.all([
|
|
52
|
+
adb.reversePort(adbId, 8081),
|
|
53
|
+
adb.reversePort(adbId, 8080),
|
|
54
|
+
adb.reversePort(adbId, 3001),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
startApp: async () => {
|
|
59
|
+
await adb.startApp(
|
|
60
|
+
adbId,
|
|
61
|
+
parsedConfig.bundleId,
|
|
62
|
+
parsedConfig.activityName
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
restartApp: async () => {
|
|
66
|
+
await adb.stopApp(adbId, parsedConfig.bundleId);
|
|
67
|
+
await adb.startApp(
|
|
68
|
+
adbId,
|
|
69
|
+
parsedConfig.bundleId,
|
|
70
|
+
parsedConfig.activityName
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
stopApp: async () => {
|
|
74
|
+
await adb.stopApp(adbId, parsedConfig.bundleId);
|
|
75
|
+
},
|
|
76
|
+
dispose: async () => {
|
|
77
|
+
await adb.stopApp(adbId, parsedConfig.bundleId);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
package/src/index.ts
ADDED
package/src/utils.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { isAndroidDeviceEmulator, type AndroidDevice } from './config.js';
|
|
2
|
+
|
|
3
|
+
export const getDeviceName = (device: AndroidDevice): string => {
|
|
4
|
+
if (isAndroidDeviceEmulator(device)) {
|
|
5
|
+
return `${device.name} (emulator)`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return `${device.manufacturer} ${device.model}`;
|
|
9
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"rootDir": "src",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
|
8
|
+
"emitDeclarationOnly": false,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts"],
|
|
13
|
+
"references": [
|
|
14
|
+
{
|
|
15
|
+
"path": "../tools/tsconfig.lib.json"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "../platforms/tsconfig.lib.json"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|