Skip to content
Closed
9 changes: 7 additions & 2 deletions .github/actions/maestro-android/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ inputs:
required: false
default: x86
description: The architecture of the emulator to run
test-state-path:
required: false
default: /tmp/maestro-android-state/results.json
description: The path used to persist per-flow test results between retries

runs:
using: composite
Expand Down Expand Up @@ -64,7 +68,7 @@ runs:
cores: '4'
disable-animations: false
avd-name: e2e_emulator
script: node .github/workflow-scripts/maestro-android.js ${{ inputs.app-path }} ${{ inputs.app-id }} ${{ inputs.maestro-flow }} ${{ inputs.flavor }} ${{ inputs.working-directory }}
script: node .github/workflow-scripts/maestro-android.js ${{ inputs.app-path }} ${{ inputs.app-id }} ${{ inputs.maestro-flow }} ${{ inputs.flavor }} ${{ inputs.working-directory }} ${{ inputs.test-state-path }}
- name: Normalize APP_ID
id: normalize-app-id
shell: bash
Expand All @@ -82,9 +86,10 @@ runs:
report.xml
screen.mp4
- name: Store Logs
if: steps.run-tests.outcome == 'failure'
if: always()
uses: actions/upload-artifact@v6
with:
name: maestro-logs-android-${{ steps.normalize-app-id.outputs.app-id }}-${{ inputs.flavor }}-${{ inputs.emulator-arch }}-NewArch
overwrite: true
if-no-files-found: ignore
path: /tmp/MaestroLogs
89 changes: 89 additions & 0 deletions .github/workflow-scripts/__tests__/maestro-android-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

const fs = require('fs');
const os = require('os');
const path = require('path');

const {
collectFlows,
executeFlowSuite,
loadState,
} = require('../maestro-android');

describe('Maestro Android runner', () => {
let temporaryDirectory;

beforeEach(() => {
temporaryDirectory = fs.mkdtempSync(
path.join(os.tmpdir(), 'maestro-android-test-'),
);
jest.spyOn(console, 'error').mockImplementation(() => {});
});

afterEach(() => {
jest.restoreAllMocks();
fs.rmSync(temporaryDirectory, {recursive: true, force: true});
});

it('collects flows recursively in a stable order', () => {
const nestedDirectory = path.join(temporaryDirectory, 'nested');
fs.mkdirSync(nestedDirectory);
fs.writeFileSync(path.join(temporaryDirectory, 'second.yaml'), 'appId: x');
fs.writeFileSync(path.join(temporaryDirectory, 'image.png'), 'not a flow');
fs.writeFileSync(path.join(nestedDirectory, 'first.yml'), 'appId: x');

expect(collectFlows(temporaryDirectory)).toEqual([
path.join(nestedDirectory, 'first.yml'),
path.join(temporaryDirectory, 'second.yaml'),
]);
});

it('runs every flow and retries only flows that have not passed', () => {
const flows = ['first.yml', 'second.yml', 'third.yml'].map(file =>
path.join(temporaryDirectory, file),
);
const statePath = path.join(temporaryDirectory, 'state', 'results.json');
const firstAttempt = jest.fn(flow => {
if (flow.endsWith('second.yml')) {
throw new Error('failed assertion');
}
});

expect(() =>
executeFlowSuite({
flows,
appId: 'com.example',
state: loadState(statePath),
statePath,
executeFlow: firstAttempt,
}),
).toThrow('1 Maestro flow(s) failed');
expect(firstAttempt).toHaveBeenCalledTimes(3);

const retry = jest.fn();
executeFlowSuite({
flows,
appId: 'com.example',
state: loadState(statePath),
statePath,
executeFlow: retry,
});

expect(retry).toHaveBeenCalledTimes(1);
expect(retry.mock.calls[0][0]).toBe(flows[1]);

const finalState = loadState(statePath);
expect(Object.values(finalState.flows)).toEqual([
{status: 'passed', attempts: 1},
{status: 'passed', attempts: 2},
{status: 'passed', attempts: 1},
]);
});
});
Loading
Loading