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

var test = require( '../lib/common' );

test.log.info( 'Activate Site' );

test.driver.get( test.config.url_for.wp )
    .then( test.when_id_exists_click( 'language-continue' ) )
    .then( test.when_id_exists_type( 'weblog_title' , 'SLP Dev' ) )
    .then( test.when_id_exists_type( 'user_login' , 'admin' ) )
    .then( test.when_id_exists_type( 'pass1-text' , 'password' ) )
    .then( test.when_name_visible_click( 'pw_weak') )
    .then( test.when_id_exists_type( 'admin_email' , 'lance@lance.abc' ) )
    .then( test.when_id_exists_click( 'submit' ) )
    .then( test.driver.sleep( test.config.setting.standard_wait ) )
    ;

Common Lib

var webdriver = require('selenium-webdriver');

var config = require( '../config/config' );

// Setup Logging
//
var log = webdriver.logging.getLogger('webdriver.http');
webdriver.logging.installConsoleHandler();
log.setLevel( webdriver.logging.Level.INFO );

// Setup the Web Driver
//
var driver = new webdriver.Builder().forBrowser('safari').build();
driver.manage().window().setSize( 1200 , 800 );
driver.manage().window().setPosition( 100 , 100 );

// Expose This
//
module.exports = {
    config: config,
    driver: driver,
    log: log,

    // Selenium Aliases
    By: webdriver.By,
    until: webdriver.until,

    // Common Tests

    /**
     * Wait up to 6 seconds for an input with matching a matching ID to show up.
     * Then types something into it.
     *
     * @param string id     the ID for an input
     * @param string value  what to type in the field
     */
    when_id_exists_type : function( id, value ) {
        driver.wait( webdriver.until.elementLocated( webdriver.By.id( id ) ) , config.setting.standard_wait )
            .then( function() {
                var el = driver.findElement( webdriver.By.id( id ) );
                el.click();
                el.clear();
                el.sendKeys( value );
            });
    },

    /**
     * Wait up to 6 seconds for an input with matching a matching ID to show up.
     * Then clicks it.
     *
     * @param string id     the ID for an input
     */
    when_id_exists_click : function( id ) {
        driver.wait( webdriver.until.elementLocated( webdriver.By.id( id ) ) , config.setting.standard_wait )
            .then( driver.findElement( webdriver.By.id( id ) ).click() );
    },

    /**
     * Wait up to 6 seconds for an input with matching a matching NAME to be visible.
     * Then clicks it.
     *
     * @param string name     the name for an input
     */
    when_name_visible_click : function( name ) {
        var by = webdriver.By.name( name );
        driver.wait( webdriver.until.elementLocated( by ) , config.setting.standard_wait );
        var el = driver.findElement( by );
        driver.wait( webdriver.until.elementIsVisible( el ) , config.setting.standard_wait );
        el.click();
    }
};

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.

test.driver.get( test.config.url_for.wp )
    .then( test.when_id_exists_click( 'language-continue' ) )
    .then( test.when_id_exists_type( 'weblog_title' , 'SLP Dev' ) )
    .then( test.when_id_exists_type( 'user_login' , 'admin' ) )
    .then( test.when_id_exists_type( 'pass1-text' , 'password' ) )
    .then( test.when_name_visible_click( 'pw_weak') )
    .then( test.when_id_exists_type( 'admin_email' , 'lance@lance.bio' ) )
    .then( test.when_id_exists_click( 'submit' ) )
    .then( test.driver.sleep( test.config.setting.standard_wait ) )
    ;

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.

/**
 * Wait up to 6 seconds for an input with matching a matching ID to show up.
 * Then types something into it.
 *
 * @param string id     the ID for an input
 * @param string value  what to type in the field
 */
when_id_exists_type : function( id, value ) {
    driver.wait( webdriver.until.elementLocated( webdriver.By.id( id ) ) , config.setting.standard_wait )
        .then( function() {
            var el = driver.findElement( webdriver.By.id( id ) );
            el.click();
            el.clear();
            el.sendKeys( value );
        });
},

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.

    /**
     * Wait up to 6 seconds for an input with matching a matching NAME to be visible.
     * Then clicks it.
     *
     * @param string name     the name for an input
     */
    when_name_visible_click : function( name ) {
        var by = webdriver.By.name( name );
        driver.wait( webdriver.until.elementLocated( by ) , config.setting.standard_wait );
        var el = driver.findElement( by );
        driver.wait( webdriver.until.elementIsVisible( el ) , config.setting.standard_wait );
        el.click();
    }

 

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.

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.