Skip to content

Instantly share code, notes, and snippets.

@jrobinson01
Last active February 22, 2019 00:04
Show Gist options
  • Save jrobinson01/1b220af76ec4aa8742eaa9206accf151 to your computer and use it in GitHub Desktop.
Save jrobinson01/1b220af76ec4aa8742eaa9206accf151 to your computer and use it in GitHub Desktop.
WDIO testing best practices

WDIO Testing Best Practices

Page objects

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.

Protocol commands

Protocol commands bypass nifty features of webdriverio (TODO: like what?). They're also painful to look at. If you must use them, do it in a page object. example:

bad:

it('clicks the element', () => {
  const el = browser.$('button');
  browser.elementIdClick(el.value.ELEMENT);
  expect(browser.elementIdText(el.value.ELEMENT).value).toBe('clicked');
});

better:

it('clicks the element', () => {
  pageObject.button.click();
  expect(pageObject.button.getText()).toBe('clicked');
});

Avoid guessing at timeouts

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.

Avoid caching elements

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 browser.pause()

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.

Use mocks

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.

Gotchas

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

Safari

The Safari webdriver doesn't work with element.isDisplayed(). This is being worked on. (TODO: is there a work-around?)

Differences between running local and remote (browserstack, saucelabs, etc)

It seems that running tests remotely takes about twice as long as running them locally. This is not including startup times. When deciding on timeout values, figure out the average local timeout, and double it.

Shadow DOM

Known issues: Firefox: mozilla/geckodriver#1414 attempting to call setValue('foo') on an input inside shadow DOM fails with an error. The workaround is to set the value manually via a browser.execute(fn..) call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment