Skip to content

Selenium

glenn-barker edited this page Mar 29, 2019 · 2 revisions

Selenium WebDriver is the module used for browser UI automation. As mentioned earlier, you will need to download the web drivers for your specific browser choice(s) yourself and ensure they are available in your PATH environment variable.

Browser Instantiation and Teardown

As listed above, all setup & teardown logic should happen in the /features/environment.py module using test fixtures.

When the browser window is created, a reference is stored in the scenario's context which is globally accessible across all step definitions. Also note that in this framework, rather than storing the Selenium driver reference directly, we create and store a new SeleniumWrapper object and pass that around via context.selenium. The SeleniumWrapper contains a reference to the underlying Selenium driver but also contains a number of other helpful fields and functions which are described later.

In this example framework, browser fixtures are controlled via the @fixture.browser tag in your scenarios or features. In an automation framework where you may have a mixture of UI (Selenium browser) and non-UI (API / DB / etc.) tests, it probably makes sense to use this kind of mechanism of allowing @fixture tags to control the browser usage. In a pure UI automation framework that only contains Selenium tests, it is probably redundant to be forced to include this type of tag everywhere, so you may instead modify /features/environment.py to just always use a browser fixture at the start of every scenario or feature.

Page Object Model and Element Locators

Page Object Model (POM) is a design pattern for UI automation where you define classes/objects for key pages and controls that contain functions for any type of operation a user would perform with that page or control.

POM - Pages

A few examples are provided under /pages/wikipedia_pages/. Note how a class is defined for each page, and each class contains the relevant types of actions or information you need to perform on that page.

In this framework, each page object is a child of the basic Page class defined in /pages/page.py, which is a very simple class that merely keeps a reference to the SeleniumWrapper object that was passed in during instantiation. In this way, you can code your BDD step definitions to construct page objects, pass in the context.selenium reference that was instantiated during the test startup in /features/environment.py, and then every child page object that you define will have a reference to the appropriate SeleniumWrapper object.

It's also a good practice to keep all of your locators (XPath, CSS selectors, etc.) for a given page object in one organized place, rather than strewn throughout the class. Each of the examples provided here show one way you can go about creating a simple subclass to contain the locator definitions underneath each page object class so that they can be referenced as needed.

POM - Controls

Contrary to the name, page objects do not only have to be created for full pages. It is also useful to define page objects for individual elements or controls that exist within a page. Some examples are provided in /pages/wikipedia_controls/. Obviously you don't need to do this in every case - for instance, a simple login page that only contains two text fields and a button probably doesn't need individual page objects created for each field. But in some cases, it is useful to split out certain elements or controls into their own page objects.

One good reason to do this is for shared controls that can exist on multiple pages. For instance, on Wikipedia, the home page as well as every individual article page all contain a search bar. Rather than defining the exact same type of search bar across both page objects, it's easier and more efficient to create a single page object just for the search control that can work across pages.

Another good reason is if the control in question is of sufficient complexity that putting all of the logic within a single page object class would make the code difficult to work with. For instance, the categories box that appears at the bottom of each Wikipedia article has some additional complexity over a normal web element that requires some additional functionality to be able to locate the main container and parse out the individual links within. While we could define all of this functionality just within the ArticlePage, it might make sense to break it out into its own class.

There is some subjectivity here and it's up to you as to whether you prefer very large page objects that fully encapsulate the implementation of all complex controls contained within, or whether you prefer smaller page objects with specific implementation details of complex controls elsewhere.

Explicit Waits

Waits are used in any UI automation to increase overall stability and to reduce the brittleness of your test. Rather than failing a test if an element cannot be found right away, it's better to provide the ability to wait for an element in case it takes some time to load or to become available. (On the flip side, you do not want to use hard coded sleeps as this introduces unnecessary slowness and wait time into your automation.)

Selenium already provides a wait mechanism along with some expected conditions, but as you find yourself coding the same boilerplate code to create these wait objects and pass in the appropriate timeouts over and over again, support for this has been rolled into the SeleniumWrapper class. If you need access to a wait object, simply reference the wait or load_wait (the former for shorter waits where it's generally expected that the page is already loaded and in a ready state, the latter when the page may still be loading and a longer timeout is desired) attributes from the selenium attribute that every page object has access to.

Even better, the logic for using wait and the expected conditions to verify that an element is visible and ready to be interacted with has also been rolled into SeleniumWrapper. Simply call find_visible_element(by) and all of the logic of using explicit waits, expected conditions, and returning the element reference will already be handled for you.

Clone this wiki locally