godaddy / ekke

Ekke is a test runner for React-Native, it allows you to execute your test code directly on the device enabling you to test in the same environment as your production users.
MIT License
133 stars 9 forks source link

Plugin API #15

Open 3rd-Eden opened 5 years ago

3rd-Eden commented 5 years ago

Why do we need a plugin system?

There is a lot of amazing functionality that can be built on top of ekke. It runs inside React-Native but communicates with Nodejs using WebSocket connection. So in reality, you can have best of both worlds. My go-to example for a plugin is: screenshots. With ekke we have a render() method that renders your component on screen. We could implement a screenshot API by calling the adb shell screencap -p command on the CLI, run visual diffing against it using pixelmatch and stream back the result for an assertion. Or maybe you just want to create a plugin that allows you to interact with the file system to read files or even data directly from React-Native.

As neat as all these features are, they should not belong in the core of ekke, but we should provide a programmable API that makes this possible. In fact, if we do this correctly, even our test runners could technically be plugins, but included by default.

Plugin requirements

It needs to have an API that can communicate, we already have a WebSocket bridge which we can expose. This way plugins can:

It's not just communication with Node.js that is required, plugins should be able to modify every part of our orchestration:

The plugin API should be simple, async, and event-based. It's would not be uncommon that multiple plugins leverage the same API and by making it even based, each individual plugin can subscribe to the events. This also allows us to make the bridge function, event-based, so they can subscribe to the events they execute.

Plugin API when required in Node.js (use main in the package.json)
export default function myPlugin({ modify, bridge, register }) {
  modify('babel', async function (babel) {
    return require('babel-merge')(babel, customconfig);
  });

  modify('metro.config', async function (metro) {
    return require('metro-config').merge(metro, { custom });
  });

  bridge('screenshot', async function (id, reply) {
    const { old, new } = await makescreenshot(id);
    const diff = await compare(old, new);

    await reply(diff);
  });

  //
  // Registers a new method on the `API`.
  //
  register('method', async function () {

  });
} 
Plugin API when required in React-Native (use react-native in the package.json)
export default function myPlugin({ subscribe, bridge, register }) {
  modify('setup', async (runner) {
    // do stuff
  });

  //
  // Introduces new method on for the `plugin()` API.
  //
  register('screenshot', async function (element, name) {
      const id = name || (element.type +'@'+ JSON.stringify(props));

      await render(element);
      return await bridge('screenshot', id); 
    }
  }); 
});  
Plugin API as consumed by users.
import { render, bridge, plugin } from 'ekke';

const { screenshot } = plugin('example');

it('takes a screenshot', async function () {
  await render(<Component />);

  const diff = await screenshot(<Component />);
  assume(diff).is.a('object')
});  

Acceptance Criteria

Swaagie commented 5 years ago

Is the order in which the plugin run relevant? Or should they be capable of defining the order? It could be as simple as using the plugin array indexing to determine order, but order of execution could be bit harder to get right. This is especially important when plugins should be capable of using methods they other plugins introduce (like when runners become plugins).

3rd-Eden commented 5 years ago

@Swaagie That is indeed a valid concern, thanks for pointing that out. What I would suggest is that we add support for a second argument in these API's which would be an options object where people could supply an order property on which we sort. Similar that what I've done with storage-engine.

3rd-Eden commented 5 years ago

Just to give an update on the progress of this task. The plugin branch that is created by #17 is considered feature complete. So all required features and most basic plugin hooks are available. The current test runners have been transformed to plugins in an attempt dogfood our own system.

The only low hanging fruits are documentation and additional test for everything created. But we're close to a new release, which will most likely be a minor release as everything is currently backwards compatible and working as intended.