Selenium testing of react apps


(Pavel Volokitin) #1

Hi!

How are you guys testing your apps using Selenium or similar tool?

We are planning to add anchors to the DOM and then find nodes using this anchors. Like this:

<div data-test-id="myDiv">...</div>

But there is a problem. It would be cool to add anchors to the components and have dom-attributes to be added automatically to root nodes:

return <MyComp data-test-id="myComp" />;

Probably have to patch react for this. What are your approaches?


(Jason Brown) #2

I can’t say I recommend this strategy but if it is what you’ve decided on then just utilizing the spread operator ...this.props will put that on the component.
Of course there are other side effects of using the spread operator since all props would get passed down and that may cause unintended side-effects. You could possibly create a util to take this.props and spread them only if they contain data-?


(Pavel Volokitin) #3

Why don’t you like the idea of anchor attributes? It looks like it will make writing tests much easier.

The point of patching react is to avoid changing components. We don’t want all of our components to support the test-id attribute.


(Jason Brown) #4

The reason I don’t like it is because those DOM attributes will show up in production code, also you are no explcitily adding code to be able to test which is not a good practice.
Classnames are marginally better, but if you follow a BEM ( https://medium.com/seek-ui-engineering/block-element-modifying-your-javascript-components-d7f99fcab52b ) naming style then you don’t need additional markup just for testing.

React will never support a test-id attribute specifically. The only way this is going to work is with the spread operator.


(Pavel Volokitin) #5

Yes, adding these attributes will require some more code in the app, but it will save a ton of time on supporting Selenium tests. We can easily change order of fields on the page and tests won’t break.

Also, class names will not help here since we want to find instances rather than classes (input1 vs Input).

Good catch with a test-id attribute. We can use data-test-id. And we can strip them on the production build.


(Daniel K.) #6

Well, are you sure that you need Selenium tests at all? I mean you have whole virtual DOM made with React, you can just as easily write tests using eg. Mocha and it should be able to cover all scenarios you need in my opinion.


(Pavel Volokitin) #7

Yeah. But we develop solutions for business. We have to have integration tests that will check that not only our ui and api both work, but also that they work together. Also, developers don’t always know all the scenarios (funny, isn’t it? :smile:), and tests are written by testers who know the domain really well.

I agree that React apps is really easy to test though.


(Kevin Smith) #8

I’ve built a babel plugin for exactly this purpose. We’re using it for our staging builds and this is how it works:

It looks at all component class expressions, finds the render method. If it finds a JSX element as the return value and that JSX element has a class name property set, it will pretend that class name with the components displayName as well as all properties that start with ‘is’ when they’re true.

You’d get something like <div className="MyComponent isExpanded ..." />. This plugin is only enabled on staging builds for test automation purposes. That way, no dev has to be reminded to add anything special to any component. :slight_smile:


(Akudrevatykh) #9

Is there any chance you could share this plugin? :slight_smile:


(Kevin Smith) #10
var Transformer = require('babel-core').Transformer;
var t = require('babel-core').types;

function isRenderMethod(member) {
  return member.kind === 'method' && member.key.name === 'render';
}

function isReturnStatement(member) {
  return member.type === 'ReturnStatement';
}

function isClassNameAttribute(attribute) {
  return attribute.name && attribute.name.name === 'className';
}

function createAST() {
  /*
    The AST for:
    (() => {
      if (!this.constructor || !this.constructor.displayName) {
        return '';
      }

      if (!this.constructor.propTypes) {
        return this.constructor.displayName + ' ';
      }

      return this.constructor.displayName + ' ' + Object.keys(this.constructor.propTypes)
        .filter(propKey => propKey.indexOf('is') === 0)
        .map(propKey => this.props[propKey] ? propKey : '')
        .join(' ') + ' ';
      })();
   */
  return JSON.parse(JSON.stringify(require('./qaDecorator.AST.json')));
}

module.exports = new Transformer('add-displayName-as-class', {
  ClassDeclaration: function (node) {
    const renderMethod = node.body.body.filter(isRenderMethod)[ 0 ];
    if (!renderMethod) {
      return;
    }

    const returnStatement = renderMethod.value.body.body.filter(isReturnStatement)[ 0 ];
    if (!returnStatement) {
      return;
    }

    if (returnStatement.argument.type !== 'JSXElement') {
      return;
    }

    const classNameAttribute = returnStatement.argument.openingElement.attributes.filter(isClassNameAttribute)[ 0 ];

    if (!classNameAttribute) {
      return;
    }

    classNameAttribute.value = t.binaryExpression('+', createAST(), classNameAttribute.value);
  }
});

(Chrismcv) #11

@ksmth have you qaDecorator.AST.json somewhere too? or can you advise on how to create it?


(Kevin Smith) #12

I used http://esprima.org/demo/parse.html to generate it.


(Oliviertassinari) #13

To test my react application with selenium, I’m using customs sélections on specific components. To do so, I’m using the data-test="name" properties.
Then, I can use this CSS selector [data-test=name] with selenium.
IMHO, the advantage of this technique over @ksmth one’s is that your tests are less likely to break after a refactorisation, as long as, the data-test value stay the same.
Then, I’m using https://github.com/oliviertassinari/babel-plugin-react-remove-properties to remove the overhead of those properties in production.​


(Chrxs) #14

Hi Kevin,

The babel plugin you’ve mentioned here is precisely what I am trying to do also, but afraid I seem to be having no luck.
Do you have perhaps a GitHub repository for the plugin that I can inspect.
Also, I feel it may not work for me because I am using babel 6 and have read about changes in writing plugins.

If you are able to help in anyway it will be greatly appreciated!
Many thanks

  • Chris Miller

(suprraz) #15

I built a plugin that exposes node name and file name https://github.com/suprraz/babel-plugin-react-element-info


(coderas) #16

We recently decided to support both unit level tests and cukes using data-test-id and just use https://www.npmjs.com/package/babel-plugin-jsx-remove-data-test-id to strip it out (blatant plug, I did discover this thread before doing the work however and it helped me decide that this was indeed a thing)

hope that’s helpful to someone


(Eric R) #17

I just built out a whole UI test suite with Selenium. (React/Flux front-end, tests are in C#.)
After a bunch of research, I ended up using id attributes wherever possible, and role/ARIA attributes everywhere else. The benefits:

  • no extra attributes to worry about adding and stripping
  • role attributes are standardized, simple descriptions of layout structure
  • you should have these attributes for accessibility anyway
  • no reliance on CSS or tag types

I also like the idea that Selenium is then navigating similarly to potential live users.

Thoughts?