11

The Story:

In several places in our test codebase we assert different CSS values to be equal to expected values. Usually, this is a color, background-color, font related style properties, or cursor. This question is about dealing with colors.

Here is an example working test that currently passes:

describe("AngularJS home page", function () {
    beforeEach(function () {
        browser.get("https://angularjs.org/");
    });

    it("should show a blue Download button", function () {
        var downloadButton = element(by.partialLinkText("Download"));

        expect(downloadButton.getCssValue("background-color")).toEqual("rgba(0, 116, 204, 1)");
    });
});

It checks that the Download button on the AngularJS website has 0, 116, 204, 1 RGBA value.

Now, if the color would change, the test would fail as, for example:

Expected 'rgba(0, 116, 204, 1)' to equal 'rgba(255, 116, 204, 1)'.

The Problems:

  • As you can see, first of all, the expectation itself is not quite readable. Unless we put a comment near it, it is not obvious what color are we expecting to see.

  • Also, the error message is not informative. It is unclear what an actual color is and what color are we expecting to see.

The Question:

Is it possible to improve the test itself and the error message to be more readable and informative and operate color names instead of color RGB/RGBA values?

Desired expectation:

expect(downloadButton).toHaveBackgroundColor("midnight blue");

Desired error messages:

Expect 'blue' to equal 'black'
Expect 'dark grey' to equal 'light sea green'

Currently, I'm thinking about making a custom jasmine matcher that would convert the RGB/RGBA value to a custom Color object that would keep the original value as well as determine the closest color. color npm package looks very promising.

Would appreciate any hints.

alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • Are all the computed values guaranteed to correspond to named colors? – BoltClock Dec 27 '15 at 06:10
  • @BoltClock good question. I would stick to some base mapping between rgb->color name, and if a color name not found, we can just show the actual original rgb(a) value..I understand, it is probably not going to be bullet-proof, but I hope practical. – alecxe Dec 27 '15 at 06:11
  • 4
    Most of the websites have their own set of colors, usually there is not a lot of them, but it depends on a design. I would think of creating a simple JS object as a map for colors and color names, specific to your website, and not rely on another lib: `{ 'red': 'rgba(255, 0, 0, 1)', 'blue': 'rgba(0, 0, 255, 1)' }` and match against them. – Michael Radionov Dec 27 '15 at 09:47
  • I would opt for color roles, not colors per se.. although that's not 1:1. – user2864740 Dec 31 '15 at 03:45
  • @user2864740 do you mean that, basically, aim for the element "visual" related classes (`btn-primary`, `btn-cancel` etc) instead of actual colors? Thanks. – alecxe Dec 31 '15 at 04:08
  • 2
    @alecxe The tests I've written target classes, but even in the case of Michael's suggestion it could be `{ 'warning': 'rgb(255, 0, 0, 1)' }`. That's still lower than I'd prefer to deal with though and human 'visual' testing catches the layout/rendering flaws - that can manifest in so many ridiculous ways across different devices - much better than Selenium can. – user2864740 Dec 31 '15 at 04:46
  • @user2864740 nice, I actually really like that, thanks for the idea! – alecxe Dec 31 '15 at 04:48
  • also you can complain jasmine custom matchers and [name-that-color package](https://www.npmjs.com/package/name-that-color) – Manasov Daniel Jan 06 '16 at 16:53

2 Answers2

8

Protractor uses Jasmine as its testing framework by default and it provides a mechanism for adding custom expectations.

So, you could do something like this:

In your protractor config file:

var customMatchers = {
  toHaveBackgroundColor: function(util, customEqualityTesters) {
      compare: function(actual, expected) {
        result.pass = util.equals(extractColor(actual), convertToRGB(expected), customEqualityTesters);
        result.message = 'Expected ' + actual + ' to be color ' + expected';
      }
   }
}

jasmine.addMatchers(customMatchers);

function convertToRGB(color) {
  // convert a named color to rgb
}

function extractColor(domElement) {
  // extract background color from dom element
}

And to use it:

expect(downloadButton).toHaveBackgroundColor("midnight blue");

I haven't tried this out, but this is the basic idea of what you need.

Andrew Eisenberg
  • 28,387
  • 9
  • 92
  • 148
  • Sorry for the confusion, the "custom jasmine matcher" part is something I'm already familiar with. Here I'm basically mostly focused on the most reliable `convertToRGB` step implementation..and probably seeking for something generic we can make a reusable library from..Thank you! – alecxe Dec 27 '15 at 20:14
1

I got a working answer based on @Andrew and @Manasov recommendations.

So the custom expectation would be like this:

var nameColor = require('name-that-color/lib/ntc');
var rgbHex = require('rgb-hex');

toHaveColor: function() {
    return {
        compare: function (elem, color) {
            var result = {};
            result.pass = elem.getCssValue("color").then(function(cssColor) {
                var hexColor = rgbHex(cssColor);
                var colorName = nameColor.name('#' + hexColor.substring(0, 6).toUpperCase());
                result.message = "Expect '" + colorName[1] + "' to equal '" + color + "'";
                return colorName[1] === color;
            });
            return result;
        }
    }
}

We need to first install the necessary packages:

npm install name-that-color
npm install rgb-hex

We first need to convert the rgb color into hex. Also we have to remove the alpha from the color for name-that-color to actually match it against a color name, it can be removed from the hex conversion.

Now we can just simple call it like:

expect(downloadButton).toHaveColor("midnight blue");
eLRuLL
  • 18,488
  • 9
  • 73
  • 99