Skip to content
This repository was archived by the owner on Aug 10, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 13 additions & 94 deletions eyes_selenium/applitools/selenium/capture/eyes_webdriver_screenshot.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import absolute_import

import typing
from enum import Enum

import attr
from selenium.common.exceptions import WebDriverException

from applitools.common import (
CoordinatesType,
CoordinatesTypeConversionError,
Expand All @@ -16,25 +17,21 @@
)
from applitools.common.utils import argument_guard, image_utils
from applitools.core.capture import EyesScreenshot, EyesScreenshotFactory
from applitools.selenium import eyes_selenium_utils
from applitools.selenium.positioning import (
ScrollPositionProvider,
SeleniumPositionProvider,
from applitools.selenium.capture.screenshot_utils import (
ScreenshotType,
calc_frame_location_in_screenshot,
update_screenshot_type,
)
from selenium.common.exceptions import WebDriverException
from applitools.selenium.eyes_selenium_utils import get_updated_scroll_position
from applitools.selenium.positioning import SeleniumPositionProvider

if typing.TYPE_CHECKING:
from typing import Optional, Union
from typing import Union
from PIL import Image
from applitools.selenium.webdriver import EyesWebDriver
from applitools.selenium.frames import FrameChain


class ScreenshotType(Enum):
VIEWPORT = "VIEWPORT"
ENTIRE_FRAME = "ENTIRE_FRAME"


@attr.s
class EyesWebDriverScreenshot(EyesScreenshot):

Expand Down Expand Up @@ -88,8 +85,8 @@ def from_screenshot(cls, driver, image, screenshot_region):
def __attrs_post_init__(self):
# type: () -> None
self._frame_chain = self._driver.frame_chain.clone()
self._screenshot_type = self.update_screenshot_type(
self._screenshot_type, self._image
self._screenshot_type = update_screenshot_type(
self._screenshot_type, self._image, self._driver
)
cur_frame_position_provider = self._driver.eyes.current_frame_position_provider
if cur_frame_position_provider:
Expand All @@ -98,7 +95,7 @@ def __attrs_post_init__(self):
position_provider = self._driver.eyes.position_provider

if self._current_frame_scroll_position is None:
self._current_frame_scroll_position = self.get_updated_scroll_position(
self._current_frame_scroll_position = get_updated_scroll_position(
position_provider
)
self._frame_location_in_screenshot = self.get_updated_frame_location_in_screenshot(
Expand All @@ -123,42 +120,13 @@ def _validate_frame_window(self):
def get_updated_frame_location_in_screenshot(self, frame_location_in_screenshot):
# type: (Point) -> Point
if self.frame_chain.size > 0:
frame_location_in_screenshot = self.calc_frame_location_in_screenshot(
frame_location_in_screenshot = calc_frame_location_in_screenshot(
self._driver, self._frame_chain, self._screenshot_type
)
elif not frame_location_in_screenshot:
frame_location_in_screenshot = Point.zero()
return frame_location_in_screenshot

def get_updated_scroll_position(self, position_provider):
# type: (SeleniumPositionProvider) -> Point
try:
sp = position_provider.get_current_position()
if not sp:
sp = Point.zero()
except WebDriverException:
sp = Point.zero()

return sp

def update_screenshot_type(self, screenshot_type, image):
# type: ( Optional[ScreenshotType], Image) -> ScreenshotType
if screenshot_type is None:
viewport_size = self._driver.eyes.viewport_size
scale_viewport = self._driver.eyes.stitch_content

if scale_viewport:
pixel_ratio = self._driver.eyes.device_pixel_ratio
viewport_size = viewport_size.scale(pixel_ratio)
if (
image.width <= viewport_size["width"]
and image.height <= viewport_size["height"]
):
screenshot_type = ScreenshotType.VIEWPORT
else:
screenshot_type = ScreenshotType.ENTIRE_FRAME
return screenshot_type

@property
def image(self):
# type: () -> Union[Image, Image]
Expand All @@ -180,55 +148,6 @@ def get_frame_size(self, position_provider):
frame_size = self._driver.get_default_content_viewport_size()
return frame_size

@staticmethod
def _get_default_content_scroll_position(driver):
# type: (EyesWebDriver) -> Point
scroll_root_element = eyes_selenium_utils.current_frame_scroll_root_element(
driver
)
return ScrollPositionProvider.get_current_position_static(
driver, scroll_root_element
)

@staticmethod
def get_default_content_scroll_position(current_frames, driver):
if current_frames.size == 0:
scroll_position = EyesWebDriverScreenshot._get_default_content_scroll_position(
driver
)
else:
current_fc = driver.eyes._original_frame_chain
with driver.switch_to.frames_and_back(current_fc):
scroll_position = EyesWebDriverScreenshot._get_default_content_scroll_position(
driver
)
return scroll_position

@staticmethod
def calc_frame_location_in_screenshot(driver, frame_chain, screenshot_type):
window_scroll = EyesWebDriverScreenshot.get_default_content_scroll_position(
frame_chain, driver
)
logger.info("Getting first frame...")
first_frame = frame_chain[0]
location_in_screenshot = Point(first_frame.location.x, first_frame.location.y)
# We only need to consider the scroll of the default content if the screenshot is a
# viewport screenshot. If this is a full page screenshot, the frame location will not
# change anyway.
if screenshot_type == ScreenshotType.VIEWPORT:
location_in_screenshot = location_in_screenshot.offset(
-window_scroll.x, -window_scroll.y
)

# For inner frames we must calculate the scroll
inner_frames = frame_chain[1:]
for frame in inner_frames:
location_in_screenshot = location_in_screenshot.offset(
frame.location.x - frame.parent_scroll_position.x,
frame.location.y - frame.parent_scroll_position.y,
)
return location_in_screenshot

@property
def frame_chain(self):
# type: () -> FrameChain
Expand Down
85 changes: 85 additions & 0 deletions eyes_selenium/applitools/selenium/capture/screenshot_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from enum import Enum
from typing import TYPE_CHECKING, Optional

from applitools.common import Point, Region, logger
from applitools.common.utils import image_utils
from applitools.selenium import eyes_selenium_utils
from applitools.selenium.eyes_selenium_utils import (
get_cur_position_provider,
get_updated_scroll_position,
)

if TYPE_CHECKING:
from PIL import Image
from applitools.selenium.webdriver import EyesWebDriver


class ScreenshotType(Enum):
VIEWPORT = "VIEWPORT"
ENTIRE_FRAME = "ENTIRE_FRAME"


def update_screenshot_type(screenshot_type, image, driver):
# type: ( Optional[ScreenshotType], Image, EyesWebDriver) -> ScreenshotType
if screenshot_type is None:
viewport_size = driver.eyes.viewport_size
scale_viewport = driver.eyes.stitch_content

if scale_viewport:
pixel_ratio = driver.eyes.device_pixel_ratio
viewport_size = viewport_size.scale(pixel_ratio)
if (
image.width <= viewport_size["width"]
and image.height <= viewport_size["height"]
):
screenshot_type = ScreenshotType.VIEWPORT
else:
screenshot_type = ScreenshotType.ENTIRE_FRAME
return screenshot_type


def cut_to_viewport_size_if_required(driver, image):
# type: (EyesWebDriver, Image) -> Image
# Some browsers return always full page screenshot (IE).
# So we cut such images to viewport size
position_provider = get_cur_position_provider(driver)
curr_frame_scroll = get_updated_scroll_position(position_provider)
screenshot_type = update_screenshot_type(None, image, driver)
if screenshot_type != ScreenshotType.VIEWPORT:
viewport_size = driver.eyes.viewport_size
image = image_utils.crop_image(
image,
region_to_crop=Region(
top=curr_frame_scroll.x,
left=0,
height=viewport_size["height"],
width=viewport_size["width"],
),
)
return image


def calc_frame_location_in_screenshot(driver, frame_chain, screenshot_type):
window_scroll = eyes_selenium_utils.get_default_content_scroll_position(
frame_chain, driver
)
logger.info("Getting first frame...")
first_frame = frame_chain[0]
location_in_screenshot = Point(first_frame.location.x, first_frame.location.y)
# We only need to consider the scroll of the default content if the screenshot is a
# viewport screenshot. If this is a full page screenshot, the frame location will
# not
# change anyway.
if screenshot_type == ScreenshotType.VIEWPORT:
location_in_screenshot = location_in_screenshot.offset(
-window_scroll.x, -window_scroll.y
)

# For inner frames we must calculate the scroll
inner_frames = frame_chain[1:]
for frame in inner_frames:
location_in_screenshot = location_in_screenshot.offset(
frame.location.x - frame.parent_scroll_position.x,
frame.location.y - frame.parent_scroll_position.y,
)
return location_in_screenshot
64 changes: 59 additions & 5 deletions eyes_selenium/applitools/selenium/eyes_selenium_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
from applitools.common import Point, RectangleSize, logger

if tp.TYPE_CHECKING:
from typing import Text, Optional, Any, Iterator, Union
from typing import Text, Optional, Any, Union, Iterator
from applitools.selenium.frames import FrameChain
from applitools.selenium.positioning import SeleniumPositionProvider
from applitools.selenium.webdriver import EyesWebDriver
from applitools.selenium.webelement import EyesWebElement
from applitools.selenium.fluent import SeleniumCheckSettings, FrameLocator

from applitools.common.utils.custom_types import (
AnyWebDriver,
ViewPort,
Expand All @@ -35,6 +38,8 @@
"hide_scrollbars",
"set_overflow",
"parse_location_string",
"get_cur_position_provider",
"get_updated_scroll_position",
)

_NATIVE_APP = "NATIVE_APP"
Expand Down Expand Up @@ -440,7 +445,16 @@ def parse_location_string(position):
xy = position.split(";")
if len(xy) != 2:
raise WebDriverException("Could not get scroll position!")
return Point(float(xy[0]), float(xy[1]))
return Point(round(float(xy[0])), round(float(xy[1])))


def get_current_position(driver, element):
# type: (AnyWebDriver, AnyWebElement) -> Point
element = get_underlying_webelement(element)
xy = driver.execute_script(
"return arguments[0].scrollLeft+';'+arguments[0].scrollTop;", element
)
return parse_location_string(xy)


def scroll_root_element_from(driver, container=None):
Expand Down Expand Up @@ -469,13 +483,53 @@ def root_html():
return scroll_root_element


def current_frame_scroll_root_element(driver):
# type: (EyesWebDriver) -> EyesWebElement
def current_frame_scroll_root_element(driver, scroll_root_element=None):
# type: (EyesWebDriver, Optional[AnyWebElement]) -> EyesWebElement
fc = driver.frame_chain.clone()
cur_frame = fc.peek
root_element = None
if cur_frame:
root_element = cur_frame.scroll_root_element
if root_element is None and not driver.is_mobile_app:
root_element = driver.find_element_by_tag_name("html")
if scroll_root_element:
root_element = scroll_root_element
else:
root_element = driver.find_element_by_tag_name("html")
return root_element


def get_cur_position_provider(driver):
# type: (EyesWebDriver) -> SeleniumPositionProvider
cur_frame_position_provider = driver.eyes.current_frame_position_provider
if cur_frame_position_provider:
return cur_frame_position_provider
else:
return driver.eyes.position_provider


def get_updated_scroll_position(position_provider):
# type: (SeleniumPositionProvider) -> Point
try:
sp = position_provider.get_current_position()
if not sp:
sp = Point.zero()
except WebDriverException:
sp = Point.zero()

return sp


def get_default_content_scroll_position(current_frames, driver):
# type: (FrameChain, EyesWebDriver) -> Point
if current_frames.size == 0:

scroll_position = get_current_position(
driver, current_frame_scroll_root_element(driver)
)
else:
current_fc = driver.eyes.original_frame_chain
with driver.switch_to.frames_and_back(current_fc):
scroll_position = get_current_position(
driver, current_frame_scroll_root_element(driver)
)
return scroll_position
Loading