We all know the importance of the Test Automation Pyramid and why it makes sense to align various automations in this way. Given that guiding principle, end-to-end GUI tests sit at the top, with a considerably small number of tests compared to other types (Unit, Integration, Service tests), and they are useful to verify business workflows. In the book Agile Testing: A Practical Guide for Testers and Agile Teams, the authors explain the testing quadrants, the GUI tests’ fit in the grand scheme of things, how to rationalize intention, and be smart about overall Quality strategy.
Until some other technique evolves, webdriver-based GUI tests are the best choice today. When we’re trying to mimic the end-user journey through our application, these tests naturally represent an outside-in perspective. Testing from the surface of the application creates a vulnerability to many hidden factors, such as browser stability, browser performance, network speed, latency, sub-systems’ performance underneath the GUI, etc. Due to the many moving parts with every small action, GUI tests have a non-deterministic nature. How do we win the game? The following are the top 5 coding practices that helped me achieve success.
You’ll want to define responsibility for these layers as well (this post from Martin Fowler explains the process in detail).
Now that tests and page object responsibilities have been defined, what is the protocol between them? Where do we leave the logic to perform the action for the behavior defined by the test?
We could perform these steps right from the test. However, one of the OOP principles recommends that instead of asking the page object for data and then acting on it, we should “tell” the page object what to do. This principle enables us to move the logic (steps 1 through 3 above) into the page object and combine data and behavior. This tightly couples data and the logic that operates on the data. This co-location helps developers achieve better understanding, and allows for easier maintenance.
Now that the page object owns the logic and data, where should we do the assertions? Who is responsible for assertions, exception handling and reporting?
I would suggest keeping this responsibility with the test and let the page object action always return an object with necessary information, either to assert or handle an exception. I prefer assertion-free page objects. For example, the below page object action returns an error object on the exception and expects the test to handle exceptions, report, etc.
One of the common challenges is figuring out how to deal with the asynchronous behavior of an application. Webdriver offers implicit and explicit waits where you can wait for “x” seconds before proceeding; however, this is unreliable because we are waiting for that magic number of “x” seconds. While there are ways to handle this in the framework, I would suggest thinking about it from the end-user experience perspective. Remember that E-E GUI tests’ intention is to verify the app from the business/end-user standpoint. So, how long are we asking our end-user to wait between actions? If an expected element is not visible, clickable, or whatever the case may be after a certain point, bubble an exception back to the test and let the test fail consistently.
Since a page object represents composite actions, they can be very handy and reusable for many behaviors.
Depending on the stack, Lint the page object code, since it likely contains logic and data handling, etc. Good coding practices are critical.
Run Sonar analysis and set quality gates on this codebase. I would even suggest writing custom rules to prevent some traditional mistakes such as Thread.sleep(), path selectors, etc.
Overall, treat test code the same way you treat production code. Write test code like you’re developing an app, not letting your developers run your tests.