admc / wd

A node.js client for webdriver/selenium 2.
Other
1.53k stars 404 forks source link

WD.js

NPM version Downloads Dependency Status devDependency Status Build Status Selenium Test Status

Selenium Test Status

node.js Webdriver/Selenium 2 client

This library is designed to be a maleable implementation of the webdriver protocol in Node, exposing functionality via a number of programming paradigms. If you are looking for a more polished, opinionated and active library - I would suggest webdriver.io.

Release Notes

here

Install

npm install wd

Note: WD.js does not start the selenium server. You may use the selenium-standalone package to install and start a selenium server.

Authors

License

Usage

Q promises + chaining

...

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();

full code here

Pure async

...

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() {
          browser.eval("window.location.href", function(err, href) {
            href.should.include('guinea-pig2');
            browser.quit();
          });
        });
      });
    });
  });
});

full code here

Q promises without chaining

See example here.

Generators api

Yiewd

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();
});

Mocha integration

...

describe("using promises 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");
  });
});

example here

Repl

If wd was installed via npm run:

./node_modules/.bin/wd shell

Or for local install run:

node lib/bin.js shell

Then within the shell:

): wd shell
> x = wd.remote() or wd.remote("ondemand.saucelabs.com", 80, "username", "apikey") or wd.remote("hub.browserstack.com", 80, "username", "apikey") or wd.remote("hub.testingbot.com", 80, "key", "secret")

> 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()

Doc

Api

jsonwire mapping + api doc

full jsonwire mapping

JsonWireProtocol

WD is simply implementing the Selenium JsonWireProtocol, for more details see the official docs:

WD is incrementally implementing the Mobile JsonWireProtocol draft, see proposal docs:

Check which Mobile JsonWire Protocol methods are supported in /doc/jsonwire-mobile.md

Browser initialization

Indexed parameters

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");
// or
var browser = wd.remote("hub.browserstack.com", 80, "username", "apikey");
// or
var browser = wd.remote("hub.testingbot.com", 80, "key", "secret");

Named parameters

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',
});

Url string

var browser = wd.remote('http://localhost:4444/wd/hub');
// or
var browser = wd.remote('http://user:apiKey@ondemand.saucelabs.com/wd/hub');
// or
var browser = wd.remote('http://user:apiKey@hub.browserstack.com/wd/hub');
// or
var browser = wd.remote('http://key:secret@hub.testingbot.com/wd/hub');
// or
var browser = wd.remote('https://user:apiKey@api.kobiton.com/wd/hub');

Url object created via url.parse

URL module documentation

var url = require('url');
var browser = wd.remote(url.parse('http://localhost:4444/wd/hub'));
// or
var browser = wd.remote(url.parse('http://user:apiKey@ondemand.saucelabs.com:80/wd/hub'));
// or
var browser = wd.remote(url.parse('http://user:apiKey@hub.browserstack.com:80/wd/hub'));
// or
var browser = wd.remote(url.parse('http://key:secret@hub.testingbot.com:80/wd/hub'));
// or
var browser = wd.remote(url.parse('https://user:apiKey@api.kobiton.com/wd/hub'));

Defaults

{
    protocol: 'http:'
    hostname: '127.0.0.1',
    port: '4444'
    path: '/wd/hub'
}

Specifying driver type in remote

You may pass async,promise or promiseChain to remote to specify the driver type instead of calling the driver specific method.

var browser = wd.remote('promiseChain')
// or
var browser = wd.remote('localhost', 8888, 'promise');
// or
var browser = wd.remote('localhost', 'promiseChain');
// or
var browser = wd.remote({
  hostname: '127.0.0.1',
  port: 4444,
  user: 'username',
  pwd: 'password',
}, 'promise');
// or
var browser = wd.remote({
  hostname: '127.0.0.1',
  port: 4444,
  auth: 'username:password',
}, 'promiseChain');

Attach to an already-existing session

Instead of calling 'init' use 'attach' using the WebDriver session ID. Use detach to detach from the session (callbacks are optional).

var browser = wd.remote('http://localhost:4444/wd/hub');
browser.attach('df606fdd-f4b7-4651-aaba-fe37a39c86e3', function(err, capabilities) {
  // The 'capabilities' object as returned by sessionCapabilities
  if (err) { /* that session doesn't exist */ }
  else {
    browser.elementByCss("button.groovy-button", function(err, el) {
      ...
    });
  }
});
...
browser.detach();

Capabilities

doc here.

Element function chaining (using promise chains)

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:

If you need to do something more complicated, like reusing an element for 2 calls, then can either Q promise functionality (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

Waiting for something

Below are the methods to use to wait for a condition:

NOTE: When using waitForConditionInBrowser you must first set the async script timeout using setAsyncScriptTimeout(). For instance:

// init phase
browser
  .init()
  .setAsyncScriptTimeout(30000);
// test
browser
  .waitForConditionInBrowser("document.querySelectorAll('.foo').length > 0", 10000);

You should be able to use ready to use asserters, in most cases. Here is a simple example. Please refer to the asserter category in the api doc here.

Custom asserters should be written using either models below . target may be browser and/or element depending on the context.

// async
var asyncAsserter = new Asserter(
  function(target,cb) {
    ...
    cb(err, satisfied, value);
  }
);

// promise
var promiseAsserter = new Asserter(
  function(target) {
    ...
    return promise; // promise resolved with the wait_for return value.

    // Promise asserter should throw errors marked with `err.retriable=true`
    // when the condition is not satisfied.
  }
);

Here is a custom asserter example.

Adding custom methods

If you are only using the promise chain api, you should probably stick with wd.addPromiseChainMethod.

Custom methods may be removed with wd.removeMethod(name). That will remove the method from the 3 prototypes.

Please refer to the following examples:

Note: No need to call rewrap anymore.

Promise helpers

This is an alternative to adding custom methods. See example here.

Starting the promise chain

The browser and element object are not themselves promises (cause that would lead to chaos), so you cannot call Q core methods on them. However you may call one of the method below to initiate the promise chain:

The resolve methods work like Q thenResolve.

Extra promise methods:

NOTE: When using functions such as nth(), first(), second() you must use the "plural" versions of the get functions.

Working with external promise libraries

wd uses Q internally, but you may use promises from other libraries with the following methods:

The external promise will be automatically wrapped within a Q promise using new Q(externalPromise).

See example here.

Http configuration / base url

Http behaviour and base url may be configured via the configureHttp method as in the code below:

// global config
wd.configureHttp({
  timeout: 60000,
  retries: 3,
  retryDelay: 100,
  baseUrl: 'http://example.com/'
});
// per browser config
browser.configureHttp({
  timeout: 60000,
  retries: 3,
  retryDelay: 100,
  baseUrl: 'http://example.com/'
});

Environment variables for Saucelabs

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.

Safe Methods

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

Working with mobile device emulators

It is possible to use wd to test mobile devices using either Selenium or Appium. However in either case the full JsonWire protocol is not supported (or is buggy).

Examples here.

Selenium

Both Android (using AndroidDriver) and ios (using ios-driver) are supported, locally or using Sauce Labs cloud.

Appium

Android and iOS work locally and on Sauce Labs or BrowserStack.

Run the tests!

# Install the Selenium server, Chromedriver connect
node_modules/.bin/install_selenium
node_modules/.bin/install_chromedriver
# NOTE: You may need to upgrade /tmp/sv-selenium/chromedriver to match your Chrome version!

#Run the selenium server with chromedriver:
node_modules/.bin/start_selenium_with_chromedriver

#Run the test
gulp test

//TODO: better doc + sauce test doc

Adding new method / Contributing

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.

Generating doc

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/api.md
make full_mapping > doc/jsonwire-full-mapping.md
make unsupported_mapping > doc/jsonwire-unsupported-mapping.md

Publishing

npm version [patch|minor|major]
git push origin master
git push --tags
npm publish

Test Coverage

test coverage