Testing Web Apps With Selenium and JavaScript

It turns out I’ve been making the setup of Selenium 2 (Selenium Server + Webdriver) far too complicated.   As noted in my prior article, getting the client side of the equation setup with Safari is as simple as going to the developer menu and selecting “Allow Remote Automation”.

The server side of things is almost as easy.  I’ve opted to build my tests in JavaScript since that is the “way of the web app world” these days and because it will fit in very nicely with my PHP-heavy web apps written for WordPress.    That means I can keep my phpStorm environment intact and use the built-in Node, Grunt, WordPress-aware code highlighting AND run my tests from the IDE.   Very nice.

For this example I am using Safari 11 (released September 2017) and adding a Node.js applet to my PHP project for testing my WordPress plugin.    I’ve setup the project with source in phpStorm already so from here I just need a “home” for running the web tests.

Install Node.js

I’ve already setup Node on my MacOS box, you can do the same by downloading and installing the node package on Node.js.

Setup A Directory

I created a directory from the MacOS terminal called “selenium_for_myslp” where I have been storing my Selenium IDE tests from past testing cycles.   Within this directory I I’ve created a new “webdriver_tests” directory where I will write and execute all of my new Selenium (aka Selenium 2 or Selenium + Webdriver) tests written in JavaScript using node packages.

Install The Nodes

From the command line you will need to install both the selenium-drivers and selenium-webdrivers packages.   Go to your project directory and install those modules.

Since I’ve not defined any package dependencies, licenses, repos, or other “overhead” for a typical Node app I see some notices about that.

npm WARN enoent ENOENT: no such file or directory, open ‘/Users/lancecleveland/Store Locator Plus/selenium_for_myslp/package.json’

npm WARN selenium_for_myslp No description

npm WARN selenium_for_myslp No repository field.

npm WARN selenium_for_myslp No README data

npm WARN selenium_for_myslp No license field.

No worries there, the testing app will run fine.

Create The Test Applet

Now I get into phpStorm and add the new directory , the top-level “selenium_for_myslp” directory in my case, to my project.   I see my old IDE tests, the newly-minted node_modules, my old sample_deployments, and my new webdriver_tests directories.

I’ll create a new “generic” subdirectory and in it add a new JavaScript file.    I’ll call it “test.js” because it is a test.

A straight copy-paste from the selenium-drivers example, then replacing “chrome” with “safari”, and I’m in business.

Right-click in the source window and run the code.

Selenium and Node in phpStorm 2017-09-21_16-24-09.png

If all is well a new Safari window will fire up with a dialogue box about running automated tests.   I let it run and it opens the google site in my test case.

Selenium Driven Google

I’m now ready to fully automate my site scripts using JavaScript and Selenium.

Installing Safari Webdriver for Selenium 2

Automated testing with Selenium is now your best option for scripted web testing if you have been using Selenium IDE with Firefox for the past few years.  As of Firefox 55, released in August of 2017, Selenium IDE no longer works “out of the box” with the latest browser.  While the folks over at SeleniumHQ work on “Selenium IDE TNG” (The Next Generation, I assume) you are going to be left either running older Firefox 54 which will try to auto-update at every turn or take the opportunity to learn the “big boy” techniques of using Selenium 2 with Webdriver.

Selenium 2 Versus Selenium IDE

Unlike Selenium IDE which was essentially an “all in one” script recorder and playback utility for Firefox, Selenium 2 (Selenium Server + Webdriver) is a client and server combo-meal that uses the server to run logic and send commands to the client that sits on your favorite browser and executes the commands.

Installing Webdriver On Safari

Since this article is about Webdriver and Safari 11, we are going to focus on what it takes to get this running on MacOS with Safari.  The latest version, Safari 11, was released in September 2017 so we’ll focus on that.   Though Safari 10 “Webdriver” configuration is similar. Note that I use “Webdriver” in quotes.  For Safari 10 and 11 it was known as “Safaridriver” instead.

Since Safari 10 there is NOTHING TO DOWNLOAD.   That’s right.  Nothing.  It is already baked-in to the shipping version of Safari.  That’s pretty damn sweet.

WebDriver Support

Safari on macOS supports WebDriver, which lets you automate web-content testing. It provides a set of interfaces to manipulate DOM elements and control the browser’s behavior. You can enable Remote Automation in the Develop menu and then launch the server using /usr/bin/safaridriver. For information about library integrations as they become available, see the information about Selenium WebDriver.

How do you enable the web driver then?

Go to the Develop menu on Safari and select “Allow Remote Automation”. 

That’s it.  You’re done with setting up the client.

Webdriver on Safari 11 2017-09-21_15-00-02.png

My next article will go over how to use this automation to test web apps using a JavaScript-based command set.    There are a lot of options for languages and far deeper tests you can run with the coding logic than the Selenium IDE setup.    While not as simple as record-and-play of Selenium IDE, any coder that was trying to coerce Selenium IDE with rollups and built-in JavaScript code snippets will find this process a little more work with a lot more power.

Selenium IDE Rollups With StoredVars Logic

Creating rollups in Selenium IDE with execution logic based on storedVars can be tricky.   Storing the value of an element or its presence within the rollup command list and using if or other logic blocks via the various “flow” add ins will not work as you expect.   Nor will storing the values and then using JavaScript logic within your rollup.     Getting storedVars working in a rollup is a special kind of black magic.

Here are my methods that are working for me with my home-grown “Go With The Flow” add-in I brewed based on similar control flow plugins.

Set Vars In A Separate Rollup

I find that setting my variables in a separate rollup is a simple solution.   This ensures your variable stack exists and is set using standard Selenium IDE commands.   My example for testing if I am already logged in before prompting for user and password data and running the login:

/**
 * Set My Vars
 */
manager.addRollupRule({
    name: 'set_my_vars',
    description: 'Set variables we might care about.',
    commandMatchers: [],
    getExpandedCommands: function(args) {
        var commands = [];
        commands.push({ command: 'setTimeout'           , target: '10506'        , value: ''                 });
        commands.push({ command: 'setSpeed'             , target: '0'            , value: ''                 });
        commands.push({ command: 'storeElementPresent'  , target: 'id=colophon'  , value: 'not_logged_in'    });
        return commands;
    }
});

Use JavaScript Logic

Once your variables are set they are standard JavaScript variables that will be available throughout your rollups.  You can use this fact to build standard JavaScript logic in your rollup and decide what commands the rollup will push on the stack during each execution.   In your Selenium IDE command list you can then change storedVars using various store commands and then call the rollup afterwards; in essence changing what commands are executed “on the fly”.

You’ll notice that I also do a quick check at the top to ensure the set_my_vars rollup was already run by checking that the storedVars key I need is set.   If not it echos a message to the Selenium IDE log console and skips doing anything else.

/**
 * Super Admin Login
 */
manager.addRollupRule({
    name: 'sa_login',
    description: 'Super Admin Login must run rollup set_my_vars first.',
    commandMatchers: [],
    getExpandedCommands: function(args) {
        var commands = [];
        if (typeof storedVars[ 'not_logged_in' ] === 'undefined') {
            commands.push({ command: 'echo', target: "run rollup set_my_vars before running this rollup" , value: '' });
            return commands;
        }

        if ( storedVars[ 'not_logged_in' ] ) {
            commands.push({ command: 'open'                 , target: '/'                                                               , value: ''             });
            commands.push({ command: 'storeEval'            , target: "prompt('Dashboard Admin Login' , 'me@storelocatorplus.com')"     , value: 'sa_user'      });
            commands.push({ command: 'storeEval'            , target: "prompt('Password' , 'Jeny8675309' )"                             , value: 'sa_pwd'       });
            commands.push({ command: 'type'                 , target: 'id=email'                                                        , value: '${sa_user}'   });
            commands.push({ command: 'type'                 , target: 'id=login-password'                                               , value: '${sa_pwd}'    });
            commands.push({ command: 'clickAndWait'         , target: 'css=input[type="submit"]'                                        , value: ''             });
            commands.push({ command: 'waitForElementPresent', target: 'id=footer-thankyou'                                              , value: ''             });
        } else {
            commands.push({ command: 'echo', target: "already logged in ( not_logged_in : " + storedVars[ 'not_logged_in' ] + " )" , value: '' });
        }
        return commands;
    }
});

 

Hopefully these tricks will help you with your Selenium IDE rollup builds.    Some things to keep in mind is that things like Selenium labels or endif markers do not exist within the rollup itself if you are creating them in the rollup.    Same concept with storedVars, the storedVars[<key>] will not exists and be available to your rollup JavaScript logic if you create the var inside the rollup itself.      When a rollup that creates a storedVars entry is complete it is then available for any future commands, including rollups.

Selenium IDE Rollups With Arguments

As I prepare another release of Store Locator Plus with some new features I’ve decided it is time to up my QA-fu with Selenium.   I’ve been using Selenium IDE for a while now and find that , despite being free, it is one of the best user experience testing tools out there.    I’ve paid for a few testing tools over the years and I always come back to Selenium IDE.     The paid tools are do not offer a lot more and are just as complex to learn to get advanced testing techniques in place.

Simple Rollups

Speaking of advanced techniques, here is some new skills I picked up regarding Selenium ID rollups.   If you are not aware of what a rollup is, in the simplest form it is an easy way to group together oft-repeated commands in Selenium into a single entry.  This makes your test files easier to read and limits errors by ensuring consistency across your tests.   For example, of of my earlier rollups does a simple “syntax error” check which is very useful when running tests of my WordPress plugins or the SaaS version of the application when I have debug mode enabled.  It does little more than scan the body of the web page for known strings that typically mean the app crashed.

Here is what that rollup looks like:

 

Rollups With Arguments

Today I added a new level to my rollups: parameter passing.

This allows you to create a single rollup that serves multiple purposes.  For example, before today I had 4 different rollups.  One to open each of 4 different tabs in my application.   I’d call rollup open_general_tab or open_experience_tab depending on what I was about to test next.

Now I have a smaller more efficient rollup thanks to the use of arguments.   I now call rollup open_tab tab=slp_general or rollup open_tab tab=slp_experience and the back end runs a single small set of code.  That means less memory being consumed in the browser and less chance of errors as the codebase is reduced by nearly a factor of 4 in this case.

Here is my open tab rollup:

 

And here is how it looks in the IDE:

Selenium IDE Rollup With Args 2017-04-18_16-05-38

Selenium IDE Rollup With Args 2017-04-18_16-05-38

If you are not familiar with extending Selenium, you will want to review their documentation on creating rollups.    It is a basic JavaScript file that you include in Selenium IDE by setting the file name as a core extension.   I also strongly recommend getting a flow control extension to Selenium IDE as well.  I’ve “rolled my own” based on the ide-flow-control modules of others called “Go With The Flow” the includes basic if, gotoIf, etc. calls.

 

Selenium IDE Extensions Hacking

I use Selenium IDE as a tool for testing the Store Locator Plus WordPress plugin.   It is a great tool for automating browser interactions and sussing out basic problems.  With the launch of the My Store Locator Plus SaaS service we need to build more complex tests for multiple accounts and services.    Thankfully Selenium IDE not only has a myriad of plugins but makes it easy to create your own.

While “officially sanctioned” Selenium Plugins are true Firefox browser plugins you can deploy your own commands written using the Selenium IDE objects and interfaces.   These can be included by listing your local JavaScript file in the core extensions file list.

Now for the bad news.   The documentation for building extensions is horrid, outdated, and typically non-existent.   Which is where this article comes in.   This is my personal notes on how to hackify your own Selenium IDE extensions.

You can create your Selenium Extension as any other JavaScript program.    When you’ve created it you can add it via the Selenium Options menu drop down under core menu extensions.

Selenium.prototype.do<command>

This is the JavaScript command that you use to define a new Selenium IDE command.   If needs to point to a function that is passed 2 parameters.  The first parameter is what the user put in the target field.   The second parameter is what the user put in the value field.

This would add a new command you invoke with MyCommand in Selenium IDE.

The command definition in the JavaScript prototype must start with an uppercase.

The command in the SCRIPT FILE will start with a lowercase.

this.continueFromRow( <int> )

Run the Selenium IDE command in the current test from the specified entry.

throw new Error( <string> )

Generate an error that will be logged by Selenium.

Selenium Variables – storedVars

Any store commands will place the variables in a named index in storedVars[<name>] where name is the value you assigned during the store.

testCase

The Selenium object that contains details about the current test being run.

testCase.debugContext.debugIndex

Get the current debug index.    In theory the currently executing row.

testCase.commands

Contains the array of all the commands for the current test case.

testCase.commands[<int>].type

Type of command.    At least one valid type is ‘command‘.

testCase.commands[<int>].command

The command the user entered, first entry in a Selenium row.

 

More Info

You can find some of my hacks based on the Sideflow Selenium plugin on Go With The Flow over at Bitbucket.

%d bloggers like this: