Skip to content

Commit

Permalink
Merge pull request #2050 from wix/feat/external-config
Browse files Browse the repository at this point in the history
feat(config): external configuration files
  • Loading branch information
noomorph committed May 25, 2020
2 parents d7ce69f + 45e3278 commit 08e6931
Show file tree
Hide file tree
Showing 73 changed files with 2,511 additions and 1,280 deletions.
40 changes: 21 additions & 19 deletions detox/__tests__/setupJest.js
Expand Up @@ -5,27 +5,29 @@ const path = require('path');

function callCli(modulePath, cmd) {
return new Promise((resolve, reject) => {
try {
yargs
.scriptName('detox')
.command(require(path.join(__dirname, "../local-cli", modulePath)))
.exitProcess(false)
.fail((msg, err) => reject(err || msg))
.parse(cmd, (err, argv, output) => {
err ? reject(err) : setImmediate(() => resolve(output));
});
} catch (e) {
reject(e);
}
});
}
const originalModule = require(path.join(__dirname, "../local-cli", modulePath));
const originalHandler = originalModule.handler;
const spiedModule = {
...originalModule,
handler: async program => {
try {
return await originalHandler(program);
} catch (e) {
reject(e);
} finally {
resolve();
}
}
};

function mockPackageJson(mockContent) {
jest.mock(path.join(process.cwd(), 'package.json'), () => ({
detox: mockContent
}));
return yargs
.scriptName('detox')
.command(spiedModule)
.exitProcess(false)
.fail((msg, err) => reject(err || msg))
.parse(cmd, (err) => err && reject(err));
});
}

global.mockPackageJson = mockPackageJson;
global.callCli = callCli;
global.IS_RUNNING_DETOX_UNIT_TESTS = true;
10 changes: 10 additions & 0 deletions detox/local-cli/__mocks__/detox.config.js
@@ -0,0 +1,10 @@
module.exports = {
configurations: {
only: {
type: 'ios.simulator',
device: {
type: 'iPhone 11'
}
},
},
};
13 changes: 0 additions & 13 deletions detox/local-cli/__snapshots__/build.test.js.snap

This file was deleted.

6 changes: 5 additions & 1 deletion detox/local-cli/__snapshots__/test.test.js.snap
@@ -1,3 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`test fails with a different runner 1`] = `"ava is not supported in detox cli tools. You can still run your tests with the runner's own cli tool"`;
exports[`test fails with a different runner 1`] = `
"\\"ava\\" is not supported in Detox CLI tools.
HINT: You can still run your tests with the runner's own CLI tool"
`;
16 changes: 11 additions & 5 deletions detox/local-cli/build.js
@@ -1,27 +1,33 @@
const _ = require('lodash');
const cp = require('child_process');
const log = require('../src/utils/logger').child({ __filename });
const {getDefaultConfiguration, getConfigurationByKey} = require('./utils/configurationUtils');
const {composeDetoxConfig} = require('../src/configuration');

module.exports.command = 'build';
module.exports.desc = "Convenience method. Run the command defined in 'build' property of the specified configuration.";
module.exports.builder = {
C: {
alias: 'config-path',
group: 'Configuration:',
describe: 'Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or "detox" section in package.json',
},
c: {
alias: 'configuration',
group: 'Configuration:',
describe:
"Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it",
default: getDefaultConfiguration(),
}
},
};

module.exports.handler = async function build(argv) {
const buildScript = getConfigurationByKey(argv.configuration).build;
const { errorBuilder, deviceConfig } = await composeDetoxConfig({ argv });

const buildScript = deviceConfig.build;

if (buildScript) {
log.info(buildScript);
cp.execSync(buildScript, { stdio: 'inherit' });
} else {
throw new Error(`Could not find build script in detox.configurations["${argv.configuration}"].build`);
throw errorBuilder.missingBuildScript();
}
};
101 changes: 31 additions & 70 deletions detox/local-cli/build.test.js
@@ -1,85 +1,46 @@
jest.mock('child_process');
jest.mock('../src/utils/logger');
jest.mock('../src/configuration');

describe('build', () => {
let mockExec;
beforeEach(() => {
mockExec = jest.fn();
jest.mock('child_process', () => ({
execSync: mockExec
}));
});
const DetoxConfigErrorBuilder = require('../src/errors/DetoxConfigErrorBuilder');

it('runs the build script if there is only one config', async () => {
mockPackageJson({
configurations: {
only: {
build: 'echo "only"'
}
}
});
describe('build', () => {
let execSync, composeDetoxConfig, detoxConfig;

await callCli('./build', 'build');
expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('only'), expect.anything());
beforeEach(() => {
detoxConfig = {
artifactsConfig: {},
behaviorConfig: {},
deviceConfig: {},
sessionConfig: {},
errorBuilder: new DetoxConfigErrorBuilder(),
};

execSync = require('child_process').execSync;
composeDetoxConfig = require('../src/configuration').composeDetoxConfig;
composeDetoxConfig.mockReturnValue(Promise.resolve(detoxConfig));
});

it('runs the build script of selected config', async () => {
mockPackageJson({
configurations: {
only: {
build: 'echo "only"'
},
myconf: {
build: 'echo "myconf"'
}
}
});

await callCli('./build', 'build -c myconf');
expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('myconf'), expect.anything());
});
it('passes argv to composeConfig', async () => {
await callCli('./build', 'build -C /etc/.detoxrc.js -c myconf').catch(() => {});

it('fails with multiple configs if none is selected', async () => {
mockPackageJson({
configurations: {
only: {
build: 'echo "only"'
},
myconf: {
build: 'echo "myconf"'
}
}
expect(composeDetoxConfig).toHaveBeenCalledWith({
argv: expect.objectContaining({
'config-path': '/etc/.detoxrc.js',
'configuration': 'myconf',
}),
});

await expect(callCli('./build', 'build')).rejects.toThrowErrorMatchingSnapshot();
expect(mockExec).not.toHaveBeenCalled();
});

it('fails without configurations', async () => {
mockPackageJson({});
it('runs the build script from the composed device config', async () => {
detoxConfig.deviceConfig.build = 'yet another command';

await expect(callCli('./build', 'build')).rejects.toThrowErrorMatchingSnapshot();
expect(mockExec).not.toHaveBeenCalled();
});

it('fails without build script', async () => {
mockPackageJson({
configurations: {
only: {}
}
});

await expect(callCli('./build', 'build -c only')).rejects.toThrowErrorMatchingSnapshot();
expect(mockExec).not.toHaveBeenCalled();
await callCli('./build', 'build');
expect(execSync).toHaveBeenCalledWith('yet another command', expect.anything());
});

it('fails without build script and configuration', async () => {
mockPackageJson({
configurations: {
only: {}
}
});

await expect(callCli('./build', 'build')).rejects.toThrowErrorMatchingSnapshot();
expect(mockExec).not.toHaveBeenCalled();
it('fails with an error if a build script has not been found', async () => {
delete detoxConfig.deviceConfig.build;
await expect(callCli('./build', 'build')).rejects.toThrowError(/Could not find a build script/);
});
});
84 changes: 36 additions & 48 deletions detox/local-cli/init.js
Expand Up @@ -24,16 +24,9 @@ module.exports.handler = async function init(argv) {
switch (runner) {
case 'mocha':
createMochaFolderE2E();
patchDetoxConfigInPackageJSON({
runner: 'mocha',
runnerConfig: 'e2e/.mocharc'
});
break;
case 'jest':
createJestFolderE2E();
patchDetoxConfigInPackageJSON({
runner: 'jest',
});
break;
default:
throw new Error([
Expand Down Expand Up @@ -67,6 +60,13 @@ function createFolder(dir, files) {
}

function createFile(filename, content) {
if (fs.existsSync(filename)) {
return reportError(
`Failed to create ${filename} file, ` +
`because it already exists at path: ${path.resolve(filename)}`
);
}

try {
fs.writeFileSync(filename, content);
log.info(`Created a file at path: ${filename}`);
Expand All @@ -77,10 +77,16 @@ function createFile(filename, content) {

function createMochaFolderE2E() {
createFolder('e2e', {
'.mocharc': mochaTemplates.runnerConfig,
'.mocharc.json': mochaTemplates.runnerConfig,
'init.js': mochaTemplates.initjs,
'firstTest.spec.js': mochaTemplates.firstTest
});

createFile('.detoxrc.json', JSON.stringify({
testRunner: 'mocha',
runnerConfig: 'e2e/.mocharc.json',
configurations: createDefaultConfigurations(),
}, null, 2))
}

function createJestFolderE2E() {
Expand All @@ -89,49 +95,31 @@ function createJestFolderE2E() {
'init.js': jestTemplates.initjs,
'firstTest.spec.js': jestTemplates.firstTest
});
}

function parsePackageJson(filepath) {
try {
return require(filepath);
} catch (err) {
reportError(`Failed to parse package.json due to an error:\n${err.message}`);
}
}

function loggedSet(obj, path, value) {
_.set(obj, path, value);

const pathString = path.map(segment => `[${JSON.stringify(segment)}]`).join('');
log.info(` json${pathString} = ${JSON.stringify(value)};`);
createFile('.detoxrc.json', JSON.stringify({
testRunner: 'jest',
runnerConfig: 'e2e/config.json',
configurations: createDefaultConfigurations(),
}, null, 2))
}

function savePackageJson(filepath, json) {
try {
fs.writeFileSync(filepath, JSON.stringify(json, null, 2) + '\n');
} catch (err) {
reportError(`Failed to write changes back into package.json due to an error:\n${err.message}`);
}
}

function patchDetoxConfigInPackageJSON({ runner, runnerConfig }) {
const packageJsonPath = path.join(process.cwd(), 'package.json');

if (fs.existsSync(packageJsonPath)) {
log.info(`Patching package.json at path: ${packageJsonPath}`);

const packageJson = parsePackageJson(packageJsonPath);
if (packageJson) {
loggedSet(packageJson, ['detox', 'test-runner'], runner);
if (runnerConfig) {
loggedSet(packageJson, ['detox', 'runner-config'], runnerConfig);
}

savePackageJson(packageJsonPath, packageJson);
}
} else {
reportError(`Failed to find package.json at path: ${packageJsonPath}`);
}
function createDefaultConfigurations() {
return {
ios: {
type: 'ios.simulator',
binaryPath: 'SPECIFY_PATH_TO_YOUR_APP_BINARY',
device: {
type: 'iPhone 11',
},
},
android: {
type: 'android.emulator',
binaryPath: 'SPECIFY_PATH_TO_YOUR_APP_BINARY',
device: {
avdName: 'Pixel_2_API_29',
},
},
};
}

function reportError(...args) {
Expand Down
3 changes: 1 addition & 2 deletions detox/local-cli/templates/jest.js
Expand Up @@ -8,7 +8,6 @@ const runnerConfig = `{
`;

const initjsContent = `const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/jest/adapter');
const specReporter = require('detox/runners/jest/specReporter');
Expand All @@ -22,7 +21,7 @@ jasmine.getEnv().addReporter(adapter);
jasmine.getEnv().addReporter(specReporter);
beforeAll(async () => {
await detox.init(config);
await detox.init();
}, 300000);
beforeEach(async () => {
Expand Down
3 changes: 1 addition & 2 deletions detox/local-cli/templates/mocha.js
Expand Up @@ -8,11 +8,10 @@ const mochaRcContent = JSON.stringify({
}, null, 4);

const initjsContent = `const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/mocha/adapter');
before(async () => {
await detox.init(config);
await detox.init();
});
beforeEach(async function () {
Expand Down

0 comments on commit 08e6931

Please sign in to comment.