initial commit
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import pytest
|
||||
import os
|
||||
from screenshot_tests.page_objects.elements import PageBoundGeneric
|
||||
from conftest import Config
|
||||
from typing import Type
|
||||
|
||||
|
||||
class TestCase:
|
||||
"""Base class for all tests."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def set_driver(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def configure(self, request):
|
||||
self.base_url = request.config.getoption(Config.BASE_URL)
|
||||
self.staging = request.config.getoption(Config.STAGING)
|
||||
|
||||
def get_page(self, page_class: Type[PageBoundGeneric]) -> PageBoundGeneric:
|
||||
"""Create instance of web page and return it."""
|
||||
path = page_class.path
|
||||
|
||||
if path is None:
|
||||
raise TypeError(f"Path in {page_class} is None!")
|
||||
|
||||
page = page_class(self.driver)
|
||||
self.driver.get(os.path.join(self.base_url, path))
|
||||
return page
|
||||
@@ -0,0 +1,143 @@
|
||||
import pytest
|
||||
import time
|
||||
import logging
|
||||
import allure
|
||||
|
||||
from screenshot_tests.page_objects.custom_web_element import CustomWebElement
|
||||
from urllib.parse import urlparse
|
||||
from screenshot_tests.page_objects.elements import Locators
|
||||
from screenshot_tests.utils import common
|
||||
from screenshot_tests.image_proccessing.image_processor import ImageProcessor
|
||||
from typing import Tuple
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# https://github.com/allure-framework/allure2/tree/master/plugins/screen-diff-plugin
|
||||
@allure.label('testType', 'screenshotDiff')
|
||||
class TestCase(common.TestCase):
|
||||
"""Base class for all screenshot tests."""
|
||||
|
||||
BODY_LOCATOR = (Locators.CSS_SELECTOR, "body")
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def screenshot_prepare(self):
|
||||
self.image_processor = ImageProcessor()
|
||||
# количество попыток для снятия скриншота
|
||||
self.attempts = 5
|
||||
|
||||
def _scroll(self, x: int, y: int):
|
||||
scroll_string = f"window.scrollTo({x}, {y})"
|
||||
self.driver.execute_script(scroll_string)
|
||||
time.sleep(0.2)
|
||||
logging.info(f"Scroll to «{scroll_string}»")
|
||||
|
||||
def _make_screenshot_whole_page(self):
|
||||
total_width = self.driver.execute_script("return document.body.offsetWidth")
|
||||
total_height = self.driver.execute_script("return document.body.parentNode.scrollHeight")
|
||||
viewport_width = self.driver.execute_script("return document.body.clientWidth")
|
||||
viewport_height = self.driver.execute_script("return window.innerHeight")
|
||||
screenshots = []
|
||||
offset = 0
|
||||
assert viewport_width == total_width, "Ширина вьюпорта, и ширина экрана должны совпадать"
|
||||
|
||||
self._scroll(0, 0)
|
||||
while offset <= total_height:
|
||||
screenshots.append(self.driver.get_screenshot_as_png())
|
||||
offset += viewport_height
|
||||
self._scroll(0, offset)
|
||||
|
||||
return self.image_processor.paste(screenshots)
|
||||
|
||||
def _get_coords_by_locator(self, by, locator) -> Tuple[int, int, int, int]:
|
||||
# После того, как дождались видимости элемента, ждем еще 2 секунды, чтобы точно завершились разные анимации
|
||||
time.sleep(2)
|
||||
el = self.driver.find_element(by, locator)
|
||||
location = el.location
|
||||
size = el.size
|
||||
x = location["x"]
|
||||
y = location["y"]
|
||||
width = location["x"] + size['width']
|
||||
height = location["y"] + size['height']
|
||||
return x * 2, y * 2, width * 2, height * 2
|
||||
|
||||
def _get_element_screenshot(self, by, locator, action, finalize) \
|
||||
-> Tuple[Image.Image, Tuple[int, int, int, int]]:
|
||||
"""Get screenshot of element.
|
||||
|
||||
Can't use session/{sessionId}/element/{elementId}/screenshot because it's available only in Edge
|
||||
https://stackoverflow.com/questions/36084257/im-trying-to-take-a-screenshot-of-an-element-with-selenium-webdriver-but-unsup
|
||||
"""
|
||||
if callable(action):
|
||||
action()
|
||||
|
||||
coordinates = self._get_coords_by_locator(by, locator)
|
||||
screen = self._make_screenshot_whole_page()
|
||||
logging.info(f"element: {locator}, coordinates: {coordinates}")
|
||||
|
||||
if callable(finalize):
|
||||
finalize()
|
||||
|
||||
return screen.crop(coordinates), coordinates
|
||||
|
||||
def _get_diff(self, element: CustomWebElement, action=None, full_page=False, finalize=None):
|
||||
"""Get screenshot from test environment and compare with production.
|
||||
|
||||
:param element: element for check (instance of CustomWebElement)
|
||||
:param action: callback executed before making screenshot. Use it when need prepare page for screenshot.
|
||||
:param full_page: ignore element, and compare whole page.
|
||||
:param finalize: callback executed after screenshot.
|
||||
"""
|
||||
if full_page:
|
||||
by, locator = self.BODY_LOCATOR
|
||||
else:
|
||||
by, locator = element.by, element.locator
|
||||
|
||||
saved_url = urlparse(self.driver.current_url)
|
||||
# noinspection PyProtectedMember
|
||||
prod_url = saved_url._replace(netloc=urlparse(self.staging).netloc)
|
||||
|
||||
# Открываем странички пока размеры элементов на них не совпадут
|
||||
coords_equal = False
|
||||
attempts = 0
|
||||
while not coords_equal and attempts < self.attempts:
|
||||
logging.info(f'Try make screenshots. Attempts: {attempts}')
|
||||
|
||||
# На текущей странице делаем первый скриншот
|
||||
first_image, coords_test = self._get_element_screenshot(by, locator, action, finalize)
|
||||
|
||||
# Теперь делаем скриншот в проде
|
||||
self.driver.get(prod_url.geturl())
|
||||
second_image, coords_prod = self._get_element_screenshot(by, locator, action, finalize)
|
||||
|
||||
# Возращаемся на тестовый стенд. Всегда нужно возвращаться на тестовый стенд. На это завязаны тесты и отчеты
|
||||
self.driver.get(saved_url.geturl())
|
||||
|
||||
# Если размеры элементов на странице не совпали, и выбраны не все попытки, пробуем снова
|
||||
attempts += 1
|
||||
# По размеру блока, оставим на случай, если по координатам способ будет не работать не очень
|
||||
x1, y1, x2, y2 = coords_test
|
||||
x_1, y_1, x_2, y_2 = coords_prod
|
||||
size_test = round(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5)
|
||||
size_prod = round(((x_2 - x_1) ** 2 + (y_2 - y_1) ** 2) ** 0.5)
|
||||
logging.info(f"Coords test: {coords_test}, coords prod: {coords_prod}")
|
||||
logging.info(f"Size test: {size_test}, size prod: {size_prod}")
|
||||
coords_equal = size_prod == size_test
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
diff, result, first, second = self.image_processor.get_images_diff(first_image, second_image)
|
||||
|
||||
# Для добавления в отчет (https://github.com/allure-framework/allure2/tree/master/plugins/screen-diff-plugin)
|
||||
allure.attach(result, "diff", allure.attachment_type.PNG)
|
||||
allure.attach(first, "actual", allure.attachment_type.PNG)
|
||||
allure.attach(second, "expected", allure.attachment_type.PNG)
|
||||
|
||||
return diff, saved_url, prod_url
|
||||
|
||||
def get_diff(self, *args, **kwargs):
|
||||
diff, _, _ = self._get_diff(*args, **kwargs)
|
||||
return diff
|
||||
|
||||
def check_by_screenshot(self, element: CustomWebElement, *args, **kwargs):
|
||||
diff, saved_url, prod_url = self._get_diff(element, *args, **kwargs)
|
||||
info = element.description
|
||||
assert diff == 0, f"{info} отличается на страницах:\n{saved_url.geturl()}\nи\n{prod_url.geturl()}"
|
||||
Reference in New Issue
Block a user