Skip to content

Commit

Permalink
Improve integ. with Detox in terms of logging
Browse files Browse the repository at this point in the history
  • Loading branch information
d4vidi committed May 1, 2019
1 parent b30ef59 commit df45d8f
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 44 deletions.
19 changes: 12 additions & 7 deletions detox/local-cli/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ module.exports.handler = async function init(argv) {
switch (runner) {
case 'mocha':
createMochaFolderE2E();
patchTestRunnerFieldInPackageJSON('mocha');
patchDetoxConfigInPackageJSON('mocha');
break;
case 'jest':
createJestFolderE2E();
patchTestRunnerFieldInPackageJSON('jest');
patchDetoxConfigInPackageJSON('jest', 'e2e/config.json');
break;
default:
throw new Error([
Expand Down Expand Up @@ -89,11 +89,16 @@ function parsePackageJson(filepath) {
}
}

function patchPackageJson(packageJson, runnerName) {
function patchPackageJson(packageJson, runnerName, runnerConfigFile) {
log.info(PREFIX, 'Patched ./package.json with these changes:');

_.set(packageJson, ['detox', 'test-runner'], runnerName);
log.info(PREFIX, ` set detox->test-runner to "${runnerName}"`);

log.info(PREFIX, 'Patched ./package.json with commands:');
log.info(PREFIX, `_.set(packageJson, ['detox', 'test-runner'], "${runnerName}")`);
if (runnerConfigFile) {
_.set(packageJson, ['detox', 'runner-config'], runnerConfigFile);
log.info(PREFIX, ` set detox->runner-config to "${runnerConfigFile}"`);
}
}

function savePackageJson(filepath, json) {
Expand All @@ -104,12 +109,12 @@ function savePackageJson(filepath, json) {
}
}

function patchTestRunnerFieldInPackageJSON(runnerName) {
function patchDetoxConfigInPackageJSON(runnerName, runnerConfigFile) {
const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = parsePackageJson(packageJsonPath);

if (packageJson) {
patchPackageJson(packageJson, runnerName);
patchPackageJson(packageJson, runnerName, runnerConfigFile);
savePackageJson(packageJsonPath, packageJson);
}
}
20 changes: 14 additions & 6 deletions detox/local-cli/templates/jest.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
const firstTestContent = require('./firstTestContent');
const runnerConfig = `{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node"
"testEnvironment": "node",
"reporters": ["<rootDir>/../node_modules/detox/runners/jest/DetoxJestReporter.js"],
"verbose": true
}`;

const initjsContent = `const detox = require('detox');
const config = require('../package.json').detox;
const initjsContent = `const config = require('../package.json').detox;
const adapter = require('detox/runners/jest/adapter');
const traceAdapter = require('detox/runners/jest/traceAdapter');
// Set the default timeout
jest.setTimeout(120000);
jasmine.getEnv().addReporter(adapter);
// This takes care of generating status logs on a per-test basis.
// By default, jest only reports at file-level.
// This is strictly optional.
jasmine.getEnv().addReporter(traceAdapter);
beforeAll(async () => {
await detox.init(config);
await adapter.beforeAll(config);
});
beforeEach(async () => {
Expand All @@ -21,8 +29,8 @@ beforeEach(async () => {
afterAll(async () => {
await adapter.afterAll();
await detox.cleanup();
});`;
});
`;

exports.initjs = initjsContent;
exports.firstTest = firstTestContent;
Expand Down
91 changes: 91 additions & 0 deletions detox/runners/jest/DetoxJestReporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const {VerboseReporter} = require('@jest/reporters'); // eslint-disable-line
const DetoxRuntimeError = require('../../src/errors/DetoxRuntimeError');

class DetoxJestReporter extends VerboseReporter {

constructor(globalConfig) {
super(globalConfig);
this._assertConfig();
}

/**
* The super's impl does the following:
* - For the <b>stderr</b> stream, it overrides the 'write' method with a simple bulked output mechanism,
* which aggregates output onto a buffer but flushes it immediately.
* - For the <b>stdout</b> stream, it overrides the 'write' method with a time-based bulked output mechanism,
* which aggregates output onto a buffer and flushes only in 100ms intervals.
*
* This gives priority, to a certain extent, to stderr output, over stdout. Typically, user logs are sent
* to stdout, and Jest reporter's (e.g. test-suite summary) - to stderr.
*
* ---
* Our goal is to have these 3 types of output stream-lined in real-time:
*
* 1. Jest suite-level lifecycle logging, typically done by the super-class' impl.
* Note: jest does not notify spec-level events to reporters.
* 2. Jasmine real-time, spec-level lifecycle logging.
* 3. User in-test logging (e.g. for debugging).
*
* It's easy to see that this cannot be done while stderr and stdout are not of equal priority. Therefore, the
* solution introduced here is to apply the super's immediate-flushing approach to <b>both</b> flavors.
*/
_wrapStdio(stream) {
const originalWrite = stream.write;
let buffer = [];

const flushBufferedOutput = () => {
const string = buffer.join('');
buffer = []; // This is to avoid conflicts between random output and status text

this._clearStatus();

if (string) {
originalWrite.call(stream, string);
}

this._printStatus();

this._bufferedOutput.delete(flushBufferedOutput);
};

this._bufferedOutput.add(flushBufferedOutput);

stream.write = chunk => {
buffer.push(chunk);
flushBufferedOutput();
return true;
};
}

_assertConfig() {
if (!this._isVerboseEnabled()) {
// Non-verbose mode makes Jest swizzle 'console' with a buffered output impl, which prevents
// user and detox' jasmine-lifecycle logs from showing in real time.
throw new DetoxRuntimeError({
message: 'Cannot run properly unless Jest is in verbose mode',
hint: 'See https://jestjs.io/docs/en/configuration#verbose-boolean for more details',
});
}

if (this._hasDefaultReporter()) {
// This class overrides jest's VerboseReporter, which is set by default. Can't have both.
throw new DetoxRuntimeError({
message: 'Cannot work alongside the default Jest reporter. Please remove it from the reporters list.',
hint: 'see https://jestjs.io/docs/en/configuration#reporters-array-modulename-modulename-options for more details',
});
}
}

_isVerboseEnabled() {
return !!this._globalConfig.verbose;
}

_hasDefaultReporter() {
return !!this._globalConfig.reporters.find(reporterDef => {
const reporterName = reporterDef[0];
return reporterName === 'default';
});
}
}

module.exports = DetoxJestReporter;
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
const DetoxRuntimeError = require('../../src/errors/DetoxRuntimeError');

class DetoxJestAdapter /* implements JasmineReporter */ {
class DetoxLifecycleAdapter /* implements JasmineReporter */ {
constructor(detox) {
this.detox = detox;
this._currentSpec = null;
this._todos = [];
}

async beforeAll(config) {
if (!config) {
throw new DetoxRuntimeError({
message: 'Detox adapter to Jest is malfunctioning.',
hint: 'You must pass specifcy the detox config from your package.json as a parameter to beforeAll()'
});
}
await this.detox.init(config);
}

async beforeEach() {
if (!this._currentSpec) {
throw new DetoxRuntimeError({
Expand All @@ -23,6 +33,11 @@ class DetoxJestAdapter /* implements JasmineReporter */ {

async afterAll() {
await this._flush();

if (!this.cleanUpCalled) {
this.cleanUpCalled = true; // This is for temporary backwards compatibility.
await this.detox.cleanup();
}
}

async _afterEach(previousSpec) {
Expand Down Expand Up @@ -70,4 +85,4 @@ class DetoxJestAdapter /* implements JasmineReporter */ {
}
}

module.exports = DetoxJestAdapter;
module.exports = DetoxLifecycleAdapter;
66 changes: 66 additions & 0 deletions detox/runners/jest/DetoxTraceAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const chalk = require('chalk').default;

class DetoxTraceAdapter /* implements JasmineReporter */ {

constructor() {
this._suites = [];
this._suitesDesc = '';
}

suiteStarted(suiteInfo) {
this._suites.push(suiteInfo);
this._regenerateSuitesDesc();
}

suiteDone() {
this._suites.pop();
this._regenerateSuitesDesc();

if (!this._suites.length) {
this._traceln('');
}
}

specStarted(result) {
this._traceSpec(result);
}

specDone(result) {
if (result.status === 'disabled') {
this._traceSpec(result, chalk.yellow('SKIPPED'));
} else if (result.status === 'failed') {
this._traceSpec(result, chalk.red('FAIL'));
} else if (result.pendingReason) {
this._traceSpec(result, chalk.yellow('PENDING'));
} else {
this._traceSpec(result, chalk.green('OK'));
}
}

_regenerateSuitesDesc() {
this._suitesDesc = '';

const total = this._suites.length;
this._suites.forEach((suite, index) => {
this._suitesDesc = this._suitesDesc
.concat((index > 0) ? ' > ' : '')
.concat(chalk.bold.white(suite.description))
.concat((index === total - 1) ? ': ' : '');
});
}

_traceSpec({description}, status) {
this._traceln(this._suitesDesc + chalk.gray(description) + chalk.gray(status ? ` [${status}]` : ''));
}

_trace(message) {
process.stdout.write(message);
}

_traceln(message) {
this._trace(message);
process.stdout.write('\n');
}
}

module.exports = DetoxTraceAdapter;
4 changes: 2 additions & 2 deletions detox/runners/jest/adapter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const detox = require('../../src/index');
const DetoxJestAdapter = require('./DetoxJestAdapter');
const DetoxLifecycleAdapter = require('./DetoxLifecycleAdapter');

module.exports = new DetoxJestAdapter(detox);
module.exports = new DetoxLifecycleAdapter(detox);
3 changes: 3 additions & 0 deletions detox/runners/jest/traceAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const DetoxTraceAdapter = require('./DetoxTraceAdapter');

module.exports = new DetoxTraceAdapter();
Loading

0 comments on commit df45d8f

Please sign in to comment.