Selenium on JavaScript : New WordPress Site Config Script

This article continues the journey into learning Selenium on JavaScript (SoJa).  It builds on the lessons of the previous articles and adds some common library functions to make our tests more readable and starts to employ the Don’t Repeat Yourself (DRY) concept.

In this case we are going to build a common library module that we will re-use in future scripts. It will load our configuration and environment.  It will also export some methods that make our test script code a lot easier to read. The configuration and environment setup functionality is covered in prior articles so we’ll leave that out of this discussion.

What This Script Does

This script automates the very first steps of setting up a WordPress site after the software has been installed.  We use this on our Store Locator Plus development systems after doing a “fresh install” of WordPress so our dev team doesn’t have to keep typing in the same 5 things over-and-over every week when we spin up a new release to be tested “from scratch”.

The script will select our language (English, the default) wait for the next screen to load and then type in the title of the site, our username, email, check some boxes and click the install button.      The other elements of setting up our site such as the database name, etc. has been completed as part of our base dev box setup so you won’t see that here.   You can easily extend this test to cover the other WordPress install forms.

The Scripts

We’ll leave out the configuration and environment scripts as they have been covered a few times in other articles. Our main test script and the common lib are below.

Main Test

Common Lib

Discussion

The Main Script

The main script itself is fairly self-explanatory thanks to our new helper methods in our common library.   The main script load the common library and assigns it to the local test variable.    That library has setup logging for us, the Selenium Webdriver, and some “nice to have” functions that we’ll get into later.

We output that our activate site test has started to the log window, load up our WordPress site URL with the get command and when the page is loaded click the continue button, wait for various form elements like the blog title to appear and fill it in.  We also click some things as well.

Simple, right?

The Magic, aka Common Library

The real magic happens in the common library.   It employs the tricks we’ve learned in previous lessons to build reusable code and make our main scripts easier to read.   It uses the standard NodeJS module export technique to define names for our methods as well as their functionality.

There are some things that are common for most web tests, type a value into a form field, click a button on a form.    We’ve added some “make this stuff behave” logic using the Selenium Promise-based style to ensure things are more stable than randomly typing stuff and hoping they are on our page.

Let’s review our new methods in more detail.

When ID Exists Type

This method is used to look for an input on a page by its HTML ID and type something into the form.    We could just “jam some key strokes” into a field and assume it is on our page.  However we often are waiting for dynamic JavaScript elements to appear on modern websites.   We also have pre-filled input fields like password suggestions to deal with.   Things are not as simple as they seem.

In this method we first tell Selenium to wait for the element to appear on our page.   Our wait loop will give the web page 6 seconds to present the element or throw an error.   That is the driver.wait() part with until.elementLocated() within.

If the element is located run the success function defined within the then() follow-on method attached to the driver.wait() call.

Notice we assign findElement() to var el.   Why?  We want to make sure the input field is EMPTY when we start.    We do that by click on the input field first, then clearing it out, then ending our value in as keystrokes.   This addresses any pre-filled fields like the WordPress password suggestion as well as any add ons your browser may be employing that auto-fill forms (if you did not disable them, which you should).

The when_id_exists_click() is the same idea as the input method but in this case we have less work to do since we are clicking a button and don’t have to clear the element out.   No need to explain further.

When Name Visible Click

This method is a bit different than the ID Exists Click or ID Exists Type methods.   While the concept is the same the difference is in the execution.    The Selenium until.elementIsVisible() method has been constructed differently than the other methods in the library.    This is one of those language inconsistencies, and every language has them, that drives developer crazy.

 

Many of the other until.blah() methods take a LOCATOR as the parameter, in other words the By.id or By.name method where the locator is what is being processed.   elementIsVisible() takes the actual web element itself.    It’s a gotcha that has tripped up more than a couple of Selenium coders.   It is why our Visible Click method is more complex.

To not repeat ourselves and ensure consistency we setup our locator in the by variable to locate an element by name.    We let the driver wait until our element is found by name on the page, using the standard 6 second delay.   Once we know the element is on the page we assign it to the el variable and then do another wait until the element is visible.

Remember, just because an element is within the web page (part of the DOM structure) it does not mean it is visible.   It is also important to know that many Selenium functions will NOT let you interact with an element that is not visible.

 

Selenium on JavaScript : User List Test

I wasn’t quite sure what to name this article.  The Selenium on JS example here can be used to scan any table to ensure every entry on a list of strings exists; should I name it Test Web Page Has All Your Important Data?   The test also uses a separate NodeJS module to configure that list of important string, in my case user account names; Using NodeJS modules to configure repetitive data lists?  It also employes the Promise construct inherent in Selenium on NodeJS so maybe The Right Way To Wait Before Testing Data?

Instead I chose “User List Test” because that is what my test case does.

In this example I want to test a list of user accounts I deem “critical test users” to ensure that none of the user accounts have gone missing after upgrading our staging or production server.  Each user account was selected because it stress tests specific parts of our MySLP SaaS application.    That means I also want to make sure the user accounts are still active before delving deeper into the test suite.

MySLP Dashboard 2017-09-26_22-06-56.png

This example is a bit more complex than the prior Selenium articles written this week and builds on an excellent resource I found online.  It talks about the Promise model and how to properly wait for things to exist versus using the “hack” driver.sleep() command to wait for stuff to appear.

Let’s get into it.    Here is a test that loops through a list of users and checks to make sure they all appear in an HTML table that is the list of users.

The App

Before I get into the code I want to show you the web app interface.  It is a modified variant of the WordPress User table.   This is where we will test that the table cells have the users I’m looking for.

MySLP Customer Blur Partial 2017-09-26_20-56-09.png

The Source

The primary test case.

The customer tests module.

The config module.

The customer list module.

The sample credentials module.

The Walk Through

I’ve already written articles on how to setup Selenium on JavaScript to drive Safari (SoJa / Safari).   It requires Safari 10+ with automation enabled plus NodeJS with the Selenium Driver and Webdriver modules installed.  I run on a MacOS from within phpStorm. You’ll find my examples and screen shots based on that environment.

The Setup

The setup has been covered in prior articles as well.  You can review those for details.  The short version of this step:

  • load the config module using the NodeJS require() method
  • add some aliases for the web driver modules we use most often
  • configure logging via the logging module
  • start up the webdriver to get our NodeJS environment talking to Safari and set the browser window size and position

Starting The Test

We start by logging in to the dashboard.   A standard driver.get() with our dashboard URL is going to kick things off.   We string together our Promise fulfillments via a series of then() methods.   In case you missed the prior articles the then() executes when the method they are attached to has “fulfilled a promise” (is done by failure or success).   Basically we are saying “go to this URL and when the page loads then do some other things.

Our other things?  Find the email element then type in our admin username.   Find the password element and type that in.  Then submit the form.

If you are wondering where the values are being set, that is coming from the config module that reads staging-example.js or production-example.js depending on what I’ve set NODE_ENV to when starting up my node script.    This way I can have separate credentials for live or production servers and only need to change my environment variable to switch not only which URL I’m testing against but my username/password to login.   You can read more about this and why the config file loads -real.js instead of -example.js in the configuration article.

Timing The After Login Stuff

One of the first things I started to realize is a simple string of .then commands was not going to work.  Selenium runs the browser command FAR faster than the server responds or a typical user types things in.   This means building commands based on waiting for things to appear in the browser.      As mentioned previously, the sleep() trick is a horrible way to manage this.

If we boil down the test case itself to its essence we get something like this:

If you look at the source that last submit button and click it code looks different.    Let’s get into that for a moment, but first here is that code snippet:

 

The first thing to know is that then() is a function that takes 2 parameters.  The first parameter is a function to run when the prior thing that then is attached to was succesful.   The second parameter is a function to run when the prior thing failed; we’ll get to that later.   In this case for the sake of simplicity we ignore the failure.   Another important tidbit here is to know that both the success and failure functions get passed a parameter.    That parameter is the result of the promise that the .then() is attached to; our driver.get() in this case.  We’ll ignore what that parameter actually is for now.

On the first 2 then calls the format looks a little different. What is that underscore and pointer thing?   It is syntactic sugar that keeps the code readable with less braces and brackets.   .then( _ => blah() ) is basically saying “assign the paramter we get for the success function to _ so we can get to it later and then run the blah() function with it.   Since we are going to throw away the parameter we use _ because it is not visually distracting.   So the first 2 then() calls say “ignore the parameter driver.get() spit out and just go find the HTML element with the ID email , or password, and type some text in there.

The third call after we find the email and password fields says “when you’ve succesfully finished those two things go run the following function”.    We first find the submit button element using the css type=submit locator and assign that to the previous_el variable.   Why we do that will be clear in the next task.    Since previous_el is now assigned to our submit button we can use that shorthand to click it.

Side Note: In general it is always faster to assign an element that a JavaScript library like our Selenium Driver here , or jQuery lookups, to a variable so you don’t keep scanning the DOM to locate the thing you just located.  

So we’re done with logging in.    We go to a specific site URL, when it finishes loading the page we find the email and password inputs and type in our login info, then find the submit button (that we saved ot our previous_el variable) and click it.

Onward and upward.

We Logged In, Now What?

Moving down a line we see another then() attached to our prior then that clicked the submit button.   Again we are using a more explicit form of the success function.   Why?  Because we want to log something to the console to tell us the login worked.   Not critical, but it is nice to know your tests are doing what you expect even when they are working.

You’ll also notice we are now using a new until.stalenessOf() method in this function.    This is where our previous_el comes in.    Selenium WebDriver has a timer-based method built in that will loop around until a specific condition is met or until the specified timeout has been reached.  In this case we are going to wait until our submit button from the previous page has been marked “stale” for up to 6 seconds (config.setting.standard_wait = 6000 in our config file).

Now you know why we recorded the WebElement for the submit button in our previous then test when we found it.     Using stalenessOf( element from a prior page ) is a great way to ensure you’ve left the previous page.    It does not require that you know anything about the next page you are about to load.     We’ll worry about that in a moment.

Loading and Testing The Customer List

Now that we’ve performed the login and waited to ensure we got past the login page we can now continue with our next step in the original driver.get().then() sequence.   Here we are going to go a little deeper into the then() nesting by “latching on” to the a new driver.wait() call.

The essense of the code:

OK, that is a LOT simpler than the code we have written.   Let’s dig a little deeper and expand on the “run our list testing function” statement to this:

Here it is important to note that we do not just string along another then() on our original driver.get() series.    It is important to specifically wait not only for the new customer list page URL to load but also to ensure that page has content.    For this example we are going to assume that if the header tag “MySLP Customers” is rendered that our customer table is complete.  In reality we will check for a specific HTML marker that is output only after the customer list is fully rendered.

The code is loading the URL then calling a function to do a lot more stuff.   That function says “don’t do anything until we see that MySLP Customers header”.     We use the standard Promise .then() method once we do see that  header to start our processing.  The firs thing we do is output something to the log to tell use the customer list page is open and ready for testing.

Testing The List

Let’s start with the code that does the test plus our couple of actual code lines outlined above.

What is var customers = require() doing?

It is loading our active_customers module.  That module sets up a JavaScript object that is assigned to customers that we can later reference.  This lets us later update the active_customer.js module in our config directory so we don’t have to hack up the main test code to add users.   It keeps our future edits somewhat isolated and will allow us ot later use other non-code-editor tools to allow our QA team to easily load a “check this user” list.    In this case the active customers we want to test will end up in the customers.active[] array.  It is an array of objects with a username key that holds the usernames we want to test.

Next Up: test_customer…

We assign our customer_tests library of customer test functions to the test_customer constant (we could have used a var just as easily) which gives us access to all our re-usable test methods.    The library, outside of loading up a bunch of our shorthand aliases and enabling logging for our module scope, provides two simple methods.   An on_list() method and a reset_tested() method.    reset_tested() only re-sets our test counter to 1, fairly simple and not really doing much in this single test.

on_list() is passed the driver, config, and customer name we are looking for.  Driver and config are only so we dont’ have to re-invoke another copy of each within every submodule.   Customer is the string that will change every time our loop moves to the next customer.

The guts of the customer_tests.js module:

Within the function we do a simple “find a TD tag that has text containing the customer string we are seeking”.   You can see that we are using the more advanced form of then() where we not only have a success and failure function but also name our parameters.    If the thing we are looking for is found within 6 seconds we print out a found message with the customer string preceding by a count of how many we’ve found so far.    If the TD with the customer cannot be located on the page we log a could not found message instead.

When It Runs

Our NodeJS app fires up a Safari window, goes to the production or staging site, logs in our admin user, goes to the customer list page, then looks for a dozen-or-so key customer accounts to ensure they are still on our customer list.   We wait 10 seconds then close the test Safari browser.

This is what we see in our phpStorm NodeJS test execution window when the test runs:

phpStorm NodeJS Customer List Test Output 2017-09-26_22-04-47.png

JavaScript Selenium Newb Cheat Sheet

Finding documentation on Selenium is hard enough for the “main languages” of Java or Python.    There are lots of examples of how to do things there in those languages but very little for the JavaScript libraries.   While you can translate most of the Java example to JavaScript there are some differences.    You’ll also find that there is a LOT of outdated information.    To make things more interesting you’ll also find that the older the example the more likely the code samples include browser or environment-specific methods.

Writing Selenium tests in JavaScript for Safari has very little documentation.   I decided to create my own cheat-sheet for future reference when I forget how I did this stuff.

Loading The “Nice To Have” Shorthands

It took me a little longer than it should have to figure out how to load up the shorthand notation for the webdriver libs so the example code provided would run outside the webdriver directory.

This neat trick now allows the script to refence things like By.id() or until.titleIs() so that we can do things like click a button and then wait until the page title is “New Page Title” to pace our test scripts.

Running Commands In Sequence

For many tests it is important to run the steps so that A finished before running B.    Opening a web page, clicking a button, then typing information into a form that was previously hidden for example.

The trick?  String together .then() methods.    I like to do this on a page get but it can also be done after nearly any webDriver command.    The simplified “on success” format works well in most cases to create a simple syntax:

Setting The Safari Window Size

The default window size for Safari while running in “Enable Remote Automation” mode is based on 1985 screen sizes.   I’ve not checked but it looks to be the size of 132 column x 80 row terminal or possibly the new-fangled 640×480 high resolution screens that came out when AOL first  came online.

I prefer to see more than a postage-stamp size view of my website when running testing.    You can set the window size after the driver is connected with the setSize() method.

Setting The Window Placement

For some reason Safari seems to like to throw the testing window near the bottom of the screen so it runs off the edge of the monitor.  If you like watching the tests run, especially during test development, this kind of sucks.   Force the position near the top-left of the screen with the setPosition() method.

Logging Things

You can console.log() the crap out of your test cases.    However most decent test suites have a logging utility.   Selenium does too and it is in webdriver.logging.     If you used the shorthand aliases above you can turn on the console handler then log every single detail to the console.

You’ll probably want to crank that down a few notches from 11, so you need to know the level names.   They are in the logging.js lib:

At first glance, Level.DEBUG seems to be a good starting point for things but we’ll see how that goes as more complex tests are developed.

To log stuff at an “Info” level:

Related

How to write reliable browser tests using Selenium and Node.js is a cool article on writing functional Selenium tests with Node.js.

Video Time

A short video on how this stuff looks on MacOS with Safari, phpStorm, and Selenium running in JavaScript with NodeJS.

Selenium : Hiding Login Credentials In An Automated Test

With most web automation and testing tasks you are going to want to login to a secure system.   You are also going to want to keep your login credentials out of the testing code repository.    For my test suite that is being rewritten with Selenium for the MySLP service I am employing a basic JavaScript module methodology along with some git ignore rules to keep my credentials private.

In this example I have also added the ability to switch between production and staging environments and load the proper credentials for each.   This can be done by setting an environment variable on my operating system before running the test.   In my case I can do this in my phpStorm execution instructions or from the MacOS command line if I choose to run my JavaScript app from there.

The Premise

In my setup I have a config subdirectory which is in the public repository that configures the variables I need that are specific to my environment.  This holds things like my URLS to get to the apps , a list of test users, and even the admin login credentials sourced from another place.

The other place for admin credentials is the env subdirectory.  The public repo has a *-example.js.  The mirror of these are the *-real.js set of files which are kept out of the repo.  It is these -real.js files that are read during the program execution.

Any of the devs on the team can copy production-example.js to production-real.js and replace the username and password with their own.   They can do the same for staging.

By default the testing is run against staging.   The QA team can switch environments to test the production servers by setting the environment variable NODE_ENV before executing the tests.  Setting it to production or staging accordingly.

The Code

The test script.

 

The configuration lib.

 

The example environment lib.

The ignore file

 

How It Works

Loading The Environment

Assigning the require of the myslp_config lib to a local myslp_config variable makes it globally accessible within the running test.   All of the exported properties from the module become properties of the myslp_config variable so things like myslp_config.url_for points to the URLs in the myslp_config file.    We can then reference myslp_config.url_for.dashboard to get to the dashboard interface or myslp_config.url_for.my to get to the main marketing interface (short for the MySLP site or to be more specific the MySLP marketing site).

 

The myslp_config file itself will look at the NODE_ENV environment variable if set and internally set the environment property to ‘staging’ or ‘production’.   The default, if the NODE_ENV variable is not set is ‘staging’.   We then use this to further refine our url_for variable to set the proper server paths.

 

Define The Test Actions

The myslp_dash_login function runs through the automated web driver, in my case for Safari, and runs the steps to type in the admin username and password and then click submit.  Using the string of .then() methods from the Promise JavaScript construct we can ensure the username and password are typed before clicking submit.

 

Execute The Test

Using the standard Selenium driver init and webDriver commands we launch the test and when done hold the window open for 10 seconds before closing it.

Details about setting up Selenium with NodeJS and automating Safari are in my other articles.

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

%d bloggers like this: