Update examples

This commit is contained in:
Mikhail Nestuley
2022-07-01 10:23:43 +03:00
parent 1ef0520d1e
commit 8e412fb6bc
9 changed files with 187 additions and 51 deletions

136
.gitignore vendored
View File

@@ -1,3 +1,135 @@
venv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Pycharm
.idea
__pycache__
# Logs
logs/*-report

View File

@@ -2,6 +2,7 @@ import pytest
import logging
import allure
from selenium.webdriver import Chrome, ChromeOptions
from webdriver_manager.chrome import ChromeDriverManager
class Config:
@@ -13,7 +14,7 @@ class Config:
def driver():
options = ChromeOptions()
options.add_argument("--headless")
webdriver = Chrome(desired_capabilities=options.to_capabilities())
webdriver = Chrome(ChromeDriverManager().install(), desired_capabilities=options.to_capabilities())
webdriver.implicitly_wait(5)
yield webdriver
allure.attach(webdriver.current_url, "last url", allure.attachment_type.URI_LIST)

0
logs/.gitkeep Normal file
View File

View File

@@ -1,7 +1,7 @@
### Example of screenshot testing in python
How to:
1) Install python 3.6+
1) Install python 3.10+
2) Install deps `pip install requirements.txt`
3) Run tests pytest screenshot_tests/tests --alluredir=report
4) Generate and open report with Allure CLI [link](https://docs.qameta.io/allure/#_commandline)
3) Run tests `pytest screenshot_tests/tests --alluredir=logs/allure-report`
4) Generate and open report: `allure serve logs/allure-report`

View File

@@ -1,5 +1,5 @@
pytest==5.1.2
flaky==3.6.1
selenium==3.141.0
allure-pytest==2.8.2
Pillow==6.2.2
pytest==7.1.2
flaky==3.7.0
selenium==4.3.0
allure-pytest==2.9.45
Pillow==9.1.1

View File

@@ -136,9 +136,8 @@ class ImageProcessor(object):
max_width = image.size[0] if image.size[0] > max_width else max_width
max_height += image.size[1]
# Склейка работает так: сначала создаем одно "пустое" изображение равное размеру всех скелееных, и вставляем в
# в него по одному все скриншоты.
# Чтобы в финальном скрине не получилось что скриншоты заняли меньше места, чем картинка, снизу отрезаем over_height
# Склейка работает так: сначала создаем одно "пустое" изображение равное размеру всех скелееных, и вставляем в него по одному все скриншоты.
# Чтобы в финальном скрине не получилось что скриншоты заняли меньше места, чем картинка, снизу отрезаем over_height.
max_height = max_height - over_height
result = Image.new('RGB', (max_width, max_height))
logging.info(f'Screen size: ({max_width}, {max_height})')
@@ -161,14 +160,16 @@ class ImageProcessor(object):
return result
def load_image_from_bytes(self, data: bytes) -> Image.Image:
@staticmethod
def load_image_from_bytes(data: bytes) -> Image.Image:
"""Загрузить изображение из байтовой строки."""
with BytesIO(data) as fp:
image: Image.Image = Image.open(fp)
image.load()
return image
def image_to_bytes(self, image: Image.Image) -> bytes:
@staticmethod
def image_to_bytes(image: Image.Image) -> bytes:
with BytesIO() as fp:
image.save(fp, "PNG")
return fp.getvalue()

View File

@@ -1,6 +1,7 @@
from screenshot_tests.utils.screenshots import TestCase
from selenium.webdriver.common.by import By
from screenshot_tests.utils.screenshots import TestCase
class TestExample(TestCase):
"""Tests for https://go.mail.ru"""
@@ -10,14 +11,14 @@ class TestExample(TestCase):
def action():
# Убираем фокус с инпута, чтобы тест не флакал из-за курсора
self.driver.find_element_by_xpath("//*[text()='найти']").click()
self.driver.find_element(By.XPATH, "//*[text()='найти']").click()
self.check_by_screenshot(None, action=action, full_page=True)
def test_main_page_flaky(self):
self.driver.get("https://go.mail.ru/")
# Чтобы посмотреть как выглядит сломанный тест в отчетe
self.driver.find_element_by_xpath("//input[not(@type='hidden')]").send_keys("foo")
# Чтобы посмотреть как выглядит сломанный тeест в отчетe
self.driver.find_element(By.XPATH, "//input[not(@type='hidden')]").send_keys("foo")
self.check_by_screenshot(None, full_page=True)
def test_search_block(self):
@@ -25,6 +26,6 @@ class TestExample(TestCase):
def action():
# Тестируем подсветку таба после переключения на другую вертикаль
self.driver.find_element_by_xpath("//span[contains(text(), 'Соцсети')]").click()
self.driver.find_element(By.XPATH, "//span[contains(text(), 'Соцсети')]").click()
self.check_by_screenshot((By.CSS_SELECTOR, ".MainVerticalsNav-listItemActive"), action=action)

View File

@@ -1,9 +1,9 @@
import pytest
import os
from conftest import Config
from typing import Type
# noinspection PyAttributeOutsideInit
class TestCase:
"""Base class for all tests."""

View File

@@ -1,17 +1,19 @@
"""Screenshot TestCase."""
import logging
import time
from typing import Tuple
from urllib.parse import urlparse
import allure
import pytest
import time
from PIL import Image
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from urllib.parse import urlparse
from screenshot_tests.utils import common
from screenshot_tests.image_proccessing.image_processor import ImageProcessor
from typing import Tuple
from PIL import Image
from screenshot_tests.utils import common
# noinspection PyAttributeOutsideInit
# аннотируем все классы всех скриншот тестов для работы плагина
@@ -100,13 +102,14 @@ class TestCase(common.TestCase):
x, y, width, height = self._get_raw_coords_by_locator(locator_type, query_string)
return x * self.pixel_ratio, y * self.pixel_ratio, width * self.pixel_ratio, height * self.pixel_ratio
def _get_element_screenshot(self,
def _get_element_screenshot(
self,
locator_type,
query_string,
action,
finalize,
scroll_and_screen) \
-> Tuple[Image.Image, Tuple[int, int, int, int]]:
scroll_and_screen
) -> Tuple[Image.Image, Tuple[int, int, int, int]]:
"""Сделать скриншот страницы и кропнуть до скриншота элемента.
Не получится использовать метод session/{sessionId}/element/{elementId}/screenshot
@@ -137,16 +140,16 @@ class TestCase(common.TestCase):
return screen.crop(coordinates), coordinates
def _get_diff(self, element, action=None, full_screen=True, full_page=False, finalize=None, scroll_and_screen=True):
def _get_diff(self, element=None, action=None, full_screen=True, full_page=False, finalize=None, scroll_and_screen=True):
"""Получит скриншоты с текущей страницы, и с эталонной.
Поблочно сравнит их, и вернет количество отличающихся блоков.
:param element: тюпл с типом локатора и локатором
:param action: функция которая подготовит страницу к снятию скриншота
:param full_screen: ресайзить ли браузер до максимума
:param full_page: скринить всю страницу, а не только переданный элемент
:param finalize: финализация после сравнения скриншотов
:param scroll_and_screen: скролить страницу (сверху к низу) и склеивать участки в один скриншот
:param element: tuple с типом локатора и локатором.
:param action: функция, которая подготовит страницу к снятию скриншота.
:param full_screen: resize ли браузер до максимума.
:param full_page: скринить всю страницу, а не только переданный элемент.
:param finalize: финализация после сравнения скриншотов.
:param scroll_and_screen: скролить страницу (сверху к низу) и склеивать участки в один скриншот.
"""
if full_screen:
self._use_full_screen()
@@ -162,13 +165,11 @@ class TestCase(common.TestCase):
prod_url = saved_url._replace(netloc=self.staging)
# На текущей странице делаем первый скриншот
first_image, coords_test = self._get_element_screenshot(locator_type, query_string, action, finalize,
scroll_and_screen)
first_image, coords_test = self._get_element_screenshot(locator_type, query_string, action, finalize, scroll_and_screen)
logging.info('Done screen on test stand')
# Теперь делаем скриншот в проде
self.driver.get(prod_url.geturl())
second_image, coords_prod = self._get_element_screenshot(locator_type, query_string, action, finalize,
scroll_and_screen)
second_image, coords_prod = self._get_element_screenshot(locator_type, query_string, action, finalize, scroll_and_screen)
logging.info('Done screen on stage stand')
# Возращаемся на тестовый стенд. Всегда нужно возвращаться на тестовый стенд. На это завязаны тесты и отчеты
@@ -186,9 +187,9 @@ class TestCase(common.TestCase):
return diff, saved_url, prod_url
def get_diff(self, *args, **kwargs):
diff, _, _ = self._get_diff(*args, **kwargs)
return diff
# def get_diff(self, *args, **kwargs):
# diff, _, _ = self._get_diff(*args, **kwargs)
# return diff
def check_by_screenshot(self, element, *args, **kwargs):
diff, saved_url, prod_url = self._get_diff(element, *args, **kwargs)