From 2608b5a2f0d2d29ab85dd121cdf4ea1e4ac730bc Mon Sep 17 00:00:00 2001 From: finalerock44 <77282157+finalerock44@users.noreply.github.com> Date: Thu, 17 Sep 2026 12:55:13 +0100 Subject: [PATCH] Add cancel-previous input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passes --cancel-previous through to the CLI, which asks the API to cancel the still-queued tests of the previous run of this job on the same branch or PR. Set check-name per job when one commit runs the action twice: it scopes the group, so without it the iOS job would cancel the Android job's tests. dist/ is deliberately not rebuilt here — the Build Source workflow does that on main. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 24 ++++++++++++++++++++++++ action.yml | 3 +++ src/index.ts | 2 ++ src/methods/params.test.ts | 6 ++++++ src/methods/params.ts | 4 ++++ 5 files changed, 39 insertions(+) diff --git a/README.md b/README.md index eb0cd93..4e74fca 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,30 @@ the failing iOS run should have held. Keep the value fixed for a given job: GitHub matches required checks by name, so a name that changes per commit can never be required. +## Cancelling superseded runs + +Push twice in quick succession and the first run's queued tests are dead +weight. `cancel-previous` cancels them when the newer run is submitted: + +```yaml +- uses: devicecloud-dev/device-cloud-for-maestro@v2 + with: + api-key: ${{ secrets.DCD_API_KEY }} + app-file: build/app.apk + cancel-previous: true + check-name: Android +``` + +The previous run is matched on repo + branch (or PR number) + `check-name`, so +set `check-name` per job whenever one commit runs this action more than once — +without it the iOS job would cancel the Android job's queued tests. Runs from +the same workflow run never cancel each other. + +Only queued tests are cancelled: anything already running on a device finishes +and reports normally. Cancelled tests are refunded at 75%. The superseded run +exits 0 rather than failing your build, sends no completion email or webhook, +and its GitHub check is closed as skipped so it cannot block a PR. + ## Migrating from Maestro Cloud Replace the `uses` line in your workflow: diff --git a/action.yml b/action.yml index 595a107..8a1d726 100644 --- a/action.yml +++ b/action.yml @@ -23,6 +23,9 @@ inputs: async: description: 'Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)' required: false + cancel-previous: + description: 'Cancel the still-queued tests of the previous run of this job on the same branch or PR. Tests already running are left to finish; cancelled tests are refunded at 75%. Set check-name per job when one commit runs this action more than once, since it scopes the group' + required: false device-locale: description: 'Locale that will be set to a device, ISO-639-1 code and uppercase ISO-3166-1 code e.g. "de_DE" for Germany' required: false diff --git a/src/index.ts b/src/index.ts index f8350fb..e8c34d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -129,6 +129,7 @@ const run = async (): Promise => { appBinaryId, appFilePath, async, + cancelPrevious, config, deviceLocale, downloadArtifacts, @@ -177,6 +178,7 @@ const run = async (): Promise => { 'app-binary-id': appBinaryId, 'app-file': appFilePath, async, + 'cancel-previous': cancelPrevious, config, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, diff --git a/src/methods/params.test.ts b/src/methods/params.test.ts index afcca5b..5b8b9f4 100644 --- a/src/methods/params.test.ts +++ b/src/methods/params.test.ts @@ -97,15 +97,21 @@ describe('getParameters', () => { it('maps boolean inputs from the string "true"', async () => { inputs['async'] = 'true'; inputs['google-play'] = 'true'; + inputs['cancel-previous'] = 'true'; inputs['debug'] = 'true'; let params = await getParameters(); expect(params.async).toBe(true); expect(params.googlePlay).toBe(true); + expect(params.cancelPrevious).toBe(true); expect(params.debug).toBe(true); inputs['async'] = 'false'; + inputs['cancel-previous'] = ''; params = await getParameters(); expect(params.async).toBe(false); + // Unset means off: the falsy-dropping reducer in index.ts then omits + // the flag entirely, so an opted-out run's command line is unchanged. + expect(params.cancelPrevious).toBe(false); }); it('normalises empty device inputs to null', async () => { diff --git a/src/methods/params.ts b/src/methods/params.ts index 33704de..a754b0c 100644 --- a/src/methods/params.ts +++ b/src/methods/params.ts @@ -16,6 +16,7 @@ export type Params = { androidDevice: string | null; excludeFlows: string; googlePlay: boolean; + cancelPrevious: boolean; iosDevice: string | null; name?: string; deviceLocale?: string; @@ -187,6 +188,8 @@ export async function getParameters(): Promise { const excludeFlows = core.getInput('exclude-flows', { required: false }); const googlePlay = core.getInput('google-play', { required: false }) === 'true'; + const cancelPrevious = + core.getInput('cancel-previous', { required: false }) === 'true'; const deviceLocale = core.getInput('device-locale', { required: false }); const downloadArtifacts = parseDownloadArtifacts( @@ -266,6 +269,7 @@ export async function getParameters(): Promise { iosDevice, excludeFlows, googlePlay, + cancelPrevious, deviceLocale, downloadArtifacts, maestroVersion,