- Use page objects
- avoid protocol methods and prefer commands
- don't use timeouts unless you have a good reason
- use waitFor, waitForVisible etc
- avoid caching elements
- avoid arbitrary pause() calls
- use mocks
Protocol commands bypass nifty features of webdriverio. They're also painful to look at. If you must use them, do it in a page object.
(Maybe add an example here of what a protocol method is versus a webdriverio command. Something like 'click')
The page-object pattern is a useful abstraction to separate the ugly implementation details from the assertions your tests need to make. Page objects are a lot like a service layer. They're responsible for querying the DOM for elements, filling out forms, waiting for things, etc. This gives your test specs a clean api to work with, and can stay focused on making assertions. Page objects are also useful in that they can be reused in multiple tests.
Unless something typically takes LONGER than the default timeout (currently, 30s), avoid using timeouts. It won't make your tests any faster, but will allow them to fail falsely more often. If you must use a timeout, try to do it in a page-object instead of your .spec files.
Caching elements leads to stale element references. It also dirties up your test code with utility methods. Instead, use getters in page objects so when you interact with an element, you're always interacting with an "as up-to-date as possible" element.
bad:
function getButton() {
return browser.$('button');
}
//.. later
it('button is enabled', () => {
expect(button.isEnabled()).toBe(true);
})
better:
// pageObject.js
get button() {
return browser.$('button');
}
// test.spec.js
it('button is enabled', () => {
expect(pageObject.button.isEnabled()).toBe(true);
});
Avoid calling browser.pause(randomNumber)
in your test code. Pause is useful in that it's very easy to use, but it's also confusing when used in .spec files. Instead, wrap it in a page-object method like so:
// pageObject.js
waitForDialog() {
browser.pause(DIALOG_ANIMATION_TIME);
}
// test.spec.js
it('should close the dialog', () => {
page.someButton.click();
page.waitForDialog();
// assert something
})
By using it this way, the pause timeout value is localized in your page object and the waitForDialog
method name gives hints as to what is going on.
Mocks are incredibly important. Mocking API responses gives you full control over timing. Without this control, you give up a lot of stability in your tests and are at the mercy of network latency. Testing against live API's is important but that should be reserved for "live" or "user" tests.
Jasmine (currently) will swallow errors in beforeAll and afterAll and afterEach. Wrap contents of those methods in try catch.
bad:
beforeAll(() => {
pageObject.open();
});
better:
beforeAll(() => {
try {
pageObject.open();
} catch(e) {
console.error('error in beforeAll', e);
throw e;
}
});
TODO: ...