webdriver-sync
Selenium testing without nested callbacks or promises!
webdriver-sync
wraps the Java WebDriver
API in a synchronous way allowing your
tests to be very concise. You can avoid the intricacies of promises
and
async ceremony
by using it.
webdriver-sync avoids this
browser.get("http://foo.html", function() {
browser.title(function(err, title) {
assert.ok(~title.indexOf('foo title'), 'Wrong title!');
browser.elementById('i am a link', function(err, el) {
browser.clickElement(el, function() {
browser.eval("window.location.href", function(err, href) {
assert.ok(~href.indexOf('foo title 2'));
browser.quit();
});
});
});
});
});
in favor of this...
Completely synchronous API! No promises or callbacks needed.
driver.get("http://foo.html");
title = driver.getTitle();
link = driver.findElement(By.id('i am a link'));
link.click();
assert(driver.getCurrentUrl().indexOf('foo title 2') > -1);
title.should.equal('foo title');
console.log(title);
driver.quit();
Highlights
- 100% synchronous selenium in JavaScript without promises or async ceremony!
- Run Chrome, Firefox, Safari, PhantomJS, Internet Explorer, and RemoteWebDriver (Android to come)!
- Reduce code by embracing a synchronous API!
- Reduce verbosity found in statically typed langs like java and c#
- Dependency management for 3rd party binaries I.E. ChromeDriver and selenium-server-standalone-x.x.x.jar.
- Always up to date binary dependencies when you update your version of
webdriver-sync
. - Run your automated tests against your app much faster on
travis-ci
. - Connect to Sauce using the RemoteWebDriver or other drivers that extend RemoteWebDriver.
Where can I file an issue?
We've disabled issues in the repository, but you can reach out to the community on Gitter!
Instantiating Drivers
Here are a few examples on how you can instantiate drivers for testing:
InternetExplorerDriver
var wd = require('webdriver-sync');
var IEDriver = wd.InternetExplorerDriver;
var driver = new IEDriver();
driver.get('http://google.com');
PhantomJS
var wd = require('webdriver-sync');
var PhantomJSDriver = wd.PhantomJSDriver;
var driver = new PhantomJSDriver();
driver.get('http://google.com');
Firefox
var wd = require('webdriver-sync');
var FirefoxDriver = wd.FirefoxDriver;
var driver = new FirefoxDriver();
driver.get('http://google.com');
ChromeDriver
There are 2 ways to run Chrome.
The straightforward way is slower as it has to start ChromeDriver each time it's instantiated:
var wd = require('webdriver-sync');
var ChromeDriver = wd.ChromeDriver;
var driver = new ChromeDriver();
driver.get('http://google.com');
This way uses a service and is much faster overall, but requires more setup. You'd likely want to wrap this in a module with a getter for a new driver when you need one:
var wd = require('webdriver-sync');
var seleniumBinaries = require('selenium-binaries');
var ChromeDriverService = wd.ChromeDriverService;
var ChromeDriver = wd.ChromeDriver;
var service = new ChromeDriverService.Builder()
.usingAnyFreePort()
.usingDriverExecutable(new File(seleniumBinaries.chromedriver))
.build();
var driver = new ChromeDriver(service);
driver.get('http://google.com');
Dealing with arrays and data
webdriver-sync
allows you to treat data as you normally would. For Arrays, you've got all the methods
you would expect:
- filter
- forEach
- map
Because webdriver-sync
is completely synchronous by nature, we're able to leverage native JavaScript methods without 3rd party libaries for asyncrony
.
Here we execute an async script, return a collection of divs, and console.log the inner text of each div. Notice that our control flow with other assertions are not affected in any way:
it('can do really cool stuff!', function(){
var numberOfElements = 0;
driver
.executeAsyncScript("var cb = arguments[arguments.length-1]; cb(document.querySelectorAll('div'));")
.forEach(function(el){
numberOfElements++;
console.log(el.getText());
});
assert(numberOfElements, 'We got here!');
});
See more tests for JavascriptExecutor
here: https://github.com/jsdevel/webdriver-sync/blob/master/test/interfaces/JavascriptExecutor.js
Explicitly Waiting with a Function
The wait
utility method pauses execution until some arbitrary condition is met. Provide a function and webdriver-sync
will invoke the function repeatedly until it returns a truthy value. You may optionally specify millisecond values for a timeout
(how long to wait before considering the operation failed and throwing an error) and a period
(how long to wait between invocations of the provided function).
For example:
driver.findElement(webdriver.By.cssSelector('button')).click();
webdriver.wait(function() {
return driver.findElements(webdriver.By.cssSelector('.thumbnail').length > 0;
}, { timeout: 1000, period: 100 });
Related Projects
webdriver-sync
's goal is to wrap the Java API and make selenium binary management simpler overall. Any other functionality or feature should be addressed in 3rd party modules.
Here is a list of 3rd party modules and why you'd want to use them:
- selenium-global. This module enables you to add
webdriver-sync
wrappings globally before running your tests. If you prefer to avoidvar wd = require('webdriver-sync');
in each of your tests then you can use this. - selenium-binaries. This module handles selenium binary management.
Design
webdriver-sync
leverages node-java to wrap the java API provided by the Selenium project which is by far the best supported of them all. Wrappings are located under src/
. In most cases, methods proxy through to their java equivalent.
You can view webdriver-sync
's API here. You can directly instantiate any of the classes directly. Interfaces are returnes by various methods and are usually not worth calling directly, but they're useful for verifying the type of returned data.
Installation
node-java
requires that you're able to compile node add ons. The install can be a bit tricky depending on your environment. Here are some general guidelines when installing webdriver-sync
:
- Ensure that your environment is setup to compile node add ons. A good module to use in verifying that you can compile node addons is microtime.
- You'll need a minimum JDK version of
1.7
installed on your system. npm install webdriver-sync
If you run into issues feel free to reach out!webdriver-sync
will downloadselenium-server-standalone-x.x.x.jar
andchromedriver
for you which makes your life easier.
Binaries will be downloaded to one of the following locations (listed in order of precedence):
- A directory defined by env var
WEBDRIVER_SYNC_BINARY_PATH
/lib/webdriver-sync
if running asroot
on *nix systems$HOME/.webdriver-sync
You can further override the download location for binaries as follows:
- ChromeDriver - Place
chromedriver
orchromedriver.exe
(for windows) on your path. - Selenium jar - Set
SELENIUM_SERVER_STANDALONE_JAR
in your env and have it point to the location where you have it on disk. You should never do this, as the API is only tested against specific versions of selenium, but it is available.
Documentation
As webdriver-sync
is a wrapper around the java API, you can browse any of the
javadocs online. You can quite literally use this module the same way you
would in java without the static typing.
Here are some links:
LOGGING
By default, webdriver-sync
disables any output from the selenium java bindings. To
change this behavior, you can set either of the following env vars to any non-empty
value:
- WEBDRIVER_SYNC_ENABLE_SELENIUM_STDOUT
- WEBDRIVER_SYNC_ENABLE_SELENIUM_STDERR
Running Headless
You can run Chrome, Firefox, Safari, and PhantomJS headless with webdriver-sync
!
You must have Xvfb
installed, or an equivalent.
Here's how ChromeDriver can be run headless:
#Run this on a tty
export DISPLAY=:99
Xvfb :99 -ac -screen 0 1280x1024x32> /dev/null &
npm test
//Use this in your tests for ChromeDriverService
var seleniumBinaries = require('selenium-binaries');
var service = new ChromeDriverService.Builder()
.usingAnyFreePort()
.usingDriverExecutable(new File(seleniumBinaries.chromedriver))
.withEnvironment({"DISPLAY":":99.0"})
.build();
var driver = new ChromeDriver(service);
//Running Headless!
Caveats
- Because
webdriver-sync
is synchronous, you can'tdriver.get()
a server you've started in the same process (thanks to @jugglinmike for discovering this!) I.E.
var driver = new (require('webdriver-sync').ChromeDriver);
require('http').createServer(function(req, res) {
res.end('This is never reached!');
}).listen('localhost');
driver.get('http://localhost');//This never completes.
LICENSE
The MIT License (MIT)
Copyright (c) 2013 Joseph Spencer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
CREDIT when it is due!
Special thanks to the developers of node-java!!!
Contributors (listed chronologically).
Anyone who contributes to webdriver-sync, either through code changes or testing will be listed here when their efforts are significant:
- Justin Searls @searls
- @alphamerchant
- Campbell Morgan @campbellwmorgan
- Nick Tulett @NickTulett
- Andrew Nichols @tandrewnichols
- jugglinmike @jugglinmike