Idea pool: your input, our challenge

For three years, our development of Ghostlab has been accompanied by our “Idea Pool” – an interactive feedback forum hosted on Uservoice that allowed everyone to post their ideas for future Ghostlab developments. By upvoting, ideas came on our radar and we’ve realized quite some of them.

We are moving the Idea Pool to our own infrastructure, and are looking to improve it’s features. This is why currently, we have paused the idea pool. But do not worry: we are working on getting it back in greater shape as soon as possible!

During the transition, we thought it would be cool to briefly think about the impact of the Idea Pool. First, let’s have a look at all the ideas we have implemented thanks to our users suggesting and voting them up. Note that as long as ideas are good, they don’t necessarily need a big number of votes…

Completed FeaturesVotesContributing User
Windows/PC Version314Anonymous
Synchronize JS event clicks47Luis Martins
Mouse to touch synchronization34darren
Add Master/Slave mode18Simon Bächler
Soft CSS refreshes13Luis Martins
Include VM name in browser list7Alan
Support SASS compilation6Asif Amin
Allow remote IE inspection5Yann
Add full screen mode2Anonymous
Add custom quick fill shortcuts2Anonymous

Of course, we haven’t been able to implement all the user ideas. But there are some that we are still working on. Currently, we’re actively developing a next major version, and the table below indicates some of the stuff we’re working on to bring out soon….

Feature we’re working onVotesContributing User
Ubuntu/Debian Version300Jeremy “Jay” Zahner
Taking screenshots of device’s viewports in Ghostlab167Guillem Mazarico
Add HTTPS support to Ghostlab server32Maarten Manders

In essence, we want to say a big *thank you* to all of you who have contributed to our idea pool. It has inspired our development, and we are looking forward to continue the conversation to improve our software. The new idea pool will be online soon – and if, in the meantime, you have a brilliant idea that can’t wait, let us know via our contact form!

Automated web shop testing with WebdriverIO

We’ve recently migrated our website to a new environment. While we were at it, we thought we could complement our simple uptime-Testing with some automated web shop testing, so that we can sleep at night knowing our site is OK. We’ve implemented some tests with WebdriverIO and thought we’d share the procedure, in case you’re looking for a quick guide. The entire test script is available for download here.

Our test scenario

When somebody buys Ghostlab from our website, they have to do several things: add Ghostlab to the cart, go to the cart page, click the checkout button, provide all required information in the checkout form, pay for the order, and finally they will expect to receive their license information via E-mail. And that’s exactly the procedure we want to pack into an automated test run.

  • Automated test steps
  • Go to Ghostlab landing page
  • Click “Buy” link to get to WooCommerce product page
  • Add Ghostlab to the cart
  • Click on “View cart” to see the cart contents
  • Proceed to checkout
  • Complete checkout form
  • Pay – we’ll do that with a trick
  • On order completion, receive E-mail with license key

 

Set up WebdriverIO project

Before you can get started, you have to set up a WebdriverIO project. It’s really easy, just follow the WebdriverIO guide. What you’re doing is basically:

  • set up your project directory
  • download selenium server and driver – be sure to start the server after download.
  • install webbdriverio via npm

Once you’re set up, let’s directly create a test runner for our project. Just follow the instructions for how to complete the testrunner setup dialog (WebdriverIO guide, “Let’s get serious”). Note that as of today (January 13 2017), there is one additional setup step in the dialog that is not described in the guide – I just selected the default value.

I’m assuming that you now have your test/specs directory. Now create a JavaScript file there that lets you write your test. You should end up with a setup comparable to the one in the image below.

# To run your test(s), simply execute the 
# following from the command line in the 
# project directory. 
./node_modules/.bin/wdio wdio.conf.js
Why WebdriverIO?
There are many tools to create automated tests. When we set up our tests, we were looking for something that was powerful, well-documented, easy to setup, and locally available (i.e. no SaaS). We gave several tools a quick spin, and WebdriverIO convinced us because the time to your first hello world is really short, and it is very well documented.

Now on to the actual test steps…we will only be looking at a small set of features of the Webdriver API – for the full reference, see WebdriverIO API Docs.

1: Navigate to landing page, follow to WooCommerce product page, add Ghostlab to cart

We want to start from our product landing page, so we need to point the browser to that location.

browser.url('https://www.vanamco.com/ghostlab');

On this page, there is a Buy button. We’d expect visitors who want to buy Ghostlab to click it and get to the WooCommerce product page. After clicking the button, we need to wait for the WooCommerce product page to load before we can perform our further actions. Therefore, we’re waiting for an element to be present that was not on the landing page, but is on the WooCommerce product page, namely the “Add to cart” button.

browser.click('=Buy');
browser.waitForExist('.single_add_to_cart_button.button', 5000);

Both the click and waitForExist function expect a Selector as first argument. A selector is used to specify the element in question – the one that we want to click on, or the one we’re waiting for to be present.

We’re specifying a timeout of 5000 milliseconds for waitForExist – in case the page does not load within this time, the test will fail. So make sure you specify a timeout long enough for your page to load.

Selectors
There’s a wide range of possible selectors, we’ve found the link text (‘=Buy’, on anchor elements) and CSS selectors to be easy to handle. Of course, which selectors best suit you depends on the content of your page. As soon as you start using selectors, be aware that changes in your page might break your test. For example, if we were to change the text of the buy button to “Get one now”, the test would no longer work. See the Developer Guide for a full reference of available selectors.

As soon as the WooCommerce product page has loaded, there will be an element to add the product to the cart, and our test run will continue. Next we actually want to add 1x Ghostlab to the cart by pressing the button. When this is done, the page will show a confirmation message that we’ve added it to the cart – and there will be a link that takes us to our shopping cart. So we wait for the confirmation to appear, then follow the cart link.

browser.click('.single_add_to_cart_button.button');
browser.waitForExist('.woocommerce-message > a', 5000);
browser.click('.woocommerce-message > a');

2: Enter coupon code so we don’t have to provide payment information, continue to checkout

As soon as the cart is displayed, we enter a coupon code we’ve prepared for the test that gives us 100% off. This means that our order totals 0$ and we can perform the checkout without entering payment information.  We detect whether the cart has loaded by checking for the presence of the coupon code field. We then wait for the confirmation message WooCommerce writes to the page to show that the code was applied, and verify that it was successfully applied by examining the content of the message.

browser.waitForExist('input[name="coupon_code"]');
browser.setValue('input[name="coupon_code"]', config.couponCode);
browser.click('input[name="apply_coupon"]');
browser.waitForExist('.woocommerce-message', 5000);
var msg = browser.getText('.woocommerce-message').trim();
assert(msg === 'Coupon code applied successfully.', 'Did not see coupon code applied successfully message');

Once the coupon code is applied, we go to the checkout form by clicking the proceed to checkout link. We detect the load of the checkout form by checking for the presence of the place order button – which we will use in the end to actually place our order.

browser.click('.wc-proceed-to-checkout > a');
browser.waitForExist('input[name="woocommerce_checkout_place_order"]');

3: Complete checkout form

Now we can fill out the checkout form. We’ve stored the field selectors and the associated values in a separate array over which we can iterate – of course, you could also solve this differently. One field we have to treat specially is the country selector – this is not a simple select, but a JavaScript-enhanced select component where you can search for values, then select them by clicking them. We are unable to simply set the value as with a regular input field, so we need to perform the click-search-select pattern that a real user would use.

 

config.checkoutForm = [
 {s: 'input[name="billing_first_name"]', v: 'Automatic'} /*, ... */
];

for (var i = 0; i < config.checkoutForm.length; i++) {
    var item = config.checkoutForm[i];
    browser.setValue(item.s, item.v);
 }
browser.click('.country_select');
myWait(1000);
browser.setValue('#s2id_autogen1_search', 'Switzerland');
myWait(500);
browser.click('.select2-result-label');

 

We are using the myWait function. This will simply make the test wait for a specified number of milliseconds by using the waitUntil function in combination with some time math. We use it on the JavaScript select box to make sure that the page has had the time to first render the search box, then filter down the results. We’ll later use the myWait function before trying to fetch the license e-mail.

/*
 * Simple function to wait for a specified number of milliseconds (roughly)
 */
function myWait(ms) {
    console.log('Waiting for ' + (ms/1000) + ' seconds');
    var now = new Date().getTime();
    browser.waitUntil(function(){
        return (new Date().getTime() - now) > ms;
    }, ms + 1000)
}

4: Place order, verify order confirmation, extract order number

Now that the form is completely filled out, we can place the order. For that, we simply click the place order button. After the order has been processed, we will see a confirmation page. We verify that we have reached it by waiting for the thank you note, and by examining the content of the message. Also, we extract the order number assigned to our order by the system. We need this to check if we have received the license e-mail.

browser.click('input[name="woocommerce_checkout_place_order"]');
browser.waitForExist('.woocommerce-thankyou-order-received');
msg = browser.getText('.woocommerce-thankyou-order-received').trim();
assert(msg === 'Thank you. Your order has been received.', 'Did not see order has been received message.');

console.log('Extracting order number...');
var orderNumber = browser.getText('.woocommerce-thankyou-order-details.order_details > li:first-child > strong').trim();

 

5: Verify that license e-mail has been sent

On order placement, our backend should have generated a license key for Ghostlab and sent it to the e-mail address we have provided in the checkout form. In order to complete the test, we need to see if that e-mail has actually been delivered. For that, we use a node imap package to connect to the e-mail account we provided on purchase. Before we try and fetch the e-mail, we wait for 30 seconds, otherwise, it might not have been delivered yet.

Mocha timeouts
By default, mocha (which we’re using in our example) has a timeout of 10s – so when we would wait for 30s, that would fail, as the global timeout would apply after 10s. You can increase the mocha timeout in the mocha options (mochaOpts.timeout), this can be found in the file wdio.conf.js, which is automatically created for your project.

We know that the Subject text of that license e-mail is unique – it is some static text combined with the order number (that’s the unique part). So we can simply search for a message with the expected subject, and will be satisfied if the number of messages returned by the search is exactly 1. Of course, we could go on to examine the content of the license email, maybe even try to activate the software product with the license – but we’ll leave that for some other time.

// Let's wait a bit before we try to fetch the E-mail
myWait(10 * 1000);

var subject = 'Ghostlab 2 license for your order ' + orderNumber + ' (1 x Ghostlab)'
var foundEmail = false;
imap.once('ready', function(){
  imap.openBox('INBOX', true, function(err, box){
    imap.search([ 'UNSEEN', ['SUBJECT', subject] ], function(err, results) {
      if (err) 
        throw err;
       var resultsLength = results.length;
       assert(resultsLength === 1, 'Did not find exactly one e-mail with license information');
       foundEmail = true;
      imap.end();
    }); 
  });
});
imap.connect();

// Wait for e-mail to be confirmed.
browser.waitUntil(function() { return foundEmail; }, 10000);

Note that the call to imap.connect() is asynchronous – so how do we “notify” our test that e-mail fetching has been successful? We use the waitUntil function and pass it a function that returns a variable. In the e-mail handling, we set the variable to true if we successfully fetch the e-mail.

The entire test script is available for download here.