node.js Webdriver/Selenium 2 client
npm install wd
- Adam Christian (admc)
- Ruben Daniels (javruben)
- Peter Braden (peterbraden)
- Seb Vincent (sebv)
- Peter 'Pita' Martischka (pita)
- Jonathan Lipps (jlipps)
- Phil Sarin (pdsarin)
- Mathieu Sabourin (OniOni)
- Bjorn Tipling (btipling)
- Santiago Suarez Ordonez (santiycr)
- Bernard Kobos (bernii)
- Jason Carr (maudineormsby)
- Matti Schneider (MattiSG)
- License - Apache 2: http://www.apache.org/licenses/LICENSE-2.0
- New wrapper: promise chain.
- Old chain api is deprecated (It is still available, but you will see a depreciation message).
- There are some changes in the way the element and webdriver classes are passed around which may affect external wrappers. External wrappers should now subclass those 2 classes.
- New test suite using the promise chain api.
browser.Q
was moved towd.Q
.
- chai-as-promised v4 compatible.
- Promise wrappers can now be monkey patched directly.
- New saucelabs helpers.
Incompatibilities:
- There is a new method to call,
wd.rewrap()
, to propagate async monkey patching to promise. (see here and the monkey patch section below) - The chai-as-promised setup has changed in v4, look out for the
transferPromiseness
(Requires chai-as-promised 4.1.0 or greater) line in the examples. (see here)
- http retry functionality.
...
browser
.init({browserName:'chrome'})
.get("http://admc.io/wd/test-pages/guinea-pig.html")
.title()
.should.become('WD Tests')
.elementById('i am a link')
.click()
.eval("window.location.href")
.should.eventually.include('guinea-pig2')
.back()
.elementByCss('#comments').type('Bonjour!')
.getValue().should.become('Bonjour!')
.fin(function() { return browser.quit(); })
.done();
...
browser.init({browserName:'chrome'}, function() {
browser.get("http://admc.io/wd/test-pages/guinea-pig.html", function() {
browser.title(function(err, title) {
title.should.include('WD');
browser.elementById('i am a link', function(err, el) {
browser.clickElement(el, function() {
/* jshint evil: true */
browser.eval("window.location.href", function(err, href) {
href.should.include('guinea-pig2');
browser.quit();
});
});
});
});
});
});
See example here.
Yiewd is a wrapper around Wd.js that uses generators in order to avoid nested callbacks, like so:
wd.remote(function*() {
yield this.init(desiredCaps);
yield this.get("http://mysite.com");
el = yield this.elementById("someId");
yield el.click();
el2 = yield this.elementById("anotherThing")
text = yield el2.text();
text.should.equal("What the text should be");
yield this.quit();
});
...
describe("using mocha-as-promised and chai-as-promised", function() {
var browser;
before(function() {
browser = wd.promiseChainRemote();
...
return browser.init({browserName:'chrome'});
});
beforeEach(function() {
return browser.get("http://admc.io/wd/test-pages/guinea-pig.html");
});
after(function() {
return browser.quit();
});
it("should retrieve the page title", function() {
return browser.title().should.become("WD Tests");
});
it("submit element should be clicked", function() {
return browser.elementById("submit").click().eval("window.location.href")
.should.eventually.include("&submit");
});
});
./node_modules/.bin/wd shell
): wd shell
> x = wd.remote() or wd.remote("ondemand.saucelabs.com", 80, "username", "apikey")
> x.init() or x.init({desired capabilities override})
> x.get("http://www.url.com")
> x.eval("window.location.href", function(e, o) { console.log(o) })
> x.quit()
WD is simply implementing the Selenium JsonWireProtocol, for more details see the official docs:
var browser = wd.remote();
// or
var browser = wd.remote('localhost');
// or
var browser = wd.remote('localhost', 8888);
// or
var browser = wd.remote("ondemand.saucelabs.com", 80, "username", "apikey");
The parameters used are similar to those in the url module.
var browser = wd.remote()
// or
var browser = wd.remote({
hostname: '127.0.0.1',
port: 4444,
user: 'username',
pwd: 'password',
});
// or
var browser = wd.remote({
hostname: '127.0.0.1',
port: 4444,
auth: 'username:password',
});
The following parameters may also be used (as in earlier versions):
var browser = wd.remote({
host: '127.0.0.1',
port: 4444,
username: 'username',
accessKey: 'password',
});
var browser = wd.remote('http://localhost:4444/wd/hub');
// or
var browser = wd.remote('http://user:[email protected]/wd/hub');
var url = require('url');
var browser = wd.remote(url.parse('http://localhost:4444/wd/hub'));
// or
var browser = wd.remote(url.parse('http://user:[email protected]:80/wd/hub'));
{
protocol: 'http:'
hostname: '127.0.0.1',
port: '4444'
path: '/wd/hub'
}
With the promise chain api the method from the browser
prototype and the
element
prototype are all available within the browser
instance, so it might
be confusing at first. However we tried to keep the logic as simple as possible
using the principles below:
- There is no state passed between calls, except for what the method returns.
- If the method returns an element the element scope is propagated.
- If the method returns nothing (click, type etc...) we make the method return the current element, so the element scope is propagated.
- If the method returns something (text, getAttribute...), the element scope is lost.
- You may use "<" as the first parameter to get out of the element scope.
- You may use ">" as the first parameter to force the call to be done within the current context (mainly used to retrieve subelements).
If you need to do something more complicated, like reusing an element for 2 calls, then can either Q promise functionnality (like then, Q.all or Q sequences), or retrieve your element twice (since the promise chain api is very terse, this is usually acceptable).
Element function chaining example here
You may want to monkey patch the webdriver class in order to add custom functionalities. Please refer to the following examples:
Caveat: You now need to call wd.rewrap()
to propagate async monkey patching to the
promise wrapper. This will ovewrite the promise wrapper prototype, so you need to do
your monkey patching in order, async first, call wd.rewrap()
, and only then promise.
This is a clean alternative to monkey patching. See example here.
Http behaviour may be configured via the browser configureHttp
method as
in the code below:
browser.configureHttp({
timeout: 60000,
retries: 3,
retryDelay: 100
});
- timeout: http timeout in ms, default is
undefined
(uses the server timeout, usually 60 seconds). Use'default'
orundefined
for server default. - retries: Number of reconnection attempts in case the connection is dropped.
Default is
3
. Pass0
oralways
to keep trying. Pass-1
ornever
to disable. - retryDelay: the number of ms to wait before reconnecting. Default is
15
. - If a field is not specified, the current configuration for this field is unchanged.
When connecting to Saucelabs, the user
and pwd
fields can also be set through the SAUCE_USERNAME
and SAUCE_ACCESS_KEY
environment variables.
The following helper are also available to update sauce jobs: sauceJobUpdate
and sauceJobStatus
.
The safeExecute
and safeEval
methods are equivalent to execute
and eval
but the code is
executed within a eval
block. They are safe in the sense that eventual
code syntax issues are tackled earlier returning as syntax error and
avoiding browser hanging in some cases.
An example below of expression hanging Chrome:
browser.eval("wrong!!!", function(err, res) { // hangs
browser.safeEval("wrong!!!", function(err, res) { // returns
browser.execute("wrong!!!", function(err, res) { //hangs
browser.safeExecute("wrong!!!", function(err, res) { //returns
# Install the Selenium server, Chromedriver connect
node_modules/.bin/install_selenium
node_modules/.bin/install_chromedriver
#Run the selenium server with chromedriver:
node_modules/.bin/start_selenium_with_chromedriver
cd wd
npm install
make test
# look at the results!
# Install Sauce Connect
node_modules/.bin/install_sauce_connect
# Set the following env variales: SAUCE_USERNAME and SAUCE_ACCESS_KEY
# Start Sauce Sonnect:
node_modules/.bin/start_sauce_connect
cd wd
npm install
make test_e2e_sauce # may be run without sauce connect
make test_midway_sauce_connect
# look at the results on Saucelabs site!
If the method you want to use is not yet implemented, that should be
easy to add it to lib/webdriver.js
. You can use the doubleclick
method as a template for methods not returning data, and getOrientation
for methods which returns data. No need to modify README as the doc
generation is automated. Other contributions are welcomed.
The JsonWire mappings in the README and mapping files are generated from code comments using dox.
To update the mappings run the following commands:
make mapping > doc/jsonwire-mapping.md
make full_mapping > doc/jsonwire-full-mapping.md
make unsupported_mapping > doc/jsonwire-unsupported-mapping.md
npm version [patch|minor|major]
git push --tags
npm publish