Skip to content

Commit 1e63505

Browse files
committed
Save Maestro debug output for iOS E2E flows
Drop the android-release-only skip: the failing flow needs to run so its debug output can show where Maestro taps. Write each attempt's debug output to its own folder under /tmp/MaestroLogs, which the maestro-ios action already uploads on failure.
1 parent 2440b5c commit 1e63505

4 files changed

Lines changed: 16 additions & 55 deletions

File tree

‎.github/actions/maestro-ios/action.yml‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ inputs:
2626
required: false
2727
default: ''
2828
description: Maestro device OS name, such as iOS-26-2
29-
exclude-tags:
30-
required: false
31-
default: ''
32-
description: Comma-separated flow tags to exclude from the test run
3329

3430
runs:
3531
using: composite
@@ -77,8 +73,7 @@ runs:
7773
"${{ inputs.flavor }}" \
7874
"${{ inputs.working-directory }}" \
7975
"${{ inputs.device-model }}" \
80-
"${{ inputs.device-os }}" \
81-
"${{ inputs.exclude-tags }}"
76+
"${{ inputs.device-os }}"
8277
- name: Store video record
8378
if: always()
8479
uses: ./.github/actions/upload-artifact

‎.github/workflow-scripts/__tests__/maestro-ios-test.js‎

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ jest.mock('child_process', () => ({
1414
jest.mock('fs', () => ({
1515
existsSync: jest.fn(),
1616
lstatSync: jest.fn(),
17-
readFileSync: jest.fn(),
1817
readdirSync: jest.fn(),
1918
}));
2019

@@ -56,29 +55,6 @@ describe('Maestro iOS runner', () => {
5655
);
5756
});
5857

59-
it('skips flows with excluded tags', async () => {
60-
jest.spyOn(console, 'info').mockImplementation(() => {});
61-
fs.existsSync.mockReturnValue(true);
62-
fs.lstatSync.mockImplementation(path => ({
63-
isDirectory: () => path === 'flows/',
64-
}));
65-
fs.readdirSync.mockReturnValue(['excluded.yml', 'regular.yml']);
66-
fs.readFileSync.mockImplementation(path =>
67-
path === 'flows/excluded.yml'
68-
? 'appId: x\ntags:\n - android-release-only\n---\n- launchApp\n'
69-
: 'appId: x\n---\n- launchApp\n',
70-
);
71-
72-
await executeFlows('com.example', 'device-id', 'flows/', 'Hermes', [
73-
'android-release-only',
74-
]);
75-
76-
expect(childProcess.execSync).toHaveBeenCalledTimes(1);
77-
expect(childProcess.execSync.mock.calls[0][0]).toContain(
78-
'test "flows/regular.yml"',
79-
);
80-
});
81-
8258
it('retries only the failing flow', async () => {
8359
fs.existsSync.mockReturnValue(false);
8460
childProcess.execSync.mockImplementationOnce(() => {

‎.github/workflow-scripts/maestro-ios.js‎

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99

1010
const childProcess = require('child_process');
1111
const fs = require('fs');
12-
const {filterFlowsByTags} = require('./maestro-android');
12+
const path = require('path');
1313

1414
const usage = `
1515
=== Usage ===
16-
node maestro-ios.js <path to app> <app_id> <maestro_flow> <jsengine> <flavor> <working_directory> [device_model] [device_os] [exclude_tags]
16+
node maestro-ios.js <path to app> <app_id> <maestro_flow> <jsengine> <flavor> <working_directory> [device_model] [device_os]
1717
1818
@param {string} appPath - Path to the app APK
1919
@param {string} appId - App ID that needs to be launched
@@ -23,11 +23,11 @@ node maestro-ios.js <path to app> <app_id> <maestro_flow> <jsengine> <flavor> <w
2323
@param {string} workingDirectory - Working directory from where to run Metro
2424
@param {string} deviceModel - Optional Maestro device model, such as iPhone-17-Pro
2525
@param {string} deviceOS - Optional Maestro device OS, such as iOS-26-2
26-
@param {string} excludeTags - Optional comma-separated flow tags to exclude
2726
==============
2827
`;
2928

3029
const MAX_ATTEMPTS = 5;
30+
const MAESTRO_LOG_DIRECTORY = '/tmp/MaestroLogs';
3131

3232
function findAvailableSimulator(deviceModel, deviceOS) {
3333
const output = childProcess.execSync(
@@ -187,7 +187,14 @@ async function executeFlowWithRetries(
187187
const recProcess = startVideoRecording(udid, currentAttempt);
188188
try {
189189
const timeout = 1000 * 60 * 10; // 10 minutes
190-
const command = `$HOME/.maestro/bin/maestro --udid="${udid}" test "${flow}" --format junit -e APP_ID="${appId}"`;
190+
// Keep each attempt's commands, screenshots, and logs; retries would
191+
// otherwise overwrite them.
192+
const debugOutput = path.join(
193+
MAESTRO_LOG_DIRECTORY,
194+
path.basename(flow, path.extname(flow)),
195+
`attempt-${currentAttempt}`,
196+
);
197+
const command = `$HOME/.maestro/bin/maestro --udid="${udid}" test "${flow}" --format junit -e APP_ID="${appId}" --debug-output "${debugOutput}"`;
191198
console.info(`Executing flow: ${flow} (attempt ${currentAttempt})`);
192199
console.log(command);
193200
childProcess.execSync(`MAESTRO_DRIVER_STARTUP_TIMEOUT=1500000 ${command}`, {
@@ -217,13 +224,7 @@ async function executeFlowWithRetries(
217224
}
218225
}
219226

220-
async function executeFlows(
221-
appId,
222-
udid,
223-
maestroFlow,
224-
jsengine,
225-
excludeTags = [],
226-
) {
227+
async function executeFlows(appId, udid, maestroFlow, jsengine) {
227228
if (!fs.existsSync(maestroFlow) || !fs.lstatSync(maestroFlow).isDirectory()) {
228229
await executeFlowWithRetries(appId, udid, maestroFlow, jsengine, 1);
229230
return;
@@ -237,18 +238,15 @@ async function executeFlows(
237238
if (file === 'helpers') {
238239
continue;
239240
}
240-
await executeFlows(appId, udid, filePath, jsengine, excludeTags);
241+
await executeFlows(appId, udid, filePath, jsengine);
241242
} else if (file.endsWith('.yml') || file.endsWith('.yaml')) {
242-
if (filterFlowsByTags([filePath], excludeTags).length === 0) {
243-
continue;
244-
}
245243
await executeFlowWithRetries(appId, udid, filePath, jsengine, 1);
246244
}
247245
}
248246
}
249247

250248
async function main(args = process.argv.slice(2)) {
251-
if (args.length < 6 || args.length > 9) {
249+
if (args.length < 6 || args.length > 8) {
252250
throw new Error(`Invalid number of arguments.\n${usage}`);
253251
}
254252

@@ -260,10 +258,6 @@ async function main(args = process.argv.slice(2)) {
260258
const workingDirectory = args[5];
261259
const deviceModel = args[6] || null;
262260
const deviceOS = args[7] || null;
263-
const excludeTags = (args[8] ?? '')
264-
.split(',')
265-
.map(tag => tag.trim())
266-
.filter(Boolean);
267261

268262
console.info('\n==============================');
269263
console.info('Running tests for iOS with the following parameters:');
@@ -275,15 +269,14 @@ async function main(args = process.argv.slice(2)) {
275269
console.info(`WORKING_DIRECTORY: ${workingDirectory}`);
276270
console.info(`DEVICE_MODEL: ${deviceModel ?? '<automatic>'}`);
277271
console.info(`DEVICE_OS: ${deviceOS ?? '<automatic>'}`);
278-
console.info(`EXCLUDE_TAGS: ${excludeTags.join(',') || '<none>'}`);
279272
console.info('==============================\n');
280273

281274
const simulator = findAvailableSimulator(deviceModel, deviceOS);
282275
launchSimulator(simulator);
283276
installAppOnSimulator(appPath, simulator.udid);
284277
bringSimulatorInForeground();
285278
await launchAppOnSimulator(appId, simulator.udid, isDebug);
286-
await executeFlows(appId, simulator.udid, maestroFlow, jsengine, excludeTags);
279+
await executeFlows(appId, simulator.udid, maestroFlow, jsengine);
287280
console.log('Test finished');
288281
}
289282

‎.github/workflows/e2e-ios-rntester.yml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ jobs:
4646
flavor: ${{ matrix.flavor }}
4747
device-model: iPhone-17-Pro
4848
device-os: iOS-26-2
49-
# The maintainVisibleContentPosition flows fail on iOS since the
50-
# RNTester tab bar inset change in #58900. Skip them until it's fixed.
51-
exclude-tags: android-release-only
5249
- name: Report status
5350
id: report-status
5451
if: ${{ always() && steps.run-tests.outcome == 'failure' }}

0 commit comments

Comments
 (0)