simplify tests

This commit is contained in:
a.krasnov
2020-12-01 23:41:11 +03:00
parent 3b5714d6ef
commit 44662ae0a4
61 changed files with 235 additions and 1591 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1,41 +0,0 @@
INFO root:screenshots.py:103 Try make screenshots. Attempts: 0
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 0)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1800)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 2400)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3000)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4800)»
INFO root:image_processor.py:101 Screen size: (1600, 9600)
INFO root:image_processor.py:105 Image added, offset is 0
INFO root:image_processor.py:105 Image added, offset is 1200
INFO root:image_processor.py:105 Image added, offset is 2400
INFO root:image_processor.py:105 Image added, offset is 3600
INFO root:image_processor.py:105 Image added, offset is 4800
INFO root:image_processor.py:105 Image added, offset is 6000
INFO root:image_processor.py:105 Image added, offset is 7200
INFO root:image_processor.py:105 Image added, offset is 8400
INFO root:screenshots.py:75 element: .search2, coordinates: (366, 542, 1570, 614)
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 0)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1800)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 2400)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3000)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4800)»
INFO root:image_processor.py:101 Screen size: (1600, 9600)
INFO root:image_processor.py:105 Image added, offset is 0
INFO root:image_processor.py:105 Image added, offset is 1200
INFO root:image_processor.py:105 Image added, offset is 2400
INFO root:image_processor.py:105 Image added, offset is 3600
INFO root:image_processor.py:105 Image added, offset is 4800
INFO root:image_processor.py:105 Image added, offset is 6000
INFO root:image_processor.py:105 Image added, offset is 7200
INFO root:image_processor.py:105 Image added, offset is 8400
INFO root:screenshots.py:75 element: .search2, coordinates: (366, 544, 1570, 616)
INFO root:screenshots.py:122 Coords test: (366, 542, 1570, 614), coords prod: (366, 544, 1570, 616)
INFO root:screenshots.py:123 Size test: 1206, size prod: 1206

View File

@@ -1 +0,0 @@
https://yandex.ru/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -1,45 +0,0 @@
INFO root:screenshots.py:103 Try make screenshots. Attempts: 0
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 0)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1800)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 2400)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3000)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4800)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 5400)»
INFO root:image_processor.py:101 Screen size: (1600, 10800)
INFO root:image_processor.py:105 Image added, offset is 0
INFO root:image_processor.py:105 Image added, offset is 1200
INFO root:image_processor.py:105 Image added, offset is 2400
INFO root:image_processor.py:105 Image added, offset is 3600
INFO root:image_processor.py:105 Image added, offset is 4800
INFO root:image_processor.py:105 Image added, offset is 6000
INFO root:image_processor.py:105 Image added, offset is 7200
INFO root:image_processor.py:105 Image added, offset is 8400
INFO root:image_processor.py:105 Image added, offset is 9600
INFO root:screenshots.py:75 element: .news__header, coordinates: (24, 104, 1082, 412)
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 0)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 1800)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 2400)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3000)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 3600)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4200)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 4800)»
INFO root:screenshots.py:32 Scroll to «window.scrollTo(0, 5400)»
INFO root:image_processor.py:101 Screen size: (1600, 10800)
INFO root:image_processor.py:105 Image added, offset is 0
INFO root:image_processor.py:105 Image added, offset is 1200
INFO root:image_processor.py:105 Image added, offset is 2400
INFO root:image_processor.py:105 Image added, offset is 3600
INFO root:image_processor.py:105 Image added, offset is 4800
INFO root:image_processor.py:105 Image added, offset is 6000
INFO root:image_processor.py:105 Image added, offset is 7200
INFO root:image_processor.py:105 Image added, offset is 8400
INFO root:image_processor.py:105 Image added, offset is 9600
INFO root:screenshots.py:75 element: .news__header, coordinates: (24, 104, 1082, 412)
INFO root:screenshots.py:122 Coords test: (24, 104, 1082, 412), coords prod: (24, 104, 1082, 412)
INFO root:screenshots.py:123 Size test: 1102, size prod: 1102

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1 +0,0 @@
https://yandex.ru/

View File

@@ -1,2 +0,0 @@
"Epic","Feature","Story","FAILED","BROKEN","PASSED","SKIPPED","UNKNOWN"
"","","","2","0","0","0","0"
1 Epic Feature Story FAILED BROKEN PASSED SKIPPED UNKNOWN
2 2 0 0 0 0

View File

@@ -1,29 +0,0 @@
{
"uid" : "b1a8273437954620fa374b796ffaacdd",
"name" : "behaviors",
"children" : [ {
"name" : "test_search_field",
"uid" : "ff3cf8e351b44c24",
"parentUid" : "b1a8273437954620fa374b796ffaacdd",
"status" : "failed",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"flaky" : false,
"parameters" : [ ]
}, {
"name" : "test_news_widget",
"uid" : "35bf2eac0f3b5d9d",
"parentUid" : "b1a8273437954620fa374b796ffaacdd",
"status" : "failed",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"flaky" : false,
"parameters" : [ ]
} ]
}

View File

@@ -1,2 +0,0 @@
"Category","FAILED","BROKEN","PASSED","SKIPPED","UNKNOWN"
"Product defects","2","0","0","0","0"
1 Category FAILED BROKEN PASSED SKIPPED UNKNOWN
2 Product defects 2 0 0 0 0

View File

@@ -1,41 +0,0 @@
{
"uid" : "4b4757e66a1912dae1a509f688f20b0f",
"name" : "categories",
"children" : [ {
"name" : "Product defects",
"children" : [ {
"name" : "AssertionError: «Поисковый блок» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"children" : [ {
"name" : "test_search_field",
"uid" : "ff3cf8e351b44c24",
"parentUid" : "21cceaf81a44534c14791402ab60e7a0",
"status" : "failed",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"flaky" : false,
"parameters" : [ ]
} ],
"uid" : "21cceaf81a44534c14791402ab60e7a0"
}, {
"name" : "AssertionError: «Хэдер с новостями» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"children" : [ {
"name" : "test_news_widget",
"uid" : "35bf2eac0f3b5d9d",
"parentUid" : "3095de4d040f98faf6d1c7fbdd61b3ce",
"status" : "failed",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"flaky" : false,
"parameters" : [ ]
} ],
"uid" : "3095de4d040f98faf6d1c7fbdd61b3ce"
} ],
"uid" : "8fb3a91ba5aaf9de24cc8a92edc82b5d"
} ]
}

View File

@@ -1,33 +0,0 @@
{
"uid" : "83edc06c07f9ae9e47eb6dd1b683e4e2",
"name" : "packages",
"children" : [ {
"name" : "screenshot_tests.tests.yandex_main_page_test",
"children" : [ {
"name" : "test_search_field",
"uid" : "ff3cf8e351b44c24",
"parentUid" : "4a62f88a06567bac990f04c32bc01572",
"status" : "failed",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"flaky" : false,
"parameters" : [ ]
}, {
"name" : "test_news_widget",
"uid" : "35bf2eac0f3b5d9d",
"parentUid" : "4a62f88a06567bac990f04c32bc01572",
"status" : "failed",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"flaky" : false,
"parameters" : [ ]
} ],
"uid" : "screenshot_tests.tests.yandex_main_page_test"
} ]
}

View File

@@ -1,3 +0,0 @@
"Status","Start Time","Stop Time","Duration in ms","Parent Suite","Suite","Sub Suite","Test Class","Test Method","Name","Description"
"failed","Sun Sep 22 21:17:01 MSK 2019","Sun Sep 22 21:17:27 MSK 2019","25996","screenshot_tests.tests","yandex_main_page_test","TestYandexMainPage","","","test_search_field",""
"failed","Sun Sep 22 21:17:02 MSK 2019","Sun Sep 22 21:17:30 MSK 2019","27582","screenshot_tests.tests","yandex_main_page_test","TestYandexMainPage","","","test_news_widget","Test for news widget."
1 Status Start Time Stop Time Duration in ms Parent Suite Suite Sub Suite Test Class Test Method Name Description
2 failed Sun Sep 22 21:17:01 MSK 2019 Sun Sep 22 21:17:27 MSK 2019 25996 screenshot_tests.tests yandex_main_page_test TestYandexMainPage test_search_field
3 failed Sun Sep 22 21:17:02 MSK 2019 Sun Sep 22 21:17:30 MSK 2019 27582 screenshot_tests.tests yandex_main_page_test TestYandexMainPage test_news_widget Test for news widget.

View File

@@ -1,41 +0,0 @@
{
"uid" : "98d3104e051c652961429bf95fa0b5d6",
"name" : "suites",
"children" : [ {
"name" : "screenshot_tests.tests",
"children" : [ {
"name" : "yandex_main_page_test",
"children" : [ {
"name" : "TestYandexMainPage",
"children" : [ {
"name" : "test_search_field",
"uid" : "ff3cf8e351b44c24",
"parentUid" : "14e9fffecf2f365bb44ac41687f56ae6",
"status" : "failed",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"flaky" : false,
"parameters" : [ ]
}, {
"name" : "test_news_widget",
"uid" : "35bf2eac0f3b5d9d",
"parentUid" : "14e9fffecf2f365bb44ac41687f56ae6",
"status" : "failed",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"flaky" : false,
"parameters" : [ ]
} ],
"uid" : "14e9fffecf2f365bb44ac41687f56ae6"
} ],
"uid" : "09b294b6c70fff4ed23fcba65129c6aa"
} ],
"uid" : "24b6faeef3ea7966bb42e6050a32e636"
} ]
}

View File

@@ -1,214 +0,0 @@
{
"uid" : "35bf2eac0f3b5d9d",
"name" : "test_news_widget",
"fullName" : "screenshot_tests.tests.yandex_main_page_test.TestYandexMainPage#test_news_widget",
"historyId" : "3ef35865270ff1d210fb39b9ab2432e0",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"description" : "Test for news widget.",
"descriptionHtml" : "<p>Test for news widget.</p>\n",
"status" : "failed",
"statusMessage" : "AssertionError: «Хэдер с новостями» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"statusTrace" : "self = <yandex_main_page_test.TestYandexMainPage object at 0x103a6d2b0>\n\n def test_news_widget(self):\n \"\"\"Test for news widget.\"\"\"\n page = self.get_page(YandexMainPage)\n> self.check_by_screenshot(page.news_header)\n\nscreenshot_tests/tests/yandex_main_page_test.py:12: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <yandex_main_page_test.TestYandexMainPage object at 0x103a6d2b0>\nelement = <screenshot_tests.page_objects.custom_web_element.CustomWebElement object at 0x103ab4710>\nargs = (), kwargs = {}, diff = 435\nsaved_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\nprod_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\ninfo = '«Хэдер с новостями»'\n\n def check_by_screenshot(self, element: CustomWebElement, *args, **kwargs):\n diff, saved_url, prod_url = self._get_diff(element, *args, **kwargs)\n info = element.description\n> assert diff == 0, f\"{info} отличается на страницах:\\n{saved_url.geturl()}\\nи\\n{prod_url.geturl()}\"\nE AssertionError: «Хэдер с новостями» отличается на страницах:\nE https://yandex.ru/\nE и\nE https://yandex.ru/\n\nscreenshot_tests/utils/screenshots.py:143: AssertionError",
"flaky" : false,
"beforeStages" : [ {
"name" : "_verify_url",
"time" : {
"start" : 1569176221097,
"stop" : 1569176221097,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "screenshot_prepare",
"time" : {
"start" : 1569176221097,
"stop" : 1569176221097,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "driver",
"time" : {
"start" : 1569176221097,
"stop" : 1569176222535,
"duration" : 1438
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "set_driver",
"time" : {
"start" : 1569176222536,
"stop" : 1569176222536,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "base_url",
"time" : {
"start" : 1569176221096,
"stop" : 1569176221096,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "configure",
"time" : {
"start" : 1569176221097,
"stop" : 1569176221097,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
} ],
"testStage" : {
"description" : "Test for news widget.",
"status" : "failed",
"statusMessage" : "AssertionError: «Хэдер с новостями» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"statusTrace" : "self = <yandex_main_page_test.TestYandexMainPage object at 0x103a6d2b0>\n\n def test_news_widget(self):\n \"\"\"Test for news widget.\"\"\"\n page = self.get_page(YandexMainPage)\n> self.check_by_screenshot(page.news_header)\n\nscreenshot_tests/tests/yandex_main_page_test.py:12: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <yandex_main_page_test.TestYandexMainPage object at 0x103a6d2b0>\nelement = <screenshot_tests.page_objects.custom_web_element.CustomWebElement object at 0x103ab4710>\nargs = (), kwargs = {}, diff = 435\nsaved_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\nprod_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\ninfo = '«Хэдер с новостями»'\n\n def check_by_screenshot(self, element: CustomWebElement, *args, **kwargs):\n diff, saved_url, prod_url = self._get_diff(element, *args, **kwargs)\n info = element.description\n> assert diff == 0, f\"{info} отличается на страницах:\\n{saved_url.geturl()}\\nи\\n{prod_url.geturl()}\"\nE AssertionError: «Хэдер с новостями» отличается на страницах:\nE https://yandex.ru/\nE и\nE https://yandex.ru/\n\nscreenshot_tests/utils/screenshots.py:143: AssertionError",
"steps" : [ ],
"attachments" : [ {
"uid" : "15b144bb82e16a13",
"name" : "diff",
"source" : "15b144bb82e16a13.png",
"type" : "image/png",
"size" : 63598
}, {
"uid" : "8f9801e50746a9d2",
"name" : "actual",
"source" : "8f9801e50746a9d2.png",
"type" : "image/png",
"size" : 63036
}, {
"uid" : "ae0890108418dd6e",
"name" : "expected",
"source" : "ae0890108418dd6e.png",
"type" : "image/png",
"size" : 63550
}, {
"uid" : "d77bd5a097581fae",
"name" : "log",
"source" : "d77bd5a097581fae.txt",
"type" : "text/plain",
"size" : 3118
} ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 4,
"shouldDisplayMessage" : true,
"hasContent" : true
},
"afterStages" : [ {
"name" : "driver::0",
"time" : {
"start" : 1569176250196,
"stop" : 1569176250287,
"duration" : 91
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ {
"uid" : "7c5f405b92682e2f",
"name" : "last url",
"source" : "7c5f405b92682e2f.uri",
"type" : "text/uri-list",
"size" : 18
} ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 1,
"shouldDisplayMessage" : false,
"hasContent" : true
} ],
"labels" : [ {
"name" : "testType",
"value" : "screenshotDiff"
}, {
"name" : "parentSuite",
"value" : "screenshot_tests.tests"
}, {
"name" : "suite",
"value" : "yandex_main_page_test"
}, {
"name" : "subSuite",
"value" : "TestYandexMainPage"
}, {
"name" : "host",
"value" : "macbook-pro"
}, {
"name" : "thread",
"value" : "53352-MainThread"
}, {
"name" : "framework",
"value" : "pytest"
}, {
"name" : "language",
"value" : "cpython3"
}, {
"name" : "package",
"value" : "screenshot_tests.tests.yandex_main_page_test"
}, {
"name" : "resultFormat",
"value" : "allure2"
} ],
"parameters" : [ ],
"links" : [ ],
"hidden" : false,
"retry" : false,
"extra" : {
"severity" : "normal",
"retries" : [ ],
"categories" : [ {
"name" : "Product defects",
"matchedStatuses" : [ ],
"flaky" : false
} ],
"tags" : [ ]
},
"source" : "35bf2eac0f3b5d9d.json",
"parameterValues" : [ ]
}

View File

@@ -1,253 +0,0 @@
{
"uid" : "ff3cf8e351b44c24",
"name" : "test_search_field",
"fullName" : "screenshot_tests.tests.yandex_main_page_test.TestYandexMainPage#test_search_field",
"historyId" : "1c6180a6a50469c5cd152002213dbd36",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"status" : "failed",
"statusMessage" : "AssertionError: «Поисковый блок» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"statusTrace" : "self = <yandex_main_page_test.TestYandexMainPage object at 0x10cb5ccc0>\n\n def test_search_field(self):\n words = [\"foo\", \"bar\", \"lol\", \"kek\", \"cheburek\", \"otus\", \"yandex\", \"google\"]\n page = self.get_page(YandexMainPage)\n \n def action():\n page.search_input.send_keys(random.choice(words))\n \n> self.check_by_screenshot(page.search_field, action)\n\nscreenshot_tests/tests/yandex_main_page_test.py:21: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <yandex_main_page_test.TestYandexMainPage object at 0x10cb5ccc0>\nelement = <screenshot_tests.page_objects.custom_web_element.CustomWebElement object at 0x10cb875c0>\nargs = (<function TestYandexMainPage.test_search_field.<locals>.action at 0x10cb717b8>,)\nkwargs = {}, diff = 16\nsaved_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\nprod_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\ninfo = '«Поисковый блок»'\n\n def check_by_screenshot(self, element: CustomWebElement, *args, **kwargs):\n diff, saved_url, prod_url = self._get_diff(element, *args, **kwargs)\n info = element.description\n> assert diff == 0, f\"{info} отличается на страницах:\\n{saved_url.geturl()}\\nи\\n{prod_url.geturl()}\"\nE AssertionError: «Поисковый блок» отличается на страницах:\nE https://yandex.ru/\nE и\nE https://yandex.ru/\n\nscreenshot_tests/utils/screenshots.py:143: AssertionError",
"flaky" : false,
"beforeStages" : [ {
"name" : "set_driver",
"time" : {
"start" : 1569176221588,
"stop" : 1569176221588,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "base_url",
"time" : {
"start" : 1569176221097,
"stop" : 1569176221097,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "configure",
"time" : {
"start" : 1569176221097,
"stop" : 1569176221098,
"duration" : 1
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "driver",
"time" : {
"start" : 1569176221098,
"stop" : 1569176221588,
"duration" : 490
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "screenshot_prepare",
"time" : {
"start" : 1569176221098,
"stop" : 1569176221098,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
}, {
"name" : "_verify_url",
"time" : {
"start" : 1569176221097,
"stop" : 1569176221097,
"duration" : 0
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : false
} ],
"testStage" : {
"status" : "failed",
"statusMessage" : "AssertionError: «Поисковый блок» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"statusTrace" : "self = <yandex_main_page_test.TestYandexMainPage object at 0x10cb5ccc0>\n\n def test_search_field(self):\n words = [\"foo\", \"bar\", \"lol\", \"kek\", \"cheburek\", \"otus\", \"yandex\", \"google\"]\n page = self.get_page(YandexMainPage)\n \n def action():\n page.search_input.send_keys(random.choice(words))\n \n> self.check_by_screenshot(page.search_field, action)\n\nscreenshot_tests/tests/yandex_main_page_test.py:21: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <yandex_main_page_test.TestYandexMainPage object at 0x10cb5ccc0>\nelement = <screenshot_tests.page_objects.custom_web_element.CustomWebElement object at 0x10cb875c0>\nargs = (<function TestYandexMainPage.test_search_field.<locals>.action at 0x10cb717b8>,)\nkwargs = {}, diff = 16\nsaved_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\nprod_url = ParseResult(scheme='https', netloc='yandex.ru', path='/', params='', query='', fragment='')\ninfo = '«Поисковый блок»'\n\n def check_by_screenshot(self, element: CustomWebElement, *args, **kwargs):\n diff, saved_url, prod_url = self._get_diff(element, *args, **kwargs)\n info = element.description\n> assert diff == 0, f\"{info} отличается на страницах:\\n{saved_url.geturl()}\\nи\\n{prod_url.geturl()}\"\nE AssertionError: «Поисковый блок» отличается на страницах:\nE https://yandex.ru/\nE и\nE https://yandex.ru/\n\nscreenshot_tests/utils/screenshots.py:143: AssertionError",
"steps" : [ {
"name" : "Send text ['lol'] to «Поисковый инпут»",
"time" : {
"start" : 1569176223631,
"stop" : 1569176223862,
"duration" : 231
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ {
"name" : "locator_type",
"value" : "'css selector'"
}, {
"name" : "locator",
"value" : "'.input__control'"
} ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : true
}, {
"name" : "Send text ['cheburek'] to «Поисковый инпут»",
"time" : {
"start" : 1569176235178,
"stop" : 1569176235397,
"duration" : 219
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ ],
"parameters" : [ {
"name" : "locator_type",
"value" : "'css selector'"
}, {
"name" : "locator",
"value" : "'.input__control'"
} ],
"stepsCount" : 0,
"attachmentsCount" : 0,
"shouldDisplayMessage" : false,
"hasContent" : true
} ],
"attachments" : [ {
"uid" : "d807dd446712ec34",
"name" : "diff",
"source" : "d807dd446712ec34.png",
"type" : "image/png",
"size" : 5806
}, {
"uid" : "43262d127c532fc2",
"name" : "actual",
"source" : "43262d127c532fc2.png",
"type" : "image/png",
"size" : 5602
}, {
"uid" : "e4728e62a9273013",
"name" : "expected",
"source" : "e4728e62a9273013.png",
"type" : "image/png",
"size" : 5814
}, {
"uid" : "6f4fd4816dedd724",
"name" : "log",
"source" : "6f4fd4816dedd724.txt",
"type" : "text/plain",
"size" : 2838
} ],
"parameters" : [ ],
"stepsCount" : 2,
"attachmentsCount" : 4,
"shouldDisplayMessage" : true,
"hasContent" : true
},
"afterStages" : [ {
"name" : "driver::0",
"time" : {
"start" : 1569176247715,
"stop" : 1569176247818,
"duration" : 103
},
"status" : "passed",
"steps" : [ ],
"attachments" : [ {
"uid" : "f764d0c1f0c002e8",
"name" : "last url",
"source" : "f764d0c1f0c002e8.uri",
"type" : "text/uri-list",
"size" : 18
} ],
"parameters" : [ ],
"stepsCount" : 0,
"attachmentsCount" : 1,
"shouldDisplayMessage" : false,
"hasContent" : true
} ],
"labels" : [ {
"name" : "testType",
"value" : "screenshotDiff"
}, {
"name" : "parentSuite",
"value" : "screenshot_tests.tests"
}, {
"name" : "suite",
"value" : "yandex_main_page_test"
}, {
"name" : "subSuite",
"value" : "TestYandexMainPage"
}, {
"name" : "host",
"value" : "macbook-pro"
}, {
"name" : "thread",
"value" : "53353-MainThread"
}, {
"name" : "framework",
"value" : "pytest"
}, {
"name" : "language",
"value" : "cpython3"
}, {
"name" : "package",
"value" : "screenshot_tests.tests.yandex_main_page_test"
}, {
"name" : "resultFormat",
"value" : "allure2"
} ],
"parameters" : [ ],
"links" : [ ],
"hidden" : false,
"retry" : false,
"extra" : {
"severity" : "normal",
"retries" : [ ],
"categories" : [ {
"name" : "Product defects",
"matchedStatuses" : [ ],
"flaky" : false
} ],
"tags" : [ ]
},
"source" : "ff3cf8e351b44c24.json",
"parameterValues" : [ ]
}

View File

@@ -1,41 +0,0 @@
{
"uid" : "ab17fc5a4eb3bca4b216b548c7f9fcbc",
"name" : "timeline",
"children" : [ {
"name" : "macbook-pro",
"children" : [ {
"name" : "53353-MainThread",
"children" : [ {
"name" : "test_search_field",
"uid" : "ff3cf8e351b44c24",
"parentUid" : "4dc7c33bb080a46cee81e32def5d44d1",
"status" : "failed",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"flaky" : false,
"parameters" : [ ]
} ],
"uid" : "4dc7c33bb080a46cee81e32def5d44d1"
}, {
"name" : "53352-MainThread",
"children" : [ {
"name" : "test_news_widget",
"uid" : "35bf2eac0f3b5d9d",
"parentUid" : "49e92ccdd5930a6380d1611838fa2485",
"status" : "failed",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"flaky" : false,
"parameters" : [ ]
} ],
"uid" : "49e92ccdd5930a6380d1611838fa2485"
} ],
"uid" : "45427d043511ae68e4401efdf1d10cf3"
} ]
}

View File

@@ -1,12 +0,0 @@
launch_status failed=2 1569176258000000000
launch_status broken=0 1569176258000000000
launch_status passed=0 1569176258000000000
launch_status skipped=0 1569176258000000000
launch_status unknown=0 1569176258000000000
launch_time duration=28529 1569176258000000000
launch_time min_duration=25996 1569176258000000000
launch_time max_duration=27582 1569176258000000000
launch_time sum_duration=53578 1569176258000000000
launch_problems product_defects=2 1569176258000000000
launch_retries retries=0 1569176258000000000
launch_retries run=2 1569176258000000000

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Allure Report summary mail</title>
</head>
<body>
Mail body
</body>
</html>

View File

@@ -1,12 +0,0 @@
launch_status_failed 2
launch_status_broken 0
launch_status_passed 0
launch_status_skipped 0
launch_status_unknown 0
launch_time_duration 28529
launch_time_min_duration 25996
launch_time_max_duration 27582
launch_time_sum_duration 53578
launch_problems_product_defects 2
launch_retries_retries 0
launch_retries_run 2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,5 +0,0 @@
[ {
"data" : {
"Product defects" : 2
}
} ]

View File

@@ -1,5 +0,0 @@
[ {
"data" : {
"duration" : 28529
}
} ]

View File

@@ -1,10 +0,0 @@
[ {
"data" : {
"failed" : 2,
"broken" : 0,
"skipped" : 0,
"passed" : 0,
"unknown" : 0,
"total" : 2
}
} ]

View File

@@ -1,42 +0,0 @@
{
"3ef35865270ff1d210fb39b9ab2432e0" : {
"statistic" : {
"failed" : 1,
"broken" : 0,
"skipped" : 0,
"passed" : 0,
"unknown" : 0,
"total" : 1
},
"items" : [ {
"uid" : "35bf2eac0f3b5d9d",
"status" : "failed",
"statusDetails" : "AssertionError: «Хэдер с новостями» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
}
} ]
},
"1c6180a6a50469c5cd152002213dbd36" : {
"statistic" : {
"failed" : 1,
"broken" : 0,
"skipped" : 0,
"passed" : 0,
"unknown" : 0,
"total" : 1
},
"items" : [ {
"uid" : "ff3cf8e351b44c24",
"status" : "failed",
"statusDetails" : "AssertionError: «Поисковый блок» отличается на страницах:\nhttps://yandex.ru/\nи\nhttps://yandex.ru/",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
}
} ]
}
}

View File

@@ -1,6 +0,0 @@
[ {
"data" : {
"run" : 2,
"retry" : 0
}
} ]

View File

@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>Allure Report</title>
<link rel="favicon" href="favicon.ico?v=2">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="plugins/screen-diff/styles.css">
</head>
<body>
<div id="alert"></div>
<div id="content">
<span class="spinner">
<span class="spinner__circle"></span>
</span>
</div>
<div id="popup"></div>
<script src="app.js"></script>
<script src="plugins/behaviors/index.js"></script>
<script src="plugins/packages/index.js"></script>
<script src="plugins/screen-diff/index.js"></script>
</body>
</html>

View File

@@ -1,108 +0,0 @@
'use strict';
allure.api.addTranslation('en', {
tab: {
behaviors: {
name: 'Behaviors'
}
},
widget: {
behaviors: {
name: 'Features by stories',
showAll: 'show all'
}
}
});
allure.api.addTranslation('ru', {
tab: {
behaviors: {
name: 'Функциональность'
}
},
widget: {
behaviors: {
name: 'Функциональность',
showAll: 'показать все'
}
}
});
allure.api.addTranslation('zh', {
tab: {
behaviors: {
name: '功能'
}
},
widget: {
behaviors: {
name: '特性场景',
showAll: '显示所有'
}
}
});
allure.api.addTranslation('de', {
tab: {
behaviors: {
name: 'Verhalten'
}
},
widget: {
behaviors: {
name: 'Features nach Stories',
showAll: 'Zeige alle'
}
}
});
allure.api.addTranslation('he', {
tab: {
behaviors: {
name: 'התנהגויות'
}
},
widget: {
behaviors: {
name: 'תכונות לפי סיפורי משתמש',
showAll: 'הצג הכול'
}
}
});
allure.api.addTranslation('br', {
tab: {
behaviors: {
name: 'Comportamentos'
}
},
widget: {
behaviors: {
name: 'Funcionalidades por história',
showAll: 'Mostrar tudo'
}
}
});
allure.api.addTab('behaviors', {
title: 'tab.behaviors.name', icon: 'fa fa-list',
route: 'behaviors(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)',
onEnter: (function (testGroup, testResult, testResultTab) {
return new allure.components.TreeLayout({
testGroup: testGroup,
testResult: testResult,
testResultTab: testResultTab,
tabName: 'tab.behaviors.name',
baseUrl: 'behaviors',
url: 'data/behaviors.json',
csvUrl: 'data/behaviors.csv'
});
})
});
allure.api.addWidget('widgets', 'behaviors', allure.components.WidgetStatusView.extend({
rowTag: 'a',
title: 'widget.behaviors.name',
baseUrl: 'behaviors',
showLinks: true
}));

View File

@@ -1,64 +0,0 @@
'use strict';
allure.api.addTranslation('en', {
tab: {
packages: {
name: 'Packages'
}
}
});
allure.api.addTranslation('ru', {
tab: {
packages: {
name: 'Пакеты'
}
}
});
allure.api.addTranslation('zh', {
tab: {
packages: {
name: '包'
}
}
});
allure.api.addTranslation('de', {
tab: {
packages: {
name: 'Pakete'
}
}
});
allure.api.addTranslation('he', {
tab: {
packages: {
name: 'חבילות'
}
}
});
allure.api.addTranslation('br', {
tab: {
packages: {
name: 'Pacotes'
}
}
});
allure.api.addTab('packages', {
title: 'tab.packages.name', icon: 'fa fa-align-left',
route: 'packages(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)',
onEnter: (function (testGroup, testResult, testResultTab) {
return new allure.components.TreeLayout({
testGroup: testGroup,
testResult: testResult,
testResultTab: testResultTab,
tabName: 'tab.packages.name',
baseUrl: 'packages',
url: 'data/packages.json'
});
})
});

View File

@@ -1,97 +0,0 @@
(function () {
var settings = allure.getPluginSettings('screen-diff', {diffType: 'diff'});
function renderImage(src) {
return '<div class="screen-diff__container">' +
'<img class="screen-diff__image" src="data/attachments/' + src + '">' +
'</div>';
}
function renderDiffContent(type, data) {
function findImage(name) {
if (data.testStage && data.testStage.attachments) {
return data.testStage.attachments.filter(function (attachment) {
return attachment.name === name;
})[0];
}
return null;
}
var diffImage = findImage('diff');
var actualImage = findImage('actual');
var expectedImage = findImage('expected');
if (!diffImage && !actualImage && !expectedImage) {
return '<span>Diff, actual and expected image have not been provided.</span>';
}
if (type === 'diff') {
if (!diffImage) {
return renderImage(actualImage.source);
}
return renderImage(diffImage.source);
}
if (type === 'overlay') {
return '<div class="screen-diff__overlay screen-diff__container">' +
'<img class="screen-diff__image" src="data/attachments/' + expectedImage.source + '">' +
'<div class="screen-diff__image-over">' +
'<img class="screen-diff__image" src="data/attachments/' + actualImage.source + '">' +
'</div>' +
'</div>';
}
}
var ScreenDiffView = Backbone.Marionette.View.extend({
className: 'pane__section',
events: {
'click [name="screen-diff-type"]': 'onDiffTypeChange',
'mousemove .screen-diff__overlay': 'onOverlayMove'
},
templateContext: function () {
return {
diffType: settings.get('diffType')
}
},
template: function (data) {
var testType = data.labels.filter(function (label) {
return label.name === 'testType'
})[0];
if (!testType || testType.value !== 'screenshotDiff') {
return '';
}
return '<h3 class="pane__section-title">Screen Diff</h3>' +
'<div class="screen-diff__content">' +
'<div class="screen-diff__switchers">' +
'<label><input type="radio" name="screen-diff-type" value="diff"> Show diff</label>' +
'<label><input type="radio" name="screen-diff-type" value="overlay"> Show overlay</label>' +
'</div>' +
renderDiffContent(data.diffType, data) +
'</div>';
},
adjustImageSize: function (event) {
var overImage = this.$(event.target);
overImage.width(overImage.width());
},
onRender: function () {
const diffType = settings.get('diffType');
this.$('[name="screen-diff-type"][value="' + diffType + '"]').prop('checked', true);
if (diffType === 'overlay') {
this.$('.screen-diff__image-over img').on('load', this.adjustImageSize.bind(this));
}
},
onOverlayMove: function (event) {
var pageX = event.pageX;
var containerScroll = this.$('.screen-diff__container').scrollLeft();
var elementX = event.currentTarget.getBoundingClientRect().left;
var delta = pageX - elementX + containerScroll;
this.$('.screen-diff__image-over').width(delta);
},
onDiffTypeChange: function (event) {
settings.save('diffType', event.target.value);
this.render();
}
});
allure.api.addTestResultBlock(ScreenDiffView, {position: 'before'});
})();

View File

@@ -1,26 +0,0 @@
.screen-diff__switchers {
margin-bottom: 1em;
}
.screen-diff__switchers label + label {
margin-left: 1em;
}
.screen-diff__overlay {
position: relative;
cursor: col-resize;
}
.screen-diff__container {
overflow-x: auto;
}
.screen-diff__image-over {
top: 0;
left: 0;
bottom: 0;
background: #fff;
position: absolute;
overflow: hidden;
box-shadow: 2px 0 1px -1px #aaa;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +0,0 @@
{
"total" : 2,
"items" : [ ]
}

View File

@@ -1,5 +0,0 @@
[ {
"data" : {
"Product defects" : 2
}
} ]

View File

@@ -1,15 +0,0 @@
{
"total" : 1,
"items" : [ {
"uid" : "8fb3a91ba5aaf9de24cc8a92edc82b5d",
"name" : "Product defects",
"statistic" : {
"failed" : 2,
"broken" : 0,
"skipped" : 0,
"passed" : 0,
"unknown" : 0,
"total" : 2
}
} ]
}

View File

@@ -1,5 +0,0 @@
[ {
"data" : {
"duration" : 28529
}
} ]

View File

@@ -1,21 +0,0 @@
[ {
"uid" : "ff3cf8e351b44c24",
"name" : "test_search_field",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"status" : "failed",
"severity" : "normal"
}, {
"uid" : "35bf2eac0f3b5d9d",
"name" : "test_news_widget",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"status" : "failed",
"severity" : "normal"
} ]

View File

@@ -1 +0,0 @@
[ ]

View File

@@ -1 +0,0 @@
[ ]

View File

@@ -1,10 +0,0 @@
[ {
"data" : {
"failed" : 2,
"broken" : 0,
"skipped" : 0,
"passed" : 0,
"unknown" : 0,
"total" : 2
}
} ]

View File

@@ -1 +0,0 @@
[ ]

View File

@@ -1,6 +0,0 @@
[ {
"data" : {
"run" : 2,
"retry" : 0
}
} ]

View File

@@ -1,21 +0,0 @@
[ {
"uid" : "35bf2eac0f3b5d9d",
"name" : "test_news_widget",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"status" : "failed",
"severity" : "normal"
}, {
"uid" : "ff3cf8e351b44c24",
"name" : "test_search_field",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"status" : "failed",
"severity" : "normal"
} ]

View File

@@ -1,21 +0,0 @@
[ {
"uid" : "ff3cf8e351b44c24",
"name" : "test_search_field",
"time" : {
"start" : 1569176221591,
"stop" : 1569176247587,
"duration" : 25996
},
"status" : "failed",
"severity" : "normal"
}, {
"uid" : "35bf2eac0f3b5d9d",
"name" : "test_news_widget",
"time" : {
"start" : 1569176222538,
"stop" : 1569176250120,
"duration" : 27582
},
"status" : "failed",
"severity" : "normal"
} ]

View File

@@ -1,15 +0,0 @@
{
"total" : 1,
"items" : [ {
"uid" : "24b6faeef3ea7966bb42e6050a32e636",
"name" : "screenshot_tests.tests",
"statistic" : {
"failed" : 2,
"broken" : 0,
"skipped" : 0,
"passed" : 0,
"unknown" : 0,
"total" : 2
}
} ]
}

View File

@@ -1,20 +0,0 @@
{
"reportName" : "Allure Report",
"testRuns" : [ ],
"statistic" : {
"failed" : 2,
"broken" : 0,
"skipped" : 0,
"passed" : 0,
"unknown" : 0,
"total" : 2
},
"time" : {
"start" : 1569176221591,
"stop" : 1569176250120,
"duration" : 28529,
"minDuration" : 25996,
"maxDuration" : 27582,
"sumDuration" : 53578
}
}

View File

@@ -23,13 +23,13 @@ def driver():
def pytest_addoption(parser):
"""Command line parser."""
parser.addoption(f'--{Config.BASE_URL}',
default='https://yandex.ru',
default='https://go.mail.ru/',
dest=Config.BASE_URL,
action='store',
metavar='str',
help='Environment for run tests.')
parser.addoption(f'--{Config.STAGING}',
default='https://yandex.ru',
default='go.mail.ru',
dest=Config.STAGING,
action='store',
metavar='str',

View File

@@ -5,5 +5,3 @@ How to:
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)
or just open report from repo: `allure open allure-report`

View File

@@ -2,4 +2,4 @@ pytest==5.1.2
flaky==3.6.1
selenium==3.141.0
allure-pytest==2.8.2
Pillow==6.1.0
Pillow==6.2.2

View File

@@ -1,22 +1,36 @@
from PIL import ImageDraw, Image
from io import BytesIO
from typing import Tuple, List
import logging
from io import BytesIO
from typing import Union, List
from PIL import ImageDraw, Image
class ImageProcessor(object):
"""Class for image comparison."""
"""Класс для обработки изображений (нарезки и сравнения)"""
RED = "red"
GREEN = "green"
BLUE = "blue"
ALPHA = "alpha"
# https://github.com/rsmbl/Resemble.js/blob/dec5ae1cf1d10c9027a94400a81c17d025a9d3b6/resemble.js#L121
# https://github.com/rsmbl/Resemble.js/blob/dec5ae1cf1d10c9027a94400a81c17d025a9d3b6/resemble.js#L981
tolerance = {
RED: 32,
GREEN: 32,
BLUE: 32,
ALPHA: 32,
}
def __init__(self):
self._block_width = 20 # default
self._block_height = 20
self._accuracy = 0.0001 # less better
self._block_width = 40 # default
self._block_height = 40
def _slice_image(self, image: Image.Image) -> List[dict]:
"""Slice image on small blocks."""
"""Нарезать картинки на блоки"""
max_width, max_height = image.size
# нижний правый угол для кропа
width_change = self._block_width
height_change = self._block_height
@@ -44,20 +58,53 @@ class ImageProcessor(object):
return result
def _get_image_pixel_sum(self, image: Image.Image) -> int:
"""Get pixel sum for image."""
image_total = 0
max_width, max_height = image.size
def _is_color_similar(self, a, b, color):
"""Проверить похожесть цветов. Для того, чтобы тесты не тригеррились на антиалиазинг допуски
в self.tolerance.
"""
if a is None and b is None:
return True
diff = abs(a - b)
if diff == 0:
return True
elif diff < self.tolerance[color]:
return True
return False
def _compare_images(self, image_one: Image.Image, image_two: Image.Image) -> bool:
"""Сравнить два изображения попиксельно"""
assert image_one.size == image_two.size, \
f"Картинки должны быть одинакового размера, {image_one.size} {image_two.size}"
max_width, max_height = image_one.size
for coord_y in range(0, max_height):
for coord_x in range(0, max_width):
pixel = image.getpixel((coord_x, coord_y))
image_total += sum(pixel)
pixel_one = image_one.getpixel((coord_x, coord_y))
pixel_two = image_two.getpixel((coord_x, coord_y))
equal = self._compare_pixels(pixel_one, pixel_two)
if not equal:
return False
return image_total
return True
def get_images_diff(self, first_image: Image.Image, second_image: Image.Image) -> Tuple[int, bytes, bytes, bytes]:
"""Compare two images."""
def _compare_pixels(self, pixel_one, pixel_two) -> bool:
"""Сравнить каждый цвет, каждого писклея."""
assert len(pixel_one) == len(pixel_two), f"В одном из пикселей не хватает цветов: {pixel_one} {pixel_two}"
for item in zip(pixel_one, pixel_two, (self.RED, self.GREEN, self.BLUE, self.ALPHA)):
color_one, color_two, color = item
if not self._is_color_similar(color_one, color_two, color):
return False
return True
def get_images_diff(self, first_image: Image.Image, second_image: Image.Image) -> List[Union[int, bytes]]:
"""Поблочно сравнить два изображения и вернуть количество блоков с несовпавшими пикселями"""
result_image = first_image.copy()
first_image_blocks = self._slice_image(first_image)
@@ -68,45 +115,60 @@ class ImageProcessor(object):
mistaken_blocks = abs(len(first_image_blocks) - len(second_image_blocks))
for index in range(min(len(first_image_blocks), len(second_image_blocks))):
first_pixels = self._get_image_pixel_sum(first_image_blocks[index]["image"])
second_pixels = self._get_image_pixel_sum(second_image_blocks[index]["image"])
image_equal = self._compare_images(first_image_blocks[index]["image"], second_image_blocks[index]["image"])
# если пиксели отличаются больше чем на self.accuracy -- помечаем блок как битый
if (first_pixels != 0 and second_pixels != 0) and abs(1 - (first_pixels / second_pixels)) >= self._accuracy:
if not image_equal:
draw = ImageDraw.Draw(result_image)
draw.rectangle(first_image_blocks[index]["box"], outline="red")
mistaken_blocks += 1
result = BytesIO()
first = BytesIO()
second = BytesIO()
return [mistaken_blocks, self.image_to_bytes(result_image)]
result_image.save(result, 'PNG')
first_image.save(first, 'PNG')
second_image.save(second, 'PNG')
return mistaken_blocks, result.getvalue(), first.getvalue(), second.getvalue()
def paste(self, screenshots: List[bytes]) -> Image.Image:
"""Concatenate few images into one."""
def paste(self, screenshots: List[bytes], over_height: int) -> Image.Image:
"""Склеить массив скриншотов в одно изображение"""
max_width = 0
max_height = 0
images = []
for screenshot in screenshots:
image = self.load_image_from_bytes(screenshot)
images.append(image)
max_width = image.size[0] if image.size[0] > max_width else max_width
max_height += image.size[1]
# Склейка работает так: сначала создаем одно "пустое" изображение равное размеру всех скелееных, и вставляем в
# в него по одному все скриншоты.
# Чтобы в финальном скрине не получилось что скриншоты заняли меньше места, чем картинка, снизу отрезаем 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})')
offset = 0
for image in images:
last_image_index = len(images) - 1
for index, image in enumerate(images):
# Расскоментить если нужно посмотреть какие скрины склеиваются в один
# with open(f"screen-{index}.png", "wb") as fp:
# image.save(fp)
if last_image_index == index and over_height != 0:
# с последнего скриншота срезаем ту часть, в которой он дублирует предпоследний
logging.info(f"Crop over height: {over_height}")
image = image.crop((0, over_height, image.size[0], image.size[1]))
result.paste(image, (0, offset))
logging.info(f"Image added, offset is {offset}")
offset += image.size[1]
return result
def load_image_from_bytes(self, data: bytes):
def load_image_from_bytes(self, data: bytes) -> Image.Image:
"""Загрузить изображение из байтовой строки."""
return Image.open(BytesIO(data))
with BytesIO(data) as fp:
image: Image.Image = Image.open(fp)
image.load()
return image
def image_to_bytes(self, image: Image.Image) -> bytes:
with BytesIO() as fp:
image.save(fp, "PNG")
return fp.getvalue()

View File

@@ -1,46 +0,0 @@
import allure
from selenium.webdriver.remote.webelement import WebElement
class CustomWebElement:
"""Custom web element with allure logging."""
def __init__(self, by: str, locator: str, element: WebElement, description: str = None):
self.by = by
self.locator = locator
self.element = element
self.description = f"«{description}»" if description else "element"
def _execute_action(self, action, step):
"""Execute action with allure logging.
:param action: Function to execute. Click, send_keys, etc
:param step: Step description.
"""
@allure.step(step)
def execute_action(locator_type=self.by, locator=self.locator):
"""All arguments will be available in report."""
return action()
return execute_action()
def click(self):
self._execute_action(self.element.click,
f"Click at {self.description}")
def send_keys(self, *value):
self._execute_action(lambda: self.element.send_keys(*value),
f"Send text {[v for v in value]} to {self.description}")
def __eq__(self, element):
return self.element.__eq__(element)
def __ne__(self, element):
return self.element.__ne__(element)
def __hash__(self):
return self.element.__hash__()
def __getattr__(self, item):
"""Missing methods will be executed from WebElement."""
return getattr(self.element, item)

View File

@@ -1,42 +0,0 @@
from selenium.webdriver import Remote
from selenium.webdriver.common import by as selenium_by
from screenshot_tests.page_objects.custom_web_element import CustomWebElement
from typing import Union, TypeVar, Type
Locators = selenium_by.By
class Page:
"""Base page for all pages in PO."""
path = None
def __init__(self, driver: Remote):
self.driver = driver
PageBoundGeneric = TypeVar("PageBoundGeneric", bound=Page)
class Element:
"""Element descriptor for WebElement lazy init."""
def __init__(self, by: str, locator: str, description: str):
self.by = by
self.locator = locator
self.description = description
def __get__(self,
instance: PageBoundGeneric,
owner: Type[PageBoundGeneric]) -> Union[CustomWebElement, 'Element']:
"""
https://docs.python.org/3/howto/descriptor.html
:param instance: instance of owner
:param owner: type of owner
:return: self or CustomWebElement instance
"""
if isinstance(instance, Element):
return self
return CustomWebElement(self.by, self.locator, instance.driver.find_element(self.by, self.locator),
self.description)

View File

@@ -1,11 +0,0 @@
from screenshot_tests.page_objects.elements import Page, Element, Locators
class YandexMainPage(Page):
"""https://yandex.ru"""
path = ""
news_header = Element(Locators.CSS_SELECTOR, ".news__header", "Хэдер с новостями")
search_field = Element(Locators.CSS_SELECTOR, ".search2", "Поисковый блок")
search_input = Element(Locators.CSS_SELECTOR, ".input__control", "Поисковый инпут")

View File

@@ -0,0 +1,20 @@
from screenshot_tests.utils.screenshots import TestCase
class TestExample(TestCase):
"""Tests for https://go.mail.ru"""
def test_main_page(self):
self.driver.get("https://go.mail.ru/")
def action():
# Убираем фокус с инпута, чтобы тест не флакал из-за курсора
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/")
# Чтобы посмотреть как выглядит сломанный тест в отчетеы
self.driver.find_element_by_xpath("//input[not(@type='hidden')]").send_keys("foo")
self.check_by_screenshot(None, full_page=True)

View File

@@ -1,21 +0,0 @@
from screenshot_tests.utils.screenshots import TestCase
from screenshot_tests.page_objects.pages.yandex_main_page import YandexMainPage
import random
class TestYandexMainPage(TestCase):
"""Tests for https://yandex.ru"""
def test_news_widget(self):
"""Test for news widget."""
page = self.get_page(YandexMainPage)
self.check_by_screenshot(page.news_header)
def test_search_field(self):
words = ["foo", "bar", "lol", "kek", "cheburek", "otus", "yandex", "google"]
page = self.get_page(YandexMainPage)
def action():
page.search_input.send_keys(random.choice(words))
self.check_by_screenshot(page.search_field, action)

View File

@@ -1,6 +1,5 @@
import pytest
import os
from screenshot_tests.page_objects.elements import PageBoundGeneric
from conftest import Config
from typing import Type
@@ -16,14 +15,3 @@ class TestCase:
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

View File

@@ -1,29 +1,31 @@
import pytest
import time
"""Screenshot TestCase."""
import logging
import allure
import pytest
import time
from screenshot_tests.page_objects.custom_web_element import CustomWebElement
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
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
# noinspection PyAttributeOutsideInit
# аннотируем все классы всех скриншот тестов для работы плагина
# 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."""
"""Screenshot TestCase."""
BODY_LOCATOR = (Locators.CSS_SELECTOR, "body")
# Для мобильных устройств и хрома в режиме эмуляции плотность пикселей будет отличаться.
pixel_ratio = 1
@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})"
@@ -31,9 +33,26 @@ class TestCase(common.TestCase):
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")
def _make_screenshot_whole_page(self, locator_type, query_string):
scroll_time = 0.2
# Нужно заставить отработать все что есть с автоподгрузкой, чтобы получить настоящую длину страницы
x, y, width, height = self._get_raw_coords_by_locator(locator_type, query_string)
total_height = self.driver.execute_script("return document.body.parentNode.scrollHeight")
logging.info(f"total height: {total_height}")
while True:
old_total_height = total_height
self._scroll(0, total_height + 9999)
time.sleep(scroll_time)
total_height = self.driver.execute_script("return document.body.parentNode.scrollHeight")
logging.info(f"new total height: {total_height}")
# Если высота перестала изменяться, или элемент уже попал на скриншот.
# Второе условие позволяет не скролить до конца на стрницах с "бесконечной" длинной (выдача видео, картинок)
if (old_total_height == total_height) or (total_height > y):
break
total_width = self.driver.execute_script("return document.body.offsetWidth")
viewport_width = self.driver.execute_script("return document.body.clientWidth")
viewport_height = self.driver.execute_script("return window.innerHeight")
screenshots = []
@@ -42,16 +61,31 @@ class TestCase(common.TestCase):
self._scroll(0, 0)
while offset <= total_height:
logging.info(f"offset: {offset}, total height: {total_height}")
screenshots.append(self.driver.get_screenshot_as_png())
offset += viewport_height
self._scroll(0, offset)
return self.image_processor.paste(screenshots)
# эта часть последнего скриншота, которая дублирует предпоследний скриншот
# так просходит потому что не всегда страница делится на целое количество вьюпортов
over_height = offset - total_height
logging.info(f"offset: {offset}, total height: {total_height}, over height: {over_height}, pixel density: {self.pixel_ratio}")
return self.image_processor.paste(screenshots, over_height * self.pixel_ratio)
def _get_coords_by_locator(self, by, locator) -> Tuple[int, int, int, int]:
def _use_full_screen(self):
# хак чтобы снять целиком элемент который не помещается на страницу
# https://stackoverflow.com/questions/44085722/how-to-get-screenshot-of-full-webpage-using-selenium-and-java
# https://gist.github.com/elcamino/5f562564ecd2fb86f559
self.driver.set_window_size(1425, 2900)
def _get_raw_coords_by_locator(self, locator_type, query_string):
"""Без учета плотности пикселей."""
wait = WebDriverWait(self.driver, timeout=10, ignored_exceptions=Exception)
wait.until(lambda _: self.driver.find_element(locator_type, query_string).is_displayed(),
message="Невозможно получить размеры элемента, элемент не отображается")
# После того, как дождались видимости элемента, ждем еще 2 секунды, чтобы точно завершились разные анимации
time.sleep(2)
el = self.driver.find_element(by, locator)
el = self.driver.find_element(locator_type, query_string)
location = el.location
size = el.size
x = location["x"]
@@ -60,76 +94,93 @@ class TestCase(common.TestCase):
height = location["y"] + size['height']
return x, y, width, height
def _get_element_screenshot(self, by, locator, action, finalize) \
-> Tuple[Image.Image, Tuple[int, int, int, int]]:
"""Get screenshot of element.
def _get_coords_by_locator(self, locator_type, query_string) -> Tuple[int, int, int, int]:
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
Can't use session/{sessionId}/element/{elementId}/screenshot because it's available only in Edge
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
Потому что он имплементирован только в эдж.
https://stackoverflow.com/questions/36084257/im-trying-to-take-a-screenshot-of-an-element-with-selenium-webdriver-but-unsup
"""
if not scroll_and_screen:
# Иногда страница по дефолту открыта посередине, чтобы не ползли координаты
# элемента с scroll_and_screen=False, скролим до начала. Это нужно делать до вызова action, на случай если
# в action страницу нужно проскролить до определенной точки.
self._scroll(0, 0)
# Тут готовим страницу к снятию скриншота
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 scroll_and_screen:
screen = self._make_screenshot_whole_page(locator_type, query_string)
else:
screen = self.image_processor.load_image_from_bytes(self.driver.get_screenshot_as_png())
coordinates = self._get_coords_by_locator(locator_type, query_string)
logging.info(f"element: {query_string}, 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.
def _get_diff(self, element, action=None, full_screen=True, full_page=False, finalize=None, scroll_and_screen=True):
"""Получит скриншоты с текущей страницы, и с эталонной.
: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.
Поблочно сравнит их, и вернет количество отличающихся блоков.
:param element: любой объект у которого есть свойства locator_type, и query_string (по ним будет найден элемент)
:param action: функция которая подготовит страницу к снятию скриншота
:param full_screen: ресайзить ли браузер до максимума
:param full_page: скринить всю страницу, а не только переданный элемент
:param finalize: финализация после сравнения скриншотов
:param scroll_and_screen: скролить страницу (сверху к низу) и склеивать участки в один скриншот
"""
if full_screen:
self._use_full_screen()
if full_page:
by, locator = self.BODY_LOCATOR
locator_type, query_string = (By.XPATH, "//body")
scroll_and_screen = False
else:
by, locator = element.by, element.locator
locator_type, query_string = element.locator_type, element.query_string
saved_url = urlparse(self.driver.current_url)
# noinspection PyProtectedMember
prod_url = saved_url._replace(netloc=urlparse(self.staging).netloc)
prod_url = saved_url._replace(netloc=self.staging)
# Открываем странички пока размеры элементов на них не совпадут
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(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)
logging.info('Done screen on stage stand')
# На текущей странице делаем первый скриншот
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)
# Возращаемся на тестовый стенд. Всегда нужно возвращаться на тестовый стенд. На это завязаны тесты и отчеты
self.driver.get(saved_url.geturl())
# Для добавления в отчет (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)
# noinspection PyUnboundLocalVariable
allure.attach(self.image_processor.image_to_bytes(first_image), 'actual', allure.attachment_type.PNG)
# noinspection PyUnboundLocalVariable
allure.attach(self.image_processor.image_to_bytes(second_image), 'expected', allure.attachment_type.PNG)
# noinspection PyUnboundLocalVariable
diff, result = self.image_processor.get_images_diff(first_image, second_image)
allure.attach(result, 'diff', allure.attachment_type.PNG)
return diff, saved_url, prod_url
@@ -137,7 +188,6 @@ class TestCase(common.TestCase):
diff, _, _ = self._get_diff(*args, **kwargs)
return diff
def check_by_screenshot(self, element: CustomWebElement, *args, **kwargs):
def check_by_screenshot(self, element, *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()}"
assert diff == 0, f"Элемент отличается на страницах:\n{saved_url.geturl()}\nи\n{prod_url.geturl()}"