Conversation
Both inputs are superseded by the string-based device-os parameter (e.g. device-os: android-33, device-os: iOS-18-2). - android-api-level: kept as a documented input but marked deprecated via deprecationMessage in action.yml; emits a core.warning at runtime and is still forwarded to the API. - ios-version: removed from the typed Params and from the upload payload. A core.warning is still emitted if it is supplied so that any caller still passing it gets a clear message instead of a silent no-op (it has been undocumented since v2.0.0 but the input was still being read and forwarded). - README and device-os/device-model rows updated with the supported values for both iOS and Android. dist/ is intentionally not rebuilt in this commit.
Covers: - no warnings on the happy path - android-api-level emits a warning and is still forwarded - ios-version emits a warning and is NOT forwarded - device-os / device-model accept iOS and Android values - device-os: web bypasses the app-file requirement
…d dist - Restore ios-version forwarding so the deprecation behaviour matches android-api-level: emit a core.warning, but still pass the value through to the API. This avoids any behaviour change for users still passing the input. - Extract buildUploadRequest into its own module so the wire-format mapping can be unit-tested without pulling in node-fetch. - Add __tests__/uploadRequest.test.ts covering payload shape: androidApiLevel/iOSVersion forwarded when set and omitted when not, agent='github', appBinaryId coercion, tag forwarding, and a serialization sanity check on the legacy wire keys. - Add __tests__/inputValidation.test.ts covering app-file / app-binary-id / device-os: web mutual exclusion (mobile happy paths, both-set / neither-set rejections, web rejections when app inputs are supplied). - Update __tests__/deprecation.test.ts to assert ios-version is forwarded (was previously asserting it was dropped). - Update README note to reflect that both inputs warn-and-forward. - Rebuild dist/index.js so the published action picks up the changes.
Revert this commit before merging — main must stay on @v2.
Folded __tests__/deprecation.test.ts and __tests__/inputValidation.test.ts into __tests__/getParameters.test.ts and made them table-driven over the shared dimensions (deprecated input name, device-os platform, validation rule). Same coverage, fewer copies of the test scaffolding. Also collapsed __tests__/uploadRequest.test.ts: one assertion against the fully-populated UploadRequest covers the field-by-field forwarding, empty-string-to-undefined coercion is table-driven across the four nullable string fields, and the omit-when-unset case for the deprecated fields stays as one test. 37 tests now pass (was 43); coverage of behaviour is unchanged.
The CLI prints the upload URL the moment the upload returns so users can click through and watch the run live. The action set the URL as a step output but didn't emit it to the log until polling completed, which made every "Waiting for analyses to complete..." line feel unactionable. Print the URL (and app binary id) right after the upload, then start the poller. Mirrors maestro-cli/CloudInteractor.kt:415-425.
This reverts commit bfc27fd.
This reverts commit 877fc42.
Captures the agreed design for migrating this Action from a bundled TypeScript HTTP client to a composite action that invokes the Maestro CLI directly, aligning with the Bitrise step and Bitbucket pipe. Code change ships in a follow-up; version bump and README rewrite ship in a separate PR after that. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
23 TDD-shaped tasks across three phases (TS prelude, bash run-cloud script, action.yml + CI + deletions + CHANGELOG). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pace test Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The HTTP client, status poller, archive utilities, and app-file inspector are replaced by the Maestro CLI invocation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Also includes a plan doc correction noting that composite-action env var names must preserve hyphens (INPUT_APP-FILE) so @actions/core.getInput can read them — getInput only normalizes spaces to underscores. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove stale dist/37.index.js chunk left over from the deleted index.ts build output (Task 21 missed it). - Prefix build with `rm -rf dist` so future deletions don't accumulate orphaned chunks in dist/. - Add `npm run test:bats` for local convenience (uses npm global root for BATS_LIB_PATH). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-merge review feedback: - Guard mktemp so a temp-file failure surfaces a clear error instead of silently writing to an empty path. - Move temp-file cleanup to a trap on EXIT so it runs on every exit path, not only after a successful CLI run. - Add comments pointing to the maestro-cli source lines that produce the parsed strings, so future maintainers know what to grep when the CLI release notes change log formatting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Project IDs in Maestro Cloud are identifiers, not credentials — only the api-key needs to live in secrets. Storing project-id in secrets caused GitHub to redact it from logs, breaking the console URLs the CLI prints (e.g. https://app.maestro.dev/project/***/...). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CHANGELOG isn't tracked in this repo's conventions; the migration notes live in the PR body. The superpowers spec/plan were process artifacts for the implementation flow, not source-of-truth docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add runs-on matrix [ubuntu-latest, macos-latest]. - Inline project-id so console URLs are clickable in workflow logs. - Rename the install step for clarity (it's there for download-samples, not for the action — the action installs the CLI itself). The action reference stays pinned at @v2; once the rolling tag moves to this branch's lineage, this workflow will start exercising the new composite action on both platforms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The macOS-vs-ubuntu split didn't surface platform-specific issues in the verification run, and the iOS test path is independent of the runner OS (uploads go to Maestro Cloud regardless). Reverting to a single ubuntu-latest job keeps the smoke workflows fast and cheap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Maestro CLI doesn't expose a JSON output format, but the JUnit
reporter (JUnitTestSuiteReporter.kt) preserves the full FlowStatus
enum on each <testcase status="..."> attribute — including
WARNING/CANCELED/STOPPED. So we can pass --format junit --output
internally and parse the XML to reconstruct the v1 output contract.
- New `postprocess.ts` parses junit XML into [{name, status, errors}]
using a minimal regex-based parser (no XML lib dep) and writes
MAESTRO_CLOUD_FLOW_RESULTS via @actions/core.setOutput.
- run-cloud.sh now passes --format junit --output <tmp> to the CLI
unconditionally, then invokes the post-processor regardless of CLI
exit code (a junit XML is written even when flows fail).
- Empty array is emitted when no junit file is produced (e.g. async
upload, or CLI fails before reporting).
- Bundle build now produces both dist/prelude.js and dist/postprocess.js.
- Tests: 9 new jest cases for parseJunit (entity decoding, nested
properties, multi-suite, multiline failure messages); 4 new bats
cases verifying the wiring end-to-end via a junit-emitting fake
maestro shim.
Note: cancellationReason is not present in junit output, so it is
not reconstructed. SUCCESS/ERROR/WARNING/CANCELED/STOPPED + the
first failure message are preserved — matching v1's practical use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…able Action outputs aren't surfaced in workflow logs by default — we need a downstream step to read them back to confirm they're populated. Added a "Verify Android outputs" step (with if: always() so it runs even if iOS fails) that echoes all three outputs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Composite actions don't auto-expose their steps' $GITHUB_OUTPUT writes
as the action's outputs — they require an explicit top-level outputs:
block in action.yml that maps to a step's outputs. Without it,
\${{ steps.<id>.outputs.MAESTRO_CLOUD_* }} reads as empty in the
caller workflow.
The validation run revealed this: validate-local.yml's "Verify Android
outputs" step printed empty values for all three outputs even though
run-cloud.sh wrote them to GITHUB_OUTPUT correctly.
- Tagged the third step with id: run so we can reference it.
- Added outputs: block for MAESTRO_CLOUD_APP_BINARY_ID,
MAESTRO_CLOUD_CONSOLE_URL, MAESTRO_CLOUD_FLOW_RESULTS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| node-version: 20 | ||
| - run: npm ci | ||
| - run: npm test | ||
| - run: npm run build |
There was a problem hiding this comment.
QQ: we don't need npm install now?
There was a problem hiding this comment.
The CI now uses npm ci (cleaner reproducible install). This is an intentional swap.
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
There was a problem hiding this comment.
Not sure, if I understand the purpose of this? Do we dispatch this workflow manually from local to check if its working against a git commit reference?
There was a problem hiding this comment.
It's a manual workflow_dispatch smoke test against a real commit/CLI version. Let me add it as a comment as well.
| required: false | ||
| android-api-level: | ||
| description: "Android API level to run your flow against" | ||
| description: "[Deprecated] Android API level. Use 'device-os' instead (e.g. android-33)." |
There was a problem hiding this comment.
Will users see this message on latest version?
Is this displayed and prelude message both are displayed? Then users would see duplicated deprecation messages.
There was a problem hiding this comment.
Good catch.
You're right, that was three places saying the same thing. Consolidated to one: deprecationMessage in action.yml is now the only deprecation surface. GitHub natively renders it as a warning annotation in the run, so we don't need core.warning duplicating it from prelude.ts. Also softened the input description from [Deprecated] prefix to a "Prefer device-os" hint.
| shell: bash | ||
| env: | ||
| MDEV_CI: github | ||
| run: bash "${{ github.action_path }}/scripts/run-cloud.sh" |
There was a problem hiding this comment.
Seems like we missed:
- MAESTRO_CLOUD_UPLOAD_STATUS that sets on non async mode, its going to output empty in all cases?
- Seems like MAESTRO_CLOUD_FLOW_RESULTS have shape in readme that expects cancellationReason and now its in different shape?
Thoughts on this: what would be best way to reproduce this via a test? Lets say if we want to write a failing test for MAESTRO_CLOUD_UPLOAD_STATUS should not be empty for non async mode or even this issue later:
Our testing strategy here should fulfill that.
There was a problem hiding this comment.
Checking JUnitTestSuiteReporter.kt in maestro-cli - the emitter drops cancellationReason entirely during JUnit serialization (it's only on the in-memory UploadStatus.FlowResult model). So: cancellationReason is unrecoverable from JUnit. Dropped from FlowResult and updated README to match. We can restore it once the CLI's JUnit emitter writes it (or by switching to a richer format later).
| MAESTRO_VERSION: ${{ inputs.maestro-cli-version }} | ||
| run: | | ||
| curl -Ls --retry 3 --retry-all-errors "https://get.maestro.mobile.dev" | bash | ||
| echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" |
There was a problem hiding this comment.
This means using action-maestro-cloud, would automatically download latest maestro cloud and do the cli command now.
Is there a way in workflow dispatch we can test this for a cli version or latest release?
| run: node "${{ github.action_path }}/dist/prelude.js" | ||
|
|
||
| - name: Run maestro cloud | ||
| id: run |
There was a problem hiding this comment.
When we change this we have to also verify that run-cloud script is compatible with all type of runners since consumers of action-maestro-cloud could be ubuntu, macOS as well. Putting what claude said, might be just ubuntu specific and we need to be careful about:
run-cloud.sh:18—mktemp -t maestro-junit.XXXXXX.On BSD (macOS) -t is a prefix, on GNU it's a template. Behavior differs.
-run-cloud.sh:57—sed -E 's/\x1b\[[0-9;]*m//g'. BSD sed does not interpret \x1b; on a macOS runner this will silently fail to strip ANSI, breaking the APP_BINARY_ID / CONSOLE_URL parsing at run-cloud.sh:63-64. Either use a
portable form (printf '\033' literal, or tr -d) or restore at least one macOS leg in validate-local.yml.
There was a problem hiding this comment.
Fixed both:
mktemp -t→explicit ${TMPDIR:-/tmp}/maestro-junit.XXXXXXtemplate (BSD and GNU both treat this as a template).- ANSI strip uses a literal ESC byte from printf '\033' injected into the sed pattern, instead of \x1b which BSD sed would read literally and silently fail to strip - that would break
APP_BINARY_ID/CONSOLE_URLparsing.
Added macos-latest as a parallel matrix entry to both validate.yml and validate-local.yml (with fail-fast: false) so we'll catch this kind of regression.
| @@ -6,40 +6,47 @@ on: | |||
| jobs: | |||
| validate: | |||
There was a problem hiding this comment.
It would be worth checking following cases here as well:
- app-binary-id chaining (the headline pattern in README:298-313, and the only path where the binary-id fallback at run-cloud.sh:68-70 ever matters).
- device-model and device-os is set.
- env: | multiline — biggest regression surface since you rewrote the parser.
| --format junit | ||
| --output "$JUNIT_FILE" | ||
| "${env_args[@]+"${env_args[@]}"}" | ||
| --flows "${MDEV_WORKSPACE:-.maestro}" |
There was a problem hiding this comment.
- Do we had default of junit before, is there reason to default to that?
- If we pass both android-api-level and deviceOS here and both are non null, the logic to pick deviceOS is on cli right?
There was a problem hiding this comment.
(a) JUnit is forced because CLI has no JSON format and we need it to reconstruct MAESTRO_CLOUD_FLOW_RESULTS
(b) Yes, cli takes care of picking the right deviceOS
| iOS example: | ||
|
|
||
| ```yaml | ||
| - uses: mobile-dev-inc/action-maestro-cloud@v2.0.2 |
There was a problem hiding this comment.
Curious, what would be this version seems like v3 ?
There was a problem hiding this comment.
Yes it will v3 since its a major change
| fail) | ||
| echo "Some failure happened" | ||
| exit 1 | ||
| ;; |
There was a problem hiding this comment.
This seems like complicated logic of parsing junit, did we had this before by default?
Writing what claude raised about this:
- postprocess.ts:38 only matches . Maestro CLI's junit emitter uses for infra failures and for assertion failures. An infra-failed flow currently reports errors: [].
- postprocess.ts:38 doesn't strip wrappers. If the CLI wraps messages with special chars in CDATA (it often does), users get in their errors array.
There was a problem hiding this comment.
We didn't need this logic before because we were using the response from curl request directly. Now its CLI.
Reading JUnitTestSuiteReporter.kt in maestro-cli: the emitter only writes . It never emits or .
Swapped the regex-based parser for fast-xml-parser. Even with the simpler scope (only ), it removes manual entity decoding and gives us free CDATA handling - protection against future emitter changes. Added one CDATA test as a defensive regression check.
… from MAESTRO_CLOUD_FLOW_RESULTS
proksh
left a comment
There was a problem hiding this comment.
Ran https://github.com/mobile-dev-inc/action-maestro-cloud/actions/runs/25483317566, and its working. Failure is because of test failure on cloud which is expected.
| node-version: 20 | ||
| - run: npm ci | ||
| - run: npm test | ||
| - run: npm run build |
There was a problem hiding this comment.
The CI now uses npm ci (cleaner reproducible install). This is an intentional swap.
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
There was a problem hiding this comment.
It's a manual workflow_dispatch smoke test against a real commit/CLI version. Let me add it as a comment as well.
| --format junit | ||
| --output "$JUNIT_FILE" | ||
| "${env_args[@]+"${env_args[@]}"}" | ||
| --flows "${MDEV_WORKSPACE:-.maestro}" |
There was a problem hiding this comment.
(a) JUnit is forced because CLI has no JSON format and we need it to reconstruct MAESTRO_CLOUD_FLOW_RESULTS
(b) Yes, cli takes care of picking the right deviceOS
| iOS example: | ||
|
|
||
| ```yaml | ||
| - uses: mobile-dev-inc/action-maestro-cloud@v2.0.2 |
There was a problem hiding this comment.
Yes it will v3 since its a major change
| required: false | ||
| android-api-level: | ||
| description: "Android API level to run your flow against" | ||
| description: "[Deprecated] Android API level. Use 'device-os' instead (e.g. android-33)." |
There was a problem hiding this comment.
Good catch.
You're right, that was three places saying the same thing. Consolidated to one: deprecationMessage in action.yml is now the only deprecation surface. GitHub natively renders it as a warning annotation in the run, so we don't need core.warning duplicating it from prelude.ts. Also softened the input description from [Deprecated] prefix to a "Prefer device-os" hint.
| fail) | ||
| echo "Some failure happened" | ||
| exit 1 | ||
| ;; |
There was a problem hiding this comment.
We didn't need this logic before because we were using the response from curl request directly. Now its CLI.
Reading JUnitTestSuiteReporter.kt in maestro-cli: the emitter only writes . It never emits or .
Swapped the regex-based parser for fast-xml-parser. Even with the simpler scope (only ), it removes manual entity decoding and gives us free CDATA handling - protection against future emitter changes. Added one CDATA test as a defensive regression check.
| shell: bash | ||
| env: | ||
| MDEV_CI: github | ||
| run: bash "${{ github.action_path }}/scripts/run-cloud.sh" |
There was a problem hiding this comment.
Checking JUnitTestSuiteReporter.kt in maestro-cli - the emitter drops cancellationReason entirely during JUnit serialization (it's only on the in-memory UploadStatus.FlowResult model). So: cancellationReason is unrecoverable from JUnit. Dropped from FlowResult and updated README to match. We can restore it once the CLI's JUnit emitter writes it (or by switching to a richer format later).
| } | ||
|
|
||
| // 7. Files | ||
| if (appFile) core.exportVariable('MDEV_APP_FILE', appFile) |
There was a problem hiding this comment.
Restored glob expansion in prelude.ts via @actions/glob.
| @@ -6,40 +6,47 @@ on: | |||
| jobs: | |||
| validate: | |||
| MAESTRO_VERSION: ${{ inputs.maestro-cli-version }} | ||
| run: | | ||
| curl -Ls --retry 3 --retry-all-errors "https://get.maestro.mobile.dev" | bash | ||
| echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" |
Summary
Replaces the bundled TypeScript HTTP client with a composite GitHub Action that uses Maestro CLI, aligning the architecture with the Bitrise step and Bitbucket pipe. Removes the bespoke ApiClient/StatusPoller/archive code in favor of a small TypeScript prelude that derives env vars, a bash script that invokes maestro cloud, and a post-processor that reconstructs per-flow results from the junit report. Code-only change — tag cut, v2 pointer move, and README updates are deferred to a follow-up.
Test plan
npm test— Jest, 50 PASS (41 prelude + 9 postprocess)npm run test:bats— bats, 23 PASSnpm run build— producesdist/prelude.js+dist/postprocess.js; freshness check is cleanvalidate-local.ymlAndroid test passes onubuntu-latest(run 25175152565)validate-local.ymllogs are clickable (project ID inlined)MAESTRO_CLOUD_APP_BINARY_ID,MAESTRO_CLOUD_CONSOLE_URL, andMAESTRO_CLOUD_FLOW_RESULTSpopulated end-to-end and readable downstream