@react-native-harness/cli 1.0.0-alpha.11 → 1.0.0-alpha.13
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/commands/test.d.ts +2 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +18 -16
- 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.map +1 -1
- package/dist/errors/errorHandler.js +12 -2
- package/dist/errors/errors.d.ts +2 -2
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +6 -1
- package/dist/index.js +20 -4
- package/dist/platforms/android/index.js +1 -1
- package/dist/platforms/platform-registry.d.ts.map +1 -1
- package/dist/platforms/platform-registry.js +2 -0
- 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/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/commands/test.ts +32 -22
- package/src/discovery/index.ts +2 -0
- package/src/discovery/testDiscovery.ts +50 -0
- package/src/errors/errorHandler.ts +16 -4
- package/src/errors/errors.ts +9 -4
- package/src/index.ts +33 -5
- package/src/platforms/android/index.ts +1 -1
- package/src/platforms/platform-registry.ts +2 -0
- package/src/platforms/vega/build.ts +85 -0
- package/src/platforms/vega/device.ts +258 -0
- package/src/platforms/vega/index.ts +107 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Glob } from 'glob';
|
|
2
|
+
|
|
3
|
+
export type TestFilterOptions = {
|
|
4
|
+
testNamePattern?: string;
|
|
5
|
+
testPathPattern?: string;
|
|
6
|
+
testPathIgnorePatterns?: string[];
|
|
7
|
+
testMatch?: string[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Discovers test files based on patterns and filtering options
|
|
12
|
+
*/
|
|
13
|
+
export const discoverTestFiles = async (
|
|
14
|
+
projectRoot: string,
|
|
15
|
+
configInclude: string | string[],
|
|
16
|
+
options: TestFilterOptions = {}
|
|
17
|
+
): Promise<string[]> => {
|
|
18
|
+
// Priority: testMatch > configInclude
|
|
19
|
+
const patterns = options.testMatch || configInclude;
|
|
20
|
+
const patternArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
21
|
+
|
|
22
|
+
// Glob discovery
|
|
23
|
+
const allFiles: string[] = [];
|
|
24
|
+
for (const pattern of patternArray) {
|
|
25
|
+
const glob = new Glob(pattern, { cwd: projectRoot, nodir: true });
|
|
26
|
+
const files = await glob.walk();
|
|
27
|
+
allFiles.push(...files);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Remove duplicates
|
|
31
|
+
let uniqueFiles = [...new Set(allFiles)];
|
|
32
|
+
|
|
33
|
+
// Apply testPathPattern filtering
|
|
34
|
+
if (options.testPathPattern) {
|
|
35
|
+
const regex = new RegExp(options.testPathPattern);
|
|
36
|
+
uniqueFiles = uniqueFiles.filter((file) => regex.test(file));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Apply testPathIgnorePatterns filtering
|
|
40
|
+
if (options.testPathIgnorePatterns?.length) {
|
|
41
|
+
const ignoreRegexes = options.testPathIgnorePatterns.map(
|
|
42
|
+
(p) => new RegExp(p)
|
|
43
|
+
);
|
|
44
|
+
uniqueFiles = uniqueFiles.filter(
|
|
45
|
+
(file) => !ignoreRegexes.some((regex) => regex.test(file))
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return uniqueFiles;
|
|
50
|
+
};
|
|
@@ -128,10 +128,14 @@ export const handleError = (error: unknown): void => {
|
|
|
128
128
|
console.error(`\nPlease check your bridge connection and try again.`);
|
|
129
129
|
} else if (error instanceof AppNotInstalledError) {
|
|
130
130
|
console.error(`\n❌ App Not Installed`);
|
|
131
|
+
const deviceType =
|
|
132
|
+
error.platform === 'ios'
|
|
133
|
+
? 'simulator'
|
|
134
|
+
: error.platform === 'android'
|
|
135
|
+
? 'emulator'
|
|
136
|
+
: 'virtual device';
|
|
131
137
|
console.error(
|
|
132
|
-
`\nThe app "${error.bundleId}" is not installed on ${
|
|
133
|
-
error.platform === 'ios' ? 'simulator' : 'emulator'
|
|
134
|
-
} "${error.deviceName}".`
|
|
138
|
+
`\nThe app "${error.bundleId}" is not installed on ${deviceType} "${error.deviceName}".`
|
|
135
139
|
);
|
|
136
140
|
console.error(`\nTo resolve this issue:`);
|
|
137
141
|
if (error.platform === 'ios') {
|
|
@@ -141,13 +145,21 @@ export const handleError = (error: unknown): void => {
|
|
|
141
145
|
console.error(
|
|
142
146
|
` • Or install from Xcode: Open ios/*.xcworkspace and run the project`
|
|
143
147
|
);
|
|
144
|
-
} else {
|
|
148
|
+
} else if (error.platform === 'android') {
|
|
145
149
|
console.error(
|
|
146
150
|
` • Build and install the app: npx react-native run-android`
|
|
147
151
|
);
|
|
148
152
|
console.error(
|
|
149
153
|
` • Or build manually: ./gradlew assembleDebug && adb install android/app/build/outputs/apk/debug/app-debug.apk`
|
|
150
154
|
);
|
|
155
|
+
} else if (error.platform === 'vega') {
|
|
156
|
+
console.error(` • Build the Vega app: npm run build:app`);
|
|
157
|
+
console.error(
|
|
158
|
+
` • Install the app: kepler device install-app -p <path-to-vpkg> --device "${error.deviceName}"`
|
|
159
|
+
);
|
|
160
|
+
console.error(
|
|
161
|
+
` • Or use the combined command: kepler run-kepler <path-to-vpkg> "${error.bundleId}" -d "${error.deviceName}"`
|
|
162
|
+
);
|
|
151
163
|
}
|
|
152
164
|
console.error(`\nPlease install the app and try running the tests again.`);
|
|
153
165
|
} else if (error instanceof BundlingFailedError) {
|
package/src/errors/errors.ts
CHANGED
|
@@ -97,12 +97,17 @@ export class AppNotInstalledError extends Error {
|
|
|
97
97
|
constructor(
|
|
98
98
|
public readonly deviceName: string,
|
|
99
99
|
public readonly bundleId: string,
|
|
100
|
-
public readonly platform: 'ios' | 'android'
|
|
100
|
+
public readonly platform: 'ios' | 'android' | 'vega'
|
|
101
101
|
) {
|
|
102
|
+
const deviceType =
|
|
103
|
+
platform === 'ios'
|
|
104
|
+
? 'simulator'
|
|
105
|
+
: platform === 'android'
|
|
106
|
+
? 'emulator'
|
|
107
|
+
: 'virtual device';
|
|
108
|
+
|
|
102
109
|
super(
|
|
103
|
-
`App "${bundleId}" is not installed on ${
|
|
104
|
-
platform === 'ios' ? 'simulator' : 'emulator'
|
|
105
|
-
} "${deviceName}"`
|
|
110
|
+
`App "${bundleId}" is not installed on ${deviceType} "${deviceName}"`
|
|
106
111
|
);
|
|
107
112
|
this.name = 'AppNotInstalledError';
|
|
108
113
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,14 +13,20 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
|
13
13
|
|
|
14
14
|
const program = new Command();
|
|
15
15
|
|
|
16
|
-
logger.setVerbose(true);
|
|
17
|
-
|
|
18
16
|
program
|
|
19
17
|
.name('react-native-harness')
|
|
20
18
|
.description(
|
|
21
19
|
'React Native Test Harness - A comprehensive testing framework for React Native applications'
|
|
22
20
|
)
|
|
23
|
-
.version(packageJson.version)
|
|
21
|
+
.version(packageJson.version)
|
|
22
|
+
.option('-v, --verbose', 'Enable verbose logging')
|
|
23
|
+
.hook('preAction', (thisCommand) => {
|
|
24
|
+
// Handle global verbose option
|
|
25
|
+
const opts = thisCommand.optsWithGlobals();
|
|
26
|
+
if (opts.verbose) {
|
|
27
|
+
logger.setVerbose(true);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
24
30
|
|
|
25
31
|
program
|
|
26
32
|
.command('test')
|
|
@@ -33,9 +39,31 @@ program
|
|
|
33
39
|
'[pattern]',
|
|
34
40
|
'glob pattern to match test files (uses config.include if not specified)'
|
|
35
41
|
)
|
|
36
|
-
.
|
|
42
|
+
.option(
|
|
43
|
+
'-t, --testNamePattern <pattern>',
|
|
44
|
+
'Run only tests with names matching regex pattern'
|
|
45
|
+
)
|
|
46
|
+
.option(
|
|
47
|
+
'--testPathPattern <pattern>',
|
|
48
|
+
'Run only test files with paths matching regex pattern'
|
|
49
|
+
)
|
|
50
|
+
.option(
|
|
51
|
+
'--testPathIgnorePatterns <patterns...>',
|
|
52
|
+
'Ignore test files matching these patterns'
|
|
53
|
+
)
|
|
54
|
+
.option(
|
|
55
|
+
'--testMatch <patterns...>',
|
|
56
|
+
'Override config.include with these glob patterns'
|
|
57
|
+
)
|
|
58
|
+
.action(async (runner, pattern, options) => {
|
|
37
59
|
try {
|
|
38
|
-
|
|
60
|
+
// Convert CLI pattern argument to testMatch option
|
|
61
|
+
const mergedOptions = {
|
|
62
|
+
...options,
|
|
63
|
+
testMatch: pattern ? [pattern] : options.testMatch,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
await testCommand(runner, mergedOptions);
|
|
39
67
|
} catch (error) {
|
|
40
68
|
handleError(error);
|
|
41
69
|
process.exit(1);
|
|
@@ -69,7 +69,7 @@ const androidPlatformAdapter: PlatformAdapter = {
|
|
|
69
69
|
|
|
70
70
|
return {
|
|
71
71
|
restart: async () => {
|
|
72
|
-
await runApp(
|
|
72
|
+
await runApp(deviceId, runner.bundleId, runner.activityName);
|
|
73
73
|
},
|
|
74
74
|
dispose: async () => {
|
|
75
75
|
await killApp(deviceId, runner.bundleId);
|
|
@@ -2,11 +2,13 @@ import { PlatformAdapter } from './platform-adapter.js';
|
|
|
2
2
|
import androidPlatformAdapter from './android/index.js';
|
|
3
3
|
import iosPlatformAdapter from './ios/index.js';
|
|
4
4
|
import webPlatformAdapter from './web/index.js';
|
|
5
|
+
import vegaPlatformAdapter from './vega/index.js';
|
|
5
6
|
|
|
6
7
|
const platformAdapters = {
|
|
7
8
|
android: androidPlatformAdapter,
|
|
8
9
|
ios: iosPlatformAdapter,
|
|
9
10
|
web: webPlatformAdapter,
|
|
11
|
+
vega: vegaPlatformAdapter,
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export const getPlatformAdapter = async (
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { spawn } from '@react-native-harness/tools';
|
|
3
|
+
|
|
4
|
+
export type VegaBuildTarget = 'sim_tv_x86_64' | 'sim_tv_aarch64';
|
|
5
|
+
export type VegaBuildType = 'Debug' | 'Release';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build Vega app and produce .vpkg file
|
|
9
|
+
*/
|
|
10
|
+
export const buildVegaApp = async (
|
|
11
|
+
buildType: VegaBuildType = 'Release',
|
|
12
|
+
target?: VegaBuildTarget
|
|
13
|
+
): Promise<void> => {
|
|
14
|
+
const args = ['run', 'build:app'];
|
|
15
|
+
|
|
16
|
+
if (buildType) {
|
|
17
|
+
args.push('-b', buildType);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (target) {
|
|
21
|
+
args.push('-t', target);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await spawn('npm', args);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Clean build artifacts
|
|
29
|
+
*/
|
|
30
|
+
export const cleanBuild = async (): Promise<void> => {
|
|
31
|
+
await spawn('kepler', ['clean']);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the expected .vpkg file path based on build configuration
|
|
36
|
+
*/
|
|
37
|
+
export const getVpkgPath = (
|
|
38
|
+
appName: string,
|
|
39
|
+
buildType: VegaBuildType = 'Release',
|
|
40
|
+
target: VegaBuildTarget = 'sim_tv_x86_64'
|
|
41
|
+
): string => {
|
|
42
|
+
const buildTypeStr = buildType.toLowerCase();
|
|
43
|
+
const vpkgFileName = `${appName}_${target}.vpkg`;
|
|
44
|
+
|
|
45
|
+
return path.join(
|
|
46
|
+
process.cwd(),
|
|
47
|
+
'build',
|
|
48
|
+
`${target}-${buildTypeStr}`,
|
|
49
|
+
vpkgFileName
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Launch an already installed app on specified Vega virtual device
|
|
55
|
+
*/
|
|
56
|
+
export const runApp = async (
|
|
57
|
+
deviceId: string,
|
|
58
|
+
bundleId: string
|
|
59
|
+
): Promise<void> => {
|
|
60
|
+
await spawn('kepler', [
|
|
61
|
+
'device',
|
|
62
|
+
'launch-app',
|
|
63
|
+
'--device',
|
|
64
|
+
deviceId,
|
|
65
|
+
'--appName',
|
|
66
|
+
bundleId,
|
|
67
|
+
]);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Kill/terminate app on specified Vega virtual device
|
|
72
|
+
*/
|
|
73
|
+
export const killApp = async (
|
|
74
|
+
deviceId: string,
|
|
75
|
+
bundleId: string
|
|
76
|
+
): Promise<void> => {
|
|
77
|
+
await spawn('kepler', [
|
|
78
|
+
'device',
|
|
79
|
+
'terminate-app',
|
|
80
|
+
'--device',
|
|
81
|
+
deviceId,
|
|
82
|
+
'--appName',
|
|
83
|
+
bundleId,
|
|
84
|
+
]);
|
|
85
|
+
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { spawn } from '@react-native-harness/tools';
|
|
2
|
+
|
|
3
|
+
export type VegaVirtualDeviceStatus = 'running' | 'stopped';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List all available Vega virtual devices
|
|
7
|
+
* Returns array of device identifiers that can be used with kepler commands
|
|
8
|
+
*/
|
|
9
|
+
export const listVegaDevices = async (): Promise<string[]> => {
|
|
10
|
+
try {
|
|
11
|
+
const { stdout } = await spawn('kepler', ['device', 'list']);
|
|
12
|
+
const lines = stdout.trim().split('\n');
|
|
13
|
+
const devices: string[] = [];
|
|
14
|
+
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
if (line.trim()) {
|
|
17
|
+
// Parse device line format: "VirtualDevice : tv - x86_64 - OS - hostname"
|
|
18
|
+
// or potentially "VegaTV_1 : tv - x86_64 - OS - hostname" for named instances
|
|
19
|
+
const deviceId = line.split(' : ')[0].trim();
|
|
20
|
+
if (
|
|
21
|
+
deviceId &&
|
|
22
|
+
(deviceId === 'VirtualDevice' || deviceId.startsWith('Vega'))
|
|
23
|
+
) {
|
|
24
|
+
devices.push(deviceId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return devices;
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if a specific Vega virtual device is connected/available
|
|
37
|
+
*/
|
|
38
|
+
export const isVegaDeviceConnected = async (
|
|
39
|
+
deviceId: string
|
|
40
|
+
): Promise<boolean> => {
|
|
41
|
+
try {
|
|
42
|
+
const { stdout } = await spawn('kepler', [
|
|
43
|
+
'device',
|
|
44
|
+
'is-connected',
|
|
45
|
+
'--device',
|
|
46
|
+
deviceId,
|
|
47
|
+
]);
|
|
48
|
+
return stdout.includes('is connected');
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if an app is installed on the specified Vega virtual device
|
|
56
|
+
*/
|
|
57
|
+
export const isAppInstalled = async (
|
|
58
|
+
deviceId: string,
|
|
59
|
+
bundleId: string
|
|
60
|
+
): Promise<boolean> => {
|
|
61
|
+
try {
|
|
62
|
+
await spawn('kepler', [
|
|
63
|
+
'device',
|
|
64
|
+
'is-app-installed',
|
|
65
|
+
'--device',
|
|
66
|
+
deviceId,
|
|
67
|
+
'--appName',
|
|
68
|
+
bundleId,
|
|
69
|
+
]);
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if an app is currently running on the specified Vega virtual device
|
|
78
|
+
*/
|
|
79
|
+
export const isAppRunning = async (
|
|
80
|
+
deviceId: string,
|
|
81
|
+
bundleId: string
|
|
82
|
+
): Promise<boolean> => {
|
|
83
|
+
try {
|
|
84
|
+
await spawn('kepler', [
|
|
85
|
+
'device',
|
|
86
|
+
'is-app-running',
|
|
87
|
+
'--device',
|
|
88
|
+
deviceId,
|
|
89
|
+
'--appName',
|
|
90
|
+
bundleId,
|
|
91
|
+
]);
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Install app on specified Vega virtual device using .vpkg file
|
|
100
|
+
*/
|
|
101
|
+
export const installApp = async (
|
|
102
|
+
deviceId: string,
|
|
103
|
+
vpkgPath: string
|
|
104
|
+
): Promise<void> => {
|
|
105
|
+
await spawn('kepler', [
|
|
106
|
+
'device',
|
|
107
|
+
'install-app',
|
|
108
|
+
'-p',
|
|
109
|
+
vpkgPath,
|
|
110
|
+
'--device',
|
|
111
|
+
deviceId,
|
|
112
|
+
]);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Terminate app on specified Vega virtual device
|
|
117
|
+
*/
|
|
118
|
+
export const terminateApp = async (
|
|
119
|
+
deviceId: string,
|
|
120
|
+
bundleId: string
|
|
121
|
+
): Promise<void> => {
|
|
122
|
+
await spawn('kepler', [
|
|
123
|
+
'device',
|
|
124
|
+
'terminate-app',
|
|
125
|
+
'--device',
|
|
126
|
+
deviceId,
|
|
127
|
+
'--appName',
|
|
128
|
+
bundleId,
|
|
129
|
+
]);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Uninstall app from specified Vega virtual device
|
|
134
|
+
*/
|
|
135
|
+
export const uninstallApp = async (
|
|
136
|
+
deviceId: string,
|
|
137
|
+
bundleId: string
|
|
138
|
+
): Promise<void> => {
|
|
139
|
+
await spawn('kepler', [
|
|
140
|
+
'device',
|
|
141
|
+
'uninstall-app',
|
|
142
|
+
'--device',
|
|
143
|
+
deviceId,
|
|
144
|
+
'--appName',
|
|
145
|
+
bundleId,
|
|
146
|
+
]);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Start port forwarding for debugging on specified Vega virtual device
|
|
151
|
+
*/
|
|
152
|
+
export const startPortForwarding = async (
|
|
153
|
+
deviceId: string,
|
|
154
|
+
port: number,
|
|
155
|
+
forward: boolean = true
|
|
156
|
+
): Promise<void> => {
|
|
157
|
+
await spawn('kepler', [
|
|
158
|
+
'device',
|
|
159
|
+
'start-port-forwarding',
|
|
160
|
+
'--device',
|
|
161
|
+
deviceId,
|
|
162
|
+
'--port',
|
|
163
|
+
port.toString(),
|
|
164
|
+
'--forward',
|
|
165
|
+
forward.toString(),
|
|
166
|
+
]);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Stop port forwarding on specified Vega virtual device
|
|
171
|
+
*/
|
|
172
|
+
export const stopPortForwarding = async (
|
|
173
|
+
deviceId: string,
|
|
174
|
+
port: number,
|
|
175
|
+
forward: boolean = true
|
|
176
|
+
): Promise<void> => {
|
|
177
|
+
await spawn('kepler', [
|
|
178
|
+
'device',
|
|
179
|
+
'stop-port-forwarding',
|
|
180
|
+
'--device',
|
|
181
|
+
deviceId,
|
|
182
|
+
'--port',
|
|
183
|
+
port.toString(),
|
|
184
|
+
'--forward',
|
|
185
|
+
forward.toString(),
|
|
186
|
+
]);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get status of a specific Vega virtual device
|
|
191
|
+
* Note: Vega CLI might manage virtual devices globally, so this checks if the device is available
|
|
192
|
+
*/
|
|
193
|
+
export const getVegaDeviceStatus = async (
|
|
194
|
+
deviceId: string
|
|
195
|
+
): Promise<VegaVirtualDeviceStatus> => {
|
|
196
|
+
try {
|
|
197
|
+
// First check if the device is connected/available
|
|
198
|
+
const isConnected = await isVegaDeviceConnected(deviceId);
|
|
199
|
+
if (isConnected) {
|
|
200
|
+
return 'running';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check general virtual device status
|
|
204
|
+
const { stdout } = await spawn('kepler', ['virtual-device', 'status']);
|
|
205
|
+
// Parse the status output to determine if VVD is running
|
|
206
|
+
return stdout.toLowerCase().includes('running') ||
|
|
207
|
+
stdout.toLowerCase().includes('ready')
|
|
208
|
+
? 'running'
|
|
209
|
+
: 'stopped';
|
|
210
|
+
} catch {
|
|
211
|
+
return 'stopped';
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Start Vega Virtual Device
|
|
217
|
+
* Note: Vega might manage virtual devices globally, this starts the virtual device system
|
|
218
|
+
*/
|
|
219
|
+
export const startVirtualDevice = async (): Promise<void> => {
|
|
220
|
+
await spawn('kepler', ['virtual-device', 'start']);
|
|
221
|
+
|
|
222
|
+
// Poll for VVD status until it's running
|
|
223
|
+
let attempts = 0;
|
|
224
|
+
const maxAttempts = 30; // 30 seconds timeout
|
|
225
|
+
|
|
226
|
+
while (attempts < maxAttempts) {
|
|
227
|
+
const { stdout } = await spawn('kepler', ['virtual-device', 'status']);
|
|
228
|
+
if (
|
|
229
|
+
stdout.toLowerCase().includes('running') ||
|
|
230
|
+
stdout.toLowerCase().includes('ready')
|
|
231
|
+
) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
236
|
+
attempts++;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
throw new Error('Vega Virtual Device failed to start within timeout');
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Stop Vega Virtual Device
|
|
244
|
+
*/
|
|
245
|
+
export const stopVirtualDevice = async (): Promise<void> => {
|
|
246
|
+
await spawn('kepler', ['virtual-device', 'stop']);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Combined install and run command for specified Vega virtual device (Vega-specific convenience method)
|
|
251
|
+
*/
|
|
252
|
+
export const runKepler = async (
|
|
253
|
+
deviceId: string,
|
|
254
|
+
vpkgPath: string,
|
|
255
|
+
bundleId: string
|
|
256
|
+
): Promise<void> => {
|
|
257
|
+
await spawn('kepler', ['run-kepler', vpkgPath, bundleId, '-d', deviceId]);
|
|
258
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertVegaRunnerConfig,
|
|
3
|
+
TestRunnerConfig,
|
|
4
|
+
} from '@react-native-harness/config';
|
|
5
|
+
import { logger } from '@react-native-harness/tools';
|
|
6
|
+
|
|
7
|
+
import { type PlatformAdapter } from '../platform-adapter.js';
|
|
8
|
+
import {
|
|
9
|
+
isAppInstalled,
|
|
10
|
+
getVegaDeviceStatus,
|
|
11
|
+
isVegaDeviceConnected,
|
|
12
|
+
startVirtualDevice,
|
|
13
|
+
stopVirtualDevice,
|
|
14
|
+
startPortForwarding,
|
|
15
|
+
stopPortForwarding,
|
|
16
|
+
} from './device.js';
|
|
17
|
+
import { runApp, killApp } from './build.js';
|
|
18
|
+
import { killWithAwait } from '../../process.js';
|
|
19
|
+
import { runMetro } from '../../bundlers/metro.js';
|
|
20
|
+
import { AppNotInstalledError } from '../../errors/errors.js';
|
|
21
|
+
|
|
22
|
+
const vegaPlatformAdapter: PlatformAdapter = {
|
|
23
|
+
name: 'vega',
|
|
24
|
+
getEnvironment: async (runner: TestRunnerConfig) => {
|
|
25
|
+
assertVegaRunnerConfig(runner);
|
|
26
|
+
|
|
27
|
+
let shouldStopVirtualDevice = false;
|
|
28
|
+
|
|
29
|
+
// Check if the specific Vega device is available
|
|
30
|
+
const deviceStatus = await getVegaDeviceStatus(runner.deviceId);
|
|
31
|
+
logger.debug(`Vega device ${runner.deviceId} status: ${deviceStatus}`);
|
|
32
|
+
|
|
33
|
+
if (deviceStatus === 'stopped') {
|
|
34
|
+
logger.debug('Starting Vega Virtual Device system');
|
|
35
|
+
await startVirtualDevice();
|
|
36
|
+
shouldStopVirtualDevice = true;
|
|
37
|
+
|
|
38
|
+
// Wait a bit for the device to become available
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Verify device is now connected
|
|
43
|
+
const isConnected = await isVegaDeviceConnected(runner.deviceId);
|
|
44
|
+
if (!isConnected) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Vega device ${runner.deviceId} is not available. Make sure the virtual device is configured and running.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Start Metro bundler
|
|
51
|
+
const metroPromise = runMetro();
|
|
52
|
+
|
|
53
|
+
// Set up port forwarding for debugging (similar to Android)
|
|
54
|
+
await Promise.all([
|
|
55
|
+
startPortForwarding(runner.deviceId, 8081, false), // reverse port forwarding for JS debugging
|
|
56
|
+
startPortForwarding(runner.deviceId, 8080, false),
|
|
57
|
+
startPortForwarding(runner.deviceId, 3001, false),
|
|
58
|
+
]);
|
|
59
|
+
logger.debug('Port forwarding established');
|
|
60
|
+
|
|
61
|
+
// Check if app is installed
|
|
62
|
+
const isInstalled = await isAppInstalled(runner.deviceId, runner.bundleId);
|
|
63
|
+
logger.debug(`App is installed: ${isInstalled}`);
|
|
64
|
+
|
|
65
|
+
if (!isInstalled) {
|
|
66
|
+
throw new AppNotInstalledError(runner.deviceId, runner.bundleId, 'vega');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
logger.debug('Waiting for Metro to start');
|
|
70
|
+
const metro = await metroPromise;
|
|
71
|
+
logger.debug('Metro started');
|
|
72
|
+
|
|
73
|
+
logger.debug('Running Vega app');
|
|
74
|
+
await runApp(runner.deviceId, runner.bundleId);
|
|
75
|
+
logger.debug('Vega app running');
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
restart: async () => {
|
|
79
|
+
await runApp(runner.deviceId, runner.bundleId);
|
|
80
|
+
},
|
|
81
|
+
dispose: async () => {
|
|
82
|
+
// Kill the app
|
|
83
|
+
await killApp(runner.deviceId, runner.bundleId);
|
|
84
|
+
|
|
85
|
+
// Stop port forwarding
|
|
86
|
+
await Promise.all([
|
|
87
|
+
stopPortForwarding(runner.deviceId, 8081, false),
|
|
88
|
+
stopPortForwarding(runner.deviceId, 8080, false),
|
|
89
|
+
stopPortForwarding(runner.deviceId, 3001, false),
|
|
90
|
+
]).catch((error) => {
|
|
91
|
+
// Don't fail disposal if port forwarding cleanup fails
|
|
92
|
+
logger.debug(`Port forwarding cleanup failed: ${error.message}`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Stop Virtual Device if we started it
|
|
96
|
+
if (shouldStopVirtualDevice) {
|
|
97
|
+
await stopVirtualDevice();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Kill Metro
|
|
101
|
+
await killWithAwait(metro);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default vegaPlatformAdapter;
|