Capture Full Page Screenshots Using WebDriver with HTML2Canvas.js

WebDriver
55 Shares
Full Page Screenshots WebDriver

In my WebDriver Series, I share lots of tips and tricks about browser automation. These days I was working on a new secret project of the company (stay tuned), and I got inspired. One of the things that I have found most problematic in WebDriver was that the screenshots made from most drivers contain only the visible part of the screen which makes them almost useless in troubleshooting tests'. Today I was reading JavaScript resources and got the idea to try to generate the screenshots through JavaScript. After a couple of hours of researching and writing code, I developed a solution that works on all new browsers and generates full page screenshots. Here I will share it with you.

Capture Screenshots Standard WebDriver API

[Test]
public void TakingWebDriverScreenshot()
{
using (var driver = new InternetExplorerDriver())
{
driver.Navigate().GoToUrl(@"https://automatetheplanet.com");
var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
var tempFilePath = Path.GetTempFileName().Replace(".tmp", ".png");
screenshot.SaveAsFile(tempFilePath, ScreenshotImageFormat.Png);
}
}

The code for capturing a screenshot with WebDriver is short and concise. However, as mentioned earlier there is one big drawback. The images contain only the visible part of the screen. For the test I used the homepage of Automate The Planet which height is more than the standard screens.

ChromeDriver Screenshot Visible Part of the Screen

This is how looks like a screenshot made with ChromeDriver. The same happens in Firefox and Edge. A full page screenshot is generated only in Internet Explorer. Moreover, you need to manually maximize the browser if you want the image to look more accurate.

Capture Full Page Screenshots through HTML2Canvas.js and C#

HTML2Canvas.js

While I was reading about advanced JavaScript I read about a cool JS library called HTML2Canvas.js. Bellow you can find the official description:

Description


The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The screenshot is based on the DOM and as such may not be 100% accurate to the real representation as it does not make an actual screenshot, but builds the screenshot based on the information available on the page.

The library should work fine on the following browsers:

  • Firefox 3.5+
  • Google Chrome
  • Opera 12+
  • IE9+
  • Safari 6+

I have tested in on the latest versions of Chrome, Firefox, Edge and Internet Explorer and is working like a charm.

HTML2Canvas.js + WebDriver C#

However, to make it work together with WebDriver takes a little bit of "black magic". Below you can find the working example.

Important Notes

Note: This method of capturing screenshots is slightly slower than the regular one. On average, slows down the test with 1 second.

Note: You need to make sure that the page is fully loaded before executing the JavaScript otherwise the screenshot may look a little bit strange.

Fully Working Code for Taking Full Page Screenshots

[Test]
public void TakingHTML2CanvasFullPageScreenshot()
{
using (var driver = new ChromeDriver())
{
driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(5);
driver.Navigate().GoToUrl(@"https://automatetheplanet.com");
IJavaScriptExecutor js = driver;
var html2canvasJs = File.ReadAllText($"{GetAssemblyDirectory()}html2canvas.js");
js.ExecuteScript(html2canvasJs);
string generateScreenshotJS = @"function genScreenshot () {
var canvasImgContentDecoded;
html2canvas(document.body, {
onrendered: function (canvas) {
window.canvasImgContentDecoded = canvas.toDataURL(""image/png"");
}});
}
genScreenshot();";
js.ExecuteScript(generateScreenshotJS);
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.IgnoreExceptionTypes(typeof(InvalidOperationException));
wait.Until(wd => ((IJavaScriptExecutor)wd).ExecuteScript("return canvasImgContentDecoded;") != null);
var pngContent = (string)js.ExecuteScript("return canvasImgContentDecoded;");
pngContent = pngContent.Replace("data:image/png;base64,", string.Empty);
byte[] data = Convert.FromBase64String(pngContent);
var tempFilePath = Path.GetTempFileName().Replace(".tmp", ".png");
Image image;
using (var ms = new MemoryStream(data))
{
image = Image.FromStream(ms);
}
image.Save(tempFilePath, ImageFormat.Png);
}
}
private string GetAssemblyDirectory()
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}

Solution Explanations

Let's go though the most important parts of the code and explain the reasoning behind them.

First, you need to download the html2canvas.js file and add it to your project, make sure that the file is copied to the output folder.

IJavaScriptExecutor js = driver;
var html2canvasJs = File.ReadAllText($"{GetAssemblyDirectory()}html2canvas.js");
js.ExecuteScript(html2canvasJs);

Though these three lines we read the html2canvas.js from the output folder and execute it in the browser after we have navigated to the page.

Afterwards to use the power of html2canvas and save the result, we need to execute the following custom JavaScript.

function genScreenshot() {
var canvasImgContentDecoded;
html2canvas(document.body, {
onrendered: function(canvas) {
window.canvasImgContentDecoded = canvas.toDataURL("image/png");
}
});
}
genScreenshot();

Here we create a function, and after that, we call it. A new canvas is generated based on the current DOM structure. We can get its image representation by calling canvas.toDataURL("image/png");. Through testing I found out that the standard ExecuteScript method of WebDriver was unable to retrieve the result immediately. I also tried to use ExecuteScriptAsync and playing with the associated timeouts but without success. Though the usage of window.canvasImgContentDecoded, we can access this variable in any subsequent calls of ExecuteScript.

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.IgnoreExceptionTypes(typeof(InvalidOperationException));
wait.Until(wd => ((IJavaScriptExecutor)wd).ExecuteScript("return canvasImgContentDecoded;") != null);
var pngContent = (string)js.ExecuteScript("return canvasImgContentDecoded;");

If you try to retrieve the mentioned variable immediately, its value will be null, or it will be undefined throwing InvalidException. So we use WebDriverWait to wait until the variable is not null and we setup the wait instance to ignore all InvalidException.

Unfortunately, we are still not ready. The returned content is a base64 encoded string. We need to decode it and save it to an image. You cannot set the download location through JavaScript because of security reasons. Because of that, I decided to do the job using C#.

If you try to decode the content immediately, you will found out that it is not a valid base64 string. First, you need to remove the following text "data:image/png;base64," from the beginning of the content.

pngContent = pngContent.Replace("data:image/png;base64,", string.Empty);
byte[] data = Convert.FromBase64String(pngContent);

If you try to save the result in a file and change the file type to PNG, you will be surprised that you cannot open it afterwards. Below you can find the working code. We use MemoryStream and the Image type.

var tempFilePath = Path.GetTempFileName().Replace(".tmp", ".png");
Image image;
using (var ms = new MemoryStream(data))
{
image = Image.FromStream(ms);
}
image.Save(tempFilePath, ImageFormat.Png);

Below you can find the final result a full page screenshot of the Automate The Planet homepage.

Full Page Screenshot HTML2Canvas.js WebDriver C#

I hope that the information will be useful to you. If you find some issues or further improvements to the code, please share them in the comments.

  • org.openqa.selenium.WebDriverException: unknown error: canvasImgContentDecoded is not defined

    • Can you be a little bit more specific? Which browser and driver do you use? Do you use the exact same code or something modified? Do you use C# since the exception is formatted a little bit strange with all these small caps?

  • Giga Byte

    https://uploads.disquscdn.com/images/550830b055281b49711f212989b4a2951c4e30261d48aa21174bec978b4f206a.jpg Hi Anton,
    We tried this workout and it works fine for few pages but failed to capture all elements on a page. Page is fully loaded before taking a screenshot but still it displays header and footer perfectly in all screenshots but failed to capture all dynamic data displayed on page. Any idea what could be wrong.

    Any help would be appreciated.

    • Thank you for commenting and sorry for my late answer but it was a quite crazy week. I think your problem is related to the way HTML2Canvas works. It converts current DOM to a canvas and then to image. You can test, but my best guess is that your page has some iframes which I believe are not supported by the library. If you have more technical details how this dynamic data is displayed, maybe I will be able to give your more precise answer. Anyway, if you find some solution to the problem, please share it here, I will update the article. Good luck!

  • cdjboy

    could you provide source code in github?

    • There is a download source button at the bottom of the article where you can find the whole working code.