From 7c7f4800454777ee42780840323c4fa12618372d Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Fri, 22 Apr 2022 13:10:11 +0300 Subject: [PATCH] wip: cleanup detox test CLI command BREAKING: deprecates --workers and --no-color options because they can be passed through to Jest runner already --- detox/local-cli/test.js | 171 +---- detox/local-cli/test.test.js | 646 +++++++----------- .../testCommand/TestRunnerCommand.js | 163 +++++ .../builder.js} | 25 +- detox/local-cli/testCommand/middlewares.js | 68 ++ .../{utils => testCommand}/warnings.js | 10 - detox/local-cli/utils/jestInternals.js | 15 +- detox/local-cli/utils/splitArgv.js | 93 --- detox/local-cli/utils/yargsUtils.js | 60 ++ .../context/DetoxGlobalContext.js | 3 +- detox/realms/{top => root}/context/index.js | 0 detox/realms/{top => root}/ipc/server.js | 4 +- detox/realms/{global => runner}/index.js | 2 +- .../reporters/DetoxReporter.js | 0 .../reporters/DetoxStreamlineJestReporter.js | 0 .../reporters/FailingTestsReporter.js | 0 detox/realms/worker/environment/index.js | 4 +- detox/runners/jest/globalSetup.js | 2 +- detox/runners/jest/globalTeardown.js | 2 +- detox/runners/jest/index.js | 4 +- detox/runners/jest/reporter.js | 2 +- .../utils/buildDefaultArtifactsRootDirpath.js | 1 + detox/src/configuration/index.js | 13 - detox/src/configuration/index.test.js | 24 - .../genycloud/services/GenyInstanceNaming.js | 1 + detox/src/logger/IPCLogger.js | 1 + .../utils/misc.js => src/utils/envUtils.js} | 9 - detox/test/e2e/detox.config.js | 12 - 28 files changed, 601 insertions(+), 734 deletions(-) create mode 100644 detox/local-cli/testCommand/TestRunnerCommand.js rename detox/local-cli/{utils/testCommandArgs.js => testCommand/builder.js} (84%) create mode 100644 detox/local-cli/testCommand/middlewares.js rename detox/local-cli/{utils => testCommand}/warnings.js (76%) delete mode 100644 detox/local-cli/utils/splitArgv.js create mode 100644 detox/local-cli/utils/yargsUtils.js rename detox/realms/{top => root}/context/DetoxGlobalContext.js (99%) rename detox/realms/{top => root}/context/index.js (100%) rename detox/realms/{top => root}/ipc/server.js (93%) rename detox/realms/{global => runner}/index.js (66%) rename detox/realms/{global => runner}/reporters/DetoxReporter.js (100%) rename detox/realms/{global => runner}/reporters/DetoxStreamlineJestReporter.js (100%) rename detox/realms/{global => runner}/reporters/FailingTestsReporter.js (100%) rename detox/{local-cli/utils/misc.js => src/utils/envUtils.js} (77%) diff --git a/detox/local-cli/test.js b/detox/local-cli/test.js index 230a643d63..0cd06ef04f 100644 --- a/detox/local-cli/test.js +++ b/detox/local-cli/test.js @@ -1,165 +1,34 @@ // @ts-nocheck -const cp = require('child_process'); - -const _ = require('lodash'); -const whichSync = require('which').sync; -const unparse = require('yargs-unparser'); - const { composeDetoxConfig } = require('../src/configuration'); -const { loadLastFailedTests } = require('../src/utils/lastFailedTests'); +const failedTestsService = require('../src/utils/lastFailedTests'); const log = require('../src/utils/logger').child({ __filename }); -const { parse, quote } = require('../src/utils/shellQuote'); -const { printEnvironmentVariables } = require('./utils/misc'); -const { prependNodeModulesBinToPATH } = require('./utils/misc'); -const splitArgv = require('./utils/splitArgv'); -const { DETOX_ARGV_OVERRIDE_NOTICE, DEVICE_LAUNCH_ARGS_DEPRECATION } = require('./utils/warnings'); +const TestRunnerCommand = require('./testCommand/TestRunnerCommand'); module.exports.command = 'test'; module.exports.desc = 'Run your test suite with the test runner specified in package.json'; -module.exports.builder = require('./utils/testCommandArgs'); -module.exports.handler = async function test(argv) { - const { detoxArgs, runnerArgs } = splitArgv.detox(argv); - const { cliConfig, runnerConfig } = await composeDetoxConfig({ argv: detoxArgs }); - - const forwardedArgs = await prepareArgs({ - cliConfig, - runnerConfig, - runnerArgs, +module.exports.builder = require('./testCommand/builder'); +module.exports.middlewares = require('./testCommand/middlewares')({ log }).ordered; +module.exports.handler = async function test({ detoxArgs, runnerArgs, specs }) { + const { cliConfig, deviceConfig, runnerConfig } = await composeDetoxConfig({ argv: detoxArgs }); + const runnerCommand = new TestRunnerCommand({ + // TODO: retrieve from __top IPC + failedTestsService, + log, }); - if (detoxArgs['inspect-brk']) { - const runnerBinary = whichSync('jest', { - path: prependNodeModulesBinToPATH({ ...process.env }), - }); + runnerCommand + .setDeviceConfig(deviceConfig) + .replicateCLIConfig(cliConfig) + .setRunnerConfig(runnerConfig) + .assignArgv(runnerArgs) + .setRetries(detoxArgs.retries) + .setSpecs(specs); - forwardedArgs.argv.$0 = `node --inspect-brk ${require.resolve(runnerBinary)}`; - } else { - forwardedArgs.argv.$0 = runnerConfig.testRunner; + if (detoxArgs['inspect-brk']) { + runnerCommand.enableDebugMode(); } - await runTestRunnerWithRetries(forwardedArgs, { - retries: detoxArgs.retries, - }); + await runnerCommand.execute(); }; -module.exports.middlewares = [ - function applyEnvironmentVariableAddendum(argv, yargs) { - if (process.env.DETOX_ARGV_OVERRIDE) { - log.warn(DETOX_ARGV_OVERRIDE_NOTICE); - - return yargs.parse([ - ...process.argv.slice(2), - ...parse(process.env.DETOX_ARGV_OVERRIDE), - ]); - } - - return argv; - }, - - function warnDeviceAppLaunchArgsDeprecation(argv) { - if (argv['device-boot-args'] && process.argv.some(a => a.startsWith('--device-launch-args'))) { - log.warn(DEVICE_LAUNCH_ARGS_DEPRECATION); - } - - return argv; - } -]; - -async function prepareArgs({ cliConfig, runnerArgs, runnerConfig }) { - const { specs, passthrough } = splitArgv.jest(runnerArgs); - - const argv = _.omitBy({ - color: !cliConfig.noColor && undefined, - config: runnerConfig.runnerConfig /* istanbul ignore next */ || undefined, - maxWorkers: cliConfig.workers || undefined, - - ...passthrough, - }, _.isUndefined); - - return { - argv, - - env: _.omitBy({ - DETOX_APP_LAUNCH_ARGS: cliConfig.appLaunchArgs, - DETOX_ARTIFACTS_LOCATION: cliConfig.artifactsLocation, - DETOX_CAPTURE_VIEW_HIERARCHY: cliConfig.captureViewHierarchy, - DETOX_CLEANUP: cliConfig.cleanup, - DETOX_CONFIGURATION: cliConfig.configuration, - DETOX_CONFIG_PATH: cliConfig.configPath, - DETOX_DEBUG_SYNCHRONIZATION: cliConfig.debugSynchronization, - DETOX_DEVICE_BOOT_ARGS: cliConfig.deviceBootArgs, - DETOX_DEVICE_NAME: cliConfig.deviceName, - DETOX_FORCE_ADB_INSTALL: cliConfig.forceAdbInstall, - DETOX_GPU: cliConfig.gpu, - DETOX_HEADLESS: cliConfig.headless, - DETOX_LOGLEVEL: cliConfig.loglevel, - DETOX_READ_ONLY_EMU: cliConfig.readonlyEmu, - DETOX_RECORD_LOGS: cliConfig.recordLogs, - DETOX_RECORD_PERFORMANCE: cliConfig.recordPerformance, - DETOX_RECORD_TIMELINE: cliConfig.recordTimeline, - DETOX_RECORD_VIDEOS: cliConfig.recordVideos, - DETOX_REPORT_SPECS: cliConfig.jestReportSpecs, - DETOX_REUSE: cliConfig.reuse, - DETOX_TAKE_SCREENSHOTS: cliConfig.takeScreenshots, - DETOX_USE_CUSTOM_LOGGER: cliConfig.useCustomLogger, - }, _.isUndefined), - - specs: _.isEmpty(specs) && runnerConfig.specs ? [runnerConfig.specs] : specs, - }; -} - -function launchTestRunner({ argv, env, specs }) { - const { $0: command, ...restArgv } = argv; - const fullCommand = [ - command, - quote(unparse(_.omitBy(restArgv, _.isUndefined))), - specs.join(' ') - ].filter(Boolean).join(' '); - - log.info(printEnvironmentVariables(env) + fullCommand); - - cp.execSync(fullCommand, { - stdio: 'inherit', - env: _({}) - .assign(process.env) - .assign(env) - .omitBy(_.isUndefined) - .tap(prependNodeModulesBinToPATH) - .value() - }); -} - -async function runTestRunnerWithRetries(forwardedArgs, { keepLockFile, platform, retries }) { - let runsLeft = 1 + retries; - let launchError; - - do { - try { - if (launchError) { - const list = forwardedArgs.specs.map((file, index) => ` ${index + 1}. ${file}`).join('\n'); - log.error( - `There were failing tests in the following files:\n${list}\n\n` + - 'Detox CLI is going to restart the test runner with those files...\n' - ); - } - - launchTestRunner(forwardedArgs); - launchError = null; - } catch (e) { - launchError = e; - - const lastFailedTests = await loadLastFailedTests(); // TODO: retrieve from __top IPC - if (_.isEmpty(lastFailedTests)) { - throw e; - } - - forwardedArgs.specs = lastFailedTests; - forwardedArgs.env.DETOX_RERUN_INDEX = 1 + (forwardedArgs.env.DETOX_RERUN_INDEX || 0); - } - } while (launchError && --runsLeft > 0); - - if (launchError) { - throw launchError; - } -} diff --git a/detox/local-cli/test.test.js b/detox/local-cli/test.test.js index 5896ddf1da..d0e51d0b1b 100644 --- a/detox/local-cli/test.test.js +++ b/detox/local-cli/test.test.js @@ -14,7 +14,7 @@ const fs = require('fs-extra'); const _ = require('lodash'); const yargs = require('yargs'); -const { DEVICE_LAUNCH_ARGS_DEPRECATION } = require('./utils/warnings'); +const { DEVICE_LAUNCH_ARGS_DEPRECATION } = require('./testCommand/warnings'); describe('CLI', () => { let cp; @@ -80,445 +80,317 @@ describe('CLI', () => { }); }); - describe('(jest)', () => { - beforeEach(() => { - detoxConfig.testRunner = 'jest'; + describe('given no extra args (iOS)', () => { + beforeEach(async () => { + singleConfig().type = 'ios.simulator'; + await run(); }); - describe('given no extra args (iOS)', () => { - beforeEach(async () => { - singleConfig().type = 'ios.simulator'; - await run(); - }); - - test('should produce a default command (integration test, ios)', () => { - const args = `--config e2e/config.json --testNamePattern ${quote('^((?!:android:).)*$')}`; - expect(cliCall().command).toBe(`jest ${args}`); - }); - - test('should put default environment variables (integration test, ios)', () => { - expect(cliCall().env).toEqual({ - DETOX_START_TIMESTAMP: expect.any(Number), - DETOX_CONFIG_PATH: expect.any(String), - DETOX_REPORT_SPECS: true, - DETOX_USE_CUSTOM_LOGGER: true, - }); - }); + test('should produce a default command (integration test, ios)', () => { + expect(cliCall().command).toBe(`jest --config e2e/config.json`); }); - describe('given no extra args (Android)', () => { - beforeEach(async () => { - singleConfig().type = 'android.emulator'; - await run(); - }); - - test('should produce a default command (integration test)', () => { - const args = `--config e2e/config.json --testNamePattern ${quote('^((?!:ios:).)*$')}`; - expect(cliCall().command).toBe(`jest ${args}`); + test('should put default environment variables (integration test, ios)', () => { + expect(cliCall().envHint).toEqual({ + DETOX_CONFIG_PATH: expect.any(String), }); - - test('should put default environment variables (integration test)', () => { - expect(cliCall().env).toEqual({ - DETOX_START_TIMESTAMP: expect.any(Number), - DETOX_CONFIG_PATH: expect.any(String), - DETOX_REPORT_SPECS: true, - DETOX_USE_CUSTOM_LOGGER: true, - }); - }); - }); - - test.each([['-c'], ['--configuration']])( - '%s should provide inverted --testNamePattern that configuration (jest)', - async (__configuration) => { - detoxConfig.configurations.iosTest = { ...detoxConfig.configurations.single }; - detoxConfig.configurations.iosTest.type = 'ios.simulator'; - detoxConfig.configurations.androidTest = { ...detoxConfig.configurations.single }; - detoxConfig.configurations.androidTest.type = 'android.emulator'; - - await run(`${__configuration} androidTest`); - expect(cliCall(0).command).toContain(`--testNamePattern ${quote('^((?!:ios:).)*$')}`); - expect(cliCall(0).env.DETOX_CONFIGURATION).toBe('androidTest'); - - await run(`${__configuration} iosTest`); - expect(cliCall(1).command).toContain(`--testNamePattern ${quote('^((?!:android:).)*$')}`); - expect(cliCall(1).env.DETOX_CONFIGURATION).toBe('iosTest'); - } - ); - - test.each([['-o'], ['--runner-config']])('%s should point to the specified Jest config', async (__runnerConfig) => { - await run(`${__runnerConfig} e2e/custom.config.js`); - expect(cliCall().command).toContain(`--config e2e/custom.config.js`); - }); - - test.each([['-l'], ['--loglevel']])('%s should be passed as environment variable', async (__loglevel) => { - await run(`${__loglevel} trace`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_LOGLEVEL: 'trace' })); - }); - - test('--no-color should be passed as CLI argument', async () => { - await run(`--no-color`); - expect(cliCall().command).toContain(' --no-color '); - }); - - test.each([['-R'], ['--retries']])('%s should execute successful run once', async (__retries) => { - await run(`-R 1`); - expect(cliCall(1)).toBe(null); - }); - - test.each([['-R'], ['--retries']])('%s should clear failed tests file', async (__retries) => { - await run(`-R 1`); - const { resetLastFailedTests } = require('../src/utils/lastFailedTests'); - expect(resetLastFailedTests).toHaveBeenCalled(); - }); - - test.each([['-R'], ['--retries']])('%s should execute unsuccessful run N extra times', async (__retries) => { - const { loadLastFailedTests } = require('../src/utils/lastFailedTests'); - loadLastFailedTests.mockReturnValueOnce(['e2e/failing1.test.js', 'e2e/failing2.test.js']); - loadLastFailedTests.mockReturnValueOnce(['e2e/failing2.test.js']); - cp.execSync.mockImplementation(() => { throw new Error; }); - - await run(`-R 2`).catch(_.noop); - expect(cliCall(0).env).not.toHaveProperty('DETOX_RERUN_INDEX'); - - expect(cliCall(1).command).toMatch(/ e2e\/failing1.test.js e2e\/failing2.test.js$/); - expect(cliCall(1).env.DETOX_RERUN_INDEX).toBe(1); - - expect(cliCall(2).command).toMatch(/ e2e\/failing2.test.js$/); - expect(cliCall(2).env.DETOX_RERUN_INDEX).toBe(2); - }); - - test.each([['-R'], ['--retries']])('%s should not restart test runner if there are no failing tests paths', async (__retries) => { - const { loadLastFailedTests } = require('../src/utils/lastFailedTests'); - loadLastFailedTests.mockReturnValueOnce([]); - cp.execSync.mockImplementation(() => { throw new Error; }); - - await run(`-R 1`).catch(_.noop); - expect(cliCall(0)).not.toBe(null); - expect(cliCall(1)).toBe(null); - }); - - test.each([['-R'], ['--retries']])('%s should retain -- <...explicitPassthroughArgs>', async (__retries) => { - const { loadLastFailedTests } = require('../src/utils/lastFailedTests'); - loadLastFailedTests.mockReturnValue(['tests/failing.test.js']); - cp.execSync.mockImplementation(() => { throw new Error; }); - - await run(`-R 1 tests -- --debug`).catch(_.noop); - expect(cliCall(0).command).toMatch(/ --debug .* tests$/); - expect(cliCall(1).command).toMatch(/ --debug .* tests\/failing.test.js$/); - }); - - test.each([['-r'], ['--reuse']])('%s should be passed as environment variable', async (__reuse) => { - await run(`${__reuse}`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_REUSE: true })); - }); - - test.each([['-u'], ['--cleanup']])('%s should be passed as environment variable', async (__cleanup) => { - await run(`${__cleanup}`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_CLEANUP: true })); - }); - - test.each([['-d'], ['--debug-synchronization']])('%s should be passed as environment variable', async (__debug_synchronization) => { - await run(`${__debug_synchronization} 5000`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_DEBUG_SYNCHRONIZATION: 5000 })); - }); - - test.each([['-d'], ['--debug-synchronization']])('%s should be passed as 0 when given false', async (__debug_synchronization) => { - await run(`${__debug_synchronization} false`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_DEBUG_SYNCHRONIZATION: 0 })); - }); - - test.each([['-d'], ['--debug-synchronization']])('%s should have default value = 3000', async (__debug_synchronization) => { - await run(`${__debug_synchronization}`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_DEBUG_SYNCHRONIZATION: 3000 })); }); + }); - test.each([['-a'], ['--artifacts-location']])('%s should be passed as environment variable', async (__artifacts_location) => { - await run(`${__artifacts_location} /tmp`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_ARTIFACTS_LOCATION: '/tmp' })); + describe('given no extra args (Android)', () => { + beforeEach(async () => { + singleConfig().type = 'android.emulator'; + await run(); }); - test('--record-logs should be passed as environment variable', async () => { - await run(`--record-logs all`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_RECORD_LOGS: 'all' })); + test('should produce a default command (integration test)', () => { + expect(cliCall().command).toBe(`jest --config e2e/config.json`); }); - test('--take-screenshots should be passed as environment variable', async () => { - await run(`--take-screenshots failing`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_TAKE_SCREENSHOTS: 'failing' })); + test('should put default environment variables (integration test)', () => { + expect(cliCall().envHint).toEqual({ + DETOX_CONFIG_PATH: expect.any(String), + }); }); + }); - test('--record-videos should be passed as environment variable', async () => { - await run(`--record-videos failing`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_RECORD_VIDEOS: 'failing' })); - }); + test('should use runnerConfig.specs as default specs', async () => { + detoxConfig.specs = 'e2e/sanity'; + await run(''); + expect(cliCall().command).toMatch(/ e2e\/sanity$/); + }); - test('--record-performance should be passed as environment variable', async () => { - await run(`--record-performance all`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_RECORD_PERFORMANCE: 'all' })); - }); + test.each([['-o'], ['--runner-config']])('%s should point to the specified Jest config', async (__runnerConfig) => { + await run(`${__runnerConfig} e2e/custom.config.js`); + expect(cliCall().command).toContain(`--config e2e/custom.config.js`); + }); - test('--record-timeline should be passed as environment variable', async () => { - await run(`--record-timeline all`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_RECORD_TIMELINE: 'all' })); - }); + test.each([['-l'], ['--loglevel']])('%s should be passed as environment variable', async (__loglevel) => { + await run(`${__loglevel} trace`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_LOGLEVEL: 'trace' })); + }); - test('--capture-view-hierarchy should be passed as environment variable', async () => { - await run(`--capture-view-hierarchy enabled`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_CAPTURE_VIEW_HIERARCHY: 'enabled' })); - }); + test.each([['-R'], ['--retries']])('%s should execute successful run once', async (__retries) => { + await run(`-R 1`); + expect(cliCall(1)).toBe(null); + }); - test.each([['-w'], ['--workers']])('%s should be passed as CLI argument', async (__workers) => { - await run(`${__workers} 2`); - expect(cliCall().command).toContain('--maxWorkers 2'); - }); + test.each([['-R'], ['--retries']])('%s should clear failed tests file', async (__retries) => { + await run(`-R 1`); + const { resetLastFailedTests } = require('../src/utils/lastFailedTests'); + expect(resetLastFailedTests).toHaveBeenCalled(); + }); - test.each([['-w'], ['--workers']])('%s should be replaced with --maxWorkers ', async (__workers) => { - await run(`${__workers} 2 --maxWorkers 3`); + test.each([['-R'], ['--retries']])('%s should execute unsuccessful run N extra times', async (__retries) => { + const { loadLastFailedTests } = require('../src/utils/lastFailedTests'); + loadLastFailedTests.mockReturnValueOnce(['e2e/failing1.test.js', 'e2e/failing2.test.js']); + loadLastFailedTests.mockReturnValueOnce(['e2e/failing2.test.js']); + cp.execSync.mockImplementation(() => { throw new Error; }); - const { command } = cliCall(); - expect(command).toContain('--maxWorkers 3'); - expect(command).not.toContain('--maxWorkers 2'); - }); + await run(`-R 2`).catch(_.noop); + expect(cliCall(0).env).not.toHaveProperty('DETOX_RERUN_INDEX'); - test.each([['-w'], ['--workers']])('%s can be overriden by a later value', async (__workers) => { - await run(`${__workers} 2 ${__workers} 3`); + expect(cliCall(1).command).toMatch(/ e2e\/failing1.test.js e2e\/failing2.test.js$/); + expect(cliCall(1).env.DETOX_RERUN_INDEX).toBe(1); - const { command } = cliCall(); - expect(command).toContain('--maxWorkers 3'); - expect(command).not.toContain('--maxWorkers 2'); - }); + expect(cliCall(2).command).toMatch(/ e2e\/failing2.test.js$/); + expect(cliCall(2).env.DETOX_RERUN_INDEX).toBe(2); + }); - test.each([['-w'], ['--workers']])('%s should not warn anything for iOS', async (__workers) => { - singleConfig().type = 'ios.simulator'; - await run(`${__workers} 2`); - expect(logger.warn).not.toHaveBeenCalled(); - }); + test.each([['-R'], ['--retries']])('%s should not restart test runner if there are no failing tests paths', async (__retries) => { + const { loadLastFailedTests } = require('../src/utils/lastFailedTests'); + loadLastFailedTests.mockReturnValueOnce([]); + cp.execSync.mockImplementation(() => { throw new Error; }); - test.each([['-w'], ['--workers']])('%s should not put readOnlyEmu environment variable for iOS', async (__workers) => { - singleConfig().type = 'ios.simulator'; - await run(`${__workers} 2`); - expect(cliCall().env).not.toHaveProperty('DETOX_READ_ONLY_EMU'); - }); + await run(`-R 1`).catch(_.noop); + expect(cliCall(0)).not.toBe(null); + expect(cliCall(1)).toBe(null); + }); - test.each([['-w'], ['--workers']])('%s should not put readOnlyEmu environment variable for android.attached', async (__workers) => { - singleConfig().type = 'android.attached'; - await run(`${__workers} 2`); - expect(cliCall().env).not.toHaveProperty('DETOX_READ_ONLY_EMU'); - }); + test.each([['-R'], ['--retries']])('%s should retain -- <...explicitPassthroughArgs>', async (__retries) => { + const { loadLastFailedTests } = require('../src/utils/lastFailedTests'); + loadLastFailedTests.mockReturnValue(['tests/failing.test.js']); + cp.execSync.mockImplementation(() => { throw new Error; }); - test.each([['-w'], ['--workers']])('%s should not put readOnlyEmu environment variable for android.emulator if there is a single worker', async (__workers) => { - singleConfig().type = 'android.emulator'; - await run(`${__workers} 1`); - expect(cliCall().env).not.toHaveProperty('DETOX_READ_ONLY_EMU'); - }); + await run(`-R 1 tests -- --debug`).catch(_.noop); + expect(cliCall(0).command).toMatch(/ --debug .* tests$/); + expect(cliCall(1).command).toMatch(/ --debug .* tests\/failing.test.js$/); + }); - test.each([['-w'], ['--workers']])('%s should put readOnlyEmu environment variable for Android if there are multiple workers', async (__workers) => { - singleConfig().type = 'android.emulator'; - await run(`${__workers} 2`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_READ_ONLY_EMU: true })); - }); + test.each([['-r'], ['--reuse']])('%s should be passed as environment variable', async (__reuse) => { + await run(`${__reuse}`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_REUSE: true })); + }); - test('should omit --testNamePattern for custom platforms', async () => { - singleConfig().type = tempfile('.js', aCustomDriverModule()); + test.each([['-u'], ['--cleanup']])('%s should be passed as environment variable', async (__cleanup) => { + await run(`${__cleanup}`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_CLEANUP: true })); + }); - await run(); - expect(cliCall().command).not.toContain('--testNamePattern'); - }); + test.each([['-d'], ['--debug-synchronization']])('%s should be passed as environment variable', async (__debug_synchronization) => { + await run(`${__debug_synchronization} 5000`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_DEBUG_SYNCHRONIZATION: 5000 })); + }); - test.each([['-t'], ['--testNamePattern']])('should override --testNamePattern if a custom %s value is passed', async (__testNamePattern) => { - await run(`${__testNamePattern} customPattern`); - const { command } = cliCall(); + test.each([['-d'], ['--debug-synchronization']])('%s should be passed as 0 when given false', async (__debug_synchronization) => { + await run(`${__debug_synchronization} false`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_DEBUG_SYNCHRONIZATION: 0 })); + }); - expect(command).not.toMatch(/--testNamePattern .*(ios|android)/); - expect(command).toMatch(/--testNamePattern customPattern($| )/); - }); + test.each([['-d'], ['--debug-synchronization']])('%s should have default value = 3000', async (__debug_synchronization) => { + await run(`${__debug_synchronization}`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_DEBUG_SYNCHRONIZATION: 3000 })); + }); - test('--jest-report-specs, by default, should be true, as environment variable', async () => { - await run(); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_REPORT_SPECS: true })); - }); + test.each([['-a'], ['--artifacts-location']])('%s should be passed as environment variable', async (__artifacts_location) => { + await run(`${__artifacts_location} /tmp`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_ARTIFACTS_LOCATION: '/tmp' })); + }); - test('--jest-report-specs, by default, should be false, if multiple workers are enabled', async () => { - await run('--workers 2'); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_REPORT_SPECS: false })); - }); + test('--record-logs should be passed as environment variable', async () => { + await run(`--record-logs all`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_RECORD_LOGS: 'all' })); + }); - test('--jest-report-specs, set explicitly, should override single worker defaults', async () => { - await run('--jest-report-specs false'); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_REPORT_SPECS: false })); - }); + test('--take-screenshots should be passed as environment variable', async () => { + await run(`--take-screenshots failing`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_TAKE_SCREENSHOTS: 'failing' })); + }); - test('--jest-report-specs, set explicitly, should override multiple workers defaults', async () => { - await run('--workers 2 --jest-report-specs'); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_REPORT_SPECS: true })); - }); + test('--record-videos should be passed as environment variable', async () => { + await run(`--record-videos failing`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_RECORD_VIDEOS: 'failing' })); + }); - test.each([['-H'], ['--headless']])('%s should be passed as environment variable', async (__headless) => { - await run(`${__headless}`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_HEADLESS: true })); - }); + test('--record-performance should be passed as environment variable', async () => { + await run(`--record-performance all`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_RECORD_PERFORMANCE: 'all' })); + }); - test('--gpu should be passed as environment variable', async () => { - await run(`--gpu angle_indirect`); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_GPU: 'angle_indirect' })); - }); + test('--record-timeline should be passed as environment variable', async () => { + await run(`--record-timeline all`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_RECORD_TIMELINE: 'all' })); + }); - test('--device-boot-args should be passed as an environment variable (without deprecation warnings)', async () => { - await run(`--device-boot-args "--verbose"`); - expect(cliCall().env).toEqual(expect.objectContaining({ - DETOX_DEVICE_BOOT_ARGS: '--verbose' - })); - expect(logger.warn).not.toHaveBeenCalledWith(DEVICE_LAUNCH_ARGS_DEPRECATION); - }); + test('--capture-view-hierarchy should be passed as environment variable', async () => { + await run(`--capture-view-hierarchy enabled`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_CAPTURE_VIEW_HIERARCHY: 'enabled' })); + }); - test('--device-launch-args should serve as a deprecated alias to --device-boot-args', async () => { - await run(`--device-launch-args "--verbose"`); - expect(cliCall().env.DETOX_DEVICE_BOOT_ARGS).toBe('--verbose'); - expect(logger.warn).toHaveBeenCalledWith(DEVICE_LAUNCH_ARGS_DEPRECATION); - }); + test('--jest-report-specs, set explicitly, should be passed as an environment variable', async () => { + await run('--jest-report-specs'); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_REPORT_SPECS: true })); + }); - test('--app-launch-args should be passed as an environment variable', async () => { - await run(`--app-launch-args "--debug yes"`); - expect(cliCall().env).toEqual(expect.objectContaining({ - DETOX_APP_LAUNCH_ARGS: '--debug yes', - })); - }); + test.each([['-H'], ['--headless']])('%s should be passed as environment variable', async (__headless) => { + await run(`${__headless}`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_HEADLESS: true })); + }); - test('--use-custom-logger false should be prevent passing environment variable', async () => { - await run(`--use-custom-logger false`); - expect(cliCall().env).toEqual(expect.objectContaining({ - DETOX_USE_CUSTOM_LOGGER: false - })); - }); + test('--gpu should be passed as environment variable', async () => { + await run(`--gpu angle_indirect`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_GPU: 'angle_indirect' })); + }); - test('--force-adb-install should be ignored for iOS', async () => { - singleConfig().type = 'ios.simulator'; - await run(`--force-adb-install`); - expect(cliCall().env).not.toHaveProperty('DETOX_FORCE_ADB_INSTALL'); - }); + test('--device-boot-args should be passed as an environment variable (without deprecation warnings)', async () => { + await run(`--device-boot-args "--verbose"`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ + DETOX_DEVICE_BOOT_ARGS: '--verbose' + })); + expect(logger.warn).not.toHaveBeenCalledWith(DEVICE_LAUNCH_ARGS_DEPRECATION); + }); - test('--force-adb-install should be passed as environment variable', async () => { - singleConfig().type = 'android.emulator'; - await run(`--force-adb-install`); - expect(cliCall().env).toEqual(expect.objectContaining({ - DETOX_FORCE_ADB_INSTALL: true, - })); - }); + test('--device-launch-args should serve as a deprecated alias to --device-boot-args', async () => { + await run(`--device-launch-args "--verbose"`); + expect(cliCall().envHint.DETOX_DEVICE_BOOT_ARGS).toBe('--verbose'); + expect(logger.warn).toHaveBeenCalledWith(DEVICE_LAUNCH_ARGS_DEPRECATION); + }); - test.each([['-n'], ['--device-name']])('%s should be passed as environment variable', async (__device_name) => { - await run(`${__device_name} TheDevice`); - expect(cliCall().env).toEqual(expect.objectContaining({ - DETOX_DEVICE_NAME: 'TheDevice', - })); - }); + test('--app-launch-args should be passed as an environment variable', async () => { + await run(`--app-launch-args "--debug yes"`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ + DETOX_APP_LAUNCH_ARGS: '--debug yes', + })); + }); - test('specifying direct test paths', async () => { - await run(`e2e/01.sanity.test.js e2e/02.sanity.test.js`); - expect(cliCall().command).not.toMatch(/ e2e /); - expect(cliCall().command).not.toMatch(/ e2e$/); - expect(cliCall().command).toMatch(/ e2e\/01.sanity.test.js e2e\/02.sanity.test.js$/); - }); + test('--use-custom-logger false should be prevent passing environment variable', async () => { + await run(`--use-custom-logger false`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ + DETOX_USE_CUSTOM_LOGGER: false + })); + }); - // TODO: fix --inspect-brk behavior on Windows, and replace (cmd|js) with js here - test.each([ - ['--inspect-brk e2eFolder', /^node --inspect-brk .*jest\.(?:cmd|js) .* e2eFolder$/, {}], - ['-d e2eFolder', / e2eFolder$/, { DETOX_DEBUG_SYNCHRONIZATION: 3000 }], - ['--debug-synchronization e2eFolder', / e2eFolder$/, { DETOX_DEBUG_SYNCHRONIZATION: 3000 }], - ['-r e2eFolder', / e2eFolder$/, { DETOX_REUSE: true }], - ['--reuse e2eFolder', / e2eFolder$/, { DETOX_REUSE: true }], - ['-u e2eFolder', / e2eFolder$/, { DETOX_CLEANUP: true }], - ['--cleanup e2eFolder', / e2eFolder$/, { DETOX_CLEANUP: true }], - ['--jest-report-specs e2eFolder', / e2eFolder$/, { DETOX_REPORT_SPECS: true }], - ['-H e2eFolder', / e2eFolder$/, { DETOX_HEADLESS: true }], - ['--headless e2eFolder', / e2eFolder$/, { DETOX_HEADLESS: true }], - ['--keepLockFile e2eFolder', / e2eFolder$/, {}], - ['--use-custom-logger e2eFolder', / e2eFolder$/, { DETOX_USE_CUSTOM_LOGGER: true }], - ['--force-adb-install e2eFolder', / e2eFolder$/, { DETOX_FORCE_ADB_INSTALL: true }], - ])('"%s" should be disambigued correctly', async (command, commandMatcher, envMatcher) => { - singleConfig().type = 'android.emulator'; - await run(command); + test('--force-adb-install should be ignored for iOS', async () => { + singleConfig().type = 'ios.simulator'; + await run(`--force-adb-install`); + expect(cliCall().envHint).not.toHaveProperty('DETOX_FORCE_ADB_INSTALL'); + }); - expect(cliCall().command).toMatch(commandMatcher); - expect(cliCall().env).toEqual(expect.objectContaining(envMatcher)); - }); + test('--force-adb-install should be passed as environment variable', async () => { + singleConfig().type = 'android.emulator'; + await run(`--force-adb-install`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ + DETOX_FORCE_ADB_INSTALL: true, + })); + }); - test('e.g., --debug should be passed through', async () => { - await run(`--debug`); - expect(cliCall().command).toContain('--debug'); - }); + test.each([['-n'], ['--device-name']])('%s should be passed as environment variable', async (__device_name) => { + await run(`${__device_name} TheDevice`); + expect(cliCall().envHint).toEqual(expect.objectContaining({ + DETOX_DEVICE_NAME: 'TheDevice', + })); + }); - test('e.g., --coverageProvider v8 should be passed through', async () => { - await run(`--coverageProvider v8`); - expect(cliCall().command).toContain('--coverageProvider v8'); - }); + test('specifying direct test paths', async () => { + await run(`e2e/01.sanity.test.js e2e/02.sanity.test.js`); + expect(cliCall().command).not.toMatch(/ e2e /); + expect(cliCall().command).not.toMatch(/ e2e$/); + expect(cliCall().command).toMatch(/ e2e\/01.sanity.test.js e2e\/02.sanity.test.js$/); + }); - test('e.g., --debug e2e/Login.test.js should be split to --debug and e2e/Login.test.js', async () => { - await run(`--debug e2e/Login.test.js --coverageProvider v8`); - expect(cliCall().command).toMatch(/--debug --coverageProvider v8 e2e\/Login.test.js$/); - }); + // TODO: fix --inspect-brk behavior on Windows, and replace (cmd|js) with js here + test.each([ + ['--inspect-brk e2eFolder', /^node --inspect-brk \.\/node_modules\/.*jest.* .* e2eFolder$/, {}], + ['-d e2eFolder', / e2eFolder$/, { DETOX_DEBUG_SYNCHRONIZATION: 3000 }], + ['--debug-synchronization e2eFolder', / e2eFolder$/, { DETOX_DEBUG_SYNCHRONIZATION: 3000 }], + ['-r e2eFolder', / e2eFolder$/, { DETOX_REUSE: true }], + ['--reuse e2eFolder', / e2eFolder$/, { DETOX_REUSE: true }], + ['-u e2eFolder', / e2eFolder$/, { DETOX_CLEANUP: true }], + ['--cleanup e2eFolder', / e2eFolder$/, { DETOX_CLEANUP: true }], + ['--jest-report-specs e2eFolder', / e2eFolder$/, { DETOX_REPORT_SPECS: true }], + ['-H e2eFolder', / e2eFolder$/, { DETOX_HEADLESS: true }], + ['--headless e2eFolder', / e2eFolder$/, { DETOX_HEADLESS: true }], + ['--keepLockFile e2eFolder', / e2eFolder$/, {}], + ['--use-custom-logger e2eFolder', / e2eFolder$/, { DETOX_USE_CUSTOM_LOGGER: true }], + ['--force-adb-install e2eFolder', / e2eFolder$/, { DETOX_FORCE_ADB_INSTALL: true }], + ])('"%s" should be disambigued correctly', async (command, commandMatcher, envMatcher) => { + singleConfig().type = 'android.emulator'; + await run(command); + + expect(cliCall().command).toMatch(commandMatcher); + expect(cliCall().envHint).toEqual(expect.objectContaining(envMatcher)); + }); - test.each([ - [`--testNamePattern "should tap"`, `--testNamePattern ${quote('should tap')}`], - [`"e2e tests/first test.spec.js"`, `"e2e tests/first test.spec.js"`], - ])('should escape %s when forwarding it as a CLI argument', async (cmd, expected) => { - await run(cmd); - expect(cliCall().command).toContain(` ${expected}`); - }); + test('e.g., --debug should be passed through', async () => { + await run(`--debug`); + expect(cliCall().command).toContain('--debug'); + }); - test(`should deduce wrapped jest CLI`, async () => { - detoxConfig.testRunner = `nyc jest`; - await run(); - expect(cliCall().command).toMatch(RegExp(`nyc jest `)); - }); + test('e.g., --coverageProvider v8 should be passed through', async () => { + await run(`--coverageProvider v8`); + expect(cliCall().command).toContain('--coverageProvider v8'); + }); - describe.each([['ios.simulator'], ['android.emulator']])('for %s', (deviceType) => { - beforeEach(() => { - Object.values(detoxConfig.configurations)[0].type = deviceType; - }); + test('e.g., --debug e2e/Login.test.js should be split to --debug and e2e/Login.test.js', async () => { + await run(`--debug e2e/Login.test.js --coverageProvider v8`); + expect(cliCall().command).toMatch(/--debug --coverageProvider v8 e2e\/Login.test.js$/); + }); - test('--keepLockFile should be suppress clearing the device lock file', async () => { - await run('--keepLockFile'); - expect(DeviceRegistry).not.toHaveBeenCalled(); - }); + test.each([ + [`"e2e tests/first test.spec.js"`, `"e2e tests/first test.spec.js"`], + ])('should escape %s when forwarding it as a CLI argument', async (cmd, expected) => { + await run(cmd); + expect(cliCall().command).toContain(` ${expected}`); + }); - test('--keepLockFile omission means clearing the device lock file', async () => { - await run(); - expect(DeviceRegistry.mock.instances[0].reset).toHaveBeenCalled(); - }); - }); + test(`should be able to use custom test runner commands`, async () => { + detoxConfig.testRunner = `nyc jest`; + await run(); + expect(cliCall().command).toMatch(RegExp(`nyc jest `)); + }); - test('-- <...explicitPassthroughArgs> should be forwarded to the test runner CLI as-is', async () => { - await run('--device-boot-args detoxArgs e2eFolder -- a -a --a --device-boot-args runnerArgs'); - expect(cliCall().command).toMatch(/a -a --a --device-boot-args runnerArgs .* e2eFolder$/); - expect(cliCall().env).toEqual(expect.objectContaining({ DETOX_DEVICE_BOOT_ARGS: 'detoxArgs' })); - }); + test('-- <...explicitPassthroughArgs> should be forwarded to the test runner CLI as-is', async () => { + await run('--device-boot-args detoxArgs e2eFolder -- a -a --a --device-boot-args runnerArgs'); + expect(cliCall().command).toMatch(/a -a --a --device-boot-args runnerArgs .* e2eFolder$/); + expect(cliCall().envHint).toEqual(expect.objectContaining({ DETOX_DEVICE_BOOT_ARGS: 'detoxArgs' })); + }); - test('-- <...explicitPassthroughArgs> should omit double-dash "--" itself, when forwarding args', async () => { - await run('./detox -- --forceExit'); + test('-- <...explicitPassthroughArgs> should omit double-dash "--" itself, when forwarding args', async () => { + await run('./detox -- --forceExit'); - expect(cliCall().command).toMatch(/ --forceExit .* \.\/detox$/); - expect(cliCall().command).not.toMatch(/ -- --forceExit .* \.\/detox$/); - }); + expect(cliCall().command).toMatch(/ --forceExit .* \.\/detox$/); + expect(cliCall().command).not.toMatch(/ -- --forceExit .* \.\/detox$/); + }); - test('--inspect-brk should prepend "node --inspect-brk" to the command', async () => { - // TODO: fix --inspect-brk behavior on Windows - if (process.platform === 'win32') return; + test('--inspect-brk should prepend "node --inspect-brk" to the command', async () => { + await run('--inspect-brk'); - await run('--inspect-brk'); - const absolutePathToTestRunnerJs = require.resolve(`.bin/jest`); - expect(cliCall().command).toMatch(RegExp(`^node --inspect-brk ${absolutePathToTestRunnerJs}`)); - }); + if (process.platform === 'win32') { + expect(cliCall().command).toMatch(/^node --inspect-brk \.\/node_modules\/jest\/bin\/jest\.js/); + } else { + expect(cliCall().command).toMatch(/^node --inspect-brk \.\/node_modules\/\.bin\/jest/); + } + }); - test('should append $DETOX_ARGV_OVERRIDE to detox test ... command and print a warning', async () => { - process.env.PLATFORM = 'ios'; - process.env.DETOX_ARGV_OVERRIDE = '--inspect-brk --testNamePattern "[$PLATFORM] tap" e2e/sanity/*.test.js'; - await run(); + test('should append $DETOX_ARGV_OVERRIDE to detox test ... command and print a warning', async () => { + process.env.PLATFORM = 'ios'; + process.env.DETOX_ARGV_OVERRIDE = '--inspect-brk --testNamePattern "[$PLATFORM] tap" e2e/sanity/*.test.js'; + await run(); - const pattern = new RegExp(`^node --inspect-brk.* --testNamePattern ${quote('\\[ios\\] tap')}.* e2e/sanity/\\*\\.test.js$`); + const pattern = new RegExp(`^node --inspect-brk.* --testNamePattern ${quote('\\[ios\\] tap')}.* e2e/sanity/\\*\\.test.js$`); - expect(cliCall().command).toMatch(pattern); - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('$DETOX_ARGV_OVERRIDE is detected')); - }); + expect(cliCall().command).toMatch(pattern); + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('$DETOX_ARGV_OVERRIDE is detected')); }); // Helpers @@ -588,9 +460,17 @@ describe('CLI', () => { const [command, opts] = mockCall; + const envHint = _.chain(logger) + .thru(({ log }) => log.mock.calls) + .map(([_level, _childMeta, meta]) => meta && meta.env) + .filter(Boolean) + .get(index) + .value(); + return { command, env: _.omitBy(opts.env, (_value, key) => key in process.env), + envHint, }; } @@ -605,14 +485,4 @@ describe('CLI', () => { function quote(s, q = isInCMD() ? `"` : `'`) { return q + s + q; } - - function aCustomDriverModule() { - return ` - class RuntimeDriverClass {}; - class DeviceAllocationDriverClass {}; - class DeviceDeallocationDriverClass {}; - class ExpectClass {}; - module.exports = { RuntimeDriverClass, DeviceAllocationDriverClass, DeviceDeallocationDriverClass, ExpectClass } - `; - } }); diff --git a/detox/local-cli/testCommand/TestRunnerCommand.js b/detox/local-cli/testCommand/TestRunnerCommand.js new file mode 100644 index 0000000000..8a827fc9ab --- /dev/null +++ b/detox/local-cli/testCommand/TestRunnerCommand.js @@ -0,0 +1,163 @@ +const cp = require('child_process'); +const os = require('os'); + +const _ = require('lodash'); +const unparse = require('yargs-unparser'); + +const { printEnvironmentVariables, prependNodeModulesBinToPATH } = require('../../src/utils/envUtils'); +const { quote } = require('../../src/utils/shellQuote'); + +class TestRunnerCommand { + constructor(config) { + this._$0 = 'jest'; + this._argv = {}; + this._env = {}; + this._envHint = {}; + this._retries = 0; + this._specs = []; + this._deviceConfig = null; + + this._log = config.log; + this._failedTestsService = config.failedTestsService; + } + + setDeviceConfig(config) { + this._deviceConfig = config; + + return this; + } + + setRunnerConfig({ testRunner, runnerConfig, specs }) { + this._$0 = testRunner; + + this.setSpecs(specs); + this.assignArgv({ config: runnerConfig }); + + return this; + } + + assignArgv(runnerArgs) { + Object.assign(this._argv, runnerArgs); + return this; + } + + setRetries(count) { + this._retries = count; + return this; + } + + setSpecs(specs) { + if (!_.isEmpty(specs)) { + this._specs = Array.isArray(specs) ? specs : [specs]; + } + + return this; + } + + enableDebugMode() { + /* istanbul ignore if */ + if (os.platform() === 'win32') { + this._$0 = `node --inspect-brk ./node_modules/jest/bin/jest.js`; + } else { + this._$0 = `node --inspect-brk ./node_modules/.bin/jest`; + } + + this._env = this._envHint; + this._argv.runInBand = true; + } + + replicateCLIConfig(cliConfig) { + this._envHint = _.omitBy({ + DETOX_APP_LAUNCH_ARGS: cliConfig.appLaunchArgs, + DETOX_ARTIFACTS_LOCATION: cliConfig.artifactsLocation, + DETOX_CAPTURE_VIEW_HIERARCHY: cliConfig.captureViewHierarchy, + DETOX_CLEANUP: cliConfig.cleanup, + DETOX_CONFIGURATION: cliConfig.configuration, + DETOX_CONFIG_PATH: cliConfig.configPath, + DETOX_DEBUG_SYNCHRONIZATION: cliConfig.debugSynchronization, + DETOX_DEVICE_BOOT_ARGS: cliConfig.deviceBootArgs, + DETOX_DEVICE_NAME: cliConfig.deviceName, + DETOX_FORCE_ADB_INSTALL: this._deviceConfig.type.startsWith('android.') + ? cliConfig.forceAdbInstall + : undefined, + DETOX_GPU: cliConfig.gpu, + DETOX_HEADLESS: cliConfig.headless, + DETOX_KEEP_LOCKFILE: cliConfig.keepLockFile, + DETOX_LOGLEVEL: cliConfig.loglevel, + DETOX_READ_ONLY_EMU: cliConfig.readonlyEmu, + DETOX_RECORD_LOGS: cliConfig.recordLogs, + DETOX_RECORD_PERFORMANCE: cliConfig.recordPerformance, + DETOX_RECORD_TIMELINE: cliConfig.recordTimeline, + DETOX_RECORD_VIDEOS: cliConfig.recordVideos, + DETOX_REPORT_SPECS: cliConfig.jestReportSpecs, + DETOX_REUSE: cliConfig.reuse, + DETOX_TAKE_SCREENSHOTS: cliConfig.takeScreenshots, + DETOX_USE_CUSTOM_LOGGER: cliConfig.useCustomLogger, + }, _.isUndefined); + + return this; + } + + async execute() { + let runsLeft = 1 + this._retries; + let launchError; + + await this._failedTestsService.resetLastFailedTests(); + + do { + try { + if (launchError) { + const list = this._specs.map((file, index) => ` ${index + 1}. ${file}`).join('\n'); + this._log.error( + `There were failing tests in the following files:\n${list}\n\n` + + 'Detox CLI is going to restart the test runner with those files...\n' + ); + } + + await this._doExecute(); + launchError = null; + } catch (e) { + launchError = e; + + const lastFailedTests = await this._failedTestsService.loadLastFailedTests(); + if (_.isEmpty(lastFailedTests)) { + throw e; + } + + this.setSpecs(lastFailedTests); + this._env.DETOX_RERUN_INDEX = 1 + (this._env.DETOX_RERUN_INDEX || 0); + } + } while (launchError && --runsLeft > 0); + + if (launchError) { + throw launchError; + } + } + + async _doExecute() { + const command = this._$0; + const restArgv = this._argv; + const fullCommand = [ + command, + quote(unparse(_.omitBy(restArgv, _.isUndefined))), + this._specs.join(' ') + ].filter(Boolean).join(' '); + + this._log.info( + { env: this._envHint }, + printEnvironmentVariables(this._envHint) + fullCommand + ); + + cp.execSync(fullCommand, { + stdio: 'inherit', + env: _({}) + .assign(process.env) + .assign(this._env) + .omitBy(_.isUndefined) + .tap(prependNodeModulesBinToPATH) + .value() + }); + } +} + +module.exports = TestRunnerCommand; diff --git a/detox/local-cli/utils/testCommandArgs.js b/detox/local-cli/testCommand/builder.js similarity index 84% rename from detox/local-cli/utils/testCommandArgs.js rename to detox/local-cli/testCommand/builder.js index 23f0b4cc63..dfe224e18c 100644 --- a/detox/local-cli/utils/testCommandArgs.js +++ b/detox/local-cli/testCommand/builder.js @@ -21,14 +21,10 @@ module.exports = { choices: ['fatal', 'error', 'warn', 'info', 'verbose', 'trace'], describe: 'Log level', }, - 'no-color': { - describe: 'Disable colors in log output', - boolean: true, - }, R: { alias: 'retries', group: 'Execution:', - describe: '[Jest Circus Only] Re-spawn the test runner for individual failing suite files until they pass, or times at least.', + describe: 'Re-spawn the test runner for individual failing suite files until they pass, or times at least.', number: true, default: 0, }, @@ -48,10 +44,6 @@ module.exports = { alias: 'debug-synchronization', group: 'Debugging:', coerce(value) { - if (value == null) { - return undefined; - } - if (value === false || value === 'false') { return 0; } @@ -99,17 +91,11 @@ module.exports = { 'record-timeline': { group: 'Debugging:', choices: ['all', 'none'], - describe: '[Jest Only] Record tests and events timeline, for visual display on the chrome://tracing tool.', - }, - w: { - alias: 'workers', - group: 'Execution:', - describe: 'Specifies the number of workers the test runner should spawn. Requires a test runner with parallel execution support (e.g. Jest)', - number: true, + describe: 'Record tests and events timeline, for visual display on the chrome://tracing tool.', }, 'jest-report-specs': { group: 'Execution:', - describe: '[Jest Only] Whether to output logs per each running spec, in real-time. By default, disabled with multiple workers.', + describe: 'Whether to output logs per each running spec, in real-time. By default, disabled with multiple workers.', boolean: true, }, H: { @@ -122,7 +108,7 @@ module.exports = { group: 'Execution:', describe: '[Android Only] Launch emulator with the specific -gpu [gpu mode] parameter.', }, - keepLockFile:{ + keepLockFile: { group: 'Configuration:', describe:'Keep the device lock file when running Detox tests', boolean: true, @@ -143,9 +129,8 @@ module.exports = { }, 'use-custom-logger': { boolean: true, - default: true, group: 'Execution:', - describe: `Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation (e.g. Jest / Mocha).`, + describe: `Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation`, }, 'force-adb-install': { boolean: true, diff --git a/detox/local-cli/testCommand/middlewares.js b/detox/local-cli/testCommand/middlewares.js new file mode 100644 index 0000000000..e737ab3bd5 --- /dev/null +++ b/detox/local-cli/testCommand/middlewares.js @@ -0,0 +1,68 @@ +const _ = require('lodash'); + +const { parse } = require('../../src/utils/shellQuote'); +const { getJestBooleanArgs } = require('../utils/jestInternals'); +const { extractKnownKeys, disengageBooleanArgs } = require('../utils/yargsUtils'); + +const testCommandArgs = require('./builder'); +const { DETOX_ARGV_OVERRIDE_NOTICE, DEVICE_LAUNCH_ARGS_DEPRECATION } = require('./warnings'); + +module.exports = function createMiddlewares({ log }) { + function applyEnvironmentVariableAddendum(argv, yargs) { + if (process.env.DETOX_ARGV_OVERRIDE) { + log.warn(DETOX_ARGV_OVERRIDE_NOTICE); + + return yargs.parse([ + ...process.argv.slice(2), + ...parse(process.env.DETOX_ARGV_OVERRIDE), + ]); + } + + return argv; + } + + function warnDeviceAppLaunchArgsDeprecation(argv) { + if (argv['device-boot-args'] && process.argv.some(a => a.startsWith('--device-launch-args'))) { + log.warn(DEVICE_LAUNCH_ARGS_DEPRECATION); + } + + return argv; + } + + /** + * @param {Record} argv + * @returns {{ + * detoxArgs: Record, + * specs: string[], + * runnerArgs: Record + * }} + */ + function splitArgv(argv) { + const aliases = extractKnownKeys(testCommandArgs); + const isDetoxArg = (_value, key) => aliases.has(key); + + const detoxArgs = _.pickBy(argv, isDetoxArg); + const runnerArgv = _.omitBy(argv, isDetoxArg); + runnerArgv._ = runnerArgv._.slice(1); // omit 'test' string, as in 'detox test' + if (typeof detoxArgs['debug-synchronization'] === 'string') { + const erroneousPassthrough = detoxArgs['debug-synchronization']; + detoxArgs['debug-synchronization'] = 3000; + runnerArgv._.unshift(erroneousPassthrough); + } + + const { specs, passthrough } = disengageBooleanArgs(runnerArgv, getJestBooleanArgs()); + return { detoxArgs, runnerArgs: passthrough, specs }; + } + + return { + applyEnvironmentVariableAddendum, + warnDeviceAppLaunchArgsDeprecation, + splitArgv, + + ordered: [ + applyEnvironmentVariableAddendum, + warnDeviceAppLaunchArgsDeprecation, + splitArgv, + ], + }; +}; diff --git a/detox/local-cli/utils/warnings.js b/detox/local-cli/testCommand/warnings.js similarity index 76% rename from detox/local-cli/utils/warnings.js rename to detox/local-cli/testCommand/warnings.js index 8a7c0ad634..dd3ff7e95b 100644 --- a/detox/local-cli/utils/warnings.js +++ b/detox/local-cli/testCommand/warnings.js @@ -1,13 +1,4 @@ const { DEVICE_LAUNCH_ARGS_DEPRECATION } = require('../../src/configuration/utils/warnings'); -const log = require('../../src/utils/logger').child({ __filename }); - -function coerceDeprecation(option) { - return function coerceDeprecationFn(value) { - log.warn(`Beware: ${option} will be removed in the next version of Detox.`); - - return value; - }; -} const DETOX_ARGV_OVERRIDE_NOTICE = ` _____ _____ ___________ @@ -30,7 +21,6 @@ const DETOX_ARGV_OVERRIDE_NOTICE = ` `; module.exports = { - coerceDeprecation, DETOX_ARGV_OVERRIDE_NOTICE, DEVICE_LAUNCH_ARGS_DEPRECATION, }; diff --git a/detox/local-cli/utils/jestInternals.js b/detox/local-cli/utils/jestInternals.js index 5e70e63f13..d161f229f2 100644 --- a/detox/local-cli/utils/jestInternals.js +++ b/detox/local-cli/utils/jestInternals.js @@ -2,9 +2,12 @@ const Module = require('module'); const path = require('path'); +const _ = require('lodash'); const resolveFrom = require('resolve-from'); -const DetoxRuntimeError = require('../../src/errors/DetoxRuntimeError'); +const { DetoxRuntimeError } = require('../../src/errors'); + +const { extractKnownKeys } = require('./yargsUtils'); const getNodeModulePaths = (dir) => Module._nodeModulePaths(dir); @@ -65,7 +68,15 @@ async function readJestConfig(argv) { return readConfig(argv, process.cwd(), false); } +function getJestBooleanArgs() { + return _(resolveJestCliArgs()) + .thru(args => args.options) + .pickBy(({ type }) => type === 'boolean') + .thru(extractKnownKeys) + .value(); +} + module.exports = { - resolveJestCliArgs, + getJestBooleanArgs, readJestConfig, }; diff --git a/detox/local-cli/utils/splitArgv.js b/detox/local-cli/utils/splitArgv.js deleted file mode 100644 index 9a8128b09a..0000000000 --- a/detox/local-cli/utils/splitArgv.js +++ /dev/null @@ -1,93 +0,0 @@ -const _ = require('lodash'); - -const { resolveJestCliArgs } = require('./jestInternals'); -const testCommandArgs = require('./testCommandArgs'); - -function extractKnownKeys(yargsBuilder) { - return Object.entries(yargsBuilder).reduce( - (set, [key, option]) => { - if (option.alias) { - if (Array.isArray(option.alias)) { - for (const value of option.alias) { - set.add(value); - } - } else { - set.add(option.alias); - } - } - - return set.add(key); - }, - new Set() - ); -} - -function disengageBooleanArgs(argv, booleanKeys) { - const result = {}; - const passthrough = []; - - for (const entry of Object.entries(argv)) { - const [key, value] = entry; - if (key === '_' || key === '--') { - continue; - } - - const positiveKey = key.startsWith('no-') ? key.slice(3) : key; - if (booleanKeys.has(positiveKey) && typeof value !== 'boolean') { - result[positiveKey] = key === positiveKey; - passthrough.push(value); - } else { - result[key] = value; - } - } - - return { - specs: passthrough.concat(argv._), - passthrough: { - _: argv['--'] || [], - ...result, - }, - }; -} - -function getJestBooleanArgs() { - return _(resolveJestCliArgs()) - .thru(args => args.options) - .pickBy(({ type }) => type === 'boolean') - .thru(extractKnownKeys) - .value(); -} - -function splitDetoxArgv(argv) { - const aliases = extractKnownKeys(testCommandArgs); - const isDetoxArg = (_value, key) => aliases.has(key); - - const detoxArgs = _.pickBy(argv, isDetoxArg); - const runnerArgs = _.omitBy(argv, isDetoxArg); - runnerArgs._ = runnerArgs._.slice(1); // omit 'test' string, as in 'detox test' - if (typeof detoxArgs['debug-synchronization'] === 'string') { - const erroneousPassthrough = detoxArgs['debug-synchronization']; - detoxArgs['debug-synchronization'] = 3000; - runnerArgs._.unshift(erroneousPassthrough); - } - - return { detoxArgs, runnerArgs }; -} - -function splitJestArgv(argv) { - return realiasJestArgv(disengageBooleanArgs(argv, getJestBooleanArgs())); -} - -function realiasJestArgv({ specs, passthrough }) { - if (passthrough.hasOwnProperty('t')) { - passthrough.testNamePattern = passthrough.t; - delete passthrough.t; - } - - return { specs, passthrough }; -} - -module.exports = { - detox: splitDetoxArgv, - jest: splitJestArgv, -}; diff --git a/detox/local-cli/utils/yargsUtils.js b/detox/local-cli/utils/yargsUtils.js new file mode 100644 index 0000000000..b5cdba8aea --- /dev/null +++ b/detox/local-cli/utils/yargsUtils.js @@ -0,0 +1,60 @@ +/** + * @param {Record>} yargsBuilder + * @returns {Set} + */ +function extractKnownKeys(yargsBuilder) { + return Object.entries(yargsBuilder).reduce( + (set, [key, option]) => { + if (option.alias) { + if (Array.isArray(option.alias)) { + for (const value of option.alias) { + set.add(value); + } + } else { + set.add(option.alias); + } + } + + return set.add(key); + }, + new Set() + ); +} + +/** + * @param {Record} argv + * @param {Set} booleanKeys + * @returns {{specs: string[], passthrough: Record}} + */ +function disengageBooleanArgs(argv, booleanKeys) { + const result = {}; + const passthrough = []; + + for (const entry of Object.entries(argv)) { + const [key, value] = entry; + if (key === '_' || key === '--') { + continue; + } + + const positiveKey = key.startsWith('no-') ? key.slice(3) : key; + if (booleanKeys.has(positiveKey) && typeof value !== 'boolean') { + result[positiveKey] = key === positiveKey; + passthrough.push(value); + } else { + result[key] = value; + } + } + + return { + specs: passthrough.concat(argv._), + passthrough: { + _: argv['--'] || [], + ...result, + }, + }; +} + +module.exports = { + extractKnownKeys, + disengageBooleanArgs, +}; diff --git a/detox/realms/top/context/DetoxGlobalContext.js b/detox/realms/root/context/DetoxGlobalContext.js similarity index 99% rename from detox/realms/top/context/DetoxGlobalContext.js rename to detox/realms/root/context/DetoxGlobalContext.js index 855117ea32..c78dc398b2 100644 --- a/detox/realms/top/context/DetoxGlobalContext.js +++ b/detox/realms/root/context/DetoxGlobalContext.js @@ -7,9 +7,9 @@ const _ = require('lodash'); const configuration = require('../../../src/configuration'); const DeviceRegistry = require('../../../src/devices/DeviceRegistry'); const GenyDeviceRegistryFactory = require('../../../src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory'); -const ipcServer = require('../ipc/server'); const DetoxServer = require('../../../src/server/DetoxServer'); const logger = require('../../../src/utils/logger'); +const ipcServer = require('../ipc/server'); const log = logger.child({ __filename }); class DetoxRootContext { @@ -60,7 +60,6 @@ class DetoxRootContext { }) ); - if (!this._cliConfig.keepLockFile) { await this._resetLockFile(); } diff --git a/detox/realms/top/context/index.js b/detox/realms/root/context/index.js similarity index 100% rename from detox/realms/top/context/index.js rename to detox/realms/root/context/index.js diff --git a/detox/realms/top/ipc/server.js b/detox/realms/root/ipc/server.js similarity index 93% rename from detox/realms/top/ipc/server.js rename to detox/realms/root/ipc/server.js index 832286e182..bd19c53bcd 100644 --- a/detox/realms/top/ipc/server.js +++ b/detox/realms/root/ipc/server.js @@ -11,12 +11,12 @@ module.exports = { async start({ sessionId, detoxConfig }) { state.detoxConfig = detoxConfig; - debugger; ipc.config.id = process.env.DETOX_IPC_SERVER_ID = `detox-${sessionId}`; ipc.config.retry = 1500; ipc.config.sync = true; - return new Promise((resolve, reject) => { + // TODO: handle reject + return new Promise((resolve, _reject) => { ipc.serve(function() { resolve(); diff --git a/detox/realms/global/index.js b/detox/realms/runner/index.js similarity index 66% rename from detox/realms/global/index.js rename to detox/realms/runner/index.js index e1efcf50fd..ffa3ec5287 100644 --- a/detox/realms/global/index.js +++ b/detox/realms/runner/index.js @@ -1,4 +1,4 @@ module.exports = { DetoxReporter: require('./reporters/DetoxReporter'), - context: require('../top/context'), + context: require('../root/context'), }; diff --git a/detox/realms/global/reporters/DetoxReporter.js b/detox/realms/runner/reporters/DetoxReporter.js similarity index 100% rename from detox/realms/global/reporters/DetoxReporter.js rename to detox/realms/runner/reporters/DetoxReporter.js diff --git a/detox/realms/global/reporters/DetoxStreamlineJestReporter.js b/detox/realms/runner/reporters/DetoxStreamlineJestReporter.js similarity index 100% rename from detox/realms/global/reporters/DetoxStreamlineJestReporter.js rename to detox/realms/runner/reporters/DetoxStreamlineJestReporter.js diff --git a/detox/realms/global/reporters/FailingTestsReporter.js b/detox/realms/runner/reporters/FailingTestsReporter.js similarity index 100% rename from detox/realms/global/reporters/FailingTestsReporter.js rename to detox/realms/runner/reporters/FailingTestsReporter.js diff --git a/detox/realms/worker/environment/index.js b/detox/realms/worker/environment/index.js index 111ea04449..533aab140f 100644 --- a/detox/realms/worker/environment/index.js +++ b/detox/realms/worker/environment/index.js @@ -1,13 +1,13 @@ // @ts-nocheck const NodeEnvironment = require('jest-environment-node'); -const DetoxWorkerContext = require('../context/DetoxWorkerContext'); const { DetoxError } = require('../../../src/errors'); const ipcClient = require('../../../src/ipc/client'); +const Timer = require('../../../src/utils/Timer'); +const DetoxWorkerContext = require('../context/DetoxWorkerContext'); const DetoxCoreListener = require('./listeners/DetoxCoreListener'); const DetoxInitErrorListener = require('./listeners/DetoxInitErrorListener'); -const Timer = require('../../../src/utils/Timer'); const assertExistingContext = require('./utils/assertExistingContext'); const assertJestCircus26 = require('./utils/assertJestCircus26'); const wrapErrorWithNoopLifecycle = require('./utils/wrapErrorWithNoopLifecycle'); diff --git a/detox/runners/jest/globalSetup.js b/detox/runners/jest/globalSetup.js index a142c7f96a..ad031c94af 100644 --- a/detox/runners/jest/globalSetup.js +++ b/detox/runners/jest/globalSetup.js @@ -1,3 +1,3 @@ -const { context } = require('../../realms/global'); +const { context } = require('../../realms/runner'); module.exports = context.setup; diff --git a/detox/runners/jest/globalTeardown.js b/detox/runners/jest/globalTeardown.js index ceea7cf73c..6e428044ef 100644 --- a/detox/runners/jest/globalTeardown.js +++ b/detox/runners/jest/globalTeardown.js @@ -1,3 +1,3 @@ -const { context } = require('../../realms/global'); +const { context } = require('../../realms/runner'); module.exports = context.teardown; diff --git a/detox/runners/jest/index.js b/detox/runners/jest/index.js index 0f4140e30d..2b19f6e517 100644 --- a/detox/runners/jest/index.js +++ b/detox/runners/jest/index.js @@ -18,11 +18,11 @@ module.exports = { }, get globalSetup() { - return require('../../realms/global').context.setup; + return require('../../realms/runner').context.setup; }, get globalTeardown() { - return require('../../realms/global').context.teardown; + return require('../../realms/runner').context.teardown; }, //#endregion diff --git a/detox/runners/jest/reporter.js b/detox/runners/jest/reporter.js index 00b733dc57..d99d0d594c 100644 --- a/detox/runners/jest/reporter.js +++ b/detox/runners/jest/reporter.js @@ -1 +1 @@ -module.exports = require('../../realms/global').DetoxReporter; +module.exports = require('../../realms/runner').DetoxReporter; diff --git a/detox/src/artifacts/utils/buildDefaultArtifactsRootDirpath.js b/detox/src/artifacts/utils/buildDefaultArtifactsRootDirpath.js index 0a46eb98df..729c1ad07a 100644 --- a/detox/src/artifacts/utils/buildDefaultArtifactsRootDirpath.js +++ b/detox/src/artifacts/utils/buildDefaultArtifactsRootDirpath.js @@ -7,6 +7,7 @@ function buildDefaultRootForArtifactsRootDirpath(configuration, rootDir) { return rootDir; } + // TODO: remove this dependency const seed = Number(process.env.DETOX_START_TIMESTAMP || String(Date.now())); const subdir = `${configuration}.${getTimeStampString(new Date(seed))}`; return path.join(rootDir, subdir); diff --git a/detox/src/configuration/index.js b/detox/src/configuration/index.js index 4241174b2f..5610aa1b7f 100644 --- a/detox/src/configuration/index.js +++ b/detox/src/configuration/index.js @@ -13,10 +13,6 @@ const composeSessionConfig = require('./composeSessionConfig'); const loadExternalConfig = require('./loadExternalConfig'); const selectConfiguration = require('./selectConfiguration'); -const hooks = { - UNSAFE_configReady: [], -}; - async function composeDetoxConfig({ cwd = process.cwd(), argv = undefined, @@ -105,18 +101,9 @@ async function composeDetoxConfig({ sessionConfig, }; - for (const fn of hooks.UNSAFE_configReady) { - await fn({ ...result, argv }); - } - return result; } -function hook(event, listener) { - hooks[event].push(listener); -} - module.exports = { composeDetoxConfig, - hook, }; diff --git a/detox/src/configuration/index.test.js b/detox/src/configuration/index.test.js index 3c7074509c..05e6137632 100644 --- a/detox/src/configuration/index.test.js +++ b/detox/src/configuration/index.test.js @@ -128,29 +128,5 @@ describe('composeDetoxConfig', () => { }), }); }); - - it('should enable to add hooks on UNSAFE_configReady', async () => { - const listener = jest.fn(); - configuration.hook('UNSAFE_configReady', listener); - - await configuration.composeDetoxConfig({ - cwd: path.join(__dirname, '__mocks__/configuration/packagejson'), - override: { - configurations: { - simple: { - binaryPath: 'path/to/app', - }, - }, - }, - }); - - expect(listener).toHaveBeenCalledWith(expect.objectContaining({ - appsConfig: expect.any(Object), - artifactsConfig: expect.any(Object), - behaviorConfig: expect.any(Object), - cliConfig: expect.any(Object), - deviceConfig: expect.any(Object), - })); - }); }); }); diff --git a/detox/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js b/detox/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js index bf0c54f304..102d3787ab 100644 --- a/detox/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +++ b/detox/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js @@ -2,6 +2,7 @@ const getWorkerId = require('../../../../../../utils/getWorkerId'); class GenyInstanceNaming { constructor(nowProvider = () => Date.now()) { + // TODO: remove this dependency this.uniqueSessionId = Number(process.env.DETOX_START_TIMESTAMP); this.nowProvider = nowProvider; this._workerId = getWorkerId() || (this.nowProvider() - this.uniqueSessionId); diff --git a/detox/src/logger/IPCLogger.js b/detox/src/logger/IPCLogger.js index daf2df08dc..23a2c573f9 100644 --- a/detox/src/logger/IPCLogger.js +++ b/detox/src/logger/IPCLogger.js @@ -1,4 +1,5 @@ const _ = require('lodash'); + const ipcClient = require('../ipc/client'); class IPCLogger { diff --git a/detox/local-cli/utils/misc.js b/detox/src/utils/envUtils.js similarity index 77% rename from detox/local-cli/utils/misc.js rename to detox/src/utils/envUtils.js index bbda90d42e..5c07e02f71 100644 --- a/detox/local-cli/utils/misc.js +++ b/detox/src/utils/envUtils.js @@ -1,13 +1,5 @@ const path = require('path'); -function getPlatformSpecificString(platform) { - switch (platform) { - case 'ios': return ':android:'; - case 'android': return ':ios:'; - default: return undefined; - } -} - function printEnvironmentVariables(envObject) { return Object.entries(envObject).reduce((cli, [key, value]) => { if (value == null || value === '') { @@ -33,7 +25,6 @@ function prependNodeModulesBinToPATH(env) { } module.exports = { - getPlatformSpecificString, printEnvironmentVariables, prependNodeModulesBinToPATH, }; diff --git a/detox/test/e2e/detox.config.js b/detox/test/e2e/detox.config.js index 869407dc64..bb57add20e 100644 --- a/detox/test/e2e/detox.config.js +++ b/detox/test/e2e/detox.config.js @@ -1,15 +1,3 @@ -const detox = require('detox'); - -detox.hook('UNSAFE_configReady', ({ deviceConfig }) => { - if (process.env.CI && !process.env.DEMO_MAX_WORKERS) { - process.env.DEMO_MAX_WORKERS = ({ - 'ios.simulator': '4', - 'android.emulator': '3', - 'android.genycloud': '5', - })[deviceConfig.type] || '1'; - } -}); - const launchArgs = { app: 'le', goo: 'gle?',