yiewd

Generator-based WebDriver client, wrapper around Wd.js

Yiewd

Yiewd is a Wd.js wrapper that uses V8's new generators for cleaner code! It's called yiewd because it uses the new yield syntax with wd. yield + wd = yiewd. Amazing, right? And a great way to exercise vowel pronunciation.

Yiewd is made possible with the monocle-js library.

Install with: npm install yiewd

Let's say we want to write a webdriver test:

var wd = require('wd')
  , driver = wd.remote();
 
driver.init(desiredCaps, function(errsessionId) {
  if (err) return postTest(err);
  driver.get("http://mysite.com", function(err) {
    if (err) return postTest(err);
    driver.elementById("someId", function(errel) {
      if (err) return postTest(err);
      el.click(function(err) {
        if (err) return postTest(err);
        setTimeout(function() { // pause a bit 
          driver.elementById("anotherThing", function(errel2) {
            if (err) return postTest(err);
            el2.text(function(errtext) {
              if (err) return postTest(err);
              text.should.equal("What the text should be");
              driver.quit(postTest);
            });
          });
        }, 1500); // what's this random number doing here? It goes with the pause! 
      });
    });
  });
});

Yeah, that sucks. Look at that callback pyramid! Look at all those repetitive error checks!

Let's all be a little more sane, shall we?

var yiewd = require('yiewd')
  , driver = yiewd.remote();
 
driver.run(function*() {
  var sessionId, el, el2, text;
  sessionId = yield this.init(desiredCaps);
  yield this.get("http://mysite.com");
  yield this.elementById("someId").click();
  yield this.sleep(1.5);
  text = yield this.elementById("anotherThing").text();
  text.should.equal("What the text should be");
  yield this.quit();
});

Niiice.

Basically, you get a driver object as a result of the call to wd.remote(). You can use this driver object inside a monocle o-routine to yield to asynchronous function execution rather than using callbacks. And you'll get the result of the callback as the assignment to the yield expression!

Once you have a driver object, you can use driver.run as a way to kick off a series of commands inside a generator. Here you have access to the driver object as this, so you can do things like yield this.get("http://mysite.com").

It's relatively easy to break up bits of sessions between testcases and so on. Here's what a simple mocha test suite could look like:

var yiewd = require('yiewd');
 
describe('my cool feature', function() {
  var driver = null;  // driver object used across testcases 
 
  // global setUp, tearDown 
  before(function(done) {
    driver = yiewd.remote();
    driver.run(function*() {
      yield this.init(desiredCaps);
      done();
    });
  });
  after(function(done) {
    driver.run(function*() {
      yield this.quit();
      done();
    });
  });
 
  it('should do some thing', function(done) {
    driver.run(function*() {
      // test logic 
      done();
    });
  });
 
  it('should do another thing', function(done) {
    driver.run(function*() {
      // test logic 
      done();
    });
  });
});

Notice how you get a driver object from yiewd.remote(). You can hold onto this and later use driver.run() and pass it another generator which will take over execution for the driver. Easy!

Using monocle-js, you can compose your own custom automation behaviors:

var o_O = require('monocle-js').o_O
  , yiewd = require('yiewd')
  , driver = yiewd.remote();
 
var flow1 = o_O(function*() {
  yield driver.get('http://mywebpage.com');
  yield driver.elementByCss('a').click();
});
 
var flow2 = o_O(function*() {
  yield driver.elementByCss('input[type=text]').sendKeys("my text");
  yield driver.elementById('submit').click();
});
 
describe('my cool feature', function() {
  it('should do some things', function(done) {
    driver.run(function*() {
      yield this.init(desiredCaps);
      yield flow1();
      yield flow2();
      yield flow1(); // reuse flow1 
      done();
    });
  });
});

Often in WebDriver-land, you only want to find an element in order to do something with it. In those cases, it's a bit tedious to do something like this:

var el1 = yield driver.elementById('someEl');
var text = yield el1.text();
text.should.equal("hello world");

Of course, using Javascript™ we can already "chain" these calls:

var text = yield (yield driver.elementById('someEl')).text();
text.should.equal("hello world");

But we have this goofy double-yield business. So Yiewd lets you do away with it:

var text = yield driver.elementById('someEl').text()
text.should.equal("hello world");

We've got some special sauce so you can sauce while you Sauce:

var yiewd = require('yiewd')
  , driver = yiewd.sauce(userName, accessKey);
 
driver.run(function*() {
  yield this.init(desiredCaps);
  yield this.get('http://saucelabs.com/guinea-pig/');
  try {
    var title = yield this.title();
    title.should.include("I am a page title");
    yield this.reportPass();
  } catch (e) {
    yield this.reportFail();
  }
  yield this.quit();
});

Probably the pass/fail reporting would be handled in some kind of global tearDown method, of course.

Either:

  • Node >= 0.11.3 (one with generators)
  • Make sure you start your test runner with the --harmony flag; this might be non-trivial but for mocha, see below.

Or:

  • Any recent node

  • Run your test scripts through regenerator to get generator support, e.g.:

    regenerator my_test.js > my_test_es5.js && mocha my_test_es5.js

And:

  • For running tests: npm install -g mocha

Make sure you have your chromedriver-enabled Selenium server running, then:

mocha -R spec -t 60000 --harmony test/es6/

This is a simple wrapper around Wd.js that is really easy to maintain: (a) new methods from Wd.js can be added with one word in Yiewd, (b) there's nothing really to maintain beyond the generator glue which should stabilize quickly.

Give it a whirl and contribute bugfixes! Pull requests welcome. Biggest area of need right now is filling out our testsuite to make sure everything works correctly.