diff --git a/README.md b/README.md index 16718a7a4..bb662e4fe 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ jobs: | `disable-spellchecker` | Optional | `false` | Whether to disable spellchecker - `true` or `false`. | | `disable-linux-hw-accel` | Optional | `auto` | Whether to disable hardware acceleration on Linux machines - `true`, `false` or `auto`.| | `enable-hw-keyboard` | Optional | `false` | Whether to enable hardware keyboard - `true` or `false`. | +| `keep-running` | Optional | `false` | Whether to keep the emulator running after the script so subsequent steps in the same job can use it - `true` or `false`. | | `emulator-build` | Optional | N/A | Build number of a specific version of the emulator binary to use e.g. `6061023` for emulator v29.3.0.0. | | `working-directory` | Optional | `./` | A custom working directory - e.g. `./android` if your root Gradle project is under the `./android` sub-directory within your repository. Will be used for `script` & `pre-emulator-launch-script`. | | `ndk` | Optional | N/A | Version of NDK to install - e.g. `21.0.6113669` | diff --git a/__tests__/input-validator.test.ts b/__tests__/input-validator.test.ts index 068501208..d9cc55fe5 100644 --- a/__tests__/input-validator.test.ts +++ b/__tests__/input-validator.test.ts @@ -190,6 +190,27 @@ describe('enable-hw-keyboard validator tests', () => { }); }); +describe('keep-running validator tests', () => { + it('Throws if keep-running is not a boolean', () => { + const func = () => { + validator.checkKeepRunning('yes'); + }; + expect(func).toThrowError(`Input for input.keep-running should be either 'true' or 'false'.`); + }); + + it('Validates successfully if keep-running is either true or false', () => { + const func1 = () => { + validator.checkKeepRunning('true'); + }; + expect(func1).not.toThrow(); + + const func2 = () => { + validator.checkKeepRunning('false'); + }; + expect(func2).not.toThrow(); + }); +}); + describe('emulator-build validator tests', () => { it('Throws if emulator-build is not a number', () => { const func = () => { diff --git a/action-types.yml b/action-types.yml index 49bcc51be..a9b8c0fed 100644 --- a/action-types.yml +++ b/action-types.yml @@ -54,6 +54,8 @@ inputs: type: string enable-hw-keyboard: type: boolean + keep-running: + type: boolean emulator-build: type: string working-directory: diff --git a/action.yml b/action.yml index 4f9767ce6..636a3b2bd 100644 --- a/action.yml +++ b/action.yml @@ -57,6 +57,9 @@ inputs: enable-hw-keyboard: description: 'whether to enable hardware keyboard - `true` or `false`.' default: 'false' + keep-running: + description: 'whether to keep the emulator running after the script so subsequent steps can use it - `true` or `false`.' + default: 'false' emulator-build: description: 'build number of a specific version of the emulator binary to use - e.g. `6061023` for emulator v29.3.0.0' working-directory: diff --git a/lib/input-validator.js b/lib/input-validator.js index 7a69636dd..9afc600a8 100644 --- a/lib/input-validator.js +++ b/lib/input-validator.js @@ -10,6 +10,7 @@ exports.checkDisableAnimations = checkDisableAnimations; exports.checkDisableSpellchecker = checkDisableSpellchecker; exports.checkDisableLinuxHardwareAcceleration = checkDisableLinuxHardwareAcceleration; exports.checkEnableHardwareKeyboard = checkEnableHardwareKeyboard; +exports.checkKeepRunning = checkKeepRunning; exports.checkEmulatorBuild = checkEmulatorBuild; exports.checkDiskSize = checkDiskSize; exports.MIN_API_LEVEL = 15; @@ -69,6 +70,11 @@ function checkEnableHardwareKeyboard(enableHardwareKeyboard) { throw new Error(`Input for input.enable-hw-keyboard should be either 'true' or 'false'.`); } } +function checkKeepRunning(keepRunning) { + if (!isValidBoolean(keepRunning)) { + throw new Error(`Input for input.keep-running should be either 'true' or 'false'.`); + } +} function checkEmulatorBuild(emulatorBuild) { if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) { throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`); diff --git a/lib/main.js b/lib/main.js index 98f640469..eb978c89b 100644 --- a/lib/main.js +++ b/lib/main.js @@ -135,6 +135,11 @@ async function run() { (0, input_validator_1.checkEnableHardwareKeyboard)(enableHardwareKeyboardInput); const enableHardwareKeyboard = enableHardwareKeyboardInput === 'true'; console.log(`enable hardware keyboard: ${enableHardwareKeyboard}`); + // keep the emulator running after the script so subsequent steps can use it + const keepRunningInput = core.getInput('keep-running'); + (0, input_validator_1.checkKeepRunning)(keepRunningInput); + const keepRunning = keepRunningInput === 'true'; + console.log(`keep running: ${keepRunning}`); // emulator build const emulatorBuildInput = core.getInput('emulator-build'); if (emulatorBuildInput) { @@ -220,8 +225,18 @@ async function run() { catch (error) { core.setFailed(error instanceof Error ? error.message : error); } - // finally kill the emulator - await (0, emulator_manager_1.killEmulator)(port); + // finally kill the emulator, unless the caller asked to keep it running + if (keepRunning) { + // Leave the emulator running for subsequent workflow steps. The backgrounded + // emulator inherits this process's stdio, so the node event loop will not drain + // on its own — force-exit to let the action finish, propagating any exit code + // core.setFailed already set (e.g. when the custom script failed) so failures + // are not masked as success. + process.exit(process.exitCode ?? 0); + } + else { + await (0, emulator_manager_1.killEmulator)(port); + } } catch (error) { // kill the emulator so the action can exit diff --git a/src/input-validator.ts b/src/input-validator.ts index 2fd0c6237..f6aad4718 100644 --- a/src/input-validator.ts +++ b/src/input-validator.ts @@ -63,6 +63,12 @@ export function checkEnableHardwareKeyboard(enableHardwareKeyboard: string): voi } } +export function checkKeepRunning(keepRunning: string): void { + if (!isValidBoolean(keepRunning)) { + throw new Error(`Input for input.keep-running should be either 'true' or 'false'.`); + } +} + export function checkEmulatorBuild(emulatorBuild: string): void { if (isNaN(Number(emulatorBuild)) || !Number.isInteger(Number(emulatorBuild))) { throw new Error(`Unexpected emulator build: '${emulatorBuild}'.`); diff --git a/src/main.ts b/src/main.ts index a6c787e74..67a5b05a4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ import { checkForceAvdCreation, checkChannel, checkEnableHardwareKeyboard, + checkKeepRunning, checkDiskSize, checkPort, playstoreTargetSubstitution, @@ -134,6 +135,12 @@ async function run() { const enableHardwareKeyboard = enableHardwareKeyboardInput === 'true'; console.log(`enable hardware keyboard: ${enableHardwareKeyboard}`); + // keep the emulator running after the script so subsequent steps can use it + const keepRunningInput = core.getInput('keep-running'); + checkKeepRunning(keepRunningInput); + const keepRunning = keepRunningInput === 'true'; + console.log(`keep running: ${keepRunning}`); + // emulator build const emulatorBuildInput = core.getInput('emulator-build'); if (emulatorBuildInput) { @@ -229,8 +236,17 @@ async function run() { core.setFailed(error instanceof Error ? error.message : (error as string)); } - // finally kill the emulator - await killEmulator(port); + // finally kill the emulator, unless the caller asked to keep it running + if (keepRunning) { + // Leave the emulator running for subsequent workflow steps. The backgrounded + // emulator inherits this process's stdio, so the node event loop will not drain + // on its own — force-exit to let the action finish, propagating any exit code + // core.setFailed already set (e.g. when the custom script failed) so failures + // are not masked as success. + process.exit(process.exitCode ?? 0); + } else { + await killEmulator(port); + } } catch (error) { // kill the emulator so the action can exit await killEmulator(port);