assaf / zombie

Insanely fast, full-stack, headless browser testing using node.js
http://zombie.js.org/
MIT License
5.65k stars 518 forks source link

Complete example using Mocha #776

Open wernight opened 10 years ago

wernight commented 10 years ago

Related to #772 , I was looking at examples on https://github.com/assaf/zombie/tree/master/test for a long example doing multiple interactions in a clear manner and I found that node has just so many ways to handle it, like promises, async/await, non-promises with done...

Looking at test/promises_test.js my understanding of promises was that it should avoid having to call done()/error(). Mocha is supposed to handle promises (not sure how yet). The calls to done()/error() puzzle me.

It seems to me the cleanest way to write most tests—so that if something fails it's reported, else my tests just don't report failures!!—is either using .should.eventually. of Mocha or async/await like on test/window_test.js.

If you have an example doing a few interactions using what you'd consider the recommended way:

  1. Visit this
  2. Click that
  3. Fill that
  4. Check this is displayed
  5. Click there
  6. Check that

Ideally that example would save the source code snapshot of the page where a test or a click/browser operation failed.

Even this may sound like a lot, I believe most people are trying to achieve something like that and that's what I used to do in Java+WebDriver+PhantomJS (with a screenshot), and you can find online, but it's not as fast/lean/clean as Zombie.

wernight commented 10 years ago

I found http://redotheweb.com/2013/01/15/functional-testing-for-nodejs-using-mocha-and-zombie-js.html which is not bad but not that good to report errors well.

Instead I found that the Promise version works fine, as long as you don't handle done(). For example this seems to handles errors well:

describe('something', function() {
  it('test', function() {
    return browser.visit('/')
      .then(function() {
        browser.fill(...);
      }.then(function() {
        assert ....
      };
    }
  }
}

The same written in CoffeeScript is pretty lean. Not as lean as your async example though.

mahnunchik commented 10 years ago

+1

wernight commented 10 years ago

Another good example in here is test/forms_test.coffee in CoffeeScript and some multi-step operations (but no beforeEach which is required is many tests as you want your tests to be independent).

Previously I used PhantomJS which allows to take screenshot, so I had the screenshot AND source code saved on test errors. That helped a lot! If I get the same working with Mocha (without screenshot) I'll post here a snippet.

I also didn't find any really good debugging system. Zombie probably doesn't have one and there is no way to have it run in Chrome/Firefox during development. One option though is node-inspector but you won't see the rendered page and that's a big slow down.

wernight commented 9 years ago

After making some tests I found two good patterns and one bad.

Bad pattern IMO is using (done) callback because it's easy to miss the error callback and then your tests will not correctly fail or may even never end. So I'd keep the function(done) {... done();} usages to a minimum.

Good patterns:

describe('Visit home page', function() {
  before(function() {
    return browser.visit('/');
  });
  describe('Click link', function() {
    before(function() {
      return browser.clickLink('a[class"add"]')
        .then(function() {
          return browser.clickLink('#something-else');
        });
    });
    it('should something', function() {
      browser.assert.text('title', 'something');
    });
  });
});
assaf commented 9 years ago

By design, these two are equivalent and you can use either one to chain multiple action/wait:

// Uses callback, any error will fail the test
before(function(done) {
  browser.clickLink('#something-else', done);
});

// Uses promises, any error will fail the test
before(function() {
  return browser.clickLink('#something-else');
});

In your example, if you replace the odd then with another before, you get code that looks and behaves the same, whether you write it to use callbacks or return promises. I find that easier because it's consistent in using before to sequence asynchronous steps.

Alternatively, just put everything inside one async function (which is how I use Zombie).

wernight commented 9 years ago

In that case it is, but for example it's easy for someone to write:

browser.clickLink('#something', function(err, b) {
  if (err) {
    done(err);
  }
  brower.clickLink('#something-else', function() {
    done();
  });
});

This obviously is missing err check and there is no warning when it doesn't fail. I believe it'll also fail to catch exception in some cases. And in my memory there were function(done_callback, err_callback) which makes it a lot harder.

assaf commented 9 years ago

Just from the messages I get for Zombie alone, it seems that people are equally good at failing to properly use promises, as they are at failing to properly use callbacks.