Chaining Interface Getting Started - preceptorjs/taxi GitHub Wiki
#Chaining Interface - Getting Started
##Installation
You can install Taxi with the following command:
npm install taxi
This will install Taxi into the sub-folder node_modules
in your current working directory. However, it will not remember what you had installed. This command should only be used for trying this out.
If you want to save this information in your project, then execute the following command to update the package.json
to make it -remember- this module.
npm install --save-dev taxi
This will add Taxi to the development dependencies and will not be installed when the code is released to production - it is only there on developer machines.
##Overview
Generally, Taxi has two interfaces:
- Object-oriented interface - Every item will be treaded as its own object.
- Chaining interface - Every action has its own function.
In this documentation, we will focus on the Chaining interface as this one is the most useful for quick and easy implementations. The object-oriented interface is for bigger projects that will have a greater amount of maintenance. The benefits of maintainability, however, comes at a cost with a little higher code complexity.
The chaining interface functions are grouped into three categories:
- Element - Functions that retrieves information or act on DOM elements.
- Active-Window - Functions that retrieves information or act on the browser window itself.
- Others - Functions that interact with the driver or the browser, but not with the browser window or DOM elements.
##Synchronous & Asynchronous In addition of having two interfaces, Taxi does also support two different modes of processing:
- Synchronous - Requests to the browser are blocking; the "code" will wait for a response. This will make your test code a lot easier to read, write, and maintain, but it is unconventional with JavaScript. Some like it - some hate it.
- Asynchronous - The traditional way of executing code in JavaScript. Requests are sent to the browser, and will return at any point later. Taxi uses Promises to handle the asynchronicity to avoid the callback-hell.
In the rest of this documentation, we will concentrate on the synchronous mode since it is easier to write and understand. However, the code can be converted into the asynchronous request mode - you only need a solid understanding of Promises.
##Getting a Selenium Server
To get started, we need to connect to a Selenium server. This could be locally or somewhere in the cloud using services such as SauceLabs or BrowserStack.
In the following sections, I will explain the setup of each of the stand-alone server and how to set-up SauceLabs.
###Selenium Stand-Alone Server This is a server that can be run on you local machine and is then accessible on local port 4444. To run this server, you need to have the Java SDK installed. Go to the Oracle homepage for Java, download it and install it.
Now, we can get the Selenium server:
Go to the Selenium website and download the "Selenium server". Click on the link provided. On that page, there should be a link labeled "Download version x.x.x" where the "x"s are replaced with the most recent version of Selenium. Download the jar
file and execute it as follows in the command line:
java -jar selenium-server-standalone-x.x.x.jar
Again, the "x" should be replaced with the correct version.
Now, the Selenium should be available at port 4444. Try opening "http://localhost:4444/wd/hub" in your browser, and you should see the Selenium server interface, listing all active session (there should be non since we haven't started one yet).
Congratulations! Selenium is up and running.
Now: The only thing left here is to install some browsers that Selenium can control. Selenium expects the browsers to be at a specific location on your hard-drive. If you installed the browser into a custom directory, then you need to supply this information to Selenium. The easiest way may be to re-install it to the standard folder.
Connect to the Selenium server by supplying its URL. Also, select the browser you want to use.
// Just import Taxi
var taxi = require('taxi');
// Url of the local Selenium server
var url = 'http://localhost:4444/wd/hub'
// Select a locally installed Firefox browser (install it if you don't have it yet or change it to "chrome" or something)
var capabilities = { browserName:'firefox' };
// Run in Synchronous mode
var configuration = { mode: taxi.Driver.MODE_SYNC };
// Connect to the Selenium server to get a Selenium session with a browser
var driver = taxi(url, capabilities, configuration);
With the browserName
as firefox
, Selenium will try to find the binary of the browser, starting it up in WebDriver mode that makes it possible to remote-control the browser, and Selenium will start a session with the browser.
Now, you can use the driver
object to remote-control the browser.
###SauceLabs
The probably easiest and most painless way of getting Selenium running is to use the services of SauceLabs. SauceLabs is a cloud service that supplies you with hundreds of different browsers on different operating systems. There is a free account which limits your usage, but it should be enough for testing purposes. You can still upgrade to a paid account later on when you like the service.
To get started, go to the SauceLabs website and sign-up for an account. When you are logged in, you will see on the left side in the navigation bar (at the bottom) a text-box with an access-key. Copy this key and add it to the following url within the code. Fill-in also your username you selected during sign-up.
// Just import Taxi
var taxi = require('taxi');
// Replace <username> and <accessKey> with your username and copied access key respectively
var url = 'http://<username>:<accessKey>@ondemand.saucelabs.com/wd/hub'
// Select Chrome 41.0 on the Windows 8.1 platform
var capabilities = { browserName:'chrome', version:'41.0', platform:'Windows 8.1' };
// Run in Synchronous mode
var configuration = { mode: taxi.Driver.MODE_SYNC };
// Connect to the SauceLabs service to get a Selenium session with a browser
var driver = taxi(url, capabilities, configuration);
The Platform Configurator is a great way of finding the right strings for the browser and platform selection. Simply select the browser and platform you want and copy the strings from the Node-tab in the bottom of the page. Simple!
And that's it! Now, you can use the driver
object to remote-control the browser through the SauceLabs service. Should you need access to a website behind a firewall, then there is also a solution for that.
##Hello Browser
Let's start with a simple example: go to Yahoo and print the title of the page:
// the driver should already be available
driver.chain().navigateTo('http://www.yahoo.com').title(function (title) { console.log(title); }).end();
Yes, this is a quite long and unreadable line. So, let's break the code down into one line for each interaction with the browser:
// the driver should already be available
driver.chain()
.navigateTo('http://www.yahoo.com')
.title(function (title) {
console.log(title);
}
)
.end();
That looks much better. Ok, let's see what we have here:
-
driver.chain()
- This line requests asks the driver to retrieve the chain object which defines the chain interface. Every further interaction is done on it. -
navigateTo('http://www.yahoo.com')
- Navigates the browser to the Yahoo website.navigateTo
is defined on the chain. -
title(function (title) {
- The title-call will ask the browser what the title of the currently displayed page is. As soon as the browser returns this value, Taxi will execute the anonymous function, supplying the title as its first parameter. Also this function is defined on the chain object; every execution on this object will return the chain object again which makes it possible to chain these calls together. Results will always be delivered to callback functions. -
console.log(title);
- This line is unrelated to Taxi, but it will print out the value in the title variable which is the title of the browser. -
end();
- The browser needs to be closed when everything is said and done. This is done by calling theend()
function.
Let's move on to a more complex example.
Let's say we want to know a little bit more about, say, Lions. So, let's go to Yahoo and search for "Lion":
// the driver should already be available
driver.chain()
.navigateTo('http://www.yahoo.com')
.elementSendKeys('.searchwrapper .input-wrapper input', 'lion')
.elementClick('button.searchsubmit')
.sleep(5000)
.end();
Some of the calls are familiar from the previous example, but there are new ones. Let's have a look at the new one's in turn:
-
elementSendKeys('<css selector>', '<search-term>')
-elementSendKeys
will ask the browser to find a specific DOM element and types the supplied text as if it was entered by a user on a keyboard(here a search-term). In this example, we selected the search-box on the Yahoo page to search something, entering "lion". With the chaining interface, Taxi only supports CSS selectors for selecting a specific DOM element. This is the selector-type that is less error prone and easy to learn, especially if you are already familiar with CSS styling in HTML. -
elementClick('<css selector>)
- As you can see, there is a pattern to it. When you want to interact with a DOM element, you will use functions that start withelement
, supplying as first parameter the CSS selector, identifying a DOM element. The rest of the function name identifies the action that will be applied on the found element. In this case, we search the "submit" button and click on it. -
sleep(<ms>)
- Sleep? Why do we want to sleep? If we wouldn't do it here, the browser would close without us seeing the result. Selenium is too fast. Oh, and the first parameter of this function is the time to wait in milliseconds; we will wait here 5 seconds.
Ok. Now, we reached the result page but we don't have any information about the results yet. Let's do that now by printing out all of the result titles:
// the driver should already be available
driver.chain()
.navigateTo('http://www.yahoo.com')
.elementSendKeys('.searchwrapper .input-wrapper input', 'lion')
.elementClick('button.searchsubmit')
// All the same as before
.elementText('.searchCenterMiddle .title', function (title) {
console.log(title);
})
.end();
Most of the code is copied from the example above. One line was added:
elementText('<css selector>', function (title) {
- Again, you might see a pattern emerge: Every time you ask the browser something, retrieving information from it - not telling it what to do, then it requires a callback function that will be supplied as the last parameter. The result will then be in the first parameter of the callback execution. You might have noticed also something else: We only have one selection, but it will print out all the titles. Actually, Taxi will select all the DOM elements with this selector and execute the callback each in turn. This makes code a lot easier.
There is another thing: you can always supply a callback function as last parameter in any chained function call. Whenever Taxi is done with something, it checks if there is a callback and will then call it.
Ok. So, the title of the results might not be as helpful. What about printing out the short description for each?
Let's do that:
// the driver should already be available
driver.chain()
.navigateTo('http://www.yahoo.com')
.elementSendKeys('.searchwrapper .input-wrapper input', 'lion')
.elementClick('button.searchsubmit')
// All the same as before
.elements('.searchCenterMiddle .compTitle', function (element) {
console.log(element.getElement('.title').getText());
if (element.hasElement('div span')) {
console.log(element.getElement('div span').getText());
}
})
.end();
Yeah. That's quite a bit more code. Again, let's go through it one-by-one:
-
elements('<css selector>', function (element) {
- This call will retrieve all elements with that CSS selector and supply the element object as parameter in the callback function. Here, we only use the object-oriented interface in this callback since these are more advanced use-cases. You might wonder: Why is it advanced? See, what we do here is get each search-result entry (not just the title - see the updated selector) and then select first the title of the result and then the short description relative to the result DOM element. You wouldn't be able to do that with the chaining interface since selectors will always use the document root as the relative context. However, in the object-oriented interface, one can drill-down from one DOM element to the next, hoping from one DOM node to the next. -
element.getElement('<css selector>').getText()
- As mentioned, this is part of the object-oriented interface since we got an element and interact with that object. What it does here is to select another element (here the title) from the search-result entry and then get the text content. -
element.hasElement('<css selector>')
- Again an OO interface call, figuring out if a DOM element exist from that relative parent context. In the search results, there are some results which do not have a short description (image results for example) and therefore need to be skipped. -
element.getElement('<css selector>').getText()
- Same as above but for the short-description.
I hope this all makes a lot more sense, now.
And that concludes our journey of "Getting Started". Have a look at the Chaining API to learn more about all the other functions that are available to interact with the browser.
Enjoy the ride!