Cypress.IO Data List Processing — Testing A WordPress Plugin
Testing Store Locator Plus with lots of locations is a chore. Thankfully Cypress.IO data list processing makes this a lot easier.
It turns out that the old-school Selenium IDE scripts that we’ve been using to test Store Locator Plus for years will no longer work. We already knew Firefox versions beyond 54 broke things — but we kept an old install on hand so while we port 500+ test scripts to a new system. What finally broke the old-school Firefox bandaid was moving Store Locator Plus towards a reactive application using Vue.
Selenium IDE is not reactive friendly
Turns out Selenium IDE sucks at managing reactive apps. Vue + Vuetify has some nice user interface elements but it does lots of transitions and animations. Selenium IDE is from a day when servers spit-up fairly static HTML elements so a form field was either there and ready to be “talked to” or not. These days everything is asynchronous — like Schrödinger’s cat , it may be there but it may not. New automation tools need to fire “when something becomes available” not “assume it is already there”.
Selenium IDE for Firefox 56+ is out but it is far from complete — and it cannot load any of our old tests, so that’s not going to help. Back to Cypress.IO.
Loading form data automatically
Part of the testing process is loading up a base set of locations.
Cypress.IO data list processing is easy with a great built-in feature, something called fixtures. It provides a way to side-load data from various sources. In this case we’ll use the fixtures to load a JSON file with our default locations stored as an array.
The general concept is simple: login to WordPress, go to the Store Locator Plus locations interface and add locations one-after-another.
The old Selenium IDE tool had a very long stack of repeated “type Store A into the store field, click save, type Store B into the store field, click save” commands. The new Cypress.IO code is far more elegant.
Here is how I did it.
The data file
Since I want to automate the data process versus the old way of recording a lot of typing, let’s start with a data set of default locations.
Cypress.IO will create a ./fixtures directory when you first install it. This is where you keep your data assets.
In this case I want a JSON file that is an array of location objects. This is standard JSON stuff here.
My default_locations.json file.
[ { "store": "Amalfi's Italian Restaurant", "address": "664 E Long Point Road", "city": "Mount Pleasant", "state": "SC", "zip": "29464", "country": "USA", "url": "http://www.amalfisofmountpleasant.com/", "phone": "(843) 793-4265" }, { "store": "Charleston Coffee Roasters", "address": "289 Huger Street", "city": "Charleston", "state": "SC", "zip": "29403", "country": "USA", "url": "http://www.charlestoncoffeeroasters.com/", "phone": "(843) 266-7444", "fax": "(843) 266-7448", "email": "info@charlestoncoffeeroasters.com" } ]
The Cypress.IO code magic
The code for this specific “load a stack of data-driven locations”has a few basic parts. Most of this is standard Cypress.IO stuff so check out more info in the Cypress.IO docs.
The context
Groups together a series of tests. In this case “Add Locations”.
beforeEach
Run this before every test segment, the things inside the it() calls.
In this case it is running the WordPress login. For some reason, and maybe it is because I don’t know all the cool dark magic of Cypress.IO yet, freakin’ Cypress kills the browser session and all login meta between EVERY SINGLE it() call. That is a huge pain in the ass — it makes testing dozens of admin-level plugin features super slow. There is a 5-10 second UI draw-login/set environment hit between every test. A nightmare, but it is what is it.
Yes, you can store cookies and bypass the UI part but with WordPress nonce’s it is not as easy as it should be. And that still means re-loading the environment which is 80% of that overhead NOT the actual UI rendering.
If anyone knows a way to maintain a persistent login and chain it() commands in Cypress.IO without having to login between each test spec please share. I get the “clean slate” theory of QA testing but this is always the problem when you adhere to strict principles as a scientist when people operating in the real world, engineers, know shit never works exactly like theory. The real world is chaotic , messy, and rarely static-state.
The workhorse – it()
Ok – enough about beforeEach. Let’s talk about the main guts of this Cypress.IO test, the ‘Add default locations’ loop.
Some basic stuff —
Since Store Locator Plus opens up on the manage locations tab the URL is tested to ensure we are in the right place. My beforeEach() block not only logs in but clicks the Store Locator Plus link since that is the plugin I’m testing.
The cy.fixture() command loads in the JSON file and stores it in a JavaScript variable default_locations.
The loop —
You can load in the variable with cy.get() by using an @ to reference a Cypress.IO stored var, which is what is done here so the Cypress.IO each() can be used to iterate over the locations array — cy.get(‘@default_locations’).each( function( location ) { } ) is basically saying for each entry in the array of location objects assign them to a variable ‘location’ one-by-one and do some work on them. Some basic JavaScript here with Cypress.IO flavoring on top.
The fields variable is setup because I don’t want to be typing in a dozen “type into the store field the store name”, “type into the address field the address”. I’ve purposefully setup the JSON structure so the location object keys match the input IDs on the add form. That makes is super-easy to build the input field selector and set the value programatically. var fields = Object.keys( location ) extracts the keys names for each location into an array of strings, something like [ ‘store’ , ‘address’ , ‘city’ ]. This is done on every loop because I will eventually have locations that may have ‘address2’ and others that do not, among other optional fields. This lets the data drive the app not the other way around — in general a good design principle.
Log the store name to the Cypress sidebar so we can get a quick visual reference of which stores were loaded… then start the data entry:
- click the add button on the location menu
- for each field name present for the location find the field with cy.get and type in the value of that data field.
- click the save button on the add form
A couple of confirmation dialogs appear on the UI to confirm the location was added – close them because they might cover the “add” button stopping the next location from being able to click “Add”. Cypress has default tests when you interact with elements that they are visible and accessible “as a user would see it”. If an input is hidden in any way — covered by an alert box for example — it will throw an error if you try to interact with it. That means less explicit “is visible” tests in the code.
Cypress.IO data list processing code…
describe('Add default locations from default_locations.json fixture', function() { context('Add Locations', function () { beforeEach(function () { cy.visit('/wp-login.php'); cy.get( '#user_login' ) .type( 'admin' ) .should( 'have.value' , 'admin' ); cy.get( '#user_pass' ) .type( 'password' ) .should( 'have.value' , 'password' ); cy.get('#wp-submit') .click(); cy.contains( 'Store Locator Plus' ) .click(); }); it('Add default locations loop', function() { cy.url().should('include', '?page=slp_manage_locations'); cy.fixture('default_locations.json').as('default_locations') cy.get('@default_locations') .each( function ( location ) { var fields = Object.keys( location ); cy.log( location.store ); cy.get( 'ul.sub-navbar') .contains( 'Add' ) .click(); cy.get( 'div.subtab_add' ).within( function () { fields.forEach( function( field ) { cy.get( 'input#'+field).type( location[field] ); }) cy.contains( 'Save' ).click(); }); cy.get( 'span.button.close' ).each( function ( el ) { el.click(); }); }); }); }); });
That’s it… have some good Cypress.IO knowledge to share? Comment below.
One thought on “Cypress.IO Data List Processing — Testing A WordPress Plugin”