Whilst being involved with lots of people writing automated acceptance tests using tools like SpecFlow and WebDriver I’ve seen some ‘anti-patterns’ emerge that can make these tests non-deterministic (flaky), very fragile to change and less efficient to run.
Here’s five ‘anti-patterns’ I’ve seen and what you can do instead.
Anti-pattern One: Not using page-objects
Page objects are just a design pattern to ensure automated UI tests use reusable, modular code. Not using them, eg, writing WebDriver code directly in step definitions, means any changes to your UI will require updates in lots of different places instead of the one ‘page’ class.
Bad
[When(@"I buy some '(.*)' tea")]
public void WhenIBuySomeTea(string typeOfTea)
{
Driver.FindElement(By.Id("tea-"+typeOfTea)).Click();
Driver.FindElement(By.Id("buy")).Click();
}
Better
[When(@"I buy some '(.*)' tea")]
public void WhenIBuySomeTea(string typeOfTea)
{
MenuPage.BuyTea(typeOfTea);
}
Complicated set up scenarios within the tests themselves
Whilst there’s a place for automated end-to-end scenarios (I call these user journies), I prefer most acceptance tests to jump straight to the point.
Bad
Scenario: Accept Visa and Mastercard for Australia
Given I am on the home page for Australia
And I choose the tea menu
And I select some 'green tea'
And I add the tea to my basket
And I choose to checkout
Then I should see 'visa' is accepted
And I should see 'mastercard' is accepted
Better
This usually requires adding some special functionality to your app, but the ability for testing to ‘jump’ to certain pages with data automatically set up makes automated tests much easier to read and maintain.
Scenario: Accept Visa and Mastercard for Australia
Given I am the checkout page for Australia
Then I should see 'visa' is accepted
And I should see 'mastercard' is accepted
Using complicated x-path or CSS selectors
Using element identification selectors that have long chains from the DOM in them leads to fragile tests, as any change to that chain in the DOM will break your tests.
Bad
private static readonly By TeaTypeSelector =
By.CssSelector(
"#input-tea-type > div > div.TeaSearchRow > div.TeaSearchCell.no > div:nth-child(2) > label");
Better
Identify by ‘id’ (unique) or ‘class’. If there’s multiple elements in a group, create a parent container and iterate through them.
private static readonly By TeaTypeSelector = By.Id("teaType");
Directly executing JavaScript
Since WebDriver can directly execute any arbitrary JavaScript, it can be tempting to bypass DOM manipulation and just run the JavaScript.
Bad
public void RemoveTea(string teaType)
{
(driver as IJavaScriptExecutor).ExecuteScript(string.Format("viewModel.tea.types.removeTeaType(\"{0}\");", teaType));
}
Better
It is much better to let the WebDriver control the browser elements which should fire the correct JavaScript events and call the JavaScript, as that way you avoid having your ‘test’ JavaScript in sync to your ‘real’ JavaScript.
public void RemoveTea(string teaType)
{
driver.FindElement(By.Id("remove-"+teaType)).Click();
}
Embedding implementation detail in your features/scenarios
Acceptance test scenarios are meant to convey intention over implementation. If you start seeing things like URLs in your test scenarios you’re focusing on implementation.
Bad
Scenario: Social media links displayed on checkout page
Given I am the checkout page for Australia
Then I should see a link to 'http://twitter.com/beautifultea'
And I should see a link to 'https://facebook.com/beautifultea'
Better
Hide implementation detail in the steps (or pages, or config) and make your scenarios about the test intention.
Scenario: Social media links displayed on checkout page
Given I am the checkout page for Australia
Then I should see a link to the Beautiful Tea Twitter account
And I should see a link to the Beautiful Tea Facebook page
I hope you’ve enjoyed these anti-patterns. Leave a comment below if you have any of your own.