Automated QA Setup step by step
Part 2 — e2e tests
We built FASHION CLOUD with AngularJS so the choice for e2e testing was pretty clear… Protractor.
Most of us didn’t have much experience with automated testing, neither with Selenium or any similar tool (does POSTMAN count?). So we started the path of research, which kind of patterns were used in the industry, good practices and how to structure our tests.
We found this amazing Protractor styleguide video which turned into the official style guide, and gave us a lot of insight about how and why we should structure our tests. Things like “don’t e2e test what’s been unit tested”, “don’t mock unless you need to” and “NEVER EVER use xpath” are a good starting point for anyone that wants to write e2e tests regardless the technology choice.
They also talk about the page object pattern, which is a pretty common practice in Selenium and it basically consists on wrapping each page of the app into an object, defining a clear interface to interact with the DOM, and isolating the pages from each other. Also don’t write assertions on your object, no expects neither waitings or sleeps. Please try to not use these! we need another post to discuss the cons of using sleep, but if you’re curious about how to avoid it check this other post. All of the test related behavior just breaks the encapsulation of each page and tightly couples the abstraction to where it is being used.
This pattern is really cool because it makes your tests really readable, if we need to construct a workflow it’s just a matter of doing some calls to the page objects API and that’s it, no complicated waitings, no “if”s, no “forEach”.
After taking a look at the guide and deciding what made sense for our project, we came up with this folder structure
/qa
- package.json
- /seed
- /e2e
- package.json
- /config
- /wrappers
- /pages
- /main-interactions
If you don’t know what that seed folder is about, you should check the first part
So what are those folders?
config
config
holds global configurations for protractor, environment settings and common things like default timeouts, most of these are just exported consts.
/config
+ config.ts
+ environment.ts
+ timeout.ts
+ index.ts
pages
The pages
folder is probably the most important one, it holds our "page models". Each model is an abstraction that represents an actual page in the platform and it exports a clean interface that shows how a user could interact with it.
/pages
/signin
+ signin.page.ts
+ signin.spec.ts
/home
/products
...
Let’s dig into the signin
folder
The class has a reference to each DOM element in the page, the email input, the password input, the keep signed in checkbox and the submit button. It also provides a simple signin method that explicits the action that the user will perform and prevents us from writing the same login code over and over again for every page.
Let’s continue with.. oh wait.. what is this “data-e2e-hook” magic about?
There’re a lot of ways to reference an element in Protractor (by.tagName, by.model, by.repeater, etc etc), but 2 things happened:
- we’re on our way to migrate to Angular (2, 4, 5 already lost count), this means that some of those methods like by.model are simply not working.
- some elements don’t have a special tag or a model attached, it’s just plain HTML with CSS classes. We started doing these kind of things:
by.css(‘div.login-wrapper > form button.submit’)
No need to say that the complexity of maintaining these selectors was horrible. The tests were dependent on the current design implementation or CSS frameworks we used, any slight change in the HTML structure broke the tests.
We took advantage of the data-attribute (which is HTML compliant) to solve this problem, using the extra data as an id to locate the element by css.
We decided to write our tests using Jasmine because is the default for Protractor and most (all) of the docs use it. It’s also quite hard to find community support for other test frameworks like Mocha.
Take a look at the spec file for the signup page.
Every test looks like the sequence a user would do and it’s easy to see in which context or page this is happening. Pretty cool huh?
wrappers
You might’ve noticed these guys in the previous code
import { Router, Header } from '../../wrappers';
This is a pretty interesting idea taken from the Protractor’s style guide, this wrappers folder contains classes that represent components that are always available to interact within the app such as the header, the footer and the nav bar.
Let’s take a look at the header in Fashion Cloud
The header is visible on every page while logged in and it shows 2 buttons, the shopping cart, that let’s you show or hide the list of images you picked and want to download, and the logout button.
How does this look in a wrapper class?? Well, pretty much the same as a page object.
We created a reference to every DOM component inside the header that’s interesting to test and we defined a simple interface to interact with them.
Another useful wrapper we defined is the Router which is not a real component, but it semantically represents the user typing the url in the browser and accessing directly to a page. It comes in handy to route Protractor to the first page, which is usually the signin, in every test.
main-interactions
Last but not least we have the main-interactions folder, here we keep those complex workflows that do not fit in only one page, they usually require several user actions and navigations through the platform.
In our business logic we have the concept of an “access request”, which represents a retailer’s attempt to access some brand’s data, for example their marketing material or product data. A brand can block certain retailers, or all of them, to get access to their content. So when a retailer asks for access to a brand, one access request is created and the brand can then accept or reject the request in the rights management page.
As you might see there’re several steps involved here:
- We need to login as a brand
- Search for a retailer and block it to let the retailer ask for permissions
- After that we need to logout
- Login as the blocked retailer
- Find the brand
- Request access to that brand.
- Logout again
- Login as the brand user
- Go to the rights management page and check that the request is there.
I’m almost tired of just writing all the steps.
But.. if we take a look at the test itself it doesn’t seem scary at all!
There you go, all of the steps written in an automated test which actually looks pretty similar to what we described with words. Using this pattern we know exactly the context of where and what is going on, which makes the test pretty easy to follow, understand and change if necessary.
If you’re curious about how we run these tests in a continuous integration environment, the next post’s coming soon :)