69

After trying out various approaches... I have stumbled upon this page to take full-page screenshot with chromedriver, selenium and python.

The original code is here. (and I copy the code in this posting below)

It uses PIL and it works great! However, there is one issue... which is it captures fixed headers and repeats for the whole page and also misses some parts of the page during page change. sample url to take a screenshot:

http://www.w3schools.com/js/default.asp

How to avoid the repeated headers with this code... Or is there any better option which uses python only... ( i don't know java and do not want to use java).

Please see the screenshot of the current result and sample code below.

full page screenshot with repeated headers

test.py

"""
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"
        url = "http://www.w3schools.com/js/default.asp"
        self.driver.get(url)
        util.fullpage_screenshot(self.driver, "test.png")


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

util.py

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
sanyassh
  • 8,100
  • 13
  • 36
  • 70
ihightower
  • 3,093
  • 6
  • 34
  • 49
  • I'm taking a screenshot of a page that requires multiple scrolls/stitching. Unfortunately, it's not a public URL (you can only see the page if you're logged in). Do you know why it keeps appending the header as well? https://res.cloudinary.com/mpyr-com/image/upload/v1551372542/page2_sk5cqe.png – Rommel Paras Feb 28 '19 at 16:50
  • No stitching required: https://stackoverflow.com/a/57338909/2943191 – Klaidonis Aug 03 '19 at 13:51
  • i have now changed the answer to @lizesong1988 (below) and set the longest height to be 8000px. the ele xpath for longest element always returned values around 1100px which was not good.. so i just hardcode to 8000. this is the best and easiest answer for me. – ihightower Oct 19 '19 at 13:25
  • @ihightower thanks for writing the awesome code. I am facing the same issue. Is it possible to get the same code working for a div as well? In my case the scrollbar exists on a div. – Deepak Kumar May 09 '20 at 10:25
  • the easiest answer is now using `playwright` please see accepted answer below with new latest update info. @DeepakKumar – ihightower Nov 20 '21 at 18:39

25 Answers25

48

This answer improves upon prior answers by am05mhz and Javed Karim.

It assumes headless mode, and that a window-size option was not initially set. Before calling this function, ensure the page has loaded fully or sufficiently.

It attempts to set the width and height both to what is necessary. The screenshot of the entire page can sometimes include a needless vertical scrollbar. One way to generally avoid the scrollbar is by taking a screenshot of the body element instead. After saving a screenshot, it reverts the size to what it was originally, failing which the size for the next screenshot may not set correctly.

Ultimately this technique may still not work perfectly well for some examples.

from selenium import webdriver

def save_screenshot(driver: webdriver.Chrome, path: str = '/tmp/screenshot.png') -> None:
    # Ref: https://stackoverflow.com/a/52572919/
    original_size = driver.get_window_size()
    required_width = driver.execute_script('return document.body.parentNode.scrollWidth')
    required_height = driver.execute_script('return document.body.parentNode.scrollHeight')
    driver.set_window_size(required_width, required_height)
    # driver.save_screenshot(path)  # has scrollbar
    driver.find_element_by_tag_name('body').screenshot(path)  # avoids scrollbar
    driver.set_window_size(original_size['width'], original_size['height'])

If using Python older than 3.6, remove the type annotations from the function definition.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
  • 1
    The window size in Firefox is about 74px taller than the viewport, so `required_height + 74` works for me for now. – l0b0 Oct 31 '18 at 01:05
  • See this post https://stackoverflow.com/a/57338909/2943191 for additional explanations. – Klaidonis Aug 03 '19 at 13:49
  • I need full screenshot of iframe. I tried above code but seems not taking full screenshot, does it need any change for iframe? – Helping Hands Feb 05 '20 at 17:39
  • 2
    The last line of the code(after screenshot has been taken) is also important when working in loops, as the images will get longer and longer if the line is missed. – pjmathematician Oct 18 '20 at 17:51
  • I'd like to add on. This worked perfectly for me, except sometimes the height was too large and crashed Selenium. If anyone else has trouble with crashes, try adding an upper height limit. Change `set_window_size` to something like `driver.set_window_size(required_width, min(6000, required_height))` – Kyle Nov 24 '20 at 13:25
  • This is the answer that actually works as expected. The answer above grabs only the first screen which needs to be scrolled. – MaxCode Sep 02 '21 at 14:05
  • the easiest answer is now using `playwright` please see accepted answer below with new latest update info. – ihightower Nov 20 '21 at 18:38
  • This doesn't seem to actually record the entire page. Any resolution larger than the physical resolution of your windowing system will be ignored. – Cerin Aug 31 '22 at 01:03
  • I love the line that helps to avoid scrollbar! Thanks a lot! – Nam G VU Sep 26 '22 at 12:25
  • Tried and not working for me [view it](https://gitlab.com/namgivu_fullstack/frontend/image_crud/-/commit/7bfb94150d65feaeb39dbe932acf9136e8b684f4#bb4b664fcc5bf367cc0a8f97b39be11819c21022_28_33) – Nam G VU Nov 23 '22 at 14:18
  • 1
    This works well, but note that later versions of Selenium deprecate find_element_by_tag_name() in favour of find_element(by=By.TAG_NAME, value=tagname), which will require importing By from selenium.webdriver.common – redacted code Jan 19 '23 at 11:47
39

Screenshots are limited to the viewport but you can get around this by capturing the body element, as the webdriver will capture the entire element even if it is larger than the viewport. This will save you having to deal with scrolling and stitching images, however you might see problems with footer position (like in the screenshot below).

Tested on Windows 8 and Mac High Sierra with Chrome Driver.

from selenium import webdriver

url = 'https://stackoverflow.com/'
path = '/path/to/save/in/scrape.png'

driver = webdriver.Chrome()
driver.get(url)
el = driver.find_element_by_tag_name('body')
el.screenshot(path)
driver.quit()

Returns: (full size: https://i.stack.imgur.com/ppDiI.png)

SO_scrape

Nic Scozzaro
  • 6,651
  • 3
  • 42
  • 46
alexalex
  • 733
  • 6
  • 8
34

How it works: set browser height as longest as you can...

#coding=utf-8
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def test_fullpage_screenshot(self):
    # please note that we MUST use headless mode
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--start-maximized')

    driver = webdriver.Chrome(chrome_options=chrome_options)

    driver.get("yoururlxxx")
    time.sleep(2)

    height = driver.execute_script('return document.documentElement.scrollHeight')
    width  = driver.execute_script('return document.documentElement.scrollWidth')
    driver.set_window_size(width, height)  # the trick
    
    time.sleep(2)
    driver.save_screenshot("screenshot1.png")
    driver.quit()

if __name__ == "__main__":
    test_fullpage_screenshot()
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
lizisong1988
  • 364
  • 3
  • 4
  • 1
    This by far is the easiest and best solution for me. however, the longest height element i tried various but none of them seems to work... all about 1100px height (for the webpage in this question). However, hardcoding to `8000px total_height` works great! if there is any way for you to find the good xpath that can return the longest height automatically then will be even great! – ihightower Oct 19 '19 at 13:23
  • 1
    @ihightower you can try getting it with driver.execute_script("return document.scrollingElement.scrollHeight;") – Hrisimir Dakov Apr 18 '20 at 07:23
  • 1
    As other pointed out below, this will only work for full page if you run with `headless` – Mache May 11 '20 at 18:42
16
from selenium import webdriver

driver = webdriver.Firefox()
driver.get('https://developer.mozilla.org/')
element = driver.find_element_by_tag_name('body')
element_png = element.screenshot_as_png
with open("test2.png", "wb") as file:
    file.write(element_png)

This works for me. It saves the entire page as screenshot. For more information you can read up the api docs: http://selenium-python.readthedocs.io/api.html

Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
Javed Karim
  • 187
  • 1
  • 3
  • 2
    This technique worked for me for one page, but not for another. I waited for the page to load fully too. I have a [**newer answer**](https://stackoverflow.com/a/52572919/832230) which builds upon this answer and works a little more reliably. – Asclepius Sep 29 '18 at 22:15
  • 1
    This approach fails for many pages, example: https://www.de.abbott/media-center/press-releases/05-10-2018.html – PlsWork May 31 '19 at 13:54
16

The key is to turn on the headless mode! No stitching required and no need for loading the page twice.

Full working code:

URL = 'http://www.w3schools.com/js/default.asp'

options = webdriver.ChromeOptions()
options.headless = True

driver = webdriver.Chrome(options=options)
driver.get(URL)

S = lambda X: driver.execute_script('return document.body.parentNode.scroll'+X)
driver.set_window_size(S('Width'),S('Height')) # May need manual adjustment
driver.find_element_by_tag_name('body').screenshot('web_screenshot.png')

driver.quit()

This is practically the same code as posted by @Acumenus with slight improvements.

Summary of my findings

I decided to post this anyway because I did not find an explanation about what is happening when the headless mode is turned off (the browser is displayed) for screenshot taking purposes. As I tested (with Chrome WebDriver), if the headless mode is turned on, the screenshot is saved as desired. However, if the headless mode is turned off, the saved screenshot has approximately the correct width and height, but the outcome varies case-by-case. Usually, the upper part of the page which is visible by the screen is saved, but the rest of the image is just plain white. There was also a case with trying to save this Stack Overflow thread by using the above link; even the upper part was not saved which interestingly now was transparent while the rest still white. The last case I noticed was only once with the given W3Schools link; there where no white parts but the upper part of the page repeated until the end, including the header.

I hope this will help for many of those who for some reason are not getting the expected result as I did not see anyone explicitly explaining about the requirement of headless mode with this simple approach. Only when I discovered the solution to this problem myself, I found a post by @vc2279 mentioning that the window of a headless browser can be set to any size (which seems to be true for the opposite case too). Although, the solution in my post improves upon that that it does not require repeated browser/driver opening or page reloading.

Further suggestions

If for some pages it does not work for you, I suggest trying to add time.sleep(seconds) before getting the size of the page. Another case would be if the page requires scrolling until the bottom to load further content, which can be solved by the scheight method from this post:

scheight = .1
while scheight < 9.9:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight/%s);" % scheight)
    scheight += .01

Also, note that for some pages the content may not be in any of the top-level HTML tags like <html> or <body>, for example, YouTube uses <ytd-app> tag. As a last note, I found one page that "returned" a screenshot still with the horizontal scrollbar, the size of the window needed manual adjustment, i.e., the image width needed to be increased by 18 pixels, like so: S('Width')+18.

Klaidonis
  • 559
  • 2
  • 6
  • 22
  • 1
    HI. I have attempted to use Klaidonis's method for fullpage screenshot with the Bootstrap template "Creative" - [link](https://startbootstrap.com/themes/creative/) If I enter a custom width (not the detected width of the body) - for example `driver.set_window_size("1440",S('Height'))`, then the element with class masthead (the template header) takes the entire screenshot - without any other elements visible. On lower custom widths and/or if I use the body's width with `driver.set_window_size(S('Width'),S('Height'))` then the screenshot is correct. What could be the reason for this? – Nelly Oct 31 '19 at 14:32
  • @Nelly try writing 1440 without the quotes as it should be a number and not text. You can also try the following approach - `S('Width')+100` or whatever number you need there. – Klaidonis Nov 03 '19 at 09:01
  • 1
    Thanks @Klaidonis that you replied. But actually what helped me to resolve the problem was to set the height of the masthead class to 0 vh by using javascript executor in Selenium. – Nelly Nov 04 '19 at 10:53
  • 1
    I'm receiving exception: `selenium.common.exceptions.WebDriverException: Message: unknown command: session/a76b70801d41bf2c49ffa76c4396eb3a/element/0.039130225415212572-1/screenshot` – RhymeGuy Nov 30 '19 at 13:43
  • @RhymeGuy perhaps you have misspelled something in the code? – Klaidonis Dec 01 '19 at 06:57
  • @Klaidonis - this happens to me as well with `selenium==3.141.0` and python 3.7.2 – Mache May 11 '20 at 18:25
  • does not work for confluence pages which are long – user3754136 Oct 17 '22 at 20:13
  • @user3754136, What are confluence pages? – Klaidonis Dec 07 '22 at 15:29
  • Jira web pages for online documents. – user3754136 Dec 09 '22 at 10:36
  • Did you try other HTML tags that are on these pages besides 'body'? – Klaidonis Dec 10 '22 at 11:12
10

After knowing the approach of @Moshisho.

My full standalone working script is... (added sleep 0.2 after each scroll and position)

import sys
from selenium import webdriver
import util
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]))
                time.sleep(0.2)
                driver.execute_script("document.getElementById('topnav').setAttribute('style', 'position: absolute; top: 0px;');")
                time.sleep(0.2)
                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


driver = webdriver.Chrome()

''' Generate document-height screenshot '''
url = "http://effbot.org/imagingbook/introduction.htm"
url = "http://www.w3schools.com/js/default.asp"
driver.get(url)
fullpage_screenshot(driver, "test1236.png")
ihightower
  • 3,093
  • 6
  • 34
  • 49
8

Not sure if people are still having this issue. I've done a small hack that works pretty well and that plays nicely with dynamic zones. Hope it helps

# 1. get dimensions
browser = webdriver.Chrome(chrome_options=options)
browser.set_window_size(default_width, default_height)
browser.get(url)
time.sleep(sometime)
total_height = browser.execute_script("return document.body.parentNode.scrollHeight")
browser.quit()

# 2. get screenshot
browser = webdriver.Chrome(chrome_options=options)
browser.set_window_size(default_width, total_height)
browser.get(url)  
browser.save_screenshot(screenshot_path)
jeremie
  • 971
  • 9
  • 19
  • 2
    This needlessly loads the page twice, and fails to define the width at all. I now have a [**newer answer**](https://stackoverflow.com/a/52572919/832230) which corrects these issues. – Asclepius Sep 29 '18 at 22:09
7

Why not just getting the width and height of the page and then resize the driver? So will be something like this

total_width = driver.execute_script("return document.body.offsetWidth")
total_height = driver.execute_script("return document.body.scrollHeight")
driver.set_window_size(total_width, total_height)
driver.save_screenshot("SomeName.png")

This is going to make a screenshot of your entire page without the need to merge together different pieces.

Vali
  • 629
  • 2
  • 6
  • 14
6

You can achieve this by changing the CSS of the header before the screenshot:

topnav = driver.find_element_by_id("topnav")
driver.execute_script("arguments[0].setAttribute('style', 'position: absolute; top: 0px;')", topnav) 

EDIT: Put this line after your window scroll:

driver.execute_script("document.getElementById('topnav').setAttribute('style', 'position: absolute; top: 0px;');")

So in your util.py it will be:

driver.execute_script("window.scrollTo({0}, {1})".format(rectangle[0], rectangle[1]))
driver.execute_script("document.getElementById('topnav').setAttribute('style', 'position: absolute; top: 0px;');")

If the site is using the header tag, you can do it with find_element_by_tag_name("header")

Moshisho
  • 2,781
  • 1
  • 23
  • 39
  • hi thanks.. just adding above to script doesn't solve the problem.. however I understand the meaning.. and did disable the topnav.. by using inspector.. and need to dig around to find the javascript (not the css) that modifies the css.. and changed that to absolute.. manually. and it worked. (but the script screenshot still doesn't work though). Is there any way to improve ur script that disables the javascript css modification.. and for any new website.. do i have to dig around again to find the #id of header.. and change it. – ihightower Jan 19 '17 at 06:58
  • You can't know in advance how every website implemented their header. But you can take a guess. I'll add an example. – Moshisho Jan 19 '17 at 08:10
  • your code worked but with some minor glitch.. that is it included the header on some pages. So, after adding sleep 0.2 seconds.. it worked perfectly. i have updated the code and also marked your answer. Hope doing the edit in your answer is correct for stackoverflow. – ihightower Jan 19 '17 at 13:52
6

I changed code for Python 3.6, maybe it will be useful for someone:

from selenium import webdriver
from sys import stdout
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import unittest
#from Login_Page import Login_Page
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
from io import BytesIO
from PIL import Image

def testdenovoUIavailable(self):
        binary = FirefoxBinary("C:\\Mozilla Firefox\\firefox.exe") 
        self.driver  = webdriver.Firefox(firefox_binary=binary)
        verbose = 0

        #open page
        self.driver.get("http://yandex.ru")

        #hide fixed header        
        #js_hide_header=' var x = document.getElementsByClassName("topnavbar-wrapper ng-scope")[0];x[\'style\'] = \'display:none\';'
        #self.driver.execute_script(js_hide_header)

        #get total height of page
        js = 'return Math.max( document.body.scrollHeight, document.body.offsetHeight,  document.documentElement.clientHeight,  document.documentElement.scrollHeight,  document.documentElement.offsetHeight);'

        scrollheight = self.driver.execute_script(js)
        if verbose > 0:
            print(scrollheight)

        slices = []
        offset = 0
        offset_arr=[]

        #separate full screen in parts and make printscreens
        while offset < scrollheight:
            if verbose > 0: 
                print(offset)

            #scroll to size of page 
            if (scrollheight-offset)<offset:
                #if part of screen is the last one, we need to scroll just on rest of page
                self.driver.execute_script("window.scrollTo(0, %s);" % (scrollheight-offset))
                offset_arr.append(scrollheight-offset)
            else:
                self.driver.execute_script("window.scrollTo(0, %s);" % offset)
                offset_arr.append(offset)

            #create image (in Python 3.6 use BytesIO)
            img = Image.open(BytesIO(self.driver.get_screenshot_as_png()))


            offset += img.size[1]
            #append new printscreen to array
            slices.append(img)


            if verbose > 0:
                self.driver.get_screenshot_as_file('screen_%s.jpg' % (offset))
                print(scrollheight)

        #create image with 
        screenshot = Image.new('RGB', (slices[0].size[0], scrollheight))
        offset = 0
        offset2= 0
        #now glue all images together
        for img in slices:
            screenshot.paste(img, (0, offset_arr[offset2])) 
            offset += img.size[1]
            offset2+= 1      

        screenshot.save('test.png')
A.Minachev
  • 61
  • 1
  • 1
  • Any idea why at a very long page it stops scrolling down at a certain point and goes reverse again? I used https://www.otto.de/technik/audio/kopfhoerer/ as an example, and all goes well until we are around 5000 pixels and then the scrolling goes up again instead of down. – Wokoman Mar 27 '19 at 13:01
  • I get the same issue that it stops scrolling. Any solution for this? – Marialena Jul 25 '19 at 07:05
6

Source : https://pypi.org/project/Selenium-Screenshot/

from Screenshot import Screenshot_Clipping
from selenium import webdriver
import time

ob = Screenshot_Clipping.Screenshot()

driver = webdriver.Chrome()
url = "https://www.bbc.com/news/world-asia-china-51108726"
driver.get(url)
time.sleep(1)

img_url = ob.full_Screenshot(driver, save_path=r'.', image_name='Myimage.png')

driver.quit()
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
  • 5
    To make this answer more useful to readers of this question, consider adding a little prose to explain what you're doing. – entpnerd Jan 14 '20 at 21:19
5

For Chrome, it's also possible to use the Chrome DevTools Protocol:

import base64
...
        page_rect = browser.driver.execute_cdp_cmd("Page.getLayoutMetrics", {})
        screenshot = browser.driver.execute_cdp_cmd(
            "Page.captureScreenshot",
            {
                "format": "png",
                "captureBeyondViewport": True,
                "clip": {
                    "width": page_rect["contentSize"]["width"],
                    "height": page_rect["contentSize"]["height"],
                    "x": 0,
                    "y": 0,
                    "scale": 1
                }
            })

        with open(path, "wb") as file:
            file.write(base64.urlsafe_b64decode(screenshot["data"]))

Credits

This works both in headless and non-headless mode.

tsauerwein
  • 5,841
  • 3
  • 36
  • 49
4

Full page screenshots are not a part of the W3C spec. However, many web drivers implement their own endpoints to get a real full page screenshot. I found this method using geckodriver to be superior to the injected "screenshot, scroll, stitch" method, and far better than resizing the window in headless mode.

Example:

from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options

options = Options()
options.headless = True
service = Service('/your/path/to/geckodriver')
driver = webdriver.Firefox(options=options, service=service)

driver.get('https://www.nytimes.com/')
driver.get_full_page_screenshot_as_file('example.png')

driver.close()

geckodriver (Firefox)

If you're using geckodriver, you can hit these methods:

driver.get_full_page_screenshot_as_file
driver.save_full_page_screenshot
driver.get_full_page_screenshot_as_png
driver.get_full_page_screenshot_as_base64 

I've tested and confirmed these to be working on Selenium 4.07. I don't believe these functions are included in Selenium 3.

The best documentation I could find on these is in this merge

chromedriver (Chromium)

It appears that chromedriver has implemented their own full page screenshot functionality:

https://chromium-review.googlesource.com/c/chromium/src/+/2300980

and the Selenium team appears to be aiming for support in Selenium 4:

https://github.com/SeleniumHQ/selenium/issues/8168

Cyrus
  • 613
  • 1
  • 6
  • 22
3

My first answer on StackOverflow. I'm a newbie. The other answers quoted by the fellow expert coders are awesome & I'm not even in the competition. I'd just like to quote the steps taken from the following link: pypi.org

Refer full-page screenshot section.

open your command prompt and navigate to the directory where Python is installed

cd "enter the directory"

install the module using pip

pip install Selenium-Screenshot

The above module works for python 3. once the module is installed, try the following code by creating a separate file in python IDLE

from Screenshot import Screenshot_Clipping
from selenium import webdriver

ob = Screenshot_Clipping.Screenshot()
driver = webdriver.Chrome()
url = "https://github.com/sam4u3/Selenium_Screenshot/tree/master/test"
driver.get(url)

# the line below makes taking & saving screenshots very easy.

img_url=ob.full_Screenshot(driver, save_path=r'.', image_name='Myimage.png')
print(img_url)
driver.close()

driver.quit()
lousycoder
  • 451
  • 6
  • 10
2

Slightly modify @ihightower and @A.Minachev's code, and make it work in mac retina:

import time
from PIL import Image
from io import BytesIO

def fullpage_screenshot(driver, file, scroll_delay=0.3):
    device_pixel_ratio = driver.execute_script('return window.devicePixelRatio')

    total_height = driver.execute_script('return document.body.parentNode.scrollHeight')
    viewport_height = driver.execute_script('return window.innerHeight')
    total_width = driver.execute_script('return document.body.offsetWidth')
    viewport_width = driver.execute_script("return document.body.clientWidth")

    # this implementation assume (viewport_width == total_width)
    assert(viewport_width == total_width)

    # scroll the page, take screenshots and save screenshots to slices
    offset = 0  # height
    slices = {}
    while offset < total_height:
        if offset + viewport_height > total_height:
            offset = total_height - viewport_height

        driver.execute_script('window.scrollTo({0}, {1})'.format(0, offset))
        time.sleep(scroll_delay)

        img = Image.open(BytesIO(driver.get_screenshot_as_png()))
        slices[offset] = img

        offset = offset + viewport_height

    # combine image slices
    stitched_image = Image.new('RGB', (total_width * device_pixel_ratio, total_height * device_pixel_ratio))
    for offset, image in slices.items():
        stitched_image.paste(image, (0, offset * device_pixel_ratio))
    stitched_image.save(file)

fullpage_screenshot(driver, 'test.png')
Charlie Chen
  • 224
  • 3
  • 12
2

For Python using Selenium 4 and Chrome Driver

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
import time
import shutil

           
def take_full_page_screenshot():

    #Install chrome driver
    chrome_driver_path = ChromeDriverManager().install()
    service = Service(chrome_driver_path)
    service.start() 

    #setup chrome options
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    options.add_argument('--incognito')
    options.add_argument('--start-maximized')  
    options.add_argument('--disable-gpu')
    driver = webdriver.Chrome(chrome_driver_path, options=options)

    #open url and wait for the page to load
    driver.get('https://www.stackoverflow.com')
    time.sleep(2)
        
    #find the element with longest height on page
    element = driver.find_element(By.TAG_NAME, 'body')
    total_height = element.size["height"]+1000
    #set the window dimensions
    driver.set_window_size(1920, total_height)  

    #save screenshot
    driver.save_screenshot("screenshot.png")

    #quit driver
    driver.quit()

if __name__ == '__main__':
    take_full_page_screenshot()
Dino
  • 158
  • 1
  • 1
  • 10
1
element=driver.find_element_by_tag_name('body')
element_png = element.screenshot_as_png
with open("test2.png", "wb") as file:
    file.write(element_png)

There was an error in the code suggested earlier in line 2. Here is the corrected one. Being a noob here, not able to edit my own post as yet.

Sometimes the baove doesn't get best results. So can use another method to get height of all elements and sum them to set the capture height as below:

element=driver.find_elements_by_xpath("/html/child::*/child::*")
    eheight=set()
    for e in element:
        eheight.add(round(e.size["height"]))
    print (eheight)
    total_height = sum(eheight)
    driver.execute_script("document.getElementsByTagName('html')[0].setAttribute('style', 'height:"+str(total_height)+"px')")
    element=driver.find_element_by_tag_name('body')
    element_png = element.screenshot_as_png
    with open(fname, "wb") as file:
        file.write(element_png)

BTW, it works on FF.

Javed Karim
  • 187
  • 1
  • 3
1

You can use Splinter
Splinter is an abstraction layer on top of existing browser automation tools such as Selenium
There is a new feature browser.screenshot(..., full=True) in new version 0.10.0.
full=True option will make full screen capture for you.

Sergei
  • 157
  • 1
  • 6
1

easy by python, but slowly

import os

from selenium import webdriver
from PIL import Image


def full_screenshot(driver: webdriver):
    driver.execute_script(f"window.scrollTo({0}, {0})")
    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")
    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
            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]))
        file_name = "part_{0}.png".format(part)
        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])
        stitched_image.paste(screenshot, offset)
        del screenshot
        os.remove(file_name)
        part = part + 1
        previous = rectangle
    return stitched_image
Alex
  • 81
  • 5
1

I have modified the answer given by @ihightower, instead of saving the screenshot in that function, return the total height and total width of the webpage and then set the window size to total height and total width.

from PIL import Image
from io import BytesIO

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def open_url(url):
    options = Options()

    options.headless = True

    driver = webdriver.Chrome(chrome_options=options)

    driver.maximize_window()
    driver.get(url)
    save_screenshot(driver, 'screen.png')

def save_screenshot(driver, file_name):
    height, width = scroll_down(driver)
    driver.set_window_size(width, height)
    img_binary = driver.get_screenshot_as_png()
    img = Image.open(BytesIO(img_binary))
    img.save(file_name)
    # print(file_name)
    print(" screenshot saved ")


def scroll_down(driver):
    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")

    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

            rectangles.append((ii, i, top_width, top_height))

            ii = ii + viewport_width

        i = i + viewport_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]))
            time.sleep(0.5)
        # time.sleep(0.2)

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

        previous = rectangle

    return (total_height, total_width)

open_url("https://www.medium.com")
abhay kumar
  • 83
  • 1
  • 10
1

I'm currently using this approach:

 def take_screenshot(self, driver, screenshot_name = "debug.png"):
    elem = driver.find_element_by_tag_name('body')
    total_height = elem.size["height"] + 1000
    driver.set_window_size(1920, total_height)
    time.sleep(2)
    driver.save_screenshot(screenshot_name)
    return driver
SHiggins
  • 11
  • 1
1

If you are trying to do this post ~2021, you need to edit the find element command from:

element = driver.find_element_by_tag('body')

to:

from selenium.webdriver.common.by import By

...

element = driver.find_element(By.TAG_NAME, "body")
snarik
  • 1,035
  • 2
  • 9
  • 15
0

I have modified jeremie-s' answer so that it only get the url once.

browser = webdriver.Chrome(chrome_options=options)
browser.set_window_size(default_width, default_height)
browser.get(url)
height = browser.execute_script("return document.body.parentNode.scrollHeight")

# 2. get screenshot
browser.set_window_size(default_width, height)
browser.save_screenshot(screenshot_path)

browser.quit()
Asclepius
  • 57,944
  • 17
  • 167
  • 143
am05mhz
  • 2,727
  • 2
  • 23
  • 37
  • 2
    This fails to define `default_width` or what it was or should've been. I now have a [**newer answer**](https://stackoverflow.com/a/52572919/832230) which corrects this issue. – Asclepius Sep 29 '18 at 22:06
0

Got it!!! works like a charm

For NodeJS, but the concept is the same:

await driver.executeScript(`
      document.documentElement.style.display = "table";
      document.documentElement.style.width = "100%";
      document.body.style.display = "table-row";
`);

await driver.findElement(By.css('body')).takeScreenshot();
Moshe Quantz
  • 1,480
  • 2
  • 12
  • 8
0

This works for me

    s = Service("/opt/homebrew/bin/chromedriver")
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--start-maximized')
    driver = webdriver.Chrome(chrome_options=chrome_options, service=s)

    highest_ele = driver.find_element(By.XPATH, '//*[@id="react-app"]/div[3]/div[3]/span/span/span[2]')
    total_height = highest_ele.location['y']
    driver.set_window_size(height=total_height, width=1920)

    time.sleep(1)
    driver.save_screenshot('~/shot.png') # replace your path
wei wu
  • 11
  • 1