How to Create Selenium WebDriver Screenshots and Integrate With Jenkins
Learn how to take screenshots of your automated tests using the Selenium API and save these screenshots by integrating with your Jenkins build.
Join the DZone community and get the full member experience.
Join For FreeFrom my own experience, getting screenshots from an automation framework whenever an automated test has problems or points of failure is a very helpful feature. This ability can visually indicate a bug or malfunction in the application's code, a problem in the design of the test, or other types of irregularities. After all, it is a known fact that GUI tests sometimes have stability problems. Integrating this ability into a continuous integration environment ensures bugs are found every time a change is committed.
Selenium's API provides the necessary code for adding this capability. In this blog post, we will cover the steps you need to take to achieve this. We will start from the top and go through the steps you need to take until you are able to see all the screenshots taken during a build. They will be saved in the archive of the corresponding Jenkins build.
Before going straight into the subject, I would like to enumerate part of the benefits of running automated tests for a project in a Continuous Integration environment. As you may already know, Continuous Integration and Delivery is the practice of building and testing software each time a developer has pushed code changes to the application. The first benefit comes out of the previous sentence:
- The code is being tested every time a change is committed, instead of daily, weekly or on demand. This helps find bugs or malfunctions in the application early on.
- Another benefit is that this practice eases the work within a team, as multiple developers can commit changes at the same time without having to worry about stepping on each other's feet.
- CI reduces the manual work and also highly increases the chances of finding integration problems and bugs before the new code reaches the production environment.
- Having a functional Continuous Integration Environment allows the practice of integrating more often with the master code of the application, meaning less effort, time and headaches when testing. The reason behind it is that the longer the developers wait without integrating their changes and testing the results, the greater the chances of integration conflicts and potentially newly introduced bugs.
Once there is a basic system in place that allows for CI, continuous deployment and running automated tests - multiple features or enhancements can be added to it in order to get the most out of the automated tests.
For the purpose of this blog post, I will use an automation framework that uses Selenium — Maven — JUnit — Jenkins to deploy and run tests (written in Java) for a Java web application. If you need help setting up this type of setting framework, follow the steps in this article.
Let's get started.
Go to the code of the Selenium framework to an interface named TakesScreenshot:
This public interface, which belongs to Selenium's API, contains the method getScreenshotAs, This method has to be implemented in a class defined by the software engineer, which respects the principle of interfaces and therefore implements it. The engineer can decide on her/his own how the method is going to work, according to her/his needs.
Keep in mind what we are trying to achieve: taking a screenshot every time a test fails and seeing all the screenshots listed in the output of the Jenkins build, saved in the "Artifacts" folder. Therefore the way we implement the method from the provided interface has to reflect that.
Usually, testing frameworks have a class created by the software engineer where all the configuration and global methods are defined. Search within your project where that class is located - that's where this method is going to be implemented as well.
private void takeScreenshot(String className, String method, LocalTime timestamp) {
if (driver instanceof TakesScreenshot) {
TakesScreenshot screenshotTakingDriver = (TakesScreenshot) this.driver;
try {
File localScreenshots = new File(new File("target"), "screenshots");
if (!localScreenshots.exists() || !localScreenshots.isDirectory()) {
localScreenshots.mkdirs();
}
File screenshot = new File(localScreenshots, className + "_" + method + "_" + timestamp.getHour() + "." + timestamp.getMinute() + ".png");
FileUtils.moveFile(screenshotTakingDriver.getScreenshotAs(OutputType.FILE), screenshot);
logger.info("Screenshot for class={} method={} saved in: {}", className, method, screenshot.getAbsolutePath());
} catch (Exception e1) {
logger.error("Unable to take screenshot", e1);
}
} else {
logger.info("Driver '{}' can't take screenshots so skipping it.", driver.getClass());
}
}
After you've found the class that contains other general methods used throughout the tests, import the " TakeScreenshot " interface and start implementing the method "takeScreenshot". The implementation above can be used as an example or starting point.
A very good practice is to link the name of each screenshot to the test it belongs to, in order to be easily identified in the "target" folder. I also recommend adding a unique suffix or prefix in case there are more screenshots captured during the same test.
For these two reasons, in the implementation above, the name of each file results from concatenating the following string type parameters:
private void takeScreenshot(String className, String method, LocalTime timestamp)
- where,
- className — name of the class where the test is defined
- method — the name of the test (method)
- timestamp — value taken from the java virtual machine's local time
You can use the same implementation for creating the unique name of the file or another one which suits the project better.
Another important variable that has to be defined and instantiated is the driver, in this example, it is declared as an instance variable and used in all methods from that class.
protected WebDriver driver;
This is needed because there are different drivers for running the automated tests but not all of them have the capacity to take a screenshot during the test (e.g.HtmlUnitDriver, which doesn't render the page at all). Therefore we need to tell the program which driver we are using and that it is capable to record a screenshot:
if (driver instanceof TakesScreenshot) {
TakesScreenshot screenshotTakingDriver = (TakesScreenshot) this.driver;
The next step is to create the folder "screenshots" inside the existing folder "target", which is by default generated by the maven plugin at build time. This is the folder that will be copied by the Maven plugin onto the machine where Jenkins is installed and packaged at the end of the build as an artifact, thus giving us the possibility to see all the screenshots taken during a run of the automated tests.
File localScreenshots = new File(new File("target"), "screenshots");
if (!localScreenshots.exists() || !localScreenshots.isDirectory()) {
localScreenshots.mkdirs();
The next part of the code in the method is the one that saves the name of the screenshot in a very useful way, helping the software engineer identify which file belongs to which test. The extension used in this implementation for the image files is "png". It's a personal choice, because of its reduced size compared to other types of extensions, but it's not mandatory for this one to be used.
With the name of the file created, the next line of code is the one that actually performs the action of taking a screenshot of the browser running at a given moment of time during a test and moves the newly created file into the destination folder.
FileUtils.moveFile(screenshotTakingDriver.getScreenshotAs(OutputType.FILE), screenshot);
The method ends with several pieces of information passed through the logging framework that can be useful for the software engineer . For this example, I used log4j, which is a Java-based logging utility from Apache. The information passed are messages for a successful or failed screenshot action, and a message for notifying the engineer in case the driver used in the tests doesn't have the capability of taking screenshots. You can read more about the log4j framework here and about the methods its API provides here.
Remember that we've defined the method takeScreenshot inside a test base class. The reason behind this is that we want the feature of taking screenshots to be applied for all the failed tests in a project. This will happen through the common practice of the test base class being extended by all the Java classes containing tests.
If you want to capture a screenshot of all failed tests, the failed method from JUnit has to be overridden with the method takeScreenshot. To do that, we will use rules. Rules are a new feature, introduced starting with JUnit 4.7.
Rules provide the capability to intercept test methods and do useful things, like setting general timeouts, using temporary folders or better handling exceptions (all of these are predefined rules), before or after the actual test execution. They are defined within a Java project using the @ annotation. The JUnit framework comes with some predefined rules (some mentioned above) - one of them is @TestWacher, which allows you to log the status of each test as it runs and override the existing methods, in case of need.
So in this case, we will use TestWatcher to override the failed tests with takeScreenshot.
@Rule
public TestWatcher testWatcher = new TestWatcher() {
@Override
protected void failed(Throwable e, Description description) {
takeScreenshot(description.getTestClass().getSimpleName(), description.getMethodName(), currentTime);
}
You're now done setting up Selenium in your framework. Let's move on to integrating Selenium artifacts into Jenkins.
Now that we have everything in place: the code, the settings and the needed plugins, all we need to do is to tell Jenkins to save the artifacts for each build.
This is done via the "Post-build actions" section in Jenkins configuration.
Here are the steps:
- Go to the Jenkins plan where the tests are being run
- Click on Configure and navigate to "Post-build actions"
- Select "Archive the artifacts"
- Here we have 2 options: save either the .png files or all files under the "artifacts" folder, so the screenshots will be displayed inside the folder. Every time the test is run you can go to the folder and look at the screenshots.
If you chose to archive the entire folder structure and content, Jenkins will provide the possibility to download the artifacts as an archive or navigate through them in the Jenkins UI.
All set and ready to go! Now you have a functional and proper integrated system which can enhance the results of the automated tests by providing visual details about the problems encountered during the build. Just schedule the Selenium test to run and the screenshots will be taken automatically every time it runs!
Keep in mind that all the above steps and chosen tools represent an option for reaching the state of having screenshots saved as artifacts at the end of a build, other tools can be chosen and integrated as well.
I also would like to share a few more cool plugins and ideas I've found along the way:
- Maven Integration plugin (https://plugins.jenkins.io/maven-plugin) — although Jenkins natively provides a Maven builder, this plugin offers a more advanced integration with different types of Jenkins jobs.
- Chuck Norris plugin (https://wiki.jenkins.io/display/JENKINS/ChuckNorris+Plugin) — this one is more for a software engineer's entertainment and good mood. Whenever he checks the status of a build he or she will see a picture of Chuck Norris (instead of Jenkins the butler) and a random Chuck Norris 'The Programmer' fun fact.
There are many more plugins or third party apps that can be integrated with Jenkins, be sure to check for one that can help you when you need a build to be run or configured in a particular way.
When creating your CI workflow, make sure you also add your performance tests there. We recommend using an open source tool like JMeter. Then, you can upload your JMX script to BlazeMeter and run you stress test there. You will be able to massively scale, get advanced reports where you can drill down into KPIs and compare them and collaboration features. To shift left, you can easily integrate with Jenkins and run your test automatically.
Published at DZone with permission of Oana Radomir, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments