Adding a custom reporter to Detox

Adding a custom reporter to Detox

2021, Mar 01    

Detox is a popular automation library for mobile apps, usually those built with React Native. I recently upgraded our implementation of Detox to use Jest Circus following this as the test runner instead of Mocha, as recommended.

One of the things mentioned in the guide is the use of a CustomDetoxEnvironment, is that there is no guide for writing custom detox listeners, and indeed there isn’t! So I’ll take you through how we added one for Allure

Adding an Allure Reporter Listener for Detox

To understand what was going on I looked at the code inside DetoxCircusEnvironment, and specifically lines L52-L86

Within this file we can see the following code:

for (const listener of this.testEventListeners) {
  if (typeof listener[name] === 'function') {
    try {
      await this._timer.run(() => listener[name](event, state));
    } catch (listenerError) {
      this._logger.error(`${listenerError}`);
    }
  }
}

so if we implemented functions that matched the names of events that we are interested in listed here then it would handle the events as part of the Jest Circus lifecyle for us.

const Allure = require('allure-js-commons'); // version "1.3.2",
const fs = require('fs');
const stripAnsi = require('strip-ansi');

class AllureReporterCircus {

  constructor({ detox }) {
    this.allure = new Allure();
    this.detox = detox;
  }

  run_describe_start(event) {
    if (event.describeBlock.parent !== undefined) {
      this.allure.startSuite(event.describeBlock.name);
    }
  }

  run_describe_finish(event) {
    if (event.describeBlock.parent !== undefined) {
      this.allure.endSuite();
    }
  }

  test_start(event) {
    const { test } = event;
    this.allure.startCase(test.name)
  }

  async test_done(event) {
    if (event.test.errors.length > 0) {
      const { test } = event;
      const screenshotPath = await this.detox.device.takeScreenshot(`${test.startedAt}-failed`);
      const buffer = fs.readFileSync(`${screenshotPath}`);
      this.allure.addAttachment('Screenshot test failue', Buffer.from(buffer, 'base64'), 'image/png');

      const err = test.errors[0][0];
      err.message = stripAnsi(err.message);
      err.stack = stripAnsi(err.stack);

      this.allure.endCase('failed', err);
    }
    else {
      this.allure.endCase('passed')
    }
  }

  test_skip(event) {
    const { test } = event;
    this.allure.startCase(test.name);
    this.allure.pendingCase(test.name);
  }
}

module.exports = AllureReporterCircus;

The final step is to add the reporter as a listener with the CustomDetoxEnvironment

const {	
  DetoxCircusEnvironment,	
  SpecReporter,	
  WorkerAssignReporter,	
} = require('detox/runners/jest-circus');

const AllureReporter = require('./reporters/AllureReporterCircus')

class CustomDetoxEnvironment extends DetoxCircusEnvironment {	
  constructor(config, context) {	
    super(config, context);	

    this.initTimeout = 300000;	

    this.registerListeners({	
      SpecReporter,
      AllureReporter,
      WorkerAssignReporter,
    });	
  }	
}	

module.exports = CustomDetoxEnvironment;