# Adding a custom reporter to Detox


[Detox](https://github.com/wix/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](https://www.npmjs.com/package/jest-circus) following [this](https://github.com/wix/Detox/blob/master/docs/Guide.Jest.md) as the test runner instead of Mocha, as [recommended](https://medium.com/wix-engineering/detox-in-2020-e34525548123).

One of the things mentioned in the guide is the use of a [CustomDetoxEnvironment](https://github.com/wix/Detox/blob/master/docs/Guide.Jest.md#e2eenvironmentjs), 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](https://github.com/allure-framework)


## Adding an Allure Reporter Listener for Detox

To understand what was going on I looked at the code inside [DetoxCircusEnvironment](https://github.com/wix/Detox/blob/master/detox/runners/jest-circus/environment.js), and specifically lines [L52-L86](https://github.com/wix/Detox/blob/master/detox/runners/jest-circus/environment.js#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](https://github.com/facebook/jest/blob/master/packages/jest-types/src/Circus.ts) 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;

```
