Sam
Sam

Reputation: 1253

How to take full page screenshots with Watir and geckodriver + Firefox?

I upgraded my Watir / Firefox automation stack to the latest version, and added geckodriver with it. I was surprised to see that now screenshots are of the viewport only by default.

require 'watir'
require 'mini_magick'

b = Watir::Browser.new :firefox
b.goto "https://daringfireball.net"
base = b.screenshot.base64
blob = Base64.decode64(base)
image = MiniMagick::Image.read(blob)
image.height
 => 1760 # macOS 'Retina' resolution doubling
b.execute_script "return window.innerHeight"
 => 880 # 880 * 2 = 1760
b.execute_script "return document.documentElement.scrollHeight"
 => 34692

geckodriver does not have any API for full page screenshots, though reintroducing this feature is planned (on an infinite timescale).

How can I take screenshots of the full page with Watir driving Firefox without rolling back my environment?

Upvotes: 2

Views: 1383

Answers (3)

Sam
Sam

Reputation: 1253

It is now possible to do this in Firefox, employing a geckodriver feature. As far as I know, this feature is not baked into Selenium / probably not a part of the W3C spec.

require 'watir'

browser = Watir::Browser.new :firefox
bridge = browser.driver.session_storage.instance_variable_get(:@bridge)

server_uri = bridge.instance_variable_get(:@http).instance_variable_get(:@server_url)
sid = bridge.instance_variable_get(:@session_id)
driver_path = "session/#{sid}/moz/screenshot/full"
request_url = server_uri.to_s + driver_path

url = URI.parse(request_url)
req = Net::HTTP::Get.new(request_url)
raw = Net::HTTP.start(url.host, url.port) {|http| http.request(req) }.body

base64_screenshot = JSON.parse(raw, symbolize_names: true)[:value]

This approach is also now an option in the watir-screenshot-stitch gem:

require 'watir-screenshot-stitch'
b = Watir::Browser.new :firefox
b.goto "https://github.com/mozilla/geckodriver/issues/570"
base64_screenshot = b.base64_geckodriver

Upvotes: 0

Anton Angelov
Anton Angelov

Reputation: 1713

I solved the problem in C#. But the solution I guess can be rewritten on any language. I used a JavaScript library called HTML2Canvas to generate the full page screenshots. Here is the C# code:

[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 =>
            {
                string response = (string)js.ExecuteScript
                    ("return (typeof canvasImgContentDecoded === 'undefined' || canvasImgContentDecoded === null)");
                if (string.IsNullOrEmpty(response))
                {
                    return false;
                }
                return bool.Parse(response);
            });
        wait.Until(wd => !string.IsNullOrEmpty((string)js.ExecuteScript("return canvasImgContentDecoded;")));
        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);
    }
}

You can find more examples and explanations in the article.

Upvotes: 1

Sam
Sam

Reputation: 1253

Using Watir's .execute_script, it is possible to repeatedly take screenshots of the viewport while moving the scroll position. It is then possible to stitch images together using MiniMagick.

I developed the watir-screenshot-stitch gem to encapsulate my best approach to solving this problem, though it comes with caveats, which you can read about there. It is also memory intensive and can be slow.

This is not a true full-page screenshot solution, and I would gladly accept any alternative approaches that improve on this.

Upvotes: 1

Related Questions