Note: The code samples may be displayed improperly because of markdown. I recommend continuing to read the original article on our blog to make sure all the examples are displayed properly.
During the course of my career in automation testing with Selenium, I have come across many folks who have complaints about the stability and reliability of their automation tests. In most cases, the logic used in the implementation of test scenarios was spot-on, but the design and scalability were a matter of concern. This is a common sight for anyone who has sailed through the waters of Selenium test automation☺.
After years of working with the Selenium framework, I have realized that the ‘One size fits all’ approach does not apply to Selenium test automation. Though there are no thumb rules for the design and development of scalable automation tests, there are certain principles that you must follow when writing tests using the Selenium framework. The principles can also be termed ‘Selenium best practices’.
In this blog post, we discuss the top 16 Selenium best practices for Selenium test automation which might help you develop well-designed and scalable test suites (or test cases).
A majority of the Selenium best practices discussed subsequently in the blog are agnostic of the programming language being used for Selenium test automation. So, regardless of the language you use for automation testing with Selenium, these Selenium best practices will still be of value.
It is widely known that the behavior of web applications (or websites) depends on many external factors such as network speed, device (or machine) capabilities, access location, load on the back-end server, and more. These factors make it quite challenging to predict the actual time taken to load a specific web element. Here, adding a delay (or timeout) before performing any actions on the web element will delay the execution while allowing the particular web element to load.
Delay achieved using a blocking sleep call (e.g., Thread.sleep in Java, time.sleep in Python) blocks the test thread for the specified number of seconds. For a single-threaded application, it will block the thread and effectively the process as well. Blocking sleep calls are instrumental in adding the required delay, but the duration of delay depends on numerous factors. There is no guarantee that the delay being added will work all the time. For example, if you have added a delay of 5 seconds, the specified web element does not load even after 10 seconds.
driver = webdriver.Chrome() driver.get("https://www.lambdatest.com/") time.sleep(5000)
In the code snippet (in Python) shown above, after the test URL is loaded, a blocking sleep of 5 seconds has been added. What if the web elements on the page are successfully loaded within milliseconds? The subsequent delay of 5 seconds not only elongates the duration of the test cycle but may also cause stability issues with the UI automation tests.
Imagine the amount of delay if the above test snippet has to be executed 5000 times (on different web browsers)!
What is the potential alternative to blocking sleep calls? Selenium provides Implicit wait and Explicit wait that handle delays more efficiently than sleep. Implicit wait informs the browser to wait for a specified time duration for all the web elements present on the page. If the element is available faster than the implicit delay time, the execution moves to the next line of code execution. For example, if an implicit wait of 10 seconds is added for a specified element but the element loads in 3 seconds, the script does not wait for the remaining 7 seconds.
On the other hand, the explicit wait is another type of dynamic Selenium wait that is used to stop the script execution on a particular condition for a specified duration. WebDriverWait and ExpectedConditions can be used to achieve condition-based waits.
In the code snippet shown below, we wait for the web element (with linktext as SITEMAP) to appear on the page. If the web element is not present, an exception is thrown.
driver = webdriver.Chrome() driver.get("https://www.lambdatest.com/") timeout = 10 try: element_present = EC.presence_of_element_located((By.LINK_TEXT, 'Sitemap')) WebDriverWait(driver, timeout).until(element_present) except TimeoutException: print("Timed out while waiting for page to load")
When working in a team, there are cases where your team members may be required to enhance the tests that you had written. If you revisit the same test after a couple of months, you might not be able to figure out the purpose of a test, until you go through the complete implementation.
If some tests have failed during the execution stage, it should be easy to figure out which functionalities are broken by just taking a quick look at the test name. These problems can be easily fixed by giving naming test cases in a manner that they are self-explanatory so that neither you nor your teammates spend time unnecessarily scrolling through the implementation.
Sometimes during the Selenium test automation process, you may notice that the test implementation is not working correctly on specific browsers. This is typically the case when performing cross browser testing on outdated browsers like Internet Explorer.
Irrespective of the web browser on which automation testing with Selenium is performed, setting the browser zoom level to 100 percent is one of the Selenium best practices that should be followed. This setting gives a native mouse feel and ensures that the native mouse events are set to the correct coordinates.
Along with this setting, Protected Mode Settings (in Internet Explorer) for each zone must be the same else you may end up getting a NoSuchWindowException exception.
One of the first actions performed by a tester for Selenium test automation is taking the web page’s screenshot. Screenshots are taken during testing the process to help developers in debugging issues and help key stakeholders track product development progress. Screenshots also help detect whether test failure is due to application failure or problem in the test script being used for automation testing with Selenium.
By default, Selenium does not open the browser window in the maximized mode. This can affect the screenshot (or snapshot) of the web page that is typically attached in test reports. Maximizing the browser window immediately after the test URL is loaded ensures that a screenshot of the entire web page is captured.
This is one of the Selenium best practices that should be followed irrespective of the browser on which Selenium test automation is performed. We have a detailed blog on using Selenium WebDriver for full-page screenshots, if you’d like to dig deeper.
One of the challenges with Selenium test automation is that automation tests have to be modified if there are changes in the implementation related to locators used in the test code. ID, Name, Link text, XPath, CSS Selector, DOM Locator, etc. are some of the frequently used web locators in Selenium WebDriver.
With so many web locators, it is necessary to choose the right one to minimize the impact on tests caused due to changes in the user interface. Link Text is usually preferred if there is a dynamic situation. ID, Class, and Name are not only the easiest to use but also less brittle than other web locators.
For testing internationalized applications, we might not be able to use LinkText or partialLinkText if the anchor tags do not contain any ID or class. For localization or internationalization testing, partial href should be used so that even if the language on the site changes, the href link still points to the same location.
The ideal web selector order is: id > Name > CSSSelector > XPath. Here is an interesting thread on StackOverflow with an insightful discussion on performance aspects of popular web locators for automation testing with Selenium.
Cross browser testing is a challenging task as you need to prioritize testing on different browser + OS combinations. If we include browsers and their browser versions, it would add up to a vast number. Formalizing a list of (browser + OS + device) combinations is of prime importance as it would help in prioritizing the combinations that have to be taken up for cross browser testing. This formalized list is also called Browser Matrix or Browser Compatibility Matrix.
Browser Matrix is vital information drawn from product analytics, geolocation, and other detailed insights about audience usage patterns, stats counter, and competitor analysis. Browser Matrix will help cover all the relevant browsers (that matter to your product), thereby reducing the development and testing efforts. A sample Browser Compatibility Matrix is below:
The template for creating a Browser matrix is available in the following location.
If a particular test in an extensive test suite fails, it can become challenging to locate the failing test case. Logging can be a huge savior in such cases as console logs at appropriate places in the test code help develop a better understanding of the code and help in zeroing on the problem.
Some of the popular log levels (available in popular programming languages) debug, info, warning, error, and critical. Adding unnecessary logs in the test implementation can cause delays in the test execution. Hence, it is recommended to add logs with level error (and/or critical) in scenarios that aid in tracking the cause of failure.
Along with logging, reporting is an integral part of Selenium test automation as it helps in determining the status (pass/fail) of the tests. This is where automation test reports can play a huge role as it helps in keeping track of the progression of test suites (or test cases) and corresponding test results. Reports also help minimize the time required for the maintenance of test data due to improvement in the readability of the test output.
Analyzing features and accessing test coverage becomes easy with automation test reports.
Selenium test automation without logging and reporting defeats the sole purpose of using the Selenium framework. That is why logging and reporting are considered one of the best Selenium test practices in automation.
When writing Selenium test automation scripts, you must keep a check on its maintainability and scalability. This is possible if changes in the web page UI requires minimal (or no) changes in the test script. Suppose the scripts are not appropriately maintained, i.e. different scripts using the same web element. In that case, whenever there is a change in the web element, corresponding changes must be made at multiple places in the test script.
This is where Page Objects, a popular web UI automation pattern, comes handy as it enhances test maintenance and reduces code duplication. In-Page Object Model (POM), a centralized object repository is created for controls on a web page. The web page is implemented as a separate class. Hence, every web page being tested will have its corresponding page object class.
This eases code maintenance as Selenium automation scripts do not directly interact with the page’s web elements. Instead, a new layer (page class/page object) resides between the test code and controls on the web page.
Along with better maintainability, using POM in automation testing with Selenium helps in reducing the code size as page object methods defined in different page classes can be reused across multiple Selenium test automation scripts.
Leveraging Page Object Model is one of the Selenium best practices that can aid in:
- Improving test maintenance
- Minimizing code changes due to updates in product UI
- Enhancing code reusability
- Simplifying the visualization and model of the web page under test
Shown below is a sample directory structure for using Page Objects in a Selenium test automation project.
Behavior Driven Development, popularly called BDD, is a popular development approach that helps in writing test cases in plain English language (called Gherkin). This means that along with developers and testers, members with minimal (or no) technical know-how can participate in the development of tests.
BDD frameworks help in filling the void between business people and technical people as all of them get the opportunity to work on the enhancement of the tests. Gherkin files created for BDD testing consist of a combination of features, steps, and scenarios, along with relevant Gherkin keywords such as Given, When, Then, etc. The format of feature files and keywords being used is uniform irrespective of the BDD framework being used. This makes it easier to shift from one BDD framework to another as the learning curve is very low.
As business and technical people are on the same page, it helps in improving the product quality as tests are based on technical and business recommendations. BDD tests are more usable when compared to TDD tests as changes in business specification or feature specification would involve minimal changes in corresponding BDD features and scenarios. When compared to TDD (Test Driven Development), BDD tests have an improved shelf-life as tests are created using business and feature specifications. It is one of the most essential Selenium best practices out there. Some of the popular BDD frameworks are Cucumber, Behave, SpecFlow, etc. Shown below is a sample feature file that searches for LambdaTest on DuckDuckGo:
Feature: LambdaTest search Scenario: Search for LambdaTest on DuckDuckGo Given I am on the DuckDuckGo homepage When I enter search term as LambdaTest Then Search results for LambdaTest should appear
We have covered Business-Driven Development with Gherkin and Behave BDD framework earlier, if you’d like to explore this section more.
When working on tests that use the Selenium framework, it is essential to focus on the test code’s maintainability. A standard project can consist of Src and Test folders. The Src folder can contain sub-directories that contain Page Objects, Helper functions, and file(s) that contain web locator information used in test scenarios. The Test folder can include the actual test implementation.
We don’t have a standard rule when it comes to a directory structure for Selenium test automation. However, Selenium best practices recommend us to have a directory structure that separates the test implementation from the test automation framework. This helps in better organization of the test code.
A website (or web application) should be tested against different combinations of browsers, devices, and OS combinations (i.e., multiple datasets). Hard coding of test values in test automation scripts is not a scalable solution as it would lead to unnecessary bloatware and possible repetition of test code.
A better solution is using parameterization for achieving data-driven automation testing with Selenium. Parameterization helps in executing test cases against different input combinations (or data sets). More extensive the data set, the better is the test coverage. This, in turn, helps in improving product quality and implementing good Selenium test practices.
WebDrivers in Selenium are not interchangeable. The situation of executing automated cross browser tests on a local machine is entirely different from being executed on a continuous build server. In that environment, it will be wrong to assume that the next test will use Firefox WebDriver (or Chrome WebDriver or any other WebDriver).
When integration tests are executed are in a continuous build environment, the test will only receive a RemoteDriver (i.e., WebDriver for any target browser). Amongst all Selenium best practices, it is recommended to use Parameter notes to manage different browser types and get the code ready for simultaneous execution (or parallel testing). Small frameworks can be created in Selenium using LabelledParameterized (@Parameters in TestNG and @RunWith in JUnit).
This practice will be useful in ensuring that the implementation is flexible enough to work with different browser types.
The automation test design solely depends on the goal that you are planning to achieve from the test. Things can get complicated when the test suite consists of several tests. One of the most critical Selenium best practices is to avoid inter-dependency between different tests in a test suite and separating the specification of what the test is supposed to do from the test execution strategy.
The other major advantage of using autonomous tests is you can explore parallelism to expedite the test execution. If tests are dependent, the outcome of one test also affects the second test. Hence they cannot be executed in parallel as they are largely interconnected. You could make use of relevant decorators & markers (e.g., @pytest.mark.incremental decorator, xfail marker in PyTest) in case you still cannot avoid having dependency between tests and want to skip execution of a test if its dependent test has failed.
However, it is recommended to come up with autonomous tests (whenever possible). Otherwise, you might miss the opportunities to leverage Selenium (and test framework) that can do wonders to the test execution!
There are numerous cases in automation testing with Selenium, where you would want to halt the test execution on encountering a hard error. For example, you are using Selenium to automate testing of the Gmail login page, but the web locator being used for locating the sign-in box is not correct. In this case, assert should be issued as the remaining tests will falter out as they are dependent on the sign-in page.
Asserts should only be used when you want to halt the test execution in case of a hard failure (like the one mentioned above). If the assert condition is false, execution stops, and further tests will not be executed. On the other hand, Verify should be used where the criticality of the error is low, and you still want to proceed with test execution irrespective of the status of Verify condition.
One of the most common Selenium best practices for Selenium test automation is avoiding unnecessary duplication of code. You might be using different web locators such as XPath, ID, etc. for accessing the web elements present on the web page. The code that is frequently used in the implementation should be created as a separate API, so that code duplication is minimal.
Avoiding duplication also helps in the reduction of code size and improves the maintainability of the test code. Wrapping Selenium calls is one of the Selenium best practices that can significantly impact maintaining a complex test suite (or test code).
A primary factor for Selenium’s popularity is its support for parallel testing. Almost all popular test frameworks such as PyTest, PyUnit, TestNG, Cucumber, etc. provide features for executing tests in parallel on a Selenium Grid.
Parallel testing in Selenium lets you execute the same tests simultaneously on different environments (i.e., a combination of browsers, platforms, and device emulators). Using Selenium, it is recommended to enable parallel testing in the implementation as it reduces the test execution time by a significant margin.
Developers and testers can use a cloud-based Selenium Grid like LambdaTest that lets you perform automated browser testing on 2,000+ real browsers and operating systems online. The platform supports all popular test frameworks. The Grid can be further leveraged to improve the performance of parallel tests as execution is performed on a highly scalable and reliable Selenium Grid.
Bonus Tip – Now that we have looked at the top 16 Selenium best practices, it is time, we also deep dive into some of the worst Selenium practices that should be avoided when performing automation testing with Selenium!
In the section above, I listed down the most essential Selenium best practices to assist you in automation testing with Selenium. Now, I present some of the Selenium practices that you should avoid at any cost:
Downloading a file on any web platform is initiated by clicking on a link (or a button) that prompts users to download the file. The same operation can be automated using Selenium, but the downside is that API does not show any progress regarding the file download. Hence, you would not be aware of whether the download functionality is being tested or not.
Moreover, downloading files is not an essential aspect of testing user interaction with the web platform. Instead, the download link located using Selenium (and any required cookies) should be passed to an HTTP request library like libcurl.
CAPTCHA or ‘Completely Automated Public Turing test to tell Computers and Humans Apart’ is specifically designed to check whether a human is performing the necessary operation. In short, it is designed to prevent automation.
Disabling CAPTCHAS in the test environment and adding a hook for bypassing the CAPTCHA are the two strategies that can be used for getting around CAPTCHA checks in the test environment.
Two-factor authentication (2FA) is a security mechanism where an OTP (One-Time Password) is generated using Authenticator mobile apps. Gmail has an authenticator app that will make random codes to authenticate whether the actual account holder is trying to login to a new system (or app).
Automating 2FA in Selenium can be a considerable challenge, and it might not guarantee performance. It is better to avoid automating 2FA in test environments. You also have the option of disabling 2FA for a specific set of users to use their credentials (on any test system). Alternatively, you can also configure test machines so that 2FA is disabled for a certain family of IP addresses (or selected IP addresses).
Web crawling (or spidering) is performed using a web crawler, which is an automated script for browsing the Internet in a systematic and automated manner. Selenium is not designed to spider links, as it requires time to startup, and traversal through the DOM might take a few seconds (or even minutes).
As the time taken is variable, Selenium should never be used for spidering. Instead, use curl commands or libraries such as BeautifulSoup for crawling the web.
Using automation (with Selenium or other test frameworks) for logging in to Gmail, Facebook, or any such websites is against their policies. Secondly, the testing can be highly unreliable.
Instead, the developer can use the third-party APIs provided by the corresponding website as it is more reliable, less subject to change, and more secure to use. Using Selenium automation for login purposes (on these websites) should be avoided at any cost.
Parallelism is an integral part of cross browser testing (and Selenium automation testing) as it aids in expediting the test execution. However, the execution sequence is not guaranteed.
The development of inter-dependent tests should be completely avoided as it is a bad practice and does not guarantee reliability. The test suite might pass in one test execution, whereas the same test suite can fail in the next cycle. The practice of autonomous test case design should be followed when coming up with Selenium automation test cases (or test suites).
Selenium framework is designed for automation testing. Performance testing using Selenium and WebDriver is not recommended, as it is not optimized for doing that task and you might not get the expected results.
On a website (or a web platform), there are many external factors such as browser startup speed, the speed at which HTTP server response is received, etc. that are beyond the tester’s control. Rather than choosing Selenium WebDriver for performance testing, you should select ready-made tools that are specifically designed for performance testing.
In this detailed article, we looked at some of the Selenium best practices and selected worst practices for automation testing with Selenium. When coming up with Selenium test scenarios, you should always remember that Selenium is ideal for automation testing, so do not use the same for other types of testing since it might read favorable results. With LambdaTest, you can perform automated testing with Selenium for 2000+ browser and operating systems.
If you come across some more Selenium best practices or poor practices, do add the details in the comments section, and we would append the same to the article.
Happy Testing, Stay Safe. Stay Home ☺