Skip to content

Commit

Permalink
feat: new global lifecycle for Detox
Browse files Browse the repository at this point in the history
BREAKING: update your Jest config according to the guide
  • Loading branch information
noomorph committed Apr 21, 2022
1 parent 7f882e5 commit de932ac
Show file tree
Hide file tree
Showing 69 changed files with 699 additions and 1,002 deletions.
4 changes: 2 additions & 2 deletions detox/local-cli/templates/jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const runnerConfig = `{
"testEnvironment": "./environment",
"testTimeout": 120000,
"testMatch": ["<rootDir>/e2e/*.e2e.js"],
"reporters": ["detox/runners/jest-circus/reporter"],
"reporters": ["detox/runners/jest/reporter"],
"verbose": true
}
`;
Expand All @@ -14,7 +14,7 @@ const environmentJsContent = `const {
DetoxCircusEnvironment,
SpecReporter,
WorkerAssignReporter,
} = require('detox/runners/jest-circus');
} = require('detox/runners/jest');
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
constructor(config, context) {
Expand Down
47 changes: 8 additions & 39 deletions detox/local-cli/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ const whichSync = require('which').sync;
const unparse = require('yargs-unparser');

const { composeDetoxConfig } = require('../src/configuration');
const DeviceRegistry = require('../src/devices/DeviceRegistry');
const GenyDeviceRegistryFactory = require('../src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory');
const { loadLastFailedTests, resetLastFailedTests } = require('../src/utils/lastFailedTests');
const { loadLastFailedTests } = require('../src/utils/lastFailedTests');
const log = require('../src/utils/logger').child({ __filename });
const { parse, quote } = require('../src/utils/shellQuote');

const { readJestConfig } = require('./utils/jestInternals');
const { getPlatformSpecificString, printEnvironmentVariables } = require('./utils/misc');
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');
Expand All @@ -23,15 +20,12 @@ module.exports.desc = 'Run your test suite with the test runner specified in pac
module.exports.builder = require('./utils/testCommandArgs');
module.exports.handler = async function test(argv) {
const { detoxArgs, runnerArgs } = splitArgv.detox(argv);
const { cliConfig, deviceConfig, runnerConfig } = await composeDetoxConfig({ argv: detoxArgs });
const [platform] = deviceConfig.type.split('.');
const { cliConfig, runnerConfig } = await composeDetoxConfig({ argv: detoxArgs });

const forwardedArgs = await prepareArgs({
cliConfig,
deviceConfig,
runnerConfig,
runnerArgs,
platform,
});

if (detoxArgs['inspect-brk']) {
Expand All @@ -45,9 +39,7 @@ module.exports.handler = async function test(argv) {
}

await runTestRunnerWithRetries(forwardedArgs, {
keepLockFile: cliConfig.keepLockFile,
retries: detoxArgs.retries,
platform,
});
};

Expand All @@ -74,21 +66,17 @@ module.exports.middlewares = [
}
];

async function prepareArgs({ cliConfig, deviceConfig, runnerArgs, runnerConfig, platform }) {
async function prepareArgs({ cliConfig, runnerArgs, runnerConfig }) {
const { specs, passthrough } = splitArgv.jest(runnerArgs);
const platformFilter = getPlatformSpecificString(platform);

const argv = _.omitBy({
color: !cliConfig.noColor && undefined,
config: runnerConfig.runnerConfig /* istanbul ignore next */ || undefined,
testNamePattern: platformFilter ? `^((?!${platformFilter}).)*$` : undefined,
maxWorkers: cliConfig.workers || undefined,

...passthrough,
}, _.isUndefined);

const hasMultipleWorkers = (await readJestConfig(argv)).globalConfig.maxWorkers > 1;

return {
argv,

Expand All @@ -102,20 +90,17 @@ async function prepareArgs({ cliConfig, deviceConfig, runnerArgs, runnerConfig,
DETOX_DEBUG_SYNCHRONIZATION: cliConfig.debugSynchronization,
DETOX_DEVICE_BOOT_ARGS: cliConfig.deviceBootArgs,
DETOX_DEVICE_NAME: cliConfig.deviceName,
DETOX_FORCE_ADB_INSTALL: platform === 'android' ? cliConfig.forceAdbInstall : undefined,
DETOX_FORCE_ADB_INSTALL: cliConfig.forceAdbInstall,
DETOX_GPU: cliConfig.gpu,
DETOX_HEADLESS: cliConfig.headless,
DETOX_LOGLEVEL: cliConfig.loglevel,
DETOX_READ_ONLY_EMU: deviceConfig.type === 'android.emulator' && hasMultipleWorkers ? true : undefined,
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: _.isUndefined(cliConfig.jestReportSpecs)
? !hasMultipleWorkers
: `${cliConfig.jestReportSpecs}` === 'true',
DETOX_REPORT_SPECS: cliConfig.jestReportSpecs,
DETOX_REUSE: cliConfig.reuse,
DETOX_START_TIMESTAMP: Date.now(),
DETOX_TAKE_SCREENSHOTS: cliConfig.takeScreenshots,
DETOX_USE_CUSTOM_LOGGER: cliConfig.useCustomLogger,
}, _.isUndefined),
Expand All @@ -124,17 +109,6 @@ async function prepareArgs({ cliConfig, deviceConfig, runnerArgs, runnerConfig,
};
}

async function resetLockFile({ platform }) {
if (platform === 'ios') {
await DeviceRegistry.forIOS().reset();
}

if (platform === 'android') {
await DeviceRegistry.forAndroid().reset();
await GenyDeviceRegistryFactory.forGlobalShutdown().reset();
}
}

function launchTestRunner({ argv, env, specs }) {
const { $0: command, ...restArgv } = argv;
const fullCommand = [
Expand Down Expand Up @@ -170,17 +144,12 @@ async function runTestRunnerWithRetries(forwardedArgs, { keepLockFile, platform,
);
}

if (!keepLockFile) {
await resetLockFile({ platform });
}

await resetLastFailedTests();
launchTestRunner(forwardedArgs);
launchError = null;
} catch (e) {
launchError = e;

const lastFailedTests = await loadLastFailedTests();
const lastFailedTests = await loadLastFailedTests(); // TODO: retrieve from __top IPC
if (_.isEmpty(lastFailedTests)) {
throw e;
}
Expand Down
5 changes: 3 additions & 2 deletions detox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"ini": "^1.3.4",
"lodash": "^4.17.5",
"minimist": "^1.2.0",
"node-ipc": "^11.1.0",
"proper-lockfile": "^3.0.2",
"resolve-from": "^5.0.0",
"sanitize-filename": "^1.6.1",
Expand Down Expand Up @@ -94,8 +95,9 @@
"testRunner": "jest-circus/runner",
"roots": [
"node_modules",
"local-cli",
"src",
"local-cli"
"realms"
],
"testPathIgnorePatterns": [
"/node_modules/",
Expand Down Expand Up @@ -136,7 +138,6 @@
"src/validation/EnvironmentValidatorBase.js",
"src/validation/factories",
"src/validation/ios/IosSimulatorEnvValidator",
"src/utils/MissingDetox.js",
"src/utils/appdatapath.js",
"src/utils/debug.js",
"src/utils/environment.js",
Expand Down
4 changes: 4 additions & 0 deletions detox/realms/global/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
DetoxReporter: require('./reporters/DetoxReporter'),
context: require('../top/context'),
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class DetoxReporter extends DetoxStreamlineJestReporter {
}

async onRunComplete(contexts, results) {
// @ts-ignore
await super.onRunComplete(contexts, results);
await this._failingTestsReporter.onRunComplete(contexts, results);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// @ts-nocheck
const { VerboseReporter: JestVerboseReporter } = require('@jest/reporters'); // eslint-disable-line node/no-extraneous-require

const DetoxRuntimeError = require('../../../src/errors/DetoxRuntimeError');

// TODO: remove this since Jest 27.2.5 already fixes this problem
class DetoxStreamlineJestReporter extends JestVerboseReporter {

constructor(globalConfig) {
Expand Down
108 changes: 108 additions & 0 deletions detox/realms/top/context/DetoxGlobalContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// @ts-nocheck
const { URL } = require('url');
const util = require('util');

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 log = logger.child({ __filename });

class DetoxRootContext {
constructor(config) {
this._deviceConfig = config.deviceConfig;
this._sessionConfig = config.sessionConfig;
this._cliConfig = config.cliConfig;

this._wss = null;

this.setup = this.setup.bind(this);
this.teardown = this.teardown.bind(this);
}

async setup({ argv }) {
const detoxConfig = await configuration.composeDetoxConfig({ argv });
await ipcServer.start({
sessionId: `detox-${process.pid}`,
detoxConfig,
});

try {
await this._doSetup(detoxConfig);
} catch (e) {
await this.teardown();
throw e;
}
}

async teardown() {
if (this._wss) {
await this._wss.close();
this._wss = null;
}
}

async _doSetup(config) {
log.trace(
{ event: 'DETOX_CONFIG', config },
'creating Detox server with config:\n%s',
util.inspect(_.omit(config, ['errorComposer']), {
getters: false,
depth: Infinity,
maxArrayLength: Infinity,
maxStringLength: Infinity,
breakLength: false,
compact: false,
})
);


if (!this._cliConfig.keepLockFile) {
await this._resetLockFile();
}

const sessionConfig = this._sessionConfig;

this._wss = new DetoxServer({
port: sessionConfig.server
? new URL(sessionConfig.server).port
: 0,
standalone: false,
});

await this._wss.open();

if (!sessionConfig.server) {
sessionConfig.server = `ws://localhost:${this._wss.port}`;
}

// TODO: think if we need it to be encapsulated
process.env.DETOX_WSS_ADDRESS = sessionConfig.server;
}

async _resetLockFile() {
const deviceType = this._deviceConfig.type;

switch (deviceType) {
case 'ios.none':
case 'ios.simulator':
await DeviceRegistry.forIOS().reset();
break;
case 'android.attached':
case 'android.emulator':
case 'android.genycloud':
await DeviceRegistry.forAndroid().reset();
break;
}

if (deviceType === 'android.genycloud') {
await GenyDeviceRegistryFactory.forGlobalShutdown().reset();
}
}
}

module.exports = DetoxRootContext;
3 changes: 3 additions & 0 deletions detox/realms/top/context/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const DetoxGlobalContext = require('./DetoxGlobalContext');

module.exports = new DetoxGlobalContext();
46 changes: 46 additions & 0 deletions detox/realms/top/ipc/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const ipc = require('node-ipc').default;

const log = require('../../../src/logger');

const state = {
workers: 0,
detoxConfig: null,
};

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) => {
ipc.serve(function() {
resolve();

ipc.server.on('app.message', function(data, socket) {
const { type, ...payload } = data;
switch (type) {
case 'log': {
const { level, args } = payload;
return log[level](args);
}

case 'registerWorker': {
const { workerId } = payload;
state.workers = Math.max(state.workers, +workerId);
return ipc.server.emit(socket, 'app.message', {
type: 'registerWorkerDone',
detoxConfig: state.detoxConfig,
});
}
}
});
});

ipc.server.start();
});
},
};

0 comments on commit de932ac

Please sign in to comment.