@expo/build-tools 18.4.0 → 18.6.0
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/dist/builders/android.js +13 -1
- package/dist/builders/ios.js +1 -1
- package/dist/common/setup.js +4 -3
- package/dist/context.d.ts +1 -1
- package/dist/context.js +1 -1
- package/dist/ios/configure.js +4 -0
- package/dist/ios/credentials/provisioningProfile.d.ts +1 -0
- package/dist/ios/credentials/provisioningProfile.js +5 -0
- package/dist/steps/easFunctions.js +0 -2
- package/dist/steps/functionGroups/build.js +115 -34
- package/dist/steps/functionGroups/maestroTest.js +0 -3
- package/dist/steps/functions/calculateEASUpdateRuntimeVersion.js +2 -2
- package/dist/steps/functions/configureEASUpdateIfInstalled.js +2 -2
- package/dist/steps/functions/repack.js +20 -3
- package/dist/steps/functions/restoreBuildCache.d.ts +8 -0
- package/dist/steps/functions/restoreBuildCache.js +85 -0
- package/dist/steps/functions/saveBuildCache.d.ts +8 -0
- package/dist/steps/functions/saveBuildCache.js +57 -0
- package/dist/steps/functions/startAndroidEmulator.js +19 -0
- package/dist/steps/functions/uploadToAsc.d.ts +3 -0
- package/dist/steps/functions/uploadToAsc.js +24 -1
- package/dist/steps/utils/ios/AscApiClient.d.ts +1 -1
- package/dist/steps/utils/ios/AscApiUtils.d.ts +5 -0
- package/dist/steps/utils/ios/AscApiUtils.js +16 -13
- package/dist/steps/utils/ios/configure.js +4 -0
- package/dist/steps/utils/ios/credentials/provisioningProfile.d.ts +1 -0
- package/dist/steps/utils/ios/credentials/provisioningProfile.js +5 -0
- package/dist/utils/AndroidEmulatorUtils.d.ts +5 -0
- package/dist/utils/AndroidEmulatorUtils.js +17 -0
- package/dist/utils/appConfig.d.ts +4 -2
- package/dist/utils/appConfig.js +36 -5
- package/dist/utils/expoCli.d.ts +4 -0
- package/dist/utils/expoCli.js +37 -0
- package/dist/utils/expoUpdates.d.ts +1 -1
- package/dist/utils/expoUpdates.js +4 -4
- package/dist/utils/gradleCacheKey.d.ts +2 -0
- package/dist/utils/gradleCacheKey.js +56 -0
- package/dist/utils/packageManager.d.ts +10 -0
- package/dist/utils/packageManager.js +21 -0
- package/dist/utils/project.d.ts +2 -1
- package/dist/utils/project.js +29 -2
- package/package.json +14 -13
- package/dist/steps/functions/internalMaestroTest.d.ts +0 -9
- package/dist/steps/functions/internalMaestroTest.js +0 -534
- package/dist/utils/findMaestroPathsFlowsToExecuteAsync.d.ts +0 -8
- package/dist/utils/findMaestroPathsFlowsToExecuteAsync.js +0 -184
|
@@ -1,534 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createInternalEasMaestroTestFunction = createInternalEasMaestroTestFunction;
|
|
7
|
-
exports.getMaestroTestCommand = getMaestroTestCommand;
|
|
8
|
-
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
9
|
-
const logger_1 = require("@expo/logger");
|
|
10
|
-
const results_1 = require("@expo/results");
|
|
11
|
-
const steps_1 = require("@expo/steps");
|
|
12
|
-
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
13
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
15
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
-
const promises_1 = require("node:timers/promises");
|
|
17
|
-
const zod_1 = require("zod");
|
|
18
|
-
const AndroidEmulatorUtils_1 = require("../../utils/AndroidEmulatorUtils");
|
|
19
|
-
const IosSimulatorUtils_1 = require("../../utils/IosSimulatorUtils");
|
|
20
|
-
const findMaestroPathsFlowsToExecuteAsync_1 = require("../../utils/findMaestroPathsFlowsToExecuteAsync");
|
|
21
|
-
const retry_1 = require("../../utils/retry");
|
|
22
|
-
const strings_1 = require("../../utils/strings");
|
|
23
|
-
function createInternalEasMaestroTestFunction(ctx) {
|
|
24
|
-
return new steps_1.BuildFunction({
|
|
25
|
-
namespace: 'eas',
|
|
26
|
-
id: '__maestro_test',
|
|
27
|
-
__metricsId: 'eas/__maestro_test',
|
|
28
|
-
inputProviders: [
|
|
29
|
-
steps_1.BuildStepInput.createProvider({
|
|
30
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
31
|
-
id: 'platform',
|
|
32
|
-
required: true,
|
|
33
|
-
}),
|
|
34
|
-
steps_1.BuildStepInput.createProvider({
|
|
35
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.JSON,
|
|
36
|
-
id: 'flow_paths',
|
|
37
|
-
required: true,
|
|
38
|
-
}),
|
|
39
|
-
steps_1.BuildStepInput.createProvider({
|
|
40
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
41
|
-
id: 'retries',
|
|
42
|
-
defaultValue: 1,
|
|
43
|
-
required: false,
|
|
44
|
-
}),
|
|
45
|
-
steps_1.BuildStepInput.createProvider({
|
|
46
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
47
|
-
id: 'include_tags',
|
|
48
|
-
required: false,
|
|
49
|
-
}),
|
|
50
|
-
steps_1.BuildStepInput.createProvider({
|
|
51
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
52
|
-
id: 'exclude_tags',
|
|
53
|
-
required: false,
|
|
54
|
-
}),
|
|
55
|
-
steps_1.BuildStepInput.createProvider({
|
|
56
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
57
|
-
id: 'shards',
|
|
58
|
-
defaultValue: 1,
|
|
59
|
-
required: false,
|
|
60
|
-
}),
|
|
61
|
-
steps_1.BuildStepInput.createProvider({
|
|
62
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
63
|
-
id: 'output_format',
|
|
64
|
-
required: false,
|
|
65
|
-
}),
|
|
66
|
-
steps_1.BuildStepInput.createProvider({
|
|
67
|
-
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
68
|
-
id: 'record_screen',
|
|
69
|
-
defaultValue: false,
|
|
70
|
-
required: false,
|
|
71
|
-
}),
|
|
72
|
-
],
|
|
73
|
-
outputProviders: [
|
|
74
|
-
steps_1.BuildStepOutput.createProvider({
|
|
75
|
-
id: 'test_reports_artifact_id',
|
|
76
|
-
required: false,
|
|
77
|
-
}),
|
|
78
|
-
steps_1.BuildStepOutput.createProvider({
|
|
79
|
-
id: 'junit_report_directory',
|
|
80
|
-
required: false,
|
|
81
|
-
}),
|
|
82
|
-
],
|
|
83
|
-
fn: async (stepCtx, { inputs: _inputs, env, outputs }) => {
|
|
84
|
-
// inputs come in form of { value: unknown }. Here we parse them into a typed and validated object.
|
|
85
|
-
const { platform, flow_paths, retries, include_tags, exclude_tags, shards, output_format, record_screen, } = zod_1.z
|
|
86
|
-
.object({
|
|
87
|
-
platform: zod_1.z.enum(['ios', 'android']),
|
|
88
|
-
flow_paths: zod_1.z.array(zod_1.z.string()),
|
|
89
|
-
retries: zod_1.z.number().default(1),
|
|
90
|
-
include_tags: zod_1.z.string().optional(),
|
|
91
|
-
exclude_tags: zod_1.z.string().optional(),
|
|
92
|
-
shards: zod_1.z.number().default(1),
|
|
93
|
-
output_format: zod_1.z.string().optional(),
|
|
94
|
-
record_screen: zod_1.z.boolean().default(false),
|
|
95
|
-
})
|
|
96
|
-
.parse(Object.fromEntries(Object.entries(_inputs).map(([key, value]) => [key, value.value])));
|
|
97
|
-
const flowPathsToExecute = [];
|
|
98
|
-
for (const flowPath of flow_paths) {
|
|
99
|
-
const flowPaths = await (0, findMaestroPathsFlowsToExecuteAsync_1.findMaestroPathsFlowsToExecuteAsync)({
|
|
100
|
-
workingDirectory: stepCtx.workingDirectory,
|
|
101
|
-
flowPath,
|
|
102
|
-
logger: stepCtx.logger,
|
|
103
|
-
includeTags: include_tags ? include_tags.split(',') : undefined,
|
|
104
|
-
excludeTags: exclude_tags ? exclude_tags.split(',') : undefined,
|
|
105
|
-
});
|
|
106
|
-
if (flowPaths.length === 0) {
|
|
107
|
-
stepCtx.logger.warn(`No flows to execute found in "${flowPath}".`);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
stepCtx.logger.info(`Marking for execution:\n- ${flowPaths
|
|
111
|
-
.map(flowPath => node_path_1.default.relative(stepCtx.workingDirectory, flowPath))
|
|
112
|
-
.join('\n- ')}`);
|
|
113
|
-
stepCtx.logger.info('');
|
|
114
|
-
flowPathsToExecute.push(...flowPaths);
|
|
115
|
-
}
|
|
116
|
-
// TODO: Add support for shards. (Shouldn't be too difficult.)
|
|
117
|
-
if (shards > 1) {
|
|
118
|
-
stepCtx.logger.warn('Sharding support has been temporarily disabled. Running tests on a single shard.');
|
|
119
|
-
}
|
|
120
|
-
// eas/__maestro_test does not start devices, it expects a single device to be already running
|
|
121
|
-
// and configured with the app. Here we find the booted device and stop it.
|
|
122
|
-
let sourceDeviceIdentifier;
|
|
123
|
-
switch (platform) {
|
|
124
|
-
case 'ios': {
|
|
125
|
-
const bootedDevices = await IosSimulatorUtils_1.IosSimulatorUtils.getAvailableDevicesAsync({
|
|
126
|
-
env,
|
|
127
|
-
filter: 'booted',
|
|
128
|
-
});
|
|
129
|
-
if (bootedDevices.length === 0) {
|
|
130
|
-
throw new Error('No booted iOS Simulator found.');
|
|
131
|
-
}
|
|
132
|
-
else if (bootedDevices.length > 1) {
|
|
133
|
-
throw new Error('Multiple booted iOS Simulators found.');
|
|
134
|
-
}
|
|
135
|
-
const device = bootedDevices[0];
|
|
136
|
-
stepCtx.logger.info(`Running tests on iOS Simulator: ${device.name}.`);
|
|
137
|
-
stepCtx.logger.info(`Preparing Simulator for tests...`);
|
|
138
|
-
await (0, steps_1.spawnAsync)('xcrun', ['simctl', 'shutdown', device.udid], {
|
|
139
|
-
logger: stepCtx.logger,
|
|
140
|
-
stdio: 'pipe',
|
|
141
|
-
});
|
|
142
|
-
sourceDeviceIdentifier = device.name;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
case 'android': {
|
|
146
|
-
const connectedDevices = await AndroidEmulatorUtils_1.AndroidEmulatorUtils.getAttachedDevicesAsync({ env });
|
|
147
|
-
if (connectedDevices.length === 0) {
|
|
148
|
-
throw new Error('No booted Android Emulator found.');
|
|
149
|
-
}
|
|
150
|
-
else if (connectedDevices.length > 1) {
|
|
151
|
-
throw new Error('Multiple booted Android Emulators found.');
|
|
152
|
-
}
|
|
153
|
-
const { serialId } = connectedDevices[0];
|
|
154
|
-
const adbEmuAvdNameResult = await (0, turtle_spawn_1.default)('adb', ['-s', serialId, 'emu', 'avd', 'name'], {
|
|
155
|
-
mode: logger_1.PipeMode.COMBINED,
|
|
156
|
-
env,
|
|
157
|
-
});
|
|
158
|
-
const avdName = adbEmuAvdNameResult.stdout
|
|
159
|
-
.replace(/\r\n/g, '\n')
|
|
160
|
-
.split('\n')[0];
|
|
161
|
-
stepCtx.logger.info(`Running tests on Android Emulator: ${avdName}.`);
|
|
162
|
-
stepCtx.logger.info(`Preparing Emulator for tests...`);
|
|
163
|
-
await (0, steps_1.spawnAsync)('adb', ['-s', serialId, 'emu', 'kill'], {
|
|
164
|
-
stdio: 'pipe',
|
|
165
|
-
});
|
|
166
|
-
// Waiting for emulator to get killed, see ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL.
|
|
167
|
-
await (0, promises_1.setTimeout)(1000);
|
|
168
|
-
sourceDeviceIdentifier = avdName;
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// During tests we generate reports and device logs. We store them in temporary directories
|
|
173
|
-
// and upload them once all tests are done. When a test is retried, new reports overwrite
|
|
174
|
-
// the old ones. The files are named "flow-${index}" for easier identification.
|
|
175
|
-
const maestroReportsDir = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'maestro-reports-'));
|
|
176
|
-
const deviceLogsDir = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'device-logs-'));
|
|
177
|
-
const failedFlows = [];
|
|
178
|
-
for (const [flowIndex, flowPath] of flowPathsToExecute.entries()) {
|
|
179
|
-
stepCtx.logger.info('');
|
|
180
|
-
for (let attemptCount = 0; attemptCount < retries; attemptCount++) {
|
|
181
|
-
// Generate unique report path per attempt (not overwritten on retry)
|
|
182
|
-
const outputPath = node_path_1.default.join(maestroReportsDir, [
|
|
183
|
-
`${output_format ? output_format + '-' : ''}report-flow-${flowIndex + 1}-attempt-${attemptCount}`,
|
|
184
|
-
MaestroOutputFormatToExtensionMap[output_format ?? 'noop'],
|
|
185
|
-
]
|
|
186
|
-
.filter(Boolean)
|
|
187
|
-
.join('.'));
|
|
188
|
-
const localDeviceName = `eas-simulator-${flowIndex}-${attemptCount}`;
|
|
189
|
-
// If the test passes, but the recording fails, we don't want to make the test fail,
|
|
190
|
-
// so we return two separate results.
|
|
191
|
-
const { fnResult: { fnResult, recordingResult }, logsResult, } = await withCleanDeviceAsync({
|
|
192
|
-
platform,
|
|
193
|
-
sourceDeviceIdentifier,
|
|
194
|
-
localDeviceName,
|
|
195
|
-
env,
|
|
196
|
-
logger: stepCtx.logger,
|
|
197
|
-
fn: async ({ deviceIdentifier }) => {
|
|
198
|
-
return await maybeWithScreenRecordingAsync({
|
|
199
|
-
shouldRecord: record_screen,
|
|
200
|
-
platform,
|
|
201
|
-
deviceIdentifier,
|
|
202
|
-
env,
|
|
203
|
-
logger: stepCtx.logger,
|
|
204
|
-
fn: async () => {
|
|
205
|
-
stepCtx.logger.info('');
|
|
206
|
-
const [command, ...args] = getMaestroTestCommand({
|
|
207
|
-
flow_path: flowPath,
|
|
208
|
-
output_format,
|
|
209
|
-
output_path: outputPath,
|
|
210
|
-
});
|
|
211
|
-
try {
|
|
212
|
-
await (0, steps_1.spawnAsync)(command, args, {
|
|
213
|
-
logger: stepCtx.logger,
|
|
214
|
-
cwd: stepCtx.workingDirectory,
|
|
215
|
-
env,
|
|
216
|
-
stdio: 'pipe',
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
finally {
|
|
220
|
-
stepCtx.logger.info('');
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
// Move device logs to the device logs directory.
|
|
227
|
-
if (logsResult?.ok) {
|
|
228
|
-
try {
|
|
229
|
-
const extension = node_path_1.default.extname(logsResult.value.outputPath);
|
|
230
|
-
const destinationPath = node_path_1.default.join(deviceLogsDir, `flow-${flowIndex}-attempt-${attemptCount}${extension}`);
|
|
231
|
-
await node_fs_1.default.promises.rename(logsResult.value.outputPath, destinationPath);
|
|
232
|
-
}
|
|
233
|
-
catch (err) {
|
|
234
|
-
stepCtx.logger.warn({ err }, 'Failed to prepare device logs for upload.');
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
else if (logsResult?.reason) {
|
|
238
|
-
stepCtx.logger.error({ err: logsResult.reason }, 'Failed to collect device logs.');
|
|
239
|
-
}
|
|
240
|
-
const isLastAttempt = fnResult.ok || attemptCount === retries - 1;
|
|
241
|
-
if (isLastAttempt && recordingResult.value) {
|
|
242
|
-
try {
|
|
243
|
-
await ctx.runtimeApi.uploadArtifact({
|
|
244
|
-
logger: stepCtx.logger,
|
|
245
|
-
artifact: {
|
|
246
|
-
// TODO(sjchmiela): Add metadata to artifacts so we don't need to encode flow path and attempt in the name.
|
|
247
|
-
name: `Screen Recording (${flowIndex}-${node_path_1.default.basename(flowPath, node_path_1.default.extname(flowPath))})`,
|
|
248
|
-
paths: [recordingResult.value],
|
|
249
|
-
type: eas_build_job_1.GenericArtifactType.OTHER,
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
stepCtx.logger.warn({ err }, 'Failed to upload screen recording.');
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (fnResult.ok) {
|
|
258
|
-
stepCtx.logger.info(`Test passed.`);
|
|
259
|
-
// Break out of the retry loop.
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
if (attemptCount < retries - 1) {
|
|
263
|
-
stepCtx.logger.info(`Retrying test...`);
|
|
264
|
-
stepCtx.logger.info('');
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
// fnResult.reason is not super interesting, but it does print out the full command so we can keep it for debugging purposes.
|
|
268
|
-
stepCtx.logger.error({ err: fnResult.reason }, 'Test errored.');
|
|
269
|
-
failedFlows.push(flowPath);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
stepCtx.logger.info('');
|
|
273
|
-
// When all tests are done, we upload the reports and device logs.
|
|
274
|
-
const generatedMaestroReports = await node_fs_1.default.promises.readdir(maestroReportsDir);
|
|
275
|
-
if (generatedMaestroReports.length === 0) {
|
|
276
|
-
stepCtx.logger.warn('No reports were generated.');
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
stepCtx.logger.info(`Uploading reports...`);
|
|
280
|
-
try {
|
|
281
|
-
const { artifactId } = await ctx.runtimeApi.uploadArtifact({
|
|
282
|
-
logger: stepCtx.logger,
|
|
283
|
-
artifact: {
|
|
284
|
-
name: `${strings_1.PlatformToProperNounMap[platform]} Maestro Test Reports (${output_format})`,
|
|
285
|
-
paths: [maestroReportsDir],
|
|
286
|
-
type: eas_build_job_1.GenericArtifactType.OTHER,
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
if (artifactId) {
|
|
290
|
-
outputs.test_reports_artifact_id.set(artifactId);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
catch (err) {
|
|
294
|
-
stepCtx.logger.error({ err }, 'Failed to upload reports.');
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (output_format === 'junit') {
|
|
298
|
-
outputs.junit_report_directory.set(maestroReportsDir);
|
|
299
|
-
}
|
|
300
|
-
const generatedDeviceLogs = await node_fs_1.default.promises.readdir(deviceLogsDir);
|
|
301
|
-
if (generatedDeviceLogs.length === 0) {
|
|
302
|
-
stepCtx.logger.warn('No device logs were successfully collected.');
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
stepCtx.logger.info(`Uploading device logs...`);
|
|
306
|
-
try {
|
|
307
|
-
await ctx.runtimeApi.uploadArtifact({
|
|
308
|
-
logger: stepCtx.logger,
|
|
309
|
-
artifact: {
|
|
310
|
-
name: `Maestro Test Device Logs`,
|
|
311
|
-
paths: [deviceLogsDir],
|
|
312
|
-
type: eas_build_job_1.GenericArtifactType.OTHER,
|
|
313
|
-
},
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
stepCtx.logger.error({ err }, 'Failed to upload device logs.');
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
stepCtx.logger.info('');
|
|
321
|
-
// If any tests failed, we throw an error to mark the step as failed.
|
|
322
|
-
if (failedFlows.length > 0) {
|
|
323
|
-
throw new Error(`Some Maestro tests failed:\n- ${failedFlows
|
|
324
|
-
.map(flowPath => node_path_1.default.relative(stepCtx.workingDirectory, flowPath))
|
|
325
|
-
.join('\n- ')}`);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
stepCtx.logger.info('All Maestro tests passed.');
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
function getMaestroTestCommand(params) {
|
|
334
|
-
let outputFormatFlags = [];
|
|
335
|
-
if (params.output_format) {
|
|
336
|
-
outputFormatFlags = [`--format`, params.output_format, `--output`, params.output_path];
|
|
337
|
-
}
|
|
338
|
-
return ['maestro', 'test', ...outputFormatFlags, params.flow_path];
|
|
339
|
-
}
|
|
340
|
-
const MaestroOutputFormatToExtensionMap = {
|
|
341
|
-
junit: 'xml',
|
|
342
|
-
html: 'html',
|
|
343
|
-
};
|
|
344
|
-
const ANDROID_STARTUP_ATTEMPT_TIMEOUT_MS = [60_000, 120_000, 180_000];
|
|
345
|
-
const ANDROID_STARTUP_RETRIES_COUNT = ANDROID_STARTUP_ATTEMPT_TIMEOUT_MS.length - 1;
|
|
346
|
-
async function withCleanDeviceAsync({ platform, sourceDeviceIdentifier, localDeviceName, env, logger, fn, }) {
|
|
347
|
-
// Clone and start the device
|
|
348
|
-
let localDeviceIdentifier = null;
|
|
349
|
-
switch (platform) {
|
|
350
|
-
case 'ios': {
|
|
351
|
-
logger.info(`Cloning iOS Simulator ${sourceDeviceIdentifier} to ${localDeviceName}...`);
|
|
352
|
-
await IosSimulatorUtils_1.IosSimulatorUtils.cloneAsync({
|
|
353
|
-
sourceDeviceIdentifier: sourceDeviceIdentifier,
|
|
354
|
-
destinationDeviceName: localDeviceName,
|
|
355
|
-
env,
|
|
356
|
-
});
|
|
357
|
-
logger.info(`Starting iOS Simulator ${localDeviceName}...`);
|
|
358
|
-
const { udid } = await IosSimulatorUtils_1.IosSimulatorUtils.startAsync({
|
|
359
|
-
deviceIdentifier: localDeviceName,
|
|
360
|
-
env,
|
|
361
|
-
});
|
|
362
|
-
logger.info(`Waiting for iOS Simulator ${localDeviceName} to be ready...`);
|
|
363
|
-
await IosSimulatorUtils_1.IosSimulatorUtils.waitForReadyAsync({
|
|
364
|
-
udid,
|
|
365
|
-
env,
|
|
366
|
-
});
|
|
367
|
-
localDeviceIdentifier = localDeviceName;
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
case 'android': {
|
|
371
|
-
await (0, retry_1.retryAsync)(async (attemptCount) => {
|
|
372
|
-
const timeoutMs = ANDROID_STARTUP_ATTEMPT_TIMEOUT_MS[attemptCount];
|
|
373
|
-
const attempt = attemptCount + 1;
|
|
374
|
-
const maxAttempts = ANDROID_STARTUP_ATTEMPT_TIMEOUT_MS.length;
|
|
375
|
-
let serialId = null;
|
|
376
|
-
try {
|
|
377
|
-
logger.info(`Cloning Android Emulator ${sourceDeviceIdentifier} to ${localDeviceName} (attempt ${attempt}/${maxAttempts})...`);
|
|
378
|
-
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.cloneAsync({
|
|
379
|
-
sourceDeviceName: sourceDeviceIdentifier,
|
|
380
|
-
destinationDeviceName: localDeviceName,
|
|
381
|
-
env,
|
|
382
|
-
logger,
|
|
383
|
-
});
|
|
384
|
-
logger.info(`Starting Android Emulator ${localDeviceName} (attempt ${attempt}/${maxAttempts})...`);
|
|
385
|
-
const startResult = await AndroidEmulatorUtils_1.AndroidEmulatorUtils.startAsync({
|
|
386
|
-
deviceName: localDeviceName,
|
|
387
|
-
env,
|
|
388
|
-
});
|
|
389
|
-
serialId = startResult.serialId;
|
|
390
|
-
logger.info(`Waiting for Android Emulator ${localDeviceName} to be ready...`);
|
|
391
|
-
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.waitForReadyAsync({
|
|
392
|
-
serialId,
|
|
393
|
-
env,
|
|
394
|
-
timeoutMs,
|
|
395
|
-
logger,
|
|
396
|
-
});
|
|
397
|
-
localDeviceIdentifier = serialId;
|
|
398
|
-
}
|
|
399
|
-
catch (err) {
|
|
400
|
-
logger.warn({ err }, `Failed to start Android Emulator ${localDeviceName} on attempt ${attempt}/${maxAttempts}.`);
|
|
401
|
-
try {
|
|
402
|
-
if (serialId) {
|
|
403
|
-
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.deleteAsync({
|
|
404
|
-
serialId,
|
|
405
|
-
deviceName: localDeviceName,
|
|
406
|
-
env,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.deleteAsync({
|
|
411
|
-
deviceName: localDeviceName,
|
|
412
|
-
env,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
catch (cleanupErr) {
|
|
417
|
-
logger.warn({ err: cleanupErr }, `Failed to clean up Android Emulator ${localDeviceName}.`);
|
|
418
|
-
}
|
|
419
|
-
throw err;
|
|
420
|
-
}
|
|
421
|
-
}, {
|
|
422
|
-
logger,
|
|
423
|
-
retryOptions: {
|
|
424
|
-
retries: ANDROID_STARTUP_RETRIES_COUNT,
|
|
425
|
-
retryIntervalMs: 1_000,
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
|
-
break;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if (!localDeviceIdentifier) {
|
|
432
|
-
throw new Error('Device did not return an identifier after startup.');
|
|
433
|
-
}
|
|
434
|
-
// Run the function
|
|
435
|
-
const fnResult = await (0, results_1.asyncResult)(fn({ deviceIdentifier: localDeviceIdentifier }));
|
|
436
|
-
// Stop the device
|
|
437
|
-
let logsResult = null;
|
|
438
|
-
try {
|
|
439
|
-
switch (platform) {
|
|
440
|
-
case 'ios': {
|
|
441
|
-
logger.info(`Collecting logs from ${localDeviceName}...`);
|
|
442
|
-
logsResult = await (0, results_1.asyncResult)(IosSimulatorUtils_1.IosSimulatorUtils.collectLogsAsync({
|
|
443
|
-
deviceIdentifier: localDeviceIdentifier,
|
|
444
|
-
env,
|
|
445
|
-
}));
|
|
446
|
-
logger.info(`Cleaning up ${localDeviceName}...`);
|
|
447
|
-
await IosSimulatorUtils_1.IosSimulatorUtils.deleteAsync({
|
|
448
|
-
deviceIdentifier: localDeviceIdentifier,
|
|
449
|
-
env,
|
|
450
|
-
});
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
case 'android': {
|
|
454
|
-
logger.info(`Collecting logs from ${localDeviceName}...`);
|
|
455
|
-
logsResult = await (0, results_1.asyncResult)(AndroidEmulatorUtils_1.AndroidEmulatorUtils.collectLogsAsync({
|
|
456
|
-
serialId: localDeviceIdentifier,
|
|
457
|
-
env,
|
|
458
|
-
}));
|
|
459
|
-
logger.info(`Cleaning up ${localDeviceName}...`);
|
|
460
|
-
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.deleteAsync({
|
|
461
|
-
serialId: localDeviceIdentifier,
|
|
462
|
-
env,
|
|
463
|
-
});
|
|
464
|
-
break;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
catch (err) {
|
|
469
|
-
logger.error(`Error cleaning up device: ${err}`);
|
|
470
|
-
}
|
|
471
|
-
return { fnResult: fnResult.enforceValue(), logsResult };
|
|
472
|
-
}
|
|
473
|
-
/** Runs provided `fn` function, optionally wrapping it with starting and stopping screen recording. */
|
|
474
|
-
async function maybeWithScreenRecordingAsync({ shouldRecord, platform, deviceIdentifier, env, logger, fn, }) {
|
|
475
|
-
if (!shouldRecord) {
|
|
476
|
-
return { fnResult: await (0, results_1.asyncResult)(fn()), recordingResult: (0, results_1.result)(null) };
|
|
477
|
-
}
|
|
478
|
-
let recordingResult;
|
|
479
|
-
// Start screen recording
|
|
480
|
-
logger.info(`Starting screen recording on ${deviceIdentifier}...`);
|
|
481
|
-
switch (platform) {
|
|
482
|
-
case 'ios': {
|
|
483
|
-
recordingResult = await (0, results_1.asyncResult)(IosSimulatorUtils_1.IosSimulatorUtils.startScreenRecordingAsync({
|
|
484
|
-
deviceIdentifier: deviceIdentifier,
|
|
485
|
-
env,
|
|
486
|
-
}));
|
|
487
|
-
break;
|
|
488
|
-
}
|
|
489
|
-
case 'android': {
|
|
490
|
-
recordingResult = await (0, results_1.asyncResult)(AndroidEmulatorUtils_1.AndroidEmulatorUtils.startScreenRecordingAsync({
|
|
491
|
-
serialId: deviceIdentifier,
|
|
492
|
-
env,
|
|
493
|
-
}));
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (!recordingResult.ok) {
|
|
498
|
-
logger.warn('Failed to start screen recording.', recordingResult.reason);
|
|
499
|
-
}
|
|
500
|
-
// Run the function
|
|
501
|
-
const fnResult = await (0, results_1.asyncResult)(fn());
|
|
502
|
-
// If recording failed there's nothing to stop, so we return the results
|
|
503
|
-
if (!recordingResult.ok) {
|
|
504
|
-
return { fnResult, recordingResult: (0, results_1.result)(recordingResult.reason) };
|
|
505
|
-
}
|
|
506
|
-
// If recording started, finish it
|
|
507
|
-
try {
|
|
508
|
-
logger.info(`Stopping screen recording on ${deviceIdentifier}...`);
|
|
509
|
-
switch (platform) {
|
|
510
|
-
case 'ios': {
|
|
511
|
-
await IosSimulatorUtils_1.IosSimulatorUtils.stopScreenRecordingAsync({
|
|
512
|
-
recordingSpawn: recordingResult.value.recordingSpawn,
|
|
513
|
-
});
|
|
514
|
-
return {
|
|
515
|
-
fnResult,
|
|
516
|
-
// We know outputPath is defined, because startIosScreenRecording() should have filled it.
|
|
517
|
-
recordingResult: (0, results_1.result)(recordingResult.value.outputPath),
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
case 'android': {
|
|
521
|
-
const { outputPath } = await AndroidEmulatorUtils_1.AndroidEmulatorUtils.stopScreenRecordingAsync({
|
|
522
|
-
serialId: deviceIdentifier,
|
|
523
|
-
recordingSpawn: recordingResult.value.recordingSpawn,
|
|
524
|
-
env,
|
|
525
|
-
});
|
|
526
|
-
return { fnResult, recordingResult: (0, results_1.result)(outputPath) };
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
catch (err) {
|
|
531
|
-
logger.warn('Failed to stop screen recording.', err);
|
|
532
|
-
return { fnResult, recordingResult: (0, results_1.result)(err) };
|
|
533
|
-
}
|
|
534
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { bunyan } from '@expo/logger';
|
|
2
|
-
export declare function findMaestroPathsFlowsToExecuteAsync({ workingDirectory, flowPath, includeTags: _includeTags, excludeTags: _excludeTags, logger, }: {
|
|
3
|
-
workingDirectory: string;
|
|
4
|
-
flowPath: string;
|
|
5
|
-
includeTags: string[] | undefined;
|
|
6
|
-
excludeTags: string[] | undefined;
|
|
7
|
-
logger: bunyan;
|
|
8
|
-
}): Promise<string[]>;
|