Compare commits

...

5 Commits

Author SHA1 Message Date
Ivan Vazhenin
25a79ed807 make screenshots 2022-09-28 12:55:33 +03:00
aikrasnov
7c8a859f5e Merge pull request #8 from mnestuley-marfatech/bump_versions
Update examples
2022-07-05 13:42:33 +03:00
Mikhail Nestuley
411b7c4cf4 fix core 2022-07-04 14:07:22 +03:00
Mikhail Nestuley
8e412fb6bc Update examples 2022-07-04 14:04:42 +03:00
a.krasnov
1ef0520d1e remove unused import 2020-12-02 00:52:49 +03:00
9 changed files with 196 additions and 68 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

@@ -1,7 +1,7 @@
import pytest
import logging
import allure
from selenium.webdriver import Chrome, ChromeOptions, Remote
from selenium.webdriver import Chrome, ChromeOptions
class Config:
@@ -13,8 +13,8 @@ class Config:
def driver():
options = ChromeOptions()
options.add_argument("--headless")
webdriver = Chrome(desired_capabilities=options.to_capabilities())
webdriver.implicitly_wait(5)
webdriver = Chrome('/home/ashatora/yandexdriver', options=options)
webdriver.implicitly_wait(2)
yield webdriver
allure.attach(webdriver.current_url, "last url", allure.attachment_type.URI_LIST)
webdriver.quit()
@@ -23,13 +23,13 @@ def driver():
def pytest_addoption(parser):
"""Command line parser."""
parser.addoption(f'--{Config.BASE_URL}',
default='https://go.mail.ru/',
default='https://s57.ivazh.ru/styles/r14/#12.13/71.2832/72.13405',
dest=Config.BASE_URL,
action='store',
metavar='str',
help='Environment for run tests.')
parser.addoption(f'--{Config.STAGING}',
default='go.mail.ru',
default='s57.ivazh.ru',
dest=Config.STAGING,
action='store',
metavar='str',

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,30 +1,20 @@
from screenshot_tests.utils.screenshots import TestCase
from selenium.webdriver.common.by import By
from screenshot_tests.utils.screenshots import TestCase
import time
class TestExample(TestCase):
"""Tests for https://go.mail.ru"""
def test_main_page(self):
self.driver.get("https://go.mail.ru/")
self.driver.get("https://s57.ivazh.ru/styles/r14/#12.13/71.2832/72.13405")
def action():
# Убираем фокус с инпута, чтобы тест не флакал из-за курсора
self.driver.find_element_by_xpath("//*[text()='найти']").click()
time.sleep(5)
self.check_by_screenshot(None, action=action, full_page=True)
self.check_by_screenshot(None, full_page=True, action=action)
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")
self.check_by_screenshot(None, full_page=True)
def test_search_block(self):
self.driver.get("https://go.mail.ru/")
def action():
# Тестируем подсветку таба после переключения на другую вертикаль
self.driver.find_element_by_xpath("//span[contains(text(), 'Соцсети')]").click()
self.check_by_screenshot((By.CSS_SELECTOR, ".MainVerticalsNav-listItemActive"), action=action)
# def test_search_block(self):
# self.driver.get("https://s57.ivazh.ru/styles/r14/#12.13/71.2832/72.13405")
# self.driver.implicitly_wait(5)
#
# self.check_by_screenshot((By.CSS_SELECTOR, ".ol-mouse-position"))

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
# аннотируем все классы всех скриншот тестов для работы плагина
@@ -83,7 +85,7 @@ class TestCase(common.TestCase):
"""Без учета плотности пикселей."""
wait = WebDriverWait(self.driver, timeout=10, ignored_exceptions=Exception)
wait.until(lambda _: self.driver.find_element(locator_type, query_string).is_displayed(),
message="Невозможно получить размеры элемента, элемент не отображается")
message="Невозможно получить размеры элемента, элемент не отображается")
# После того, как дождались видимости элемента, ждем еще 2 секунды, чтобы точно завершились разные анимации
time.sleep(2)
el = self.driver.find_element(locator_type, query_string)
@@ -93,6 +95,10 @@ class TestCase(common.TestCase):
y = location["y"]
width = location["x"] + size['width']
height = location["y"] + size['height']
if width == 0:
width = 1920
if height == 0:
height = 1080
# (312, 691, 1112, 691)
return x, y, width, height
@@ -100,13 +106,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,
locator_type,
query_string,
action,
finalize,
scroll_and_screen) \
-> Tuple[Image.Image, Tuple[int, int, int, int]]:
def _get_element_screenshot(
self,
locator_type,
query_string,
action,
finalize,
scroll_and_screen
) -> Tuple[Image.Image, Tuple[int, int, int, int]]:
"""Сделать скриншот страницы и кропнуть до скриншота элемента.
Не получится использовать метод session/{sessionId}/element/{elementId}/screenshot
@@ -137,16 +144,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 +169,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')
# Возращаемся на тестовый стенд. Всегда нужно возвращаться на тестовый стенд. На это завязаны тесты и отчеты