Transcript & Code Sample: The Execute Command
WebdriverIO offers a lot of functionality out of the box, but there are times when our test requires running code in the browser itself. In the previous video, we covered creating custom commands, but that was still tied to the limits of the built-in WebdriverIO functions.
For our example, we’re going to test the video functionality on our “About Us” modal window. We use an HTML5 Video element for this, with a custom play/pause button. In our test, we’ll open up the modal, validate the video doesn’t auto-play, then click the play button and make sure the video is playing. Then we’ll click the pause button, and make sure the video is paused again.
WebdriverIO can’t access the state of the video, as Selenium doesn’t offer that functionality. This is where ‘execute’ comes in.
We’ll start off by creating a new test file and save it as ‘video.js’.
First, we’ll add our describe block, defining that we’re testing the ‘About Us Video’.
Next, we’ll add a beforeEach function, which will not only go to the homepage of our site, but also click the “About Us” link to open the modal window. We’re using a text selector here, which tells WebdriverIO to look for a link with the text of About Us.
With that set up, now it’s time for our first test. We’ll begin by checking that the video is paused to begin with.
We’ll define a variable called ‘isPaused’ to store the state of the video, then call the browser.execute command. Similar to the ‘addCommand’ functionality, we’re going to pass in a function that we want to run. Unlike addCommand though, execute will run this function from inside of the browser, not from inside our script.
It can be confusing to understand where our scripts are being run, so I’m going to add two ‘log’ statements; one will run inside of our node.js instance, and the other will run inside of the browser the Selenium is hooked up to.
We’ll be able to see the output of the node.js specific log from the command line, but the log statement that gets run inside the browser won’t be visible after the browser shuts down. To get around this, we’ll add a ‘browser.debug()’ statement, then view the console output when we run our test.
Let’s run this first test to see the effect. We’ll pass in a ‘mochaOpts.timeout’ parameter to allow the test to run a little longer than normal, giving us enough time to inspect the browser console before completing the test.
As you can see, the ‘outside the browser’ log appears in our node terminal, but ‘inside the browser’ doesn’t. If we jump in to the chrome browser that popped up and view the console, we see our ‘inside the browser’ log. And as you see, the ‘outside the browser’ log didn’t occur here.
Hopefully this sheds a little bit of light on where each part of the script is being run. Everything inside of the execute function gets run in the browser were testing on.
In order to figure out the code needed to check the video play state, I’m going to jump in to the console of a browser window I have open to our site.
Then I’ll check the state of the video by examining the paused attribute. If it’s true, then the video is paused. Otherwise it’s playing. Our execute script is going to look very similar to this. Let’s take a look.
First, let’s remove the console.logs and browser.debug statements. With that cleaned up, we’re going to add that ‘document.querySelector’ call to our execute script.
Next, we’ll return the value of ‘paused’. We need to do this because there’s a hidden barrier between our test file and the function inside of execute. Anything inside the script can’t access things outside, and anything outside the script can’t access things inside.
We can pass data back and forth though. You can pass arguments to the execute function, or in our case, return values from the function. These values can be assigned to variables just like any other WebdriverIO call. The one difference is that execute returns an object back, with our value inside the ‘value’ property.
Therefore in our assertion, we’re going to check ‘isPaused.value’, and assert here that it’s true.
Since we’re going to be using the same execute check in all of our tests, let’s take the time now to make it easier to reuse. We’ll use the ‘addCommand’ utility we looked at last lesson to move our execute function in to a special command we’re calling ‘isVideoPaused’.
In it, we’ll move in our execute function and return the value from it.
With our new custom command, we can convert the existing ‘execute’ call over. Just remove the old code, add our new command, then update the assertion.
Now it’s time for our next test. In this one, we’ll be checking that the video starts playing after the play button is clicked.
Step one in the test is to click the play button.
Then we’ll call our custom command, and expect the value to be false, signifying the video is playing.
Our last test will check that we can pause the video again.
Similar to the previous test, we’ll click the play button. Then we’ll wait half a second for the video to play. We don’t necessarily need to do this, but I think it more accurately simulates user interaction.
We’ll then click the button one more time, the get the paused state and run our assertion.
That’s all our tests. Let’s run them to see how they work.
You can’t really see it, but WebdriverIO is sending code to the browser to execute. The video is playing and pausing and being asserted.
I’ve been covering the ‘execute’ function, but there’s a sister function called “selectorExecute” that offers a slight twist. Instead of needing to use ‘document.querySelector’ inside our execute script, we can pass in the element we want to use. This element will be provided to the custom function we use inside of execute, where we can access its properties like before.
Let’s put this idea to use.
Back in our test file, we’ll change ‘execute’ to ‘selectorExecute’, then move the selector to this command. We’ll then update our function to take in that element via a function argument. Finally, we need to convert the element reference to be array based, as webdriverIO passes it in as such, whether there is one element or many.
selectorExecute doesn’t help our code all that much, but I wanted to mention it as an option, in case you find a need for it in the future.
That covers the basics of running arbitrary code inside the browser your testing. In the next video, we’ll look at how we can take advantage of the node environment to improve our tests.
Transcript & Code Samples: Using Node Functionality
WebdriverIO runs on top of Node.js, which means that we can take advantage of its functionality and ecosystem to improve our tests. In this video, I’ll show three non-WebdriverIO specific ways to improve your test writing abilities.
While it’s important to test common use cases, sometimes we want to push the limits of the website and see if it breaks. One way to do this is trigger multiple clicks on a series of elements in rapid succession. This is sometimes known as “Monkey Testing”.
Let’s add a new test to our accordion.js file. We’ll be testing that the accordion should be able to gracefully handle several quick clicks on different accordion items.
One way to do this is to copy and paste the same ‘browser.click’ call again and again and again. A better solution would be to use a for loop.
I’ll create a standard for loop, setting the limit to 20 clicks.
Inside the loop, I need to get the item to click on. I’ll use a ‘mod’ operator, which will return the value 0, 1 or 2 depending on where we are in the loop count. We need to add ‘one’ to this number in order to get the right CSS selector.
Next, I’ll call the click command, passing in the number of the item we want to click on. This number will increment from 1 to 3, then back to 1 and around again as we progress through the loop.
Once the loop is complete, we’ll run an assertion ensuring the last item we clicked is now the active element. We could also check the other items don’t have an active class, but I wanted to keep this example brief.
Let’s run the test and see it in action.
Thankfully it works, and our stress test was successful.
First, we need to install an NPM package called ‘sync-request’. There are many ‘request’ like libraries out there, which all accomplish the same task of getting data from a 3rd party website. I chose ‘sync-request’ because it’s synchronous style matches nicely with WebdriverIO’s sync mode.
At the top of our review.js file, we’ll start things off by loading the sync-request library.
Next, we’ll create a new test checking that it allows multiple reviews.
Inside our test, we’re going to call the sync-request functionality and ask it to get JSON data from the jsonplaceholder website.
This data is formatted to match a series of comments on a blog post. We’re going to use the email and name fields from the data to fill out our form multiple times.
Since the response comes back as a plain string, we need to parse it with the Node.js JSON utility.You code may differ depending on what library and API you use.
With our comments loaded as an array of objects, we next want to loop through each to use for our review. We’ll use the built-in array ‘forEach’ method to do this. The first argument we get back is a singular comment, followed by the numeric index representing where that comment lives in the array.
We can now call our custom ‘submit review’ command, passing in the comment details we need.
Next we’ll run an assertion checking that the specific review was added. We need to use the ‘:nth-of-type’ selector here to match the expected position of the new comment in the HTML structure. We add 3 to the index, because there are 2 pre-existing reviews when the page loads. The extra one comes from the fact that the index is zero-based, while nth-of-type isn’t.
We’ll follow the same pattern for our review text as well.
Saving the file, we can now run the test to see it in action. It happens pretty quickly, but you may have been able to see the multiple reviews added to our page. Again, the data for this review was loaded from the JSONplaceholder website using an HTTP request inside our node.js file.
While you may not want to rely on a third-party resource for your test data needs, I did want to introduce you to the idea of sending data to and from servers, which is especially useful when you need to pre-load information before test execution. Using this technique can enable you to create users or add specific test data on the system under test in a reliable and automated way.
The last integration we’re going to look at is a way to improve our testing workflow. By tapping in to the NPM ecosystem that lives as part of Node.js, we’re able to get a lot of functionality with only a little bit of effort.
For this exercise, we’re going to add the ‘onchange’ module to our test script, giving us a way to run our tests automatically on file change, without us re-entering the test command.
The ‘onchange’ module works by watching specific files for changes, then retriggering whatever command you passed to it during set up.
Just like sync-request, we need to install the ‘onchange’ module. Nothing too complicated here.
Next, we’ll open up our package.json file. Onchange works as a CLI, otherwise known as a command-line interface. This is the same thing our wdio command is.
Finally, we define that we want to run the
wdio command if any of those files change.
Back in the terminal, let’s call this script. Unlike
npm test, we need to add a ‘run’ to our script call, as
test:watch is not a common NPM command.
With our script running, we can make a change to our file, then see that the test suite automatically kicks off.
This is pretty nice, but it does require always having to check the terminal for the result of the tests. We can add another NPM package to send system notifications during our tests runs, helping us know the state of things
node-notifier is an NPM package that sends system event notifications for many different operating systems. I’ll demo it on a Mac, but a windows system with the proper software installed would work just as well.
Again, we’ll run an npm install to download the ‘node-notifier’ module.
With that, let’s open up our configuration file.
At the top of our file, we’ll require the node-notifier library.
Next, we’ll scroll down to the ‘hooks’ section. “Hooks” allow us to add scripts to run before, during or after our tests. The first hook we’ll link in to is
onPrepare. In it, we want to add a notification that our test run has started running. This is a good reminder that our tests are being run.
Next, we’ll add a notification if any tests fail. WebdriverIO passes the result of the test in to the ‘afterTest’ hook, allowing us to check it and run code based on the results.
Finally, we’ll add an onComplete hook to notify that the test run is complete.
With all that set up, let’s restart our watch script, then make a breaking change to our tests and see the resulting failure notification.
This might be overkill for some type of dev work, but functionality like this can be handy in specific situations, especially when you’re debugging a test."
It’s cliche to say, but the possibilities are really endless when it comes to programatic extension of your tests. I would say the biggest limitation is that we don’t want to make our tests too complicated, which can introduce bugs and faulty tests to our suite.
If you’re only calling a command once or twice, maybe you don’t need a for loop. In my opinion, a little repetition in a test file is worth it to decrease the risk of introducing a logic based bug in the code that’s supposed to prevent bugs.
Regardless, this wraps up module 5. In the next module, we’ll jump in to Page Objects, starting off with the browser.element command.
7. Page Objects
Transcript & Code Samples: Element, Elements, $ and $$
Page objects are one of my favorite concepts in automated testing, as they allow you to add semantics to your test scripts. Instead of an assortment of selectors and abstract commands, you can define named steps and elements that directly relate to your sites structure and functionality. This helps you write tests that are more maintainable and understandable.
It also allows for consolidation of element selectors in to common files, which is especially useful when testing a component used across multiple pages/flows.
The page object pattern is powerful, but it’s also complex. Before diving in to that complexity, I want to cover a simpler way to achieve a similar effect.
The element command provides a light-weight way to move the complex element selectors out of your page actions, allowing for more readable test steps.
In all of our tests so far, we’ve passed the element selector directly to each command. While this works, it’s repetitive and can be difficult to read. It’s also annoying to write.
The ‘element’ command fixes that. It allows us to define an element once, then call actions directly on it again and again.
To use the command, pass in an element selector and store the returned result to a variable. From there, use the stored reference to take actions, such as setting the element’s value or checking its state.
We’ll start off in our cart.js file by modifying the last test of the suite.
Instead of defining ‘thankYou’ as a selector to pass to your command, we’re going to call ‘browser.element’ and store the element reference returned.
Then, we’ll update the two instances were ‘thankYou’ is referenced, moving the variable usage from inside the command to where the ‘browser’ object is. Instead of saying, “browser waitForExist thankYou”, it becomes “thankYou waitForExist”. A simple change, but nice.
What about the close button? While we could wrap browser.element around it, it actually makes the test more verbose.
Thankfully, webdriverIO introduced the concept of dollar sign functions as a shortcut for browser.element. If you’ve worked in jQuery before, this may look familiar.
This first of the two functions, the single dollar sign, is a direct match with browser.element. It returns a reference to the first element that matches the selector you provide.
The other function is the double dollar sign. It’s very similar, but it returns all the elements found that match. This matches up with the browser.elements command. One difference to be aware of is that it will always return an array, even if only one element was found. Therefore, you must always use the array index or a loop if you need to access the elements inside.
Back in the cart file, I’ll replace my thankYou element with the dollar sign. I’ll also go ahead and change the close button click action to use the dollar sign as well. Even though we’re only referencing the element once, I prefer this style of writing tests.
Let’s run our tests and make sure they work.
While our test suite is running, it’s worth noting that we are able to define our element before it even exists on the page. In my experience, this isn’t a bulletproof way to do things, but it is an option when the time is right. Just be careful when trying to take actions on that element as it may not return the results you’re expecting. But don’t worry, we’ve got an alternative element declaration form coming up right now.
"At the top of our file, we define two selectors for the quantity input and buy now button. This is a great chance to use our element command.
To start off, we’re going to create a ‘cart’ object. This is going to store our element references.
Next, we’ll use the ‘get’ keyword to signify we want to define a “getter”. A “getter” is a method that gets the value of a specific property. Unlike a static variable definition like the selectors above, a ‘getter’ works by running and returning the result of the defined function.
What’s nice about getters is that you reference them just like any other variable.This helps keep the code a bit cleaner. Setters work in a similar fashion, although we’ll ignore them since we don’t need them right now.
"Back in our file, after our ‘get’ keyword, we’ll pass in the name we want to use as the variable, then the function we want to run when that variable’s value is requested. Inside the function we’ll return the element reference using the selector we already have available.
We’ll do the same thing for the quantity input.
With our object and getters defined, we can now put it to use. Inside the first test, we’ll change “browser isEnabled btn” to be “cart btn isEnabled”. This would be similar to declaring “browser element btn isEnabled”, just shorter.
We can do the same thing with the quantity input, changing it to ‘cart quantity setValue’.
And one more time with the cart button.
Continuing through updating our tests, everything is about the same
In this test, similar to the one we looked at first, we reference the thankYou element again. Let’s take a moment to move this to our cart object.
With that defined, let’s update the two thank you tests.
The final two tests are again simple updates.
That finishes it for our changes. Let’s save the file and run the tests.
This is more of a cosmetic update than a functional one, as WebdriverIO is taking the same actions in our test. We’ve simply updated how we reference the page element.
As expected, all our tests still pass.
The element command, along with the dollar sign shortcuts, are a handy tool to have in your back pocket. They can help write clearer, more concise tests, without too much overhead. Next, we’ll jump in to page objects, looking at how we can formalize the approach we outlined in this video.
Two quick footnotes having to do with webdriverIO version compatability.
Since the $ function was introduced in version 4.4, and this video course was started before that version was released, I needed to update my local copy of WebdriverIO. You may not need to do this if you recently started the course.
Also, in earlier versions of the webdriverIO mocha framework service, there was a bug with the waitUntil command and browser.element. This has been fixed since then. If you run across an error in this exercise saying that ‘cart.btn.getText’ isn’t a function, you may need to update your version.
To upgrade your dependencies, simply run
npm install —save-dev passing in the names of the packages you want to update, along with the
latest tag. NPM will then run the install script to grab the latest versions and update your package.json file for you.
Transcript & Code Samples: Defining Page Elements
Last video, we looked at the browser.element command and how we can consolidate element information in to a single object for re-use in our test file. Now let’s formalize our approach to take full advantage of page objects.
Before jumping in to the code, let’s define what page objects are. In testing, the page object pattern refers to code that abstracts out specific details from our tests. These details can be element selectors of individual commands to be run.
Take a login page for example. The test normally would include information as the login input and button selectors, along with the ‘setValue’ and ‘click’ commands. Anyone unfamiliar with the specific syntax inside the page wouldn’t be able to easily modify the test.
Using page objects, we can represent these selectors and commands in more meaningful ways. We’ll store our selectors as email and password properties. Instead of multiple commands to log in, we’ll create a ‘login’ command that encapsulates all the work needed. Now our test is much clearer to understand for those unfamiliar with the page, and is more robust to changes to the website itself.
Before moving on, I do want to disclaim that page objects don’t necessarily need to represent an entire page. In many cases, they may represent a common component used across many pages. A site nav menu is a good example of this. While I’d prefer the name ‘component object’, the term ‘page object’ is widely used and accepted to represent this idea, so I’ll stick with it.
To start, we’ll cut the current cart object out of the test file.
Then create a new file and save it as ‘cart.page.js’. Naming it with the ‘page’ suffix helps organize our files between actual tests and our page objects used inside those tests.
Since we’re working on two related files, it’s helpful to view them side-by-side. I’ll do this using Sublime Text’s “Layout” functionality.
Inside our new file, we’ll paste our cart object in.
Since we’re not planning on re-using the selectors inside the file itself, we can move them out of variables and reference them directly inside our browser element command. We’ll clean up our unneeded variables by deleting them.
To start our conversion, we’ll replace ‘var’ with ‘class’, then remove the equals sign. The only other change we need to make is to remove the trailing commas from our getters.
Classes have been supported in Node since version 4. If you’ve recently started with node, you should be good to go. You can double-check what version you’re on from the command line by running
There is an alternative way to define classes that is supported by older versions of node if this affects you. Simply check out the ‘page objects’ page on the webdriver.io website for more information.
With our class created, the last step for our file is to export it. exports are a way for Node.js to pull data and functionality out of a file for other files to use. To use an export, just save whatever code you want to make available to the module.exports property. In our case, we’re going to send a ‘new’ Cart object as our export.
We need to make one simple update to our test file to use our page object inside of it. Using node’s “require” system, we’ll create a ‘cart’ variable, then ‘require’ our page object file. This tells node to load that file and store whatever was exported to our cart variable. Everything else works just like before.
Because we introduced a new file naming pattern, we need to make sure webdriverio doesn’t treat our page objects as actual tests. Jumping in to the wdio.conf.js file, we can add a filter to our exclude property, telling webdriverio to ignore any files with a page.js extension. This means that when WebdriverIO is looking for tests to run, it will pass over any files in our test folder with page.js as the end of the filename.
Let’s run our tests to confirm all of our changes worked.
It may seem like a lot of work just to have the same effect, but page objects really do improve the maintainability, readability and functionality of your test suite. In the next video, we’ll look at defining custom functions inside our page objects to improve further upon this pattern.
Transcript & Code Samples: Defining Page Actions
So far, we’ve been using page objects simply to store element references. In this video, we’ll look at define custom commands in our objects, similar to how the
addCommand utility works, which we looked at in the previous module.
In our review.js file, we defined a custom command called “submitReview”. In it, we set the value on two input fields, then submitted the form. Our page object command will look very familiar.
First, we need to create a new file to host it. We’ll follow the ‘page’ naming convention, calling our file reviewForm.page.js.
Inside of it, we’ll create a ReviewForm class. Then we’ll export a new ReviewForm as the return value from the file.
We’ll fill out three properties, defining elements for our form, email input and review input. This is the same step we took in the previous video. As a review, we use the get keyword, name our property, then use the dollar sign function to return the specific element on the page we want to reference.
With those properties defined, let’s move on to the purpose of this video. I’ll create a new property named ‘submit’. It will be used as a regular function, so we won’t use the ‘get’ keyword. Because we’re using ES6 classes, we don’t need the ‘function’ keyword either. We’re defining this function inside of a class, so it automatically defines it as a function when we do so.
Cutting out the parameters and contents of the ‘submitReview’ custom command, we’ll move them over to our page object. After pasting that in, we’ll update our setValue commands to use our defined element references. Note the usage of ‘this’ in our commands. ‘this’ refers to the ReviewForm class we’re inside of, pointing to the element properties of it.
We now have a custom command defined on our page object. We didn’t use the ‘addCommand’ utility, as we only want this function to be part of the ReviewForm object, not the browser object.
We’ll clean up the leftover bits from our review file then add a require statement to the top of it, pulling in our new page object.
Finally, we’ll update the submitForm browser calls to use the function we just defined. Our reference to the function is similar to how we get our page object elements, except we’ll use the parenthesis to call the function and run it.
Our assertions stay as they are. We don’t move them inside of the custom function, as they don’t really belong there. Assertions should live inside of your tests, leaving your page objects as a simple abstract representation of the page.
That’s really all there is to custom functions. They work almost the same as the ‘addCommand’ utility, except you attach them to a class and not the browser object.
While we’re in here, let’s update the remainder of our test to use our newly created page object. We’ll start by defining a few more elements. Many tests relate to the error messages shown when inputting incorrect data. Therefore we’ll define a formError, emailError and reviewError property on our page object. We’ll use the text reference for our element selector.
Now we need to update our reference to these error elements. A few find and replaces will do the trick.
There are two more places we could make an update to use page objects. The first, where we’re checking the focus on elements, would be a great fit. Unfortunately there’s an issue right now with sync mode and the hasFocus command, so it doesn’t work as expected. I’ll leave this alone for now.
Sometimes things just don’t work they way you want. Oh well.
Our last test is its own special snowflake. Since we’re using dynamic data and inserting new elements inside of our page, we can’t rely on the same static selectors we’ve been looking at. Let’s ignore this test as well for now.
That’s it for our updates. Let’s now test out our tests. Again, we’ve had no real functional changes, just some behind-the-scenes things. Our tests should pass as before.
Our tests pass still and our updated page object works as expected. Things are looking better, but we still have this last test. It would be nice if we could hide this nasty :nth-of-type selector in a cleaner page object, but it’s tricky with how the value of it shifts each time through the loop.
Instead of relying on a static selector like we’ve don’e so far, we’re going to jump in to defining page objects on the fly. That’s coming up in the next video.
Transcript & Code Samples: Generic Page Objects
Before we end our trip into page objects, there’s one last technique I’d like to cover. So far, we’ve been defining static objects, where a page object only represents on single component on a page.
This has worked well for the review and cart checkout forms. But we’re going to need a different approach for dynamically defining the submitted reviews. The difference here is that reviews are a repeated element. By this, I mean that, while each review has different content, the HTML structure is the same. The content may change, but the code doesn’t.
Because of this similarity, we can define a generic review object for use as a base template. Then, when we want to create a specific review page object, we’ll pass in our specific review information.
The definitions for where to find common elements will be stored inside of the generic object; we’re just telling it which container element to search within.
We left off last video with the final test being in a somewhat messy state. In that test, several reviews are submitted to the page, then validation is done to ensure the reviews were added.
The messy part comes from these :nth-of-type selectors used to locate where our review was added. These selectors are very verbose and it would be much cleaner if we could hide that away behind a page object
The trouble is that we create multiple reviews in our test. It would be repetitive to manually create a new page object for each review we need to test. That’s where generic page objects come in.
We’ll start off by creating a new file. We’ll save it as Review.page.js. Note the capital R in review. We won’t be exporting a fully constructed review object. Instead, we’ll pass back an uninitialized class, which will allow us to construct the page object within the test itself.
This will hopefully make more sense in a minute or two. Bear with me until then.
First, we’ll define a new Review class.
These instructions can include a special ‘constructor’ function, which is code that’s run whenever a class is initialized. This is useful if you need to pass in specific information in set up your page object when it’s created, such as a container element for the component your defining.
We’re going to use this special ‘constructor’ function to store the location of the review that we’re interested in. Using the ‘constructor’ keyword, we’ll accept a numeric value, specifying how far down the list our review is.
There’s nothing requiring this value to be a number. It could be text or a true or false setting. It really depends on your needs.
Inside our constructor function, we’ll set up a reviewContainer property. We’ll use ‘this’ to store the element reference on our object, making it available to the rest of the properties when needed.
For the element selector, we’ll use the first portion of our email and comment selectors. Copying over the content, we’ll replace the ‘index plus three’ part with the reviewIndex variable. We won’t include the ‘plus 3’ part, as that’s up to the test to tell us about.
Now that we have our container reference, we can define our element getters. First up is the email address. We’ll use the ‘get’ keyword, then define the email property. Inside, we’ll return our element reference.
WebdriverIO allows you to chain elements together, so that the second element command only searches from inside the first element reference. Here, we’re going to get our reviewContainer reference, then search inside of it using the element command, looking for a class of email. This is why we needed to define
this dot reviewContainer in our constructor. Because of the common html structure of all of our reviews, we need to search within the specific review we want. Using element chaining we’re able to do this.
In a similar way, we’ll get the comment reference as well, using the ‘comment’ class for our second selector.
The final bit to our page object is our export. Normally we’d export a ‘new’ Review page object, but here we’re just going to send back the unconstructed Review class. Since we don’t know the specific review we want constructed, we need to wait to initialize this object until we’re inside our test.
Back in the review.js file, we’ll add another require statement, this time pulling in the Review.page.js file. Again, we’ll use a capital R in our review variable name, signifying that this is a generic class, not a specific instance already defined.
Jumping back down to our last test, we’ll put our new generic page object to use.
Inside the loop, we’ll define a new variable called ‘review’. Note the lowercase ‘r’. This signifies that it’s a fully initialized object. We’ll assign it as a new Review, passing in our review index of
idx + 3.
At this point our generic review class becomes a specific reference to a specific review. Just to clarify, each time we loop through our array of comments, we’re creating a new page object for the next review in our stack.
Let’s see what the usage looks like.
First, we’ll delete the old email selector and replace it with our page object’s email property.
Then we’ll do the same thing for the reviewText comment.
That’s it, we’ve now used a generic page object to define very real references.
Even though each review is unique in its own way, they all share the same HTML structure. We can use that, coupled with a constructor function that joins the specifics to the general, to create a dynamic page object that can be used on the fly inside our tests.
That concludes this module on page objects. There’s always more to learn about the technique, but these four lessons should give you a fundamental grounding in to the test practice.
This also concludes this video course. If you’ve purchased the profession add-on, you’ll need to switch over to that course to continue on. Thank you for joining me and I appreciate any and all feedback you have on the videos in this package.
8. Bonus: Cucumber And Gherkin Syntax
Transcript & Slides: Cucumber Overview
WebdriverIO supports Cucumber through the wdio-cucumber-framework module. We’ll take a look at that in the next lesson, but first I want to provide a brief background on Cucumber and why you might want to use it.
Code doesn’t always make the clearest documentation. This is especially true for those who may be less familiar with programming concepts, such as project managers or business executives.
This means that if you’re working on a team with less programming oriented people, there will likely be a need for separate documentation describing what your automation code covers.
Cucumber aims to solve this need by merging the test specification and the test code into a cohesive whole. It does this through a unifying syntax, that can be read both by people and computers.
With this syntax, several goals are made.
Cucumber aims to improve collaboration across the team’s skillsets.
It also hopes to help the folks writing the code to keep the overall business goals in mind, thereby making the tests more useful.
One other benefit is that Cucumber, and its Gherkin syntax, is supported by a wide range of programming languages. For example, say you have a legacy test suite written in Java, but implemented in Cucumber.
When you move those tests over to WebdriverIO, you’ll likely be able to keep the majority of the Features as-is, customizing only the Given-When-Then steps.
As you may not know what Features and Given-When-Then steps are, let’s take a look at a quick example.
Features are the ‘business-readable, living-documentation’ that are written in the Gherkin syntax. They let you describe test behavior without detailing how that behavior is implemented.
A Gherkin file should only contain a single Feature, which may contain multiple Scenarios.
Here, the first line starts the feature.
The next couple of lines are basically comments, which are expected to describe the business value of this feature.
Line 5 and 6 define background steps to run for every scenario.
Lines 8 through 10 are the steps for the specific scenario to be validated.
After this, we’d start the next scenario and so on.
Cucumber scenarios consist of steps, also known as Givens, Whens and Thens.
Cucumber doesn’t technically distinguish between these three kind of steps. But it’s strongly recommended that you fit the right term to the right step.
The purpose of ‘givens’ is to start in a known state before interacting with the system in the ‘when’ steps. Here, we’re navigating to the homepage of our site.
You could also use this time to prepare a database or set up a specific user type.
Next is the ‘when’ steps, which describe the key action the user performs.
In this example, we’re defining a step that allows the user to click, or double click, a link, button or element.
Cucumber steps can be written in such a way that common actions can be grouped together in a single step definition. We’ll look at how that’s done in a later video.
Once all your ‘when’ steps are completed, you’ll write your ‘then’ steps, which are used to observe the outcome of your scenario.
These observations should be related to the business value in your feature description. They should also be based on some output of the site, like a message or interface element.
There are two special keywords to mention: ‘and’ and ‘but’. To Cucumber, they are exactly the same kind of steps as all the others. Use them solely to make your tests more readable as documentation.
Before moving on to usage in WebdriverIO, I want to mention a couple of drawbacks I see with Cucumber.
First is that it adds another layer of abstraction. This means that you will need to interpret that layer between the Gherkin syntax and the WebdriverIO commands.
If you’re working solo on a test suite, Cucumber may not be worth the extra effort involved.
Secondly, Cucumber does require programming skill. There is a fantastic set of boilerplate steps for WebdriverIO already written, and we’ll get into those next. But you will likely need to write your own steps for any custom usage needs you have.
I say this to caution against folks using Cucumber solely to avoid programming. If you want to write test automation, you will need to understand programming concepts and code.
With all that said, let’s next take a look at the WebdriverIO Cucumber boilerplate project.
Transcript: The Cucumber Boilerplate Project
WebdriverIO provides support for Cucumber via a framework adapter. While setup for the adapter is fairly simple, first let’s have a look at the example cucumber boilerplate project.
This project provides a ready-to-run suite of Cucumber based tests. All we need to do is download and install our dependencies.
Let’s do this by cloning the repo to our local system. I’ll clone this to my user directory, then
cd in to that directory and run
With our dependencies installed, we’re ready to run the sample tests. Unlike our Robot Parts Emporium setup, we won’t be using the standard
npm test command, as that includes both unit and code validity tests.
Instead, we’ll used the
npm run test:features command.
When run, this command will start a local webserver and run all of our tests through PhantomJS. If everything goes as expected, we’ll have over 500 passing tests.
Now that we’ve demonstrated that the tests successfully run, let’s take a look at what they actually do.
We’ll start off by looking at the directory structure.
Unlike the way we’ve been storing our tests, this boilerplate project keeps all of the WebdriverIO tests in the ‘src’ directory. It’s set up this way as the Step Definitions, which we’ll take a look at in a second, are intended to be consumed outside of this project. We’ll show that in the next video.
The ‘test’ directory in this project contains all the unit-level tests that validate our step definitions work as intended. We’re going to skip reviewing this code as it’s not really relevant to our needs.
Before jumping in to the features and step definitions, let’s have a quick look at how they’ve set up the configuration file.
The specs refer to ‘feature’ files, which are the Gherkin formatted files used to define our test flows. It’s also important to see that the framework is set to
cucumber, letting WebdriverIO know which adapter to load.
There’s also a large section of
cucumberOpts, which define various options for Cucumber. The main option we want to know about is
require, which is used to load JS files before our tests run.
The given/when/then files required here contain the Cucumber set up code used to load our Step Definitions. We’ll take a look at those files in a minute.
For now, we’ll start by checking out one of the feature files. We’ll open up
click.feature and take a look at how it’s written.
Each file contains a single Feature, which is the feature we intend to test. This is similar to Mocha’s
describe block, and you can name your feature anything you’d like.
Since we’ve covered the overall format of the Feature file in the previous video, we won’t go in to details on the file right now.
These steps look very similar to the WebdriverIO commands we’ve covered so far. Take the
When I click on the button "#toggleMessage" step.
We have the command we want to run and the element we want to run it on. Everything else is just basic sentence structure.
Same goes for our ‘then’ statements. They match our ‘expect’ assertions very closely, passing in the element we’re testing and what we expect the result to be.
So how does WebdriverIO know what these sentences mean? While they’re close to the normal commands, there’s certainly a difference between the two.
In Cucumber, you map these sentences to commands using ‘step definitions’. All of the step definitions for this boilerplate codebase live in the ‘steps’ folder.
There are three files in this folder, given, when and then. Respectively, they match up with the given/when/then steps in our feature file.
Let’s find the
I open the site step definition. It’s a ‘given’ step, so we’ll look in that file.
If we scan down the file really quick, we can see something that looks like our ‘I open the site’ sentence. It’s a little different though, because it’s set up to take a few different variations.
Instead of a basic sentence (which you could also use), the authors wrote this step definition using a regular expression.
If you’re unfamiliar with regular expressions, they’re essentially a way to define a search pattern. This allows you to add more flexibility to your definition, accepted a wider range of sentences.
For instance, this expression checks for either ‘url’ or ‘site’, so we could have written our statement: ‘I open the url’ instead of ‘I open the site’. Functionally it would work the same, it just allows for more flexibility in writing.
Another feature is the ability to capture groups of input. Anything inside parathensis is marked as a group and passed along for later inspection.
We’ll talk about it more in a later video, but for now, know that steps are written with the text they want to match, and the function they want to run on a match.
Here, we want to run the
openWebsite function. This function is loaded from another file where it’s defined. Let’s open that file and take a look at it.
This file doesn’t have too much to it. It exports a single function, which accepts the ‘type’ and ‘page’ parameters. ‘Type’ refers to the first group, which was either ‘url’ or ‘site’, and page refers to the second group, which could be any character that isn’t a quotation mark.
Depending on the type, it will prepend the
baseUrl to the page value passed in. Then it will run the familiar
browser.url command, passing in
This completes the flow from feature to step definition to actual WebdriverIO command.
The boilerplate includes a full array of step definitions for you to use in feature files. These steps are well documented on the boilerplate github page, so have a look through them if you’re interested.
In the next video, we’re going to look at how to import and use these step definitions in our Robot Parts Emporium test suite.
Transcript & Files: Writing New Features
In the previous video, we looked at the default features written for the Cucumber Boilerplate codebase. Now we want to try using the step definitions provided to us to write our own set of features.
While we could add our new feature files to the cucumber boilerplate codebase, I’d like to show how to import the cucumber functionality provided by the boilerplate in to our existing Robot Parts Emporium test suite.
The first thing we need to do to get started is install the WebdriverIO cucumber framework adapter. We do this with
npm install wdio-cucumber-framework.
After installation, we’re going to duplicate our configuration file, naming it
wdio.cuke.conf.js. We can use the
cp command to achieve this.
Opening up the file, let’s compare it to the configuration file provided in the cucumber boilerplate project. Side-by-side, you can see they’re both fairly similar. Let’s look at the differences.
First off, the specs are different. I want to follow the ‘feature’ file pattern, so I’ll switch that over in the specs option.
Scrolling down, the next item to change is the ‘framework’ option. We’ll set this to ‘cucumber’, letting WebdriverIO know which adapter to load.
Next, we’ll create a ‘cucumberOpts’ setting. You can see that the boilerplate has a lot of options defined, but most of these are optional for our needs.
We will want to set the ‘require’ option though, since we’ll use that to load our step definition files. We can copy over and edit the setting from the other config file, changing ‘src’ to ‘test’ to match our folder structure.
If you didn’t notice, the step definitions in the Boilerplate use a tool called ‘Babel’ to add extra functionality, namely the ‘import’ statement. Because of this, we need to do either two things. Change those ‘import’ statements over to standard ‘require’ statements, or add the Babel plugin to our dependencies.
For simplicity sake, we’ll add the Babel plugin.
First, we’ll copy the
compiler information in the cucumberOpts.
Then, I’ll install the dependencies using
npm install babel-register babel-preset-es2015
Finally, I need to copy the ‘.babelrc’ file over. To do that, I’ll run
cp ../cucumber-boilerplate/.babelrc ./, which copies the file from the boilerplate to my current directory.
And before we forget, let’s copy those step definitions over. A simple drag and drop of the steps and actions folder will do it.
We can remove the mochaOpts setting, since we won’t be using mocha. Before doing so though, I want to move the timeout setting up to my cucumberOpts section. This way I can still use that ‘debug’ environment variable to extend the timeout for debugging purposes.
With that copied, we’re ready to write our first feature file.
For our example, we’ll be converting the ‘shop-button.js’ file over to a feature file. Let’s create the feature folder, then create and open up a shop button feature file inside it.
First thing we’ll add to this new file is the feature description. We’ll say that the feature we’re testing is ‘the home page shop button’.
We’ll describe this feature by providing the business value of it, which is so that we provide the customer with an easy way to access the shop page. You can format this however you’d like, as Cucumber ignores this text entirely. It’s merely there for your benefit.
Next, we’ll define a “background” step. This is similar to the ‘beforeEach’ step in Mocha, running a set of steps before each specific Scenario.
In this step, we’ll say ‘Given I open the url “/”’. This will open our homepage so that we can take actions upon it.
Now we can write our first scenario, which we’ll name “Test the initial page title”.
Our scenario is going to be a pretty basic one; we’ll just use a ‘then’ statement to validate that the title is “Robot Parts Emporium”.
One thing to be aware of is the use of double-quotation marks here. The way the step definitions were written do not allow for single quotes to be used. This is not a Cucumber limitation, but rather just a limit in the Regular Expression.
If you see an error that states “Step … is not defined”, double check your quotes.
Back to our scenario, that’s the entirety of it. Since we have our ‘given’ in our background, and we’re not taking any extra steps, we can have a single ‘then’ statement to verify it passes.
Let’s write a slightly more complicated scenario. This one is going to ‘Test the button functionality’
We’ll start it off with a ‘when’ statement. Looking at our original test, we need to click the ‘shop’ callout button. Let’s pull up the boilerplate docs to see how to do that.
Looking under the ‘when’ steps, we see the very first one is the one we need. Let’s copy it over to our test.
We’ll use ‘click’, and specify that we’re clicking an element. There is a slight difference between element and button and link. Link treats the value you pass in as a text-based selector. It does this by prepending whatever value you have with an equal sign, triggering a text-based search.
Element, on the other hand, doesn’t change anything with the selector and uses it exactly as provided. Since we’re using a CSS selector, we want to choose ‘element’.
Again, we use double-quotes for the selector, and copy it over from our other test.
Next, we’ll run our assertions. First, we’ll check that the title has updated, using the same format as our first test. Nothing new here.
We want to check a second item though. We could start it off with another ‘then’, but Cucumber lets us make things look a little nicer by treating ‘and’ as the same thing. So we’ll say ‘and’, then bring back up the documentation page to see what we need to write.
We want to check the URL, so I’ll do a text search for that in the ‘then’ steps section:
There are three ways to check the url. The first matches against the entire url. The second matches just the path. The third, which is the one we want, will check if the URL contains the value search for.
Back in our test, we’ll add our assertion. ‘I expect the url to contain “product-page.html”’ This matches our Mocha test pretty well.
Now it’s time to save the file and run our two new tests. We want to pass in our custom Cucumber configuration file, so we’ll use
npm test wdio.cuke.conf.js to do that. This ensure all of our other Mocha tests won’t get run.
A few moments, and our tests successfully run.
Notice that it says ‘6 passing tests’, yet we only have two scenarios. Cucumber counts each step in your test suite as a test. So in our feature file, we have two steps for the first test, and four steps for the second. That’s where the six tests come from.
We could keep going like this, converting our previous tests over to this new style using the Cucumber boilerplate, but there is an overall issue with this approach.
One of the key benefits of writing tests in this syntax is the ability to have a more ‘behavior-driven’ style. One of the key points of Cucumber is that it lets you describe behavior without detailing how that behavior is implemented.
In these tests, we have plenty of detail on how it’s implemented, hooking in to specific selectors and even checking page URLs.
To really improve our tests, we’re going to need to write completely custom step definitions. We’ll take a look at that next!
Transcript & Files: Writing New Steps
Last video, we looked at how we can use the predefined boilerplate step definitions to write tests in Cucumber syntax.
Now it’s time to do it on our own.
The boilerplate steps were helpful in getting started, but they don’t allow us to take full advantage of Cucumber and the Gherkin syntax.
The steps we wrote ended up being tied to the code of the page, mixing in CSS selectors and implementation specifics. This impacts the readability of our features and scenarios, and can lead to poor habits in test writing.
Now we’re going to take a look at writing our own steps, which will allow us to really separate our functionality from our features.
First, I want to rewrite the scenarios to remove the code-specific references.
We’re going to make up the step names, then add in the definitions for them later. We’ll start off with the background step.
Instead of specifying a particular URL to go to, let’s abstract this out and simply say that my ‘Given’ is that “I open the home page”.
For now, that’s simply going to replace the call to ‘browser.url’, but in the future it could be used to set up user data or do other initialization code.
Next, we’ll rewrite the first scenario. Instead of having the exact page title exposed in our test, we’ll change this to use more general assertion language. Something like “I expect to be on the homepage” will work.
This way, we can hide the specific items we’re checking in our step definition, allowing for our scenarios to stay the same even if the implementation changes.
Now let’s update the second scenario.
First, we’ll replace the element selector with a more general reference. Here I’ll use ‘the CTA button’ to describe it.
Then, similar to the first scenario, we’ll replace our title and url check with a more generic check that we’re on the product page.
What’s important about all of this is that we’re not validating a specific result or action, but rather a desired outcome. By following this pattern, we create site-specific steps that are reusable and easier to keep up to date.
For example, if the URL of the homepage changed, instead of having to update every test to include the new data, we simply update the single step definition.
Now that we’ve got our steps written in the format we’d like, let’s move on to writing our new step definitions.
First, we’ll create a new file to house our definitions. While the cucumber-boilerplate stored each step definition by the type of step if was (for example, all the ‘given’ steps went in the ‘given.js’ file), I prefer to store them by the feature they’re related to.
With that in mind, I’m going to save this new file as
home.js in the steps directory. I also need to update the configuration file to load this new set of definitions. Let’s take a look at doing that real quick.
In our configuration file, we’ll scroll down to the cucumber options section.
Since we don’t need them anymore, let’s remove the three files that we loaded through the boilerplate. Let’s also delete them from the file system, along with the ‘actions’ folder, as none of this is needed anymore.
We can also remove the compiler option, as we won’t be using Babel to compile our scripts. This will help simplify the overall set up.
With that cleaned up, we can now require our new file. Since we’ll be creating files for each section of our site, instead of requiring them individually, let’s require any file that happens to be in our steps folder.
Unfortunately, you can’t pass in a directory reference with a glob pattern. Fortunately though, there’s a simple workaround.
First, we’ll install a new module called “glob”. We’ll use the standard
npm install command to do so.
Then, we’ll require the ‘glob’ module, call the ‘sync’ function on it, and pass in the reference to the file pattern we want to load.
Now that all that is set up, we can get back to writing our step definition code.
The way we’re going to add our step definitions is going to be a little bit different from how the Cucumber-boilerplate project did. There’s been a recent update to how these steps can be defined, and the new format is much simpler.
Looking at the example in the official CucumberJS code repository, you can see their steps file is a simple set of
given, when, then definitions.
The first thing they do, and the first lines we’ll add to our file, is run a couple of require statements. This will load the ‘given, when, then’ definition functions from Cucumber, along with chai’s
expect assertion library. Let’s copy those over.
Next, they define a ‘given’ step. They do this by calling the ‘given’ function, and passing in two items. First, the text match we’re looking for, and second, the commands we want to run when this step is called.
Let’s copy over this ‘given’ step and customize it to our needs.
We’re going to change the match to be ‘I open the home page’. We’ll look at a more advanced configuration of this matching text later, but for now we’ll keep it simple.
We’ll remove the ‘number’ argument, as it’s not needed, and replace the ‘setTo’ function call with a call to
browser.url, passing in the root path reference.
Let’s give this a run and see if it works.
Just like last video, we’ll pass in our custom configuration file into our
npm test command. After the tests execute, We’ll have two passing steps and the rest will be failures. The two passing steps come from the background step running twice, once for each scenario.
Now we should add our ‘then’ definition to check that we’re on the homepage. We’ll write this one from scratch.
First, we’ll call the ‘then’ function, then copy in our step name, then define a callback function to run when this step is called.
Inside that function, we’ll run our assertion. Since it’s the same as our Mocha based test, we can actually copy it over from that file. I’ll open up our old
shop-button.js file, find the relevant lines, and copy them over in to this assertion function.
We’ve got two more steps to write. The next is the ‘when’ step, allowing us to click on our CTA button.
I like to organize my steps in ‘given, when, then’ order, so I’ll add this ‘when’ step between the two we already have written.
Like before, I’ll call the cucumber ‘when’ function, then pass in the step text to match against, and define a callback function. I’ll jump back in to my
shop-button.js file, and copy over the
browser.click command. And that’s all that’s needed for that step.
Finally, we’re going to create another ‘then’ definition, copying over our homepage one and changing it to match the product page. We’ll replace the assertion with the two checks we ran in the ‘shop-button.js’. That finishes all the step definitions needed for our ‘shop button’ feature.
That’s great, but there are two issues with what I just did. First off, the product page assertion should probably be defined in a separate ‘product’ file.
More importantly though, I’m writing code that is very similar to code I’ve already written. This can lead to increased time spent maintaining extra code, and in general it reduces the reusability of my steps.
In the next video, we’re going to take a look at fixing this duplication issue by using more complex text matching definitions. But before we do that, let’s run our tests and see how they work.
Using the same
npm test command, my tests run and all my steps pass as hoped for. Now it’s time to fix that duplication issue.
Transcript & Files: Writing Advanced Steps
In the previous lesson, we wrote a series of step definitions to improve the way we wrote our features and scenarios.
But there was a little bit of duplication in a couple of our steps, and I wanted to address how we can fix this.
When we first looked at the step definitions provided by the cucumber boilerplate project, I mentioned how Regular Expressions were used to allow for more flexibility in step writing. While it is useful, it’s also complicated to implement, and so in the last video we stuck with just plain sentences.
Now it’s time we try our hands at this regular expression stuff.
As I mentioned, the last two steps in our file look very much the same. The sentence is the same, except for a single word, and they both check the title of the page.
We’re going to combine these two steps into a single definition, using regular expressions and a little bit of coding logic.
Inside those slashes, I’m going to add
I expect to be on the. So far it looks just like my original sentence. This next part is where it gets tricky.
I’m going to add a pair of parentheses, which defines a group in my regular expression. Groups allow the text that is matched inside of it to be passed to the callback function. We can use that matching text to take specific actions. We’ll get back to that idea in a minute.
For now, we’re going to complete our sentence. After the parentheses, I’m going to add ‘page’, which is the end of our string. Our group is going to need some work, but the overall structure is complete.
To help with writing our group, I’m going to open a site I really cherish called “regexr.com”. This site is extremely helpful in visualizing how your regular expression, or regex for short, is working.
There are two main inputs to the site. The first is the regex you want to test. I’ll paste in the one we just wrote.
The second input is the text we want to test against. They include some sample text for their example, but we’re going to replace that with the two sentences we want to test.
Right now, our expression doesn’t work, otherwise the sentences we added would be highlighted in blue.
To get it working, we need to define our group. The regexr website comes with a handy sidebar full of useful documentation. We’re going to look at the ‘character classes’ information for this next step.
In regular expressions, you use these character classes to define types of text to look for. Here, we just want to look for a word, so we’ll use the ‘word’ class, or
\w. That will match any text that looks like a word. Basically any set of letters or numbers grouped together without spacing between them.
I’ve added that
\w to my group, but it still doesn’t match. That’s because I still need to define how many times I want to check for a word match. I’ll do that using a quantifier. We have many quantifiers available, but the one we want is the first in the group.
+ quantifier tells the expression to match one or more of the preceding tokens; in this case we’re looking for one or more words. To add this quantifier to our expression, we’ll add a plus sign after our
With that added, our sentences now match, and they become highlighted in blue. We can now hover over that match and view the details on it. In the first match, you see that group #1 is ‘home’, which the second match has product as the value. This is going to be very useful in a minute.
Let’s copy our finished expression back to our test. We’ve got our regex running, and now need to define the callback function. It’s much like our previous ones, but this time it’s going to take a single parameter named ‘pageName’. This could really be named anything you want; it’s up to you.
Each group defined in our regex will be passed in to our function as a separate parameter, in the order the groups are defined. Right now we only have one group, so we only have one parameter.
If, however, we added a second group, and it came before the
pageName in our expression, we’d want to add it as the first parameter in our callback, moving the
pageName to the second spot.
Basically, the order of the groups will correspond with the order of the parameters.
So, we’ve got our function and ‘pageName’ parameter defined, now it’s time to add our commands. I’m going to copy the content from the previous definition and paste it in.
Then, I’m going to go above that copied content and add a little bit of code logic.
I’m going to define a new variable called
pages is going to be an object that contains the title and url of the various pages of my site. Each page will be defined as a property.
So to define the home page, I’m going to define a ‘home’ property, describe it as an object, then add a url and title property to that object. I’ll do the same thing for the ‘product’ page as well.
Notice how the page name matches the name we use in our scenario steps? This is definitely on purpose, as it won’t work otherwise.
Next, we need to update our checks to use this pages object, instead of the text from the product page.
Defining a new ‘page’ variable, I get the page information I want via the pages, object. To reference a specific page, I pass in the
pageName variable via square brackets.
pageName. So if
pageName is ‘home’, it will return the
home property of
pages. This is called “Bracket Notation”, and more details on it can be found on the MDN documentation site.
Now that I’ve got the page information I want to test against, I’ll delete the existing text, and replace it with
With that, I can delete the previous two steps I wrote, as they’re no longer needed.
To test this all out, I’ll run my
npm test command, and ensure that the conversion over works as expected.
One final step to take, which I won’t show here, is to move this step to a ‘pages’ file. Since I’ll likely be adding more pages in the future, I don’t want to clutter my ‘home’ file with non-relevant information. By keeping it all in a pages file, I can have easier access to this script for when it needs to be updated.
So the tests still pass and our feature file is complete. That finishes off the basics of Cucumber in WebdriverIO. There are more advanced features to be aware of, but this should get you started with the framework.