78

I have searched a lot for this but couldn't find a solution. Here's a similar question with a possible solution in java.

Is there a similar solution in Python?

Community
  • 1
  • 1
streamoverflowed
  • 795
  • 1
  • 7
  • 7

8 Answers8

142

Other than Selenium, this example also requires the PIL Imaging library. Sometimes this is put in as one of the standard libraries and sometimes it's not, but if you don't have it you can install it with pip install Pillow

from selenium import webdriver
from PIL import Image
from io import BytesIO

fox = webdriver.Firefox()
fox.get('http://stackoverflow.com/')

# now that we have the preliminary stuff out of the way time to get that image :D
element = fox.find_element_by_id('hlogo') # find part of the page you want image of
location = element.location
size = element.size
png = fox.get_screenshot_as_png() # saves screenshot of entire page
fox.quit()

im = Image.open(BytesIO(png)) # uses PIL library to open image in memory

left = location['x']
top = location['y']
right = location['x'] + size['width']
bottom = location['y'] + size['height']


im = im.crop((left, top, right, bottom)) # defines crop points
im.save('screenshot.png') # saves new cropped image

and finally the output is... the Stackoverflow logo!!!

enter image description here

Now of course this would be overkill for just grabbing a static image but if your want to grab something that requires Javascript to get to this could be a viable solution.

Boris
  • 7,044
  • 6
  • 62
  • 63
RandomPhobia
  • 3,938
  • 7
  • 22
  • 22
  • 18
    You can also get the screenshot in memory directly: `img = Image.open(StringIO(base64.decodestring(driver.get_screenshot_as_base64())))` – ejk314 Oct 03 '13 at 14:15
  • I confirm that this solution (along with @ejk314's suggestion) works very well with Selenium 2.38.4, using the Firefox driver – cjauvin Jan 04 '14 at 18:58
  • 7
    An alternative for in memory loading is `img = fox.get_screenshot_as_png()` and then `img = Image.open(StringIO(img))` to load it as PIL image. – yellowcap Sep 17 '14 at 16:15
  • 6
    comment taken from a NAA of @sukrit-gupta : *RandomPhobia, your answer works great in case of static pages which do not need to be scrolled. In case, someone needs to get images from large pages which need scrolling you should use the location_once_scrolled_into_view function. So replace, location = element.location with: location = img.location_once_scrolled_into_view Also, make sure that you use Chrome instead of Firefox because Chrome takes screenshot of the visible area only whereas Firefox takes screenshot of the complete tab.* – bummi Jun 01 '15 at 22:05
  • how we capure the which show in browser without scroll a page not how whole page. – Manish Patel Jun 26 '15 at 08:03
  • 1
    For some reason `im.save('screenshot.png')` is not saving. – User Sep 22 '15 at 19:46
  • @User "some reason is not enough" U should wrap with a try/except and print str(e) from Exception every time you don't know what to expect. Maybe you didn't load or process the image correctly, so save will not work, but anyway, using try block you will get the error and will be able to solve it. – m3nda Oct 25 '15 at 19:00
  • 6
    Followup on the suggestion by @yellowcap: Note in Python 3+, you should `BytesIO` rather than `StringIO`. – Alex P. Miller May 10 '16 at 16:09
  • 4
    I'm facing resize problem, while image is zommed out. Screenshot is working but cropping the image doesn't seem to work. Did anyone face the same ? – Sumit Murari Jul 03 '17 at 08:47
  • On MacOS (retina) there is problem that web element position in pixels dont match element position in screenshot, due to resize/ratio – Outside_Box Aug 08 '18 at 18:19
  • 3
    As mentioned by @Outside_Box sometimes due to the pixel density of screens, some corrections should be done. Because **element.size is in points** and **crop function use pixels** we should use pixel ratio to transform sizes. `pixel_ratio = fox.execute_script("return window.devicePixelRatio")` `new_size = old_size * pixel_ratio` – vperezb Mar 16 '19 at 22:09
  • The size of the Screenshot and the browser window size are the same. I had to do the following 1. get the browser window size using `driver.get_window_size()` 2. Subtract the height of the address bar from the height of the browser window 3. Now resize the screenshot to the new height and width from set 1 and 2 – Venkatesh Mondi Apr 05 '19 at 07:27
  • 1
    @vperezb actually since old_size is dict and pixel_ratio is int, one needs to recalculate height and width: ```right = location['x'] + old_size['width']* pixel_ratio bottom = location['y'] + old_size['height']* pixel_ratio``` – Vesna May 07 '19 at 13:58
  • As mentioned by @User as well, the `im.save` is not saving but it is also not throwing error, should I ask a new question for this? When I type `im.show()` a transparent image window of required size is appearing – Abhiram Satputé Aug 23 '19 at 02:27
  • Hi, Thank you for clear explanation. I tried to capture screenshot from CAPTCHA, but this code screens wrong and unrelated pixels were captured. I use xpath for finding place of CAPTCHA. – hamed baziyad Oct 27 '19 at 07:29
  • Get: `AttributeError: type object 'ElementObject' has no attribute 'location'` – Gavriel Cohen Nov 24 '19 at 12:21
35

Worked for me in python3.5

from selenium import webdriver


fox = webdriver.Firefox()
fox.get('http://stackoverflow.com/')
image = fox.find_element_by_id('hlogo').screenshot_as_png

p.s.

To save to file

image=driver.find_element_by_id('hlogo').screenshot(output_file_path)
Iman Kermani
  • 511
  • 5
  • 11
  • 1
    @Julius no, it should not be - it is enough only when partial screenshot exactly matched element with element that has assigned id. – reducing activity Jul 29 '18 at 09:49
  • @Julius it's still uncertain where and how it works. In Python 2.7 with Chrome, it does not work. – User Jan 09 '19 at 00:47
  • worked on chrome/python 2.7 for me with selenium2. `image = driver.find_element_by_id('el_id').screenshot_as_png` is this an attribute of the element, and how can i save this as an image? – tanvi Apr 25 '19 at 17:38
  • how do we proceed to save the image? – Lakshmi Narayanan Sep 06 '19 at 09:53
  • 2
    @tanvi @Lakshmi You guys can do `image = driver.find_element_by_id('el_id').screenshot(output_file_path)`. Please refer [here](https://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webelement.WebElement.screenshot) for API doc. – Ken Park Feb 14 '20 at 23:47
  • I have to do 'image.open' before saving as suggested above by Gavriel Cohen. `im = Image.open(BytesIO(image)) # uses PIL library to open image in memory im.save('example.png')` – ePandit Apr 30 '20 at 05:00
  • there is also a `screenshot_as_base64` attribute – iMath Jul 23 '20 at 11:47
  • This should be the accaped answer for me. – Nam G VU Sep 27 '20 at 14:51
7

I wrote this useful python3 function.

from base64 import b64decode
from wand.image import Image
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.action_chains import ActionChains
import math

def get_element_screenshot(element: WebElement) -> bytes:
    driver = element._parent
    ActionChains(driver).move_to_element(element).perform()  # focus
    src_base64 = driver.get_screenshot_as_base64()
    scr_png = b64decode(src_base64)
    scr_img = Image(blob=scr_png)

    x = element.location["x"]
    y = element.location["y"]
    w = element.size["width"]
    h = element.size["height"]
    scr_img.crop(
        left=math.floor(x),
        top=math.floor(y),
        width=math.ceil(w),
        height=math.ceil(h),
    )
    return scr_img.make_blob()

It returns png image of displayed element as bytes. Limitation: element must fit in viewport.
You must install wand module to work with it.

eugene-bright
  • 303
  • 2
  • 9
  • 1
    Nice code! When I trying with long page in chrome, I think `x = element.location_once_scrolled_into_view["x"] y = element.location_once_scrolled_into_view["y"]` since `location` may return a y larger than the window. – Vimos Feb 22 '18 at 01:08
6

Here is a function that does just that, The sizes must be casted to integers before being passed to the crop function :

from PIL import Image
from StringIO import StringIO
def capture_element(element,driver):
  location = element.location
  size = element.size
  img = driver.get_screenshot_as_png()
  img = Image.open(StringIO(img))
  left = location['x']
  top = location['y']
  right = location['x'] + size['width']
  bottom = location['y'] + size['height']
  img = img.crop((int(left), int(top), int(right), int(bottom)))
  img.save('screenshot.png')
SEDaradji
  • 995
  • 10
  • 18
  • This is pretty much the same as the accepted answers, with the added fault that it does not uses Selenium, as the OP wanted. – iled Jun 21 '17 at 22:08
  • It does use selenium, but the import statement is not needed here, It also includes the conversion of locations from float to int, the function img.crop throws an exception if the locations are not integers – SEDaradji Jun 21 '17 at 22:36
  • `TypeError: initial_value must be str or None, not bytes ` – pyd Jan 07 '19 at 07:34
  • what is your print(img), is a method or Byte object? – pyd Jan 07 '19 at 07:36
  • i am not sure i understand your question – SEDaradji Jan 07 '19 at 19:55
5

Expanding on the comments in response to RandomPhobia's very nice answer, here are two solutions with correct import statements that will open a full-screen screenshot without first saving to a file:

from selenium import webdriver
from PIL import Image
from StringIO import StringIO
import base64

DRIVER = 'chromedriver'
browser = webdriver.Chrome(DRIVER)

browser.get( "http:\\\\www.bbc.co.uk" )

img 1 = Image.open(StringIO(base64.decodestring(browser.get_screenshot_as_base64())))

img 2 = Image.open(StringIO(browser.get_screenshot_as_png()))

And because I'm sure your next question is, "Well that's great but which one is fastest?", here's how to determine it (I find the first method to be the fastest by some distance):

import timeit

setup = '''
from selenium import webdriver
from PIL import Image
from StringIO import StringIO
import base64

DRIVER = 'chromedriver'
browser = webdriver.Chrome(DRIVER)
browser.get( "http:\\\\www.bbc.co.uk" )

file_name = 'tmp.png'
'''

print timeit.Timer('Image.open(StringIO(browser.get_screenshot_as_png()))', setup=setup).repeat(2, 10)
print timeit.Timer('Image.open(StringIO(base64.decodestring(browser.get_screenshot_as_base64())))', setup=setup).repeat(2, 10)
print timeit.Timer('browser.get_screenshot_as_file(file_name); pil_img = Image.open(file_name)', setup=setup).repeat(2, 10)
StackG
  • 2,320
  • 5
  • 24
  • 43
5

Screenshot by Element:

from PIL import Image
from io import BytesIO


image = self.browser.driver.find_element_by_class_name('example.bla.bla').screenshot_as_png
im = Image.open(BytesIO(image))  # uses PIL library to open image in memory
im.save('example.png')
Gavriel Cohen
  • 3,354
  • 27
  • 35
  • I do the same but I face with the following error: `WebDriverException: Message: unknown error: failed to parse value of getElementRegion (Session info: chrome=78.0.3904.108)` – Mostafa Ghadimi Dec 09 '19 at 08:36
  • @MostafaGhadimi pls check this: https://sqa.stackexchange.com/questions/40321/chromedriver-driver-failed-to-parse-value-of-getelementregion-understanding – Gavriel Cohen Dec 09 '19 at 09:08
0

I converted @randomphobia's answer into a function. I also used @bummis' suggestion of using location_once_scrolled_into_view instead of location in order to generalize no matter the size of the page.

from selenium import webdriver
from PIL import Image
from io import BytesIO

def take_screenshot(element, driver, filename='screenshot.png'):
  location = element.location_once_scrolled_into_view
  size = element.size
  png = driver.get_screenshot_as_png() # saves screenshot of entire page

  im = Image.open(BytesIO(png)) # uses PIL library to open image in memory

  left = location['x']
  top = location['y']
  right = location['x'] + size['width']
  bottom = location['y'] + size['height']


  im = im.crop((left, top, right, bottom)) # defines crop points
  im.save(filename) # saves new cropped image

Here's a gist: https://gist.github.com/WittmannF/b714d3ceb7b6a5cd50002f11fb5a4929

0

Just simple as that:

element = driver.find_element_by_class_name('myclass')
element.screenshot('screenshot.png')
Ali Sajjad
  • 828
  • 5
  • 16