Selenium Web App Test: Are My Web Parts There?

As per my previous articles, I am building new test cases using Selenium as a replacement for my older Selenium IDE tests.  Selenium IDE is no longer supported by Firefox 55+ and the next generation doesn’t appear as though it will be ready any time soon.  If you are going to continue testing your web apps with Selenium, now is the time to learn Webdriver.

This is a continuation of the Selenium automated web testing series.   You can  use Selenium to automate testing of your web apps for quality assurance or to automate routine data entry.    It is a great way to write “code bots” to make them do all the repetitive tasks for your app.

My Environment

I’ve setup my MacOS box to run automated testing with Safari which is driven by a Node.js app.     Since automation is built into Safari since version 10 this makes the simpler than having to find/build/install browser drivers for things like Chrome, Firefox, or IE however once you do so the remaining script coding should be the same.

You’ll want to find my prior article on setting up the environment to ensure you have Node.js installed with the relevant Selenium drivers.     In my test cases I have a directory with a webdriver_tests sudirectory where my test scripts live and a node_module subdirectory where node has installed all the libraries for running the node app.   I also run my testing through phpStorm as I prefer the smart completion and live syntax checking when writing code.   You can run your tests from any JavaScript aware IDE or from the command line using the node command to run your script.

Test Objective

In this test I want to open my target website URL and check to see critical elements are present.    I will also interact with one of the elements and to prepare further testing where I will later build an automated new user sign up test module.

In this case our MySLP service has 4 different offerings that behind-the-scenes have been assigned the green, yellow, blue, and orange service levels.    We will test that all 4 offerings have their respective divs renedered on the home page with a primary button the user can click to sign up.    Later tests we can add things like “make sure this text label is present” but we’ll leave that out for now.

MySLP Home Page 2017-09-24_12-47-40.png

In case you are wondering, abstrating by colors allows us to later change the name of the services without having to change lots of back end code or testing scripts.

The Script

As before I will start with the entire script so you can see high-level view then I’ll drill down into various components and shares notes on what they do.     You may also want to keep tabs on this as I’ve been finding that despite the strength of the testing tool itself, the Selenium documentation as a whole is horrific.   You almost need a phd in computer science just to figure out how to use it effectively.

Side Note: One of the many “good luck figuring this out” documentation examples, finding what the options are on the .forBrowser() call.    It simply says “The name of the target browser; common defaults are available on the webdriver.Browser enum” and leaves you to figure it out.    The Browser enum lives in the capabilities.js module of the selenium-webdriver lib of node.    

forBrowser enum.png

Here is the enum at the time of writing this article:

The Setup and Execution

The first part of the script and “launch” part of the script have been covered in previous articles.  We setup the configuration and use the seleniumDrivers.init() to launch it when everything is ready.  I won’t get into the details here.

The Home Content Test

The first test we run is to check the home page content is there.    It opens the web page with the Selenium Driver get() method and strings along a series of FindElement() methods to test things are as we expect them.

I am using the By.xpath() method to locate elements by their xpath.  This allows me to string along HTML dependencies like “find the div for the orange class that is INSIDE the div with the price-tables class”.  This is helpful when HTML elements that need to be tested do not have unique classes or IDs.

What is with all the .then() calls?

You’ll want to read a about the Promise construct in JavaScript.  It allows us to launch commands and let them finish “whenever they are done”.  Like finding a page element.       .then() is a construct that says “when you are done go do this next”.     By stringing together a bunch of .then() methods we can basically turn a bunch of “find this then that” commands that finish at random times into a “do this, then do that, then do that” ordered series that behaves more like a sequential (synchronous) app.

Side note: Many things in JavaScript run asynchronously.  That means stuff can happen in random order. Think of it like sending 10 people to run off and buy stuff for a party, they all leave at the same time but you have no clue who is coming back first; the guy with the burgers, the gal with the buns, or the dude with the chips.    

Why make it synchronous?

For the findElement() testing I don’t really need to test they are all there in sequence.  In the grander scheme of things I do want to know if ALL the tests in a group passed or failed.   In order to do this you’ll see that I’m seeting a boolean in the my_utils object to track if any of the tests failed.     If I were to test this after a series of independent findElement() calls it is 99.9% likely that none (and certainly not all) of the findElement() tests will have finished their work before the JavaScript engine evaluates that boolean.  Setting up a list of tests to run they testing a boolean is FAR faster (some 1,000+ times) than scanning an HTML DOM.

If you don’t believe me, comment out the last .then() test with my_utils.report_on_all_tests( ‘Home Content’ ) and move my_utils.report_on_all_tests( ‘Home Content’ ) outside the closing driver.get() call.   It will run before any of the tests begin to execute.

The Signup Test

The first test, myslp_home_content(), checks our key elements are there.    The second test looks for the ‘orange button’ and clicks it.  For now the test is simplified and only clicks the button.  It will be extended for element checking and interaction to automate the sign up process later.

Here I am showing how to interact with an element by using the .then() processor to test for 2 cases.  This is slightly different than the .then() construct used to string together tests in sequence.   Here I am using the default parameters of .then() which is a function to run when the promise has been fulfilled (success) and when it has not (failed).

If you break it down we find an element by using its xpath.   We then call the anonymous function which is passed the object we were looking for and execute the click() command on that object.    If it fails we call the my_utils.log_missing_item method which logs an error and sets a flag we can later test to report back if the entire test suite worked with the my_utils.report_all_tests() method.

In the initial code example above you may notice I intentionally left a typo in the code looking for class “pt-buttonx” instead of “pt-button”.  This is to show the output of a failed test.    Fixing this will execute the button click and the test will carry on.

Selenium Simple Web Element Testing.png

Side note:  One last hint before we wrap up this example, you can learn a bit more about how Selenium Webdriver works with JavaScript by finding the “code easter egg”.  Hidden in the selenium-webdriver node library is an example directory.   In it you’ll find, not surprisingly, examples of some basic tests.     You may even learn some new tricks in there.  It is how I learned to easily string together tests and make them behave in sequence as per my example here.

Selnium Examples .png

A Simple Selenium Script Example Using JavaScript and Safari

In my previous articles I showed how to enable Safari for web automation (hint: developer menu Allow Remote Automation) and how to get your Node.js environment setup on MacOS to begin writing automated web application scripts.    This example is a very basic script to show how to perform a basic web page load (the MySLP site in this case) with some parameter passing that can be expanded in later examples.

Enironment Preparation

My test environment is running on MacOS Safari using Safari 11.   I use phpStorm for WordPress plugin and theme development, so we’ll continue with that here.  phpStorm has excellent JavaScript support and is fairly intelligent on detecting Node.js and Grunt scripts and being able to launch those applets direct from the IDE.

Since this example is short I’ll start with the complete JavaScript app that will launch Safari and open my test site.    It requires Node.js to be installed along with the Selenium Webdriver and Selenium Drivers modules; the setup was covered in a prior article.

The Code

The Explanation

App configuration.   I’ll use this in later example to extend the list of parameters that apply to my specific set of tests so I can try to follow the Don’t Repeat Yourself (DRY) principle as much as possible.

Like the App configuration variable, I also setup a browser configuration variable.   This is passed to seleniumDrivers init() method, so we need to make sure the parameter keys and values fit their accepted values. The current options include:

  • browserName: (‘chrome’ | ‘firefox’ | ‘internet explorer’ | ‘safari’) specify browser name
  • silent: (true | false) set to false for verbose output (default: true)
  • download: (true | false) disable driver download (default: true)
  • deactivate: (true | false) deactivate library (useful when running with custom browser capabilities where driver is provided, eg: for sauceLabs, or browserStack) (default: false)

The my_driver variable is set “higher up the chain” so it is at the top of our application scope.  This allows the app to not pass around a variable holding the Selenium driver object since it will be used nearly everwhere.   This is a Selenium test script after all.

Next is our test case.  Later we will add more test cases to the mix but for a now a simple “load this web page” test is provided.  The sleep is not necessary but I like to have the browser window hang around for a bit so I know something happened.

Last thing we do is get the Selenium “Test Bot” going by calling the Selenium driver’s init() method and following on with the then method of JavaScript, an implementation of promise constructs.    You don’t need to fully understand promise constructs to make your automated scripts but if you’ve not seen the .then() method before that is the root of where it comes from.

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:

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.

 

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.

%d bloggers like this: