From 7fc5c4a1095fe14fd16dc79625a44c563e8f0865 Mon Sep 17 00:00:00 2001 From: wang Date: Sun, 29 Sep 2019 05:56:52 +0900 Subject: [PATCH 1/7] screenshot enhancement --- splinter/driver/webdriver/__init__.py | 101 ++++++++++++-------------- 1 file changed, 46 insertions(+), 55 deletions(-) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index d17801741..e279d3444 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -599,20 +599,35 @@ def check(self, name): def uncheck(self, name): self.find_by_name(name).first.uncheck() - def screenshot(self, name="", suffix=".png", full=False): + def screenshot(self, filename, full=False, waiting_time=0): + if full: + self.full_screen() + # trigger the `scroll` event to ensure lazy-load images can be rendered. + self.execute_script("window.dispatchEvent(new Event('scroll'))") - name = name or "" + if waiting_time > 0: + time.sleep(waiting_time) - (fd, filename) = tempfile.mkstemp(prefix=name, suffix=suffix) - # don't hold the file - os.close(fd) + result = self.driver.get_screenshot_as_file(filename) + + self.recover_screen() + return result + + def screenshot_as_base64(self, full=False, waiting_time=0): if full: self.full_screen() + # trigger the `scroll` event to ensure lazy-load images can be rendered. + self.execute_script("window.dispatchEvent(new Event('scroll'))") + + if waiting_time > 0: + time.sleep(waiting_time) + + result = self.driver.get_screenshot_as_base64() - self.driver.get_screenshot_as_file(filename) self.recover_screen() - return filename + + return result def select(self, name, value): self.find_by_xpath( @@ -632,17 +647,29 @@ def quit(self): def full_screen(self): self.ori_window_size = self.driver.get_window_size() - width = self.driver.execute_script("return Math.max(document.body.scrollWidth, document.body.offsetWidth);") - height = self.driver.execute_script("return Math.max(document.body.scrollHeight, document.body.offsetHeight);") - self.driver.set_window_size(width, height) + width = self.driver.execute_script("return Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.scrollWidth);") + height = self.driver.execute_script("return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight);") + self.set_viewport_size(width, height) def recover_screen(self): if self.ori_window_size: width = self.ori_window_size.get('width') height = self.ori_window_size.get('height') - self.driver.set_window_size(width, height) + self.set_viewport_size(width, height) self.ori_window_size = None + def set_viewport_size(self, width, height): + device_metrics = { + 'width': width, + 'height': height, + 'deviceScaleFactor': self.execute_script('return window.devicePixelRatio'), + 'mobile': self.execute_script("return typeof window.orientation !== 'undefined'") + } + + self.driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', device_metrics) + + return self + def html_snapshot(self, name="", suffix=".html", encoding='utf-8'): """Write the current html to a file.""" name = name or "" @@ -872,53 +899,17 @@ def drag_and_drop(self, droppable): self.scroll_to() ActionChains(self.parent.driver).drag_and_drop(self._element, droppable._element).perform() - def screenshot(self, name='', suffix='.png', full=False): - name = name or '' - - (fd, filename) = tempfile.mkstemp(prefix=name, suffix=suffix) - # don't hold the file - os.close(fd) - - if full: - self.parent.full_screen() - target = self.screenshot_as_png() - self.parent.recover_screen() - target.save(filename) - - return filename - - def screenshot_as_png(self): - try: - from PIL import Image - except ImportError: - raise NotImplementedError('Element screenshot need the Pillow dependency. ' - 'Please use "pip install Pillow" install it.') - - full_screen_png = self.parent.driver.get_screenshot_as_png() - - full_screen_bytes = BytesIO(full_screen_png) - - im = Image.open(full_screen_bytes) - im_width, im_height = im.size[0], im.size[1] - window_size = self.parent.driver.get_window_size() - window_width = window_size['width'] - - ratio = im_width * 1.0 / window_width - height_ratio = im_height / ratio - - im = im.resize((int(window_width), int(height_ratio))) - - location = self._element.location - x, y = location['x'], location['y'] + def screenshot(self, filename, waiting_time=0): + if waiting_time > 0: + time.sleep(waiting_time) - pic_size = self._element.size - w, h = pic_size['width'], pic_size['height'] + return self._element.screenshot(filename) - box = x, y, x + w, y + h - box = [int(i) for i in box] - target = im.crop(box) + def screenshot_as_base64(self, waiting_time=0): + if waiting_time > 0: + time.sleep(waiting_time) - return target + return self._element.screenshot_as_base64 def __getitem__(self, attr): return self._element.get_attribute(attr) From 20a76f4d920423d23126dd1354d059bc0ee4218a Mon Sep 17 00:00:00 2001 From: wang Date: Sun, 29 Sep 2019 23:46:43 +0900 Subject: [PATCH 2/7] Bugfix: The image scale won't change on higher pixel ratio device(window.devicePixelRatio > 1) when take screenshot of an element. I have reported the issue here: https://bugs.chromium.org/p/chromedriver/issues/detail?id=3154 --- splinter/driver/webdriver/__init__.py | 35 ++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index e279d3444..1662b65b2 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -900,16 +900,43 @@ def drag_and_drop(self, droppable): ActionChains(self.parent.driver).drag_and_drop(self._element, droppable._element).perform() def screenshot(self, filename, waiting_time=0): - if waiting_time > 0: - time.sleep(waiting_time) + base64_string = self.screenshot_as_base64(waiting_time) + png = base64.b64decode(base64_string.encode('ascii')) + + try: + with open(filename, 'wb') as f: + f.write(png) + except IOError: + return False + finally: + del png - return self._element.screenshot(filename) + return True def screenshot_as_base64(self, waiting_time=0): + self.parent.full_screen() + if waiting_time > 0: time.sleep(waiting_time) + + + location = self._element.location + size = self._element.size + + response = self.parent.driver.execute_cdp_cmd('Page.captureScreenshot', { + 'clip': { + 'x': location['x'], + 'y': location['y'], + 'width': size['width'], + 'height': size['height'], + 'scale': self.parent.execute_script('return window.devicePixelRatio') + } + }) + + self.parent.recover_screen() + + return response['data'] - return self._element.screenshot_as_base64 def __getitem__(self, attr): return self._element.get_attribute(attr) From 1f06efa8d663aee3b771b342fe1fe5c98f5f6f07 Mon Sep 17 00:00:00 2001 From: wang Date: Tue, 1 Oct 2019 02:23:05 +0900 Subject: [PATCH 3/7] Deprecated `screenshot` method. --- splinter/driver/webdriver/__init__.py | 36 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index 1662b65b2..2035c40fe 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -10,6 +10,7 @@ import sys import tempfile import time +import base64 from contextlib import contextmanager import warnings @@ -599,7 +600,20 @@ def check(self, name): def uncheck(self, name): self.find_by_name(name).first.uncheck() - def screenshot(self, filename, full=False, waiting_time=0): + def screenshot(self, name="", suffix=".png", full=False): + warnings.warn('Deprecated, use `self.capture_screenshot()` instead.') + + name = name or "" + + (fd, filename) = tempfile.mkstemp(prefix=name, suffix=suffix) + # don't hold the file + os.close(fd) + + self.capture_screenshot(filename, full) + + return filename + + def capture_screenshot(self, filename, full=False, waiting_time=0): if full: self.full_screen() # trigger the `scroll` event to ensure lazy-load images can be rendered. @@ -614,7 +628,7 @@ def screenshot(self, filename, full=False, waiting_time=0): return result - def screenshot_as_base64(self, full=False, waiting_time=0): + def capture_screenshot_as_base64(self, full=False, waiting_time=0): if full: self.full_screen() # trigger the `scroll` event to ensure lazy-load images can be rendered. @@ -899,7 +913,21 @@ def drag_and_drop(self, droppable): self.scroll_to() ActionChains(self.parent.driver).drag_and_drop(self._element, droppable._element).perform() - def screenshot(self, filename, waiting_time=0): + def screenshot(self, name='', suffix='.png', full=False): + warnings.warn('Deprecated, use `self.capture_screenshot()` instead.') + + name = name or '' + + (fd, filename) = tempfile.mkstemp(prefix=name, suffix=suffix) + # don't hold the file + os.close(fd) + + self.capture_screenshot(filename) + + return filename + + + def capture_screenshot(self, filename, waiting_time=0): base64_string = self.screenshot_as_base64(waiting_time) png = base64.b64decode(base64_string.encode('ascii')) @@ -913,7 +941,7 @@ def screenshot(self, filename, waiting_time=0): return True - def screenshot_as_base64(self, waiting_time=0): + def capture_screenshot_as_base64(self, waiting_time=0): self.parent.full_screen() if waiting_time > 0: From a4f3bdf1e5223014a4cea297ff02a4ccffa750ef Mon Sep 17 00:00:00 2001 From: wang Date: Wed, 2 Oct 2019 02:22:58 +0900 Subject: [PATCH 4/7] added TODO --- splinter/driver/webdriver/__init__.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index 2035c40fe..c47347f0d 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -610,7 +610,7 @@ def screenshot(self, name="", suffix=".png", full=False): os.close(fd) self.capture_screenshot(filename, full) - + return filename def capture_screenshot(self, filename, full=False, waiting_time=0): @@ -926,9 +926,15 @@ def screenshot(self, name='', suffix='.png', full=False): return filename + # TODO Uncomment this method and remove the hotfix one once the upstream issue(https://bugs.chromium.org/p/chromedriver/issues/detail?id=3154) has been fixed. + # def capture_screenshot(self, filename, waiting_time=0): + # if waiting_time > 0: + # time.sleep(waiting_time) + + # return self._element.screenshot(filename) def capture_screenshot(self, filename, waiting_time=0): - base64_string = self.screenshot_as_base64(waiting_time) + base64_string = self.capture_screenshot_as_base64(waiting_time) png = base64.b64decode(base64_string.encode('ascii')) try: @@ -941,13 +947,19 @@ def capture_screenshot(self, filename, waiting_time=0): return True + # TODO Uncomment this method and remove the hotfix one once the upstream issue(https://bugs.chromium.org/p/chromedriver/issues/detail?id=3154) has been fixed. + # def capture_screenshot_as_base64(self, waiting_time=0): + # if waiting_time > 0: + # time.sleep(waiting_time) + + # return self._element.screenshot_as_base64 + def capture_screenshot_as_base64(self, waiting_time=0): self.parent.full_screen() if waiting_time > 0: time.sleep(waiting_time) - location = self._element.location size = self._element.size From b1c237b221e0c96e051641d6e90c7e4a38cc3d8c Mon Sep 17 00:00:00 2001 From: wang Date: Tue, 8 Oct 2019 23:07:37 +0900 Subject: [PATCH 5/7] added capture viewport method --- splinter/driver/webdriver/__init__.py | 95 ++++++++++++++++++++------- 1 file changed, 73 insertions(+), 22 deletions(-) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index c47347f0d..ca3e826b7 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -41,6 +41,19 @@ def alert_enter(self): def alert_exit(self, type, value, traceback): pass +def create_file_from_base64_string(base64_string, filename): + file = base64.b64decode(base64_string.encode('ascii')) + + try: + with open(filename, 'wb') as f: + f.write(file) + except IOError: + return False + finally: + del file + + return True + Alert.__enter__ = alert_enter Alert.__exit__ = alert_exit Alert.fill_with = Alert.send_keys @@ -613,35 +626,78 @@ def screenshot(self, name="", suffix=".png", full=False): return filename - def capture_screenshot(self, filename, full=False, waiting_time=0): + def capture_viewport(self, viewport, filename, waiting_time=0): + base64_string = self.capture_viewport_as_base64(viewport, waiting_time) + + return create_file_from_base64_string(base64_string, filename) + + + def capture_viewport_as_base64(self, viewport, waiting_time=0): + self.full_screen() + + if waiting_time > 0: time.sleep(waiting_time) + + response = self.driver.execute_cdp_cmd('Page.captureScreenshot', { + 'clip': { + 'x': viewport['x'], + 'y': viewport['y'], + 'width': viewport['width'], + 'height': viewport['height'], + 'scale': self.driver.execute_script('return window.devicePixelRatio') + } + }) + + self.recover_screen() + + return response['data'] + + + def capture_screenshot(self, filename, full=False, viewport=None, waiting_time=0): if full: self.full_screen() + # trigger the `scroll` event to ensure lazy-load images can be rendered. self.execute_script("window.dispatchEvent(new Event('scroll'))") - if waiting_time > 0: - time.sleep(waiting_time) + if waiting_time > 0: time.sleep(waiting_time) - result = self.driver.get_screenshot_as_file(filename) + result = self.driver.get_screenshot_as_file(filename) - self.recover_screen() + self.recover_screen() + + return result + + elif viewport: + return self.capture_viewport(viewport, filename, waiting_time) - return result + else: + if waiting_time > 0: time.sleep(waiting_time) - def capture_screenshot_as_base64(self, full=False, waiting_time=0): + return self.driver.get_screenshot_as_file(filename) + + + def capture_screenshot_as_base64(self, full=False, viewport=None, waiting_time=0): if full: self.full_screen() + # trigger the `scroll` event to ensure lazy-load images can be rendered. self.execute_script("window.dispatchEvent(new Event('scroll'))") - if waiting_time > 0: - time.sleep(waiting_time) + if waiting_time > 0: time.sleep(waiting_time) - result = self.driver.get_screenshot_as_base64() + result = self.driver.get_screenshot_as_base64() - self.recover_screen() + self.recover_screen() + + return result + + elif viewport: + return self.capture_viewport_as_base64(viewport, waiting_time) - return result + else: + if waiting_time > 0: time.sleep(waiting_time) + + return self.driver.get_screenshot_as_base64() def select(self, name, value): self.find_by_xpath( @@ -660,7 +716,7 @@ def quit(self): pass def full_screen(self): - self.ori_window_size = self.driver.get_window_size() + self.set_original_window_size() width = self.driver.execute_script("return Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.scrollWidth);") height = self.driver.execute_script("return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight);") self.set_viewport_size(width, height) @@ -672,6 +728,9 @@ def recover_screen(self): self.set_viewport_size(width, height) self.ori_window_size = None + def set_original_window_size(self): + self.ori_window_size = self.driver.get_window_size() + def set_viewport_size(self, width, height): device_metrics = { 'width': width, @@ -935,17 +994,9 @@ def screenshot(self, name='', suffix='.png', full=False): def capture_screenshot(self, filename, waiting_time=0): base64_string = self.capture_screenshot_as_base64(waiting_time) - png = base64.b64decode(base64_string.encode('ascii')) - try: - with open(filename, 'wb') as f: - f.write(png) - except IOError: - return False - finally: - del png + return create_file_from_base64_string(base64_string, filename) - return True # TODO Uncomment this method and remove the hotfix one once the upstream issue(https://bugs.chromium.org/p/chromedriver/issues/detail?id=3154) has been fixed. # def capture_screenshot_as_base64(self, waiting_time=0): From 82c5deb75e31ff5e87632bd78f3c6ec1edaadb29 Mon Sep 17 00:00:00 2001 From: wang Date: Mon, 14 Oct 2019 13:57:34 +0900 Subject: [PATCH 6/7] fixed viewport scale (set to 1) --- splinter/driver/webdriver/__init__.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index ca3e826b7..78edd5b9e 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -643,7 +643,7 @@ def capture_viewport_as_base64(self, viewport, waiting_time=0): 'y': viewport['y'], 'width': viewport['width'], 'height': viewport['height'], - 'scale': self.driver.execute_script('return window.devicePixelRatio') + 'scale': 1 } }) @@ -1006,27 +1006,17 @@ def capture_screenshot(self, filename, waiting_time=0): # return self._element.screenshot_as_base64 def capture_screenshot_as_base64(self, waiting_time=0): - self.parent.full_screen() - - if waiting_time > 0: - time.sleep(waiting_time) - location = self._element.location size = self._element.size - response = self.parent.driver.execute_cdp_cmd('Page.captureScreenshot', { - 'clip': { - 'x': location['x'], - 'y': location['y'], - 'width': size['width'], - 'height': size['height'], - 'scale': self.parent.execute_script('return window.devicePixelRatio') - } - }) - - self.parent.recover_screen() + viewport = { + 'x': location['x'], + 'y': location['y'], + 'width': size['width'], + 'height': size['height'] + } - return response['data'] + return self.parent.capture_viewport_as_base64(viewport, waiting_time) def __getitem__(self, attr): From 64baf28cd7f06d879d770eaf12bc2b449f600ce5 Mon Sep 17 00:00:00 2001 From: wang Date: Mon, 14 Oct 2019 16:25:42 +0900 Subject: [PATCH 7/7] trigger scroll event when take screenshot with viewport set --- splinter/driver/webdriver/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/splinter/driver/webdriver/__init__.py b/splinter/driver/webdriver/__init__.py index 78edd5b9e..91a670a39 100644 --- a/splinter/driver/webdriver/__init__.py +++ b/splinter/driver/webdriver/__init__.py @@ -635,6 +635,9 @@ def capture_viewport(self, viewport, filename, waiting_time=0): def capture_viewport_as_base64(self, viewport, waiting_time=0): self.full_screen() + # trigger the `scroll` event to ensure lazy-load images can be rendered. + self.execute_script("window.dispatchEvent(new Event('scroll'))") + if waiting_time > 0: time.sleep(waiting_time) response = self.driver.execute_cdp_cmd('Page.captureScreenshot', {