Sunday, August 30, 2015

Generate Fullpage Screenshot in Chrome

Firefox is my go-to browser for any automation tasks at first, because...it's like that, and that's the way it is. Obviously Chrome is a great browser too, and chromedriver is one of the most useful automation tools out there. One area where Chrome falls down however is that it will only screenshot the available viewport.

Here's a workaround. NB This script requires PIL (Python Imaging Library). If you're running PY 3.x, you'll need to have Python 2.7 installed alongside it.

In this demonstration, the method is contained in a file called util.py, which is imported by test.py. All scripts and created images live in the same directory. Obviously, you can adjust format, quality of image etc.

test.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"""
This script uses a simplified version of the one here:
https://snipt.net/restrada/python-selenium-workaround-for-full-page-screenshot-using-chromedriver-2x/

It contains the *crucial* correction added in the comments by Jason Coutu.
"""

import sys

from selenium import webdriver
import unittest

import util

class Test(unittest.TestCase):
    """ Demonstration: Get Chrome to generate fullscreen screenshot """

    def setUp(self):
        self.driver = webdriver.Chrome()

    def tearDown(self):
        self.driver.quit()

    def test_fullpage_screenshot(self):
        ''' Generate document-height screenshot '''
        url = "http://effbot.org/imagingbook/introduction.htm"
        self.driver.get(url)
        util.fullpage_screenshot(self.driver, "test.png")


if __name__ == "__main__":
    unittest.main(argv=[sys.argv[0]])


util.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import os
import time

from PIL import Image

def fullpage_screenshot(driver, file):

        print("Starting chrome full page screenshot workaround ...")

        total_width = driver.execute_script("return document.body.offsetWidth")
        total_height = driver.execute_script("return document.body.parentNode.scrollHeight")
        viewport_width = driver.execute_script("return document.body.clientWidth")
        viewport_height = driver.execute_script("return window.innerHeight")
        print("Total: ({0}, {1}), Viewport: ({2},{3})".format(total_width, total_height,viewport_width,viewport_height))
        rectangles = []

        i = 0
        while i < total_height:
            ii = 0
            top_height = i + viewport_height

            if top_height > total_height:
                top_height = total_height

            while ii < total_width:
                top_width = ii + viewport_width

                if top_width > total_width:
                    top_width = total_width

                print("Appending rectangle ({0},{1},{2},{3})".format(ii, i, top_width, top_height))
                rectangles.append((ii, i, top_width,top_height))

                ii = ii + viewport_width

            i = i + viewport_height

        stitched_image = Image.new('RGB', (total_width, total_height))
        previous = None
        part = 0

        for rectangle in rectangles:
            if not previous is None:
                driver.execute_script("window.scrollTo({0}, {1})".format(rectangle[0], rectangle[1]))
                print("Scrolled To ({0},{1})".format(rectangle[0], rectangle[1]))
                time.sleep(0.2)

            file_name = "part_{0}.png".format(part)
            print("Capturing {0} ...".format(file_name))

            driver.get_screenshot_as_file(file_name)
            screenshot = Image.open(file_name)

            if rectangle[1] + viewport_height > total_height:
                offset = (rectangle[0], total_height - viewport_height)
            else:
                offset = (rectangle[0], rectangle[1])

            print("Adding to stitched image with offset ({0}, {1})".format(offset[0],offset[1]))
            stitched_image.paste(screenshot, offset)

            del screenshot
            os.remove(file_name)
            part = part + 1
            previous = rectangle

        stitched_image.save(file)
        print("Finishing chrome full page screenshot workaround...")
        return True


Credits
The script used in util.py is essentially a shortened version of the one you will find here. Many thanks to restrada for coming up with it.

5 comments:

  1. Hi This works great... but only one limitation though if the page has fixed headers... it has issues... e.g. this page here...
    http://www.w3schools.com/js/default.asp

    Anyway to avoid the repeated headers... and also missing part of the page overlap.

    ReplyDelete
  2. I am getting error for
    from PIL import Image

    Please help how to fix that

    ReplyDelete
    Replies
    1. I found this!
      https://pillow.readthedocs.io/en/5.1.x/

      Delete
  3. Hi
    This code is working fine on Windows but for Mac my screenshot get cropped from right and bottom.

    Your help is really appreciated.
    Thanks

    ReplyDelete
  4. I know this is an old post, but I was having some trouble while using it on some sites because some boxes overlayed each other, all I did to solve it was using the next line as the first instruction of the function:

    driver.execute_script("window.scrollTo(0, 0)")

    ReplyDelete