Writing automated tests for PatternFly Elements

castastrophe
PatternFly Elements
5 min readJan 15, 2019

--

Automated Tests (aka AT) are a blessing and a curse. You love them when they catch an unexpected change before it breaks in production but when you have spent the last two weeks (or more) building a new web component, the last thing you want to do is sit down and write a bunch of AT; you want to get that beast out the door and off your plate! Okay, maybe that’s just me…either way, here are my tips and tricks for writing AT on PatternFly Elements.

A snapshot of the automated tests for a PatternFly Element

Tip 1: Write tests first

Everyone preaches this but how many developers really do this? What I’m not suggesting is that you go in and write the code for all the tests before you’ve built your markup, styles, interactions, or schemas; instead, go into your test file and write out in comment form all the tests you want to run.

This past sprint I was working on building the band layout. Before I get started, I should go into the test directory and open the pfe-band_test.html file. Once there, I add the first, most basic test to ensure the component upgraded (eventually the generator will add this for you but for now, get this out of the way early!).

pfe-band_test.html

<body>
<pfe-band>
</pfe-band>
<script>
suite("<pfe-band>", () => {
// Test that the web component rendered
test("it should upgrade", () => {
assert.instanceOf(
document.querySelector("pfe-band"),
customElements.get("pfe-band"),
"pfe-band should be an instance of PfeBand"
);
});
});
</script>
</body>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Next, in comment form, start outlining the types of tests you expect to write. For example, I expect to have an object that defines all the themes supported and their expected hex values. I expect to iterate over that object to test that the background color renders as expected when the property is set. I expect to run through a set of default style tests to ensure that the rendered visual matches our expectations. Finally, I expect any properties that I write (hopefully you have an idea of what these will be from your planning) to render the expected outcomes when applied.

  <body>
<pfe-band>
</pfe-band>
<script>
// Define all the themes and their expected hex values

suite("<pfe-band>", () => {
// Test that the web component rendered
test("it should upgrade", () => {
assert.instanceOf(
document.querySelector("pfe-band"),
customElements.get("pfe-band"),
"pfe-band should be an instance of PfeBand"
);
});
// Iterate over the colors object to test expected background color results // Test that the default padding is correct // Test that the padding is reduced if the size is set to small // Write tests for aside layout
});
</script>
</body>

Now, when you’re ready to go in and start fleshing out these tests, you have already outlined what you want to write!

Tip 2: The console is your friend

Did you know you can preview your rendered test page? You have to kick off npm run start to run the localhost server, then you can navigate to the test file. The URL should start with /elements/<name of component>/test/<name of test file>, i.e., http://localhost:1234/elements/pfe-hide-show/test/pfe-hide-show_test.html.

This will let you not just see what your rendered component will look like for the test, but also let you replicate your steps from your test in the console to confirm the rendered output. It’s a great way to figure out why that getComputedStyle didn't return what you expected or your querySelector didn't find anything.

There are a few expected console failures so do not be alarmed when you see them. First, the Uncaught ReferenceError: suite is not defined is because the test suite tooling is not loaded on this page. That means your tests will not execute in this preview mode so don't add console.log outputs into your test suite. You can add console.log in the script tag on your test page as long as it occurs before your first suite declaration.

Tip 3: Objectify

When I’m writing tests that are supposed to confirm that a particular attribute applied a certain style or result, a key-value relationship is very helpful in automating those tests. For example, I have a set of 7 different background colors that are rendered via the `pfe-color` attribute. To accomplish this I start by defining my expected results in an object:

const colors = {
default: "#dfdfdf",
darker: "#464646",
darkest: "#131313",
accent: "#fe460d",
complement: "#0477a4",
lighter: "#ececec",
lightest: "#ffffff"
};‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Next I add a few helper functions to interpolate the results. This is because computed style returns an rgb or rgba string and our designers define our background colors using hex values. To make the tests easier to read and understand, we define our expected results using hex and convert those to RGB for comparison.

// Converts a hex value to RGB
const hexToRgb = hex => {
const [, r, g, b] = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})/.exec(hex);
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
};
// Gets the rgb value from an element
const getColor = (el, prop) => {
const [, r, g, b] = getComputedStyle(el, null)[prop].match(/rgba?\((\d+),\s+(\d+),\s+(\d+).*\)/).map(n => +n);
return [r, g, b];
};‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Finally, we iterate over our object, set the attribute to the value we want to test, and run the test to ensure that the rendered result matches our expectations.

// Iterate over the colors object to test expected background color results
Object.entries(colors).forEach(set => {
test(`it should have a background color of ${set[1]} when pfe-color is ${set[0]}`, () => {
// If this is not the default theme, update the variable
if(set[0] !== "default") {
//Update the color attribute
band[0].setAttribute("pfe-color", set[0]);
}
// Test that the color is rendering as expected
assert.deepEqual(getColor(band[0], "background-color"), hexToRgb(set[1]));
});
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Work smarter not harder but don’t obfuscate your tests to the point where they become unreadable. Developers and designers should be able to read and update these tests to ensure fidelity in our components so we want to make updating and altering the tests accessible.

Tip 4: Test what you can, then leave TODOs and move on

Your focus when you’re writing these tests is in completing your development work on this component. That said, you might run into things you want to update or see done differently. By all means, create an issue here or add a // @TODO comment that serves as a reminder that you want to come back and either write more tests, write them in a different way, or reimagine the way or tools we use to write. An example of this with the band layout was in wanting to test the styles applied at certain media breakpoints. At this time, we don't have a good way to control the viewport size the way webdriver.io does so writing those tests would be tricky or even impossible. Instead of squirreling on how to solve this problem during development of the band layout, I added a TODO in my file where I would like to run those tests and write a workaround instead.

// Test that the default padding is correct
test("it should have default padding when no size attribute is set", () => {
// Test that the color is rendering as expected
// @TODO need a way to adjust the viewport
if(window.outerWidth > 576) {
assert.equal(getComputedStyle(band[0], null)["padding"], "64px 16px");
} else {
assert.equal(getComputedStyle(band[0], null)["padding"], "32px 16px");
}
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

--

--

castastrophe
PatternFly Elements

Design systems engineer at Adobe. Thoughts are my own.