Skip to main content

Command Palette

Search for a command to run...

Adding a custom reporter to Detox

Published
2 min read
Adding a custom reporter to Detox

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 lifecycle 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;

More from this blog

Hugh McCamphill's Blog

11 posts

I'm Hugh - a software testing person with ESW, working on test automation, CI/CD activities, test leadership and a whole bunch besides