{"id":365133,"date":"2024-05-21T02:31:34","date_gmt":"2024-05-21T02:31:34","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=365133"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-21T02:31:34","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=365133","title":{"rendered":"<span>\u0414\u0435\u043b\u0430\u0435\u043c PageObject \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e. \u0423\u0434\u0435\u0448\u0435\u0432\u043b\u044f\u0435\u043c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0441\u043d\u0438\u0436\u0430\u0435\u043c \u043f\u043e\u0440\u043e\u0433 \u0432\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041a\u043e\u043b\u044f, \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c 7 \u043b\u0435\u0442, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u2014 6 \u043b\u0435\u0442. \u0422\u0430\u043a \u0443\u0436 \u0441\u043b\u043e\u0436\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u043d\u0435 \u043e\u0441\u043e\u0431\u043e \u043b\u044e\u0431\u043b\u044e WebUI-\u0442\u0435\u0441\u0442\u044b, \u043d\u043e \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u043d\u0438 \u0443 \u043c\u0435\u043d\u044f \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442\u0441\u044f \u043b\u0443\u0447\u0448\u0435 \u0432\u0441\u0435\u0433\u043e.<\/p>\n<p>\u0412 \u043e\u0434\u0438\u043d \u0434\u0435\u043d\u044c \u043c\u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043d\u0438\u043b \u043c\u043e\u0439 \u0434\u0440\u0443\u0433 \u0420\u043e\u043c\u0430 \u0438 \u0441\u043a\u0430\u0437\u0430\u043b: \u00ab\u041a\u043e\u043b\u044f, \u043f\u043e\u043c\u043d\u0438\u0448\u044c \u0442\u044b \u0443 \u043d\u0430\u0441 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0434\u0435\u043b\u0430\u043b \u0441 \u0441\u0435\u043b\u0435\u043d\u0438\u0443\u043c\u043e\u043c? \u041f\u043e\u043c\u043e\u0433\u0438 \u043c\u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a \u0436\u0435 \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u00bb.<\/p>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0432 \u0420\u043e\u043c\u0435 \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0441\u0432\u043e\u0439 \u0433\u0438\u0442\u0445\u0430\u0431 \u0438 \u0443\u0441\u0442\u0440\u043e\u0438\u0432 \u043f\u0430\u0440\u0443 \u0441\u043e\u0437\u0432\u043e\u043d\u043e\u0432 \u0441 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438, \u0447\u0442\u043e \u0438 \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u044f \u0434\u043e\u0431\u0438\u043b\u0441\u044f \u043e\u0442 \u043d\u0435\u0433\u043e \u0437\u0430\u0432\u0435\u0442\u043d\u043e\u0433\u043e \u00ab\u042f \u043f\u043e\u043d\u044f\u043b\u00bb.\u00a0<\/p>\n<p>\u0421\u043f\u0443\u0441\u0442\u044f \u0434\u0432\u0430 \u0434\u043d\u044f \u0420\u043e\u043c\u0430 \u043f\u043e\u0437\u0432\u043e\u043d\u0438\u043b \u0441\u043d\u043e\u0432\u0430: \u00ab\u041a\u043e\u043b\u044f, \u044f \u0432\u0441\u0451 \u043f\u043e\u043d\u044f\u043b, \u0430 \u0432\u043e\u0442 \u0440\u0435\u0431\u044f\u0442\u0430 \u0432 \u043c\u043e\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u2014 \u043d\u0435\u0442. \u041f\u043e\u043c\u043e\u0433\u0438 \u043c\u043d\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u0438\u0442\u044c \u0438\u043c\u00bb.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/24a\/514\/af5\/24a514af58a6f60abc9757c9bad36e5e.png\" width=\"1920\" height=\"1080\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/24a\/514\/af5\/24a514af58a6f60abc9757c9bad36e5e.png\"\/><\/figure>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e\u0442 \u0442\u043e\u043c, \u043a\u0430\u043a:<\/p>\n<ul>\n<li>\n<p>\u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043d\u0438\u0437\u0438\u0442\u044c \u0437\u0430\u0442\u0440\u0430\u0442\u044b \u043d\u0430 \u0438\u0445 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u043a\u0430\u0447\u0430\u0442\u044c\u0441\u044f \u0432 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u044c \u0434\u0435\u043d\u044c\u0433\u0438 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0435\u0440\u0432\u044b \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u043e\u0432.<\/p>\n<\/li>\n<\/ul>\n<h2>\u0427\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0442\u0430\u0442\u044c\u0438<\/h2>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u044d\u0432\u043e\u043b\u044e\u0446\u0438\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0435\u0433\u043e:<\/p>\n<ul>\n<li>\n<p>Python;<\/p>\n<\/li>\n<li>\n<p>Pytest;<\/p>\n<\/li>\n<li>\n<p>Selenium (\u0432\u0435\u0440\u0441\u0438\u0438 3).<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 Selenium \u0432\u0435\u0440\u0441\u0438\u0438 3.* \u043e\u0431\u0443\u0441\u043b\u043e\u0432\u043b\u0435\u043d\u043e \u0442\u0435\u043c, \u0447\u0442\u043e Selenium 4.*-\u0432\u0435\u0431\u0434\u0440\u0430\u0439\u0432\u0435\u0440 \u043d\u0435 \u0434\u0440\u0443\u0436\u0438\u0442 \u0441 \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d\u043e\u043c. \u041d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434, \u043f\u0440\u0438\u0447\u0438\u043d \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0441 3 \u043d\u0430 4 \u043d\u0435\u0442 \u0441\u043e\u0432\u0441\u0435\u043c \u2014 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 Selenium Grid, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e \u0432\u0441\u0451\u043c \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442 Selenoid. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0436\u0435\u043b\u0430\u043d\u0438\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0435-\u043d\u043e\u0432\u043e\u0435, \u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 Playwright \u2014 \u0441 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c \u043a\u043e\u0434 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u043c\u043e\u0436\u043d\u043e \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043f\u043e\u0434 \u043d\u0435\u0433\u043e.<\/p>\n<p>\u0414\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u043c \u0442\u0430\u043a:<\/p>\n<ol>\n<li>\n<p>\u043f\u0438\u0448\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u0434;<\/p>\n<\/li>\n<li>\n<p>\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043c, \u043f\u043e\u0447\u0435\u043c\u0443 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u043b\u043e\u0445\u043e;<\/p>\n<\/li>\n<li>\n<p>\u0443\u043b\u0443\u0447\u0448\u0430\u0435\u043c \u043a\u043e\u0434;<\/p>\n<\/li>\n<li>\n<p>GOTO 1.<\/p>\n<\/li>\n<\/ol>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0441\u0451 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u044d\u0442\u043e\u0442 \u043d\u0430\u0431\u043e\u0440 \u0441\u0442\u0430\u0442\u0435\u0439 \u2014 <a href=\"https:\/\/habr.com\/ru\/articles\/248559\/\">Selenium \u0434\u043b\u044f Python. \u0413\u043b\u0430\u0432\u0430 1. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \/ \u0425\u0430\u0431\u0440 (habr.com)<\/a><\/p>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u0430 \u043d\u0430 \u043e\u043f\u044b\u0442\u043d\u043e\u0433\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0437\u043d\u0430\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0441\u043b\u043e\u0432\u0430, \u043a\u0430\u043a xpath, selenium webdriver, \u0438 \u0443\u0436\u0435 \u0438\u043c\u0435\u043b \u043e\u043f\u044b\u0442 \u0441 Pytest, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043f\u0440\u043e\u043f\u0443\u0449\u0443 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f \u043e\u0441\u043d\u043e\u0432.<\/p>\n<h3>\u041f\u0438\u0448\u0435\u043c \u0442\u0435\u0441\u0442\u044b<\/h3>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0434\u0432\u0430 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f:<\/p>\n<ol>\n<li>\n<p>\u0437\u0430\u0439\u0442\u0438 \u043d\u0430 <a href=\"http:\/\/habr.com\">habr.com<\/a>, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043c\u044b \u0435\u0441\u0442\u044c \u043d\u0430 \u0445\u0430\u0431\u0440\u0435;<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u0439\u0442\u0438 \u043d\u0430 <a href=\"http:\/\/habr.com\">habr.com<\/a>, \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0444\u0440\u0430\u0437\u0435 \u201cozon tech\u201d, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0432\u0441\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0444\u0440\u0430\u0437\u0443 \u201cozon tech\u201d.<\/p>\n<\/li>\n<\/ol>\n<p>\u0417\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u0442 \u043c\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u0441\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<details class=\"spoiler\">\n<summary>requirements.txt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">selenium==3.141.0 pytest==7.4.3 urllib3&lt;=2.0<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0417\u0430\u043c\u0435\u0442\u043a\u0430 \u2014 requirements.txt \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u043e\u043c\u044f\u043d\u0443\u0442 \u0432\u0441\u0435\u0433\u043e \u0434\u0432\u0430 \u0440\u0430\u0437\u0430. \u0424\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<br \/>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 urllib3 &lt;= 2.0 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 selenium webdriver.<br \/>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 <code>pip install -r requirements.txt<\/code>.<\/p>\n<details class=\"spoiler\">\n<summary>driver.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver import Firefox   class Driver(Firefox):     def __init__(self, *args, **kwargs):         super().__init__(*args, **kwargs)         self.implicitly_wait(5)  # default time waiting for a locator<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>conftest.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import pytest  from driver import Driver   @pytest.fixture() def browser() -> Driver:     driver = Driver(executable_path=\"path\/to\/driver\")     driver.set_window_size(1920, 1080)     driver.get(\"https:\/\/habr.com\/ru\/all\")      yield driver      driver.quit()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>test_ozon.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait  from driver import Driver   def test_main_page(browser: Driver):     browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header__logo\")]')     browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header-user-menu__search\")]')     browser.find_element_by_xpath('\/\/section[@id=\"news_block_1\"]')   def test_search(browser: Driver):     search_string = \"ozon tech\"      open_search = browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header-user-menu__search\")]')     open_search.click()      search_input = browser.find_element_by_xpath('\/\/div[contains(@class, \"tm-search__input\")]\/\/input')     search_input.send_keys(search_string)      search_button = browser.find_element_by_xpath('\/\/div[contains(@class, \"tm-search__input\")]\/\/span')     search_button.click()      WebDriverWait(browser, 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, '\/\/div[contains(@class, \"placeholder-wrapper\")]')))      browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header__logo\")]')     browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header-user-menu__search\")]')      articles = browser.find_elements_by_xpath('\/\/div[@class=\"tm-articles-list\"]\/article')     assert articles, \"empty articles list\"      errors = []     for article in articles:         if search_string not in article.text.lower():             errors.append(f\"'{search_string}' not found in '{article.text.lower()}'\")      assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c PageObject<\/h3>\n<p>\u0422\u0435\u0441\u0442\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b, \u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043a\u0430\u043a-\u0442\u043e \u043d\u0435 \u0442\u0430\u043a, \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u043e\u0435 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u0438\u0437 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043c\u044b \u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043e \u0442\u0435\u0441\u0442\u0430\u0445 \u0434\u043b\u044f \u0432\u0435\u0431\u0430, \u044d\u0442\u043e\u0442 \u043f\u0430\u0442\u0442\u0435\u0440\u043d \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f PageObject \u2014 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0438 \u0435\u0451 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0441\u0442\u044c \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0442\u0443\u0434\u0430 \u043f\u0430\u0440\u0443 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b:<\/p>\n<details class=\"spoiler\">\n<summary>pages\/main_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from driver import Driver   class MainPage:     header_logo_xpath = '\/\/a[contains(@class, \"tm-header__logo\")]'     header_search_xpath = '\/\/a[contains(@class, \"tm-header-user-menu__search\")]'     search_input_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/input'     search_button_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/span'     news_block_xpath = '\/\/section[@id=\"news_block_1\"]'      @staticmethod     def verify_page(driver: Driver):         driver.find_element_by_xpath(MainPage.header_logo_xpath)         driver.find_element_by_xpath(MainPage.header_search_xpath)         driver.find_element_by_xpath(MainPage.news_block_xpath)      @staticmethod     def open_search(driver: Driver):         open_search = driver.find_element_by_xpath(MainPage.header_search_xpath)         open_search.click()      @staticmethod     def input_search(driver: Driver, search: str):         search_input = driver.find_element_by_xpath(MainPage.search_input_xpath)         search_input.send_keys(search)      @staticmethod     def press_search_button(driver: Driver):         search_button = driver.find_element_by_xpath(MainPage.search_button_xpath)         search_button.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/search_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait  from driver import Driver   class SearchPage:     header_logo_xpath = '\/\/a[contains(@class, \"tm-header__logo\")]'     header_search_xpath = '\/\/a[contains(@class, \"tm-header-user-menu__search\")]'     article_xpath = '\/\/div[@class=\"tm-articles-list\"]\/article'     preview_xpath = '\/\/div[contains(@class, \"placeholder-wrapper\")]'      @staticmethod     def wait_for_loader_off(driver: Driver):         WebDriverWait(driver, 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, SearchPage.preview_xpath)))      @staticmethod     def verify_page(driver: Driver, expected_string_in_results: str):         SearchPage.wait_for_loader_off(driver)          driver.find_element_by_xpath(SearchPage.header_logo_xpath)         driver.find_element_by_xpath(SearchPage.header_search_xpath)          articles = driver.find_elements_by_xpath(SearchPage.article_xpath)         assert articles, \"empty articles list\"          errors = []         for article in articles:             if expected_string_in_results not in article.text.lower():                 errors.append(f\"'{expected_string_in_results}' not found in '{article.text.lower()}'\")          assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u043c \u043d\u0430\u0448\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043a \u0442\u0435\u0441\u0442\u0430\u043c:<\/p>\n<details class=\"spoiler\">\n<summary>test_ozon.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from driver import Driver from pages.main_page import MainPage from pages.search_page import SearchPage   def test_main_page(browser: Driver):     MainPage.verify_page(browser)   def test_search(browser: Driver):     search_string = \"ozon tech\"      MainPage.open_search(browser)     MainPage.input_search(browser, search_string)     MainPage.press_search_button(browser)     SearchPage.verify_page(browser, search_string)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u0441\u0442\u044b \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0449\u0435, \u043d\u0435 \u0442\u0430\u043a \u043b\u0438?<\/p>\n<h3>\u041f\u0440\u0438\u043a\u043e\u043b\u0430\u0447\u0438\u0432\u0430\u0435\u043c PageElement<\/h3>\n<p>\u0421\u0442\u0430\u043b\u043e \u043b\u0443\u0447\u0448\u0435, \u043d\u043e \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c. \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u0435, \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0445\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014\u00a0 \u0434\u043b\u044f Header.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u043c PageElement \u2014 \u043e\u0431\u044b\u0447\u043d\u043e \u043e\u043d \u0440\u044f\u0434\u043e\u043c \u0441 PageObject, \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0435\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 Header, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043d\u0435\u0433\u043e \u043e\u0431\u0449\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b.<\/p>\n<details class=\"spoiler\">\n<summary>components\/header.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from driver import Driver   class ComponentHeader:     header_logo_xpath = '\/\/a[contains(@class, \"tm-header__logo\")]'     header_search_xpath = '\/\/a[contains(@class, \"tm-header-user-menu__search\")]'      @staticmethod     def verify_component(driver: Driver):         driver.find_element_by_xpath(ComponentHeader.header_logo_xpath)         driver.find_element_by_xpath(ComponentHeader.header_search_xpath)      @staticmethod     def open_search(driver: Driver):         open_search = driver.find_element_by_xpath(ComponentHeader.header_search_xpath)         open_search.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.<\/p>\n<details class=\"spoiler\">\n<summary>pages\/main_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from components.header import ComponentHeader from driver import Driver   class MainPage:     Header = ComponentHeader      search_input_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/input'     search_button_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/span'     news_block_xpath = '\/\/section[@id=\"news_block_1\"]'      @staticmethod     def verify_page(driver: Driver):         MainPage.Header.verify_component(driver)         driver.find_element_by_xpath(MainPage.news_block_xpath)      @staticmethod     def input_search(driver: Driver, search: str):         search_input = driver.find_element_by_xpath(MainPage.search_input_xpath)         search_input.send_keys(search)      @staticmethod     def press_search_button(driver: Driver):         search_button = driver.find_element_by_xpath(MainPage.search_button_xpath)         search_button.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/search_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait  from components.header import ComponentHeader from driver import Driver   class SearchPage:     Header = ComponentHeader      article_xpath = '\/\/div[@class=\"tm-articles-list\"]\/article'     preview_xpath = '\/\/div[contains(@class, \"placeholder-wrapper\")]'      @staticmethod     def wait_for_loader_off(driver: Driver):         WebDriverWait(driver, 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, SearchPage.preview_xpath)))      @staticmethod     def verify_page(driver: Driver, expected_string_in_results: str):         SearchPage.wait_for_loader_off(driver)          SearchPage.Header.verify_component(driver)          articles = driver.find_elements_by_xpath(SearchPage.article_xpath)         assert articles, \"empty articles list\"          errors = []         for article in articles:             if expected_string_in_results not in article.text.lower():                 errors.append(f\"'{expected_string_in_results}' not found in '{article.text.lower()}'\")          assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0427\u0443\u0442\u044c-\u0447\u0443\u0442\u044c \u043f\u043e\u043c\u0435\u043d\u044f\u0435\u043c \u0442\u0435\u0441\u0442\u044b \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430.<\/p>\n<details class=\"spoiler\">\n<summary>test_ozon.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">def test_search(browser: Driver):     search_string = \"ozon tech\"      MainPage.Header.open_search(browser)     MainPage.input_search(browser, search_string)     MainPage.press_search_button(browser)     SearchPage.verify_page(browser, search_string)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>Driver Driver Driver Driver. \u0423\u0431\u0438\u0440\u0430\u0435\u043c \u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u0443 \u2014 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c Singleton<\/h3>\n<p>\u041d\u044b\u043d\u0435\u0448\u043d\u0435\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u2014 \u043d\u0435\u0442 \u043f\u043e\u0438\u0441\u043a\u043e\u0432 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445, \u0432\u0441\u0451 \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445. \u0421\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u044b \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u2014 \u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043d\u0435 \u043b\u043e\u043c\u0430\u044f \u0432 \u044d\u0442\u043e \u0432\u0440\u0435\u043c\u044f \u0434\u0440\u0443\u0433\u0438\u0435.<\/p>\n<p>\u041c\u043e\u0436\u043d\u043e \u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0435\u0449\u0451 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043b\u044f \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f? \u041c\u043e\u0436\u043d\u043e. \u041d\u043e \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c, \u0430 \u0443\u0431\u0440\u0430\u0442\u044c \u2014 \u0438\u0437\u0431\u0430\u0432\u0438\u043c\u0441\u044f \u043e\u0442 \u0432\u0435\u0437\u0434\u0435\u0441\u0443\u0449\u0435\u0439 \u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u044b \u0441 \u0437\u0430\u043f\u0438\u0445\u0438\u0432\u0430\u043d\u0438\u0435\u043c \u0432\u0435\u0431-\u0434\u0440\u0430\u0439\u0432\u0435\u0440\u0430 \u0432 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b.<\/p>\n<p>\u0411\u0440\u0430\u0443\u0437\u0435\u0440 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 \u0443 \u043d\u0430\u0441 \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0434\u0438\u043d, \u0437\u0430\u0447\u0435\u043c \u0435\u0433\u043e \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u0442\u044c? \u041c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c Singleton, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u043d\u0430\u0448 \u0431\u0440\u0430\u0443\u0437\u0435\u0440.<\/p>\n<p>\u041c\u043d\u0435\u043d\u0438\u0435 \u2014 \u044f \u0432\u0435\u0440\u044e, \u0447\u0442\u043e \u0442\u0435\u0441\u0442\u044b \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u0435\u043b\u0435\u043d\u0438\u0443\u043c\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0430 \u043d\u0435 \u0431\u044d\u043a\u0435\u043d\u0434. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e, \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e, \u0432\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0435 \u0440\u0430\u0431\u043e\u0442\u0443 \u0434\u0432\u0443\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043d\u0430\u0434 \u043e\u0434\u043d\u043e\u0439 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c\u044e, \u0447\u0435\u0433\u043e \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0441\u043b\u0443\u0447\u0430\u0435\u0432 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0432 \u0440\u0430\u0431\u043e\u0442\u0443 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0447\u0435\u0440\u0435\u0437 \u0431\u044d\u043a\u0435\u043d\u0434, \u043d\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440.<\/p>\n<p>\u0412 \u043b\u044e\u0431\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Singleton \u0438 \u0434\u0432\u0443\u0445 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u0432\u0435\u0431-\u0434\u0440\u0430\u0439\u0432\u0435\u0440\u043e\u0432, \u043d\u0443\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430.<\/p>\n<p>\u041c\u0435\u043d\u044f\u0435\u043c \u043a\u043e\u0434 \u0434\u0440\u0430\u0439\u0432\u0435\u0440\u0430.<\/p>\n<details class=\"spoiler\">\n<summary>driver.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver import Firefox   class Singleton(type):     _instances = {}      def __call__(cls, *args, **kwargs):         if cls not in cls._instances:             cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)         return cls._instances[cls]   class Driver(Firefox, metaclass=Singleton):      def __init__(self, *args, **kwargs):         super().__init__(*args, **kwargs)         self.implicitly_wait(5)  # default time waiting for a locator<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f \u2014 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 urlllib3 \u0438 selenium webdriver. \u041f\u043e\u0434\u043d\u0438\u043c\u0435\u043c \u0432\u0435\u0440\u0441\u0438\u044e urllib3.<\/p>\n<details class=\"spoiler\">\n<summary>requirements.txt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">selenium==3.141.0 pytest==7.4.3 urllib3==2.1.0<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041e\u0431\u043d\u043e\u0432\u0438\u043c teardown \u0432 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0435 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c autouse:<\/p>\n<details class=\"spoiler\">\n<summary>conftest.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import pytest  from driver import Driver   @pytest.fixture(autouse=True) def browser() -> Driver:     driver = Driver(executable_path=\"path\/to\/driver\")     driver.set_window_size(1920, 1080)     driver.get(\"https:\/\/habr.com\/ru\/all\")      yield driver      try:         driver.quit()     finally:         driver.__class__._instances = {}<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0423\u0431\u0435\u0440\u0451\u043c \u043f\u0440\u043e\u043a\u0438\u0434\u044b\u0432\u0430\u043d\u0438\u0435 Driver \u0432 \u043c\u0435\u0442\u043e\u0434\u044b \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>components\/header.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from driver import Driver   class ComponentHeader:     header_logo_xpath = '\/\/a[contains(@class, \"tm-header__logo\")]'     header_search_xpath = '\/\/a[contains(@class, \"tm-header-user-menu__search\")]'      @staticmethod     def verify_component():         Driver().find_element_by_xpath(ComponentHeader.header_logo_xpath)         Driver().find_element_by_xpath(ComponentHeader.header_search_xpath)      @staticmethod     def open_search():         open_search = Driver().find_element_by_xpath(ComponentHeader.header_search_xpath)         open_search.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u043c \u0434\u043b\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446:<\/p>\n<details class=\"spoiler\">\n<summary>pages\/main_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from components.header import ComponentHeader from driver import Driver   class MainPage:     Header = ComponentHeader      search_input_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/input'     search_button_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/span'     news_block_xpath = '\/\/section[@id=\"news_block_1\"]'      @staticmethod     def verify_page():         MainPage.Header.verify_component()         Driver().find_element_by_xpath(MainPage.news_block_xpath)      @staticmethod     def input_search(search: str):         search_input = Driver().find_element_by_xpath(MainPage.search_input_xpath)         search_input.send_keys(search)      @staticmethod     def press_search_button():         search_button = Driver().find_element_by_xpath(MainPage.search_button_xpath)         search_button.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/search_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait  from components.header import ComponentHeader from driver import Driver   class SearchPage:     Header = ComponentHeader      article_xpath = '\/\/div[@class=\"tm-articles-list\"]\/article'     preview_xpath = '\/\/div[contains(@class, \"placeholder-wrapper\")]'      @staticmethod     def wait_for_loader_off():         WebDriverWait(Driver(), 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, SearchPage.preview_xpath)))      @staticmethod     def verify_page(expected_string_in_results: str):         SearchPage.wait_for_loader_off()          SearchPage.Header.verify_component()          articles = Driver().find_elements_by_xpath(SearchPage.article_xpath)         assert articles, \"empty articles list\"          errors = []         for article in articles:             if expected_string_in_results not in article.text.lower():                 errors.append(f\"'{expected_string_in_results}' not found in '{article.text.lower()}'\")          assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0447\u0438\u0441\u0442\u0438\u043c \u0442\u0435\u0441\u0442\u044b \u043e\u0442 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u044f Driver:<\/p>\n<details class=\"spoiler\">\n<summary>test_ozon.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from pages.main_page import MainPage from pages.search_page import SearchPage   def test_main_page():     MainPage.verify_page()   def test_search():     search_string = \"ozon tech\"      MainPage.Header.open_search()     MainPage.input_search(search_string)     MainPage.press_search_button()     SearchPage.verify_page(search_string)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0418\u0437\u0431\u0430\u0432\u0438\u043b\u0438\u0441\u044c \u043e\u0442 \u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u044b \u0441 webdriver, Driver, driver \u2014 \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u0435\u0437\u0434\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0435\u0433\u043e \u043a\u0430\u043a \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0432\u044b\u0437\u043e\u0432 Driver.<\/p>\n<h3>\u0417\u0430\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c PageObject \u0432 PageObject<\/h3>\n<p>\u041e\u0442 \u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u044b \u0438\u0437\u0431\u0430\u0432\u0438\u043b\u0438\u0441\u044c \u0443\u0436\u0435 \u0430\u0436 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430, \u043d\u043e \u0447\u0442\u043e, \u0435\u0441\u043b\u0438 \u044f \u0441\u043a\u0430\u0436\u0443, \u0447\u0442\u043e \u0443 \u043d\u0430\u0441 \u0432\u0441\u0451 \u0435\u0449\u0451 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u0430? \u0410 \u0447\u0442\u043e, \u0435\u0441\u043b\u0438 \u043c\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 DOM-\u0434\u0435\u0440\u0435\u0432\u0430 \u2014 \u044d\u0442\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043c\u0435\u0442\u043e\u0434\u043e\u0432?<\/p>\n<p>\u0421\u043f\u043e\u0439\u043b\u0435\u0440 \u2014 \u0442\u0430\u043a \u043e\u043d\u043e \u0438 \u0435\u0441\u0442\u044c, \u043d\u0430\u0434\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f.<\/p>\n<p>\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 DOM-\u0434\u0435\u0440\u0435\u0432\u0430. \u041f\u0443\u0441\u0442\u044c \u0443 \u043d\u0435\u0433\u043e \u0431\u0443\u0434\u0435\u0442 xpath \u0438 \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 webelement (\u043e\u0431\u044a\u0435\u043a\u0442 \u0432 DOM-\u0434\u0435\u0440\u0435\u0432\u0435 \u043d\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u0434\u0440\u0430\u0439\u0432\u0435\u0440\u043e\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435).<\/p>\n<details class=\"spoiler\">\n<summary>locator.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.remote.webelement import WebElement   class Locator:     xpath: str = None     name: str = None     webelement: WebElement = None      def __init__(self, xpath: str, name: str):         self.xpath = xpath         self.name = name<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u044f \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435:<\/p>\n<details class=\"spoiler\">\n<summary>locator.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">def is_on_page(self) -> bool:     elements = Driver().find_elements_by_xpath(self.xpath)     if elements:         return True     return False   def wait_for_disappear(self):     WebDriverWait(Driver(), 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, self.xpath)))   def get_all_elements(self):     response = []     for element in Driver().find_elements_by_xpath(self.xpath):         locator = Locator(xpath=self.xpath, name=self.name)         locator.webelement = element         response.append(locator)     return response<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u2728\u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0438\u0439\u2728 \u043c\u0435\u0442\u043e\u0434 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f webelement:<\/p>\n<details class=\"spoiler\">\n<summary>locator.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">def _webelement_required(self):     def update_webelement():         try:             self.webelement = Driver().find_element_by_xpath(self.xpath)             return self         except Exception:             raise Exception(f'Error: cannot locate {self.name}')      # magic function to update webelement if there is no webelement or if DOM element changed     if not self.webelement:         update_webelement()         return     try:         # if not fails, webelement still has the DOM object link         self.webelement.location         return     except StaleElementReferenceException:         update_webelement()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u0430\u0433\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u2014 \u0435\u0441\u043b\u0438 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a webelement, \u0437\u043d\u0430\u0447\u0438\u0442 \u043e\u043d \u0435\u0449\u0451 \u0435\u0441\u0442\u044c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u2014 \u0437\u043d\u0430\u0447\u0438\u0442 DOM-\u0434\u0435\u0440\u0435\u0432\u043e \u043f\u0435\u0440\u0435\u0441\u0442\u0440\u043e\u0438\u043b\u043e\u0441\u044c, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0430\u0439\u0442\u0438 \u043d\u043e\u0432\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442.<\/p>\n<p>\u0412\u0430\u0436\u043d\u043e \u2014 \u043d\u0435 \u0432\u0441\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c webelement. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043a webelement, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e xpath-\u043b\u043e\u043a\u0430\u0442\u043e\u0440\u0430.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a \u043a\u043b\u0438\u043a, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0438 \u0432\u0432\u043e\u0434 \u0442\u0435\u043a\u0441\u0442\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>locator.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">def click(self):     self._webelement_required()     self.webelement.click()  def text(self) -> str:     self._webelement_required()     return self.webelement.text  def input(self, value: str):     self._webelement_required()     self.webelement.send_keys(value)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 locator.py:<\/p>\n<details class=\"spoiler\">\n<summary>locator.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from __future__ import annotations  from selenium.common.exceptions import StaleElementReferenceException from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait  from driver import Driver   class Locator:     xpath: str = None     name: str = None     webelement: WebElement = None      def __init__(self, xpath: str, name: str):         self.xpath = xpath         self.name = name      def _webelement_required(self):         def update_webelement():             try:                 self.webelement = Driver().find_element_by_xpath(self.xpath)                 return self             except Exception:                 raise Exception(f'Error: cannot locate {self.name}')          # magic function to update webelement if there is no webelement or if DOM element changed         if not self.webelement:             update_webelement()             return         try:             # if not fails, webelement still has the DOM object link             self.webelement.location             return         except StaleElementReferenceException:             update_webelement()      def is_on_page(self) -> bool:         elements = Driver().find_elements_by_xpath(self.xpath)         if elements:             return True         return False      def click(self):         self._webelement_required()         self.webelement.click()      def text(self) -> str:         self._webelement_required()         return self.webelement.text      def input(self, value: str):         self._webelement_required()         self.webelement.send_keys(value)      def wait_for_disappear(self):         WebDriverWait(Driver(), 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, self.xpath)))      def get_all_elements(self) -> [Locator]:         response = []         for element in Driver().find_elements_by_xpath(self.xpath):             locator = Locator(xpath=self.xpath, name=self.name)             locator.webelement = element             response.append(locator)         return response<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u0420\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0447\u043a\u0438 \u043f\u043e\u0434 \u043d\u043e\u0432\u0443\u044e \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/h3>\n<p>\u041d\u043e\u0432\u0430\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u2014 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 xpath \u2014 \u0433\u043e\u0442\u043e\u0432\u0430, \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u0434\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0435\u0451 \u043a \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c\u0443 \u043a\u043e\u0434\u0443.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043d\u0435\u0434\u043e\u043b\u0433\u0438\u0445 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0439 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u0443:<\/p>\n<details class=\"spoiler\">\n<summary>components\/header.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from locator import Locator   class ComponentHeader:     header_logo = Locator('\/\/a[contains(@class, \"tm-header__logo\")]', \"Header logo\")     header_search = Locator('\/\/a[contains(@class, \"tm-header-user-menu__search\")]', \"Header search\")      @staticmethod     def verify_component():         assert ComponentHeader.header_logo.is_on_page()         assert ComponentHeader.header_search.is_on_page()      @staticmethod     def open_search():         ComponentHeader.header_search.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/main_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from components.header import ComponentHeader from locator import Locator   class MainPage:     Header = ComponentHeader      search_input = Locator('\/\/div[contains(@class, \"tm-search__input\")]\/\/input', \"Search input\")     search_button = Locator('\/\/div[contains(@class, \"tm-search__input\")]\/\/span', \"Search button\")     news_block = Locator('\/\/section[@id=\"news_block_1\"]', \"News block\")      @staticmethod     def verify_page():         MainPage.Header.verify_component()         assert MainPage.news_block.is_on_page()      @staticmethod     def input_search(search: str):         MainPage.search_input.input(search)      @staticmethod     def press_search_button():         MainPage.search_button.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/search_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from components.header import ComponentHeader from locator import Locator   class SearchPage:     Header = ComponentHeader      article = Locator('\/\/div[@class=\"tm-articles-list\"]\/article', \"Article\")     preview = Locator('\/\/div[contains(@class, \"placeholder-wrapper\")]', \"Article placeholder\")      @staticmethod     def wait_for_loader_off():         SearchPage.preview.wait_for_disappear()      @staticmethod     def verify_page(expected_string_in_results: str):         SearchPage.wait_for_loader_off()          SearchPage.Header.verify_component()          articles = SearchPage.article.get_all_elements()         assert articles, \"empty articles list\"          errors = []         for article in articles:             if expected_string_in_results not in article.text().lower():                 errors.append(f\"'{expected_string_in_results}' not found in '{article.text().lower()}'\")          assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u0435\u0442\u043e\u0434\u044b \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u0438\u043a\u0430\u043c\u0438. \u0421\u0435\u043a\u0443\u043d\u0434\u043e\u0447\u043a\u0443, \u044d\u0442\u043e \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f, \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043d\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a \u043a\u043b\u0430\u0441\u0441\u0443 Locator?<\/p>\n<p>\u041f\u043e\u0447\u0438\u0441\u0442\u0438\u043c \u043a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0435\u0449\u0451 \u0440\u0430\u0437, \u0430 \u043f\u043e\u0442\u043e\u043c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0442\u0435\u0441\u0442\u044b.<\/p>\n<details class=\"spoiler\">\n<summary>components\/header.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from locator import Locator   class ComponentHeader:     header_logo = Locator('\/\/a[contains(@class, \"tm-header__logo\")]', \"Header logo\")     header_search = Locator('\/\/a[contains(@class, \"tm-header-user-menu__search\")]', \"Header search\")      @staticmethod     def verify_component():         assert ComponentHeader.header_logo.is_on_page()         assert ComponentHeader.header_search.is_on_page()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/main_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from components.header import ComponentHeader from locator import Locator   class MainPage:     Header = ComponentHeader      search_input = Locator('\/\/div[contains(@class, \"tm-search__input\")]\/\/input', \"Search input\")     search_button = Locator('\/\/div[contains(@class, \"tm-search__input\")]\/\/span', \"Search button\")     news_block = Locator('\/\/section[@id=\"news_block_1\"]', \"News block\")      @staticmethod     def verify_page():         MainPage.Header.verify_component()         assert MainPage.news_block.is_on_page()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/search_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from components.header import ComponentHeader from locator import Locator   class SearchPage:     Header = ComponentHeader      article = Locator('\/\/div[@class=\"tm-articles-list\"]\/article', \"Article\")     preview = Locator('\/\/div[contains(@class, \"placeholder-wrapper\")]', \"Article placeholder\")      @staticmethod     def verify_page(expected_string_in_results: str):         SearchPage.preview.wait_for_disappear()          SearchPage.Header.verify_component()          articles = SearchPage.article.get_all_elements()         assert articles, \"empty articles list\"          errors = []         for article in articles:             if expected_string_in_results not in article.text().lower():                 errors.append(f\"'{expected_string_in_results}' not found in '{article.text().lower()}'\")          assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>test_ozon.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from pages.main_page import MainPage from pages.search_page import SearchPage   def test_main_page():     MainPage.verify_page()   def test_search():     search_string = \"ozon tech\"      MainPage.Header.header_search.click()     MainPage.search_input.input(search_string)     MainPage.search_button.click()     SearchPage.verify_page(search_string)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u043c \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0442\u0435\u0441\u0442\u043e\u0432, \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0441\u0435\u043b\u0435\u043d\u0438\u0443\u043c\u043e\u043c \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0443\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u044e \u0431\u043e\u043b\u044c \u043e\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 DOM \u0432\u043d\u0443\u0442\u0440\u044c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430.<\/p>\n<h3>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b<\/h3>\n<p>\u041a\u0430\u043a\u0438\u0435 \u043f\u043b\u044e\u0441\u044b \u043f\u043e\u0441\u043b\u0435 \u0432\u0441\u0435\u0445 \u044d\u0442\u0438\u0445 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0439:<\/p>\n<ul>\n<li>\n<p>\u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0432\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0449\u0435 \u0438 \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0437\u0430 \u0441\u0447\u0451\u0442 \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u044f \u043e\u0431\u044a\u0451\u043c\u0430 \u043a\u043e\u0434\u0430;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0449\u0435 \u0438 \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0437\u0430 \u0441\u0447\u0451\u0442 \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u044f\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 DOM;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043d\u0438\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0440\u043e\u0433\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0430 \u0441\u0447\u0451\u0442 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u043d\u0443\u0436\u0435\u043d \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u043e\u043f\u044b\u0442\u043d\u044b\u0445 \u043b\u044e\u0434\u0435\u0439 \u0434\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0435\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u044e\u0449\u0438\u043c\u0441\u044f \u0432 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430\u0447\u0430\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043f\u0440\u0438\u0441\u043c\u043e\u0442\u0440\u043e\u043c \u0441\u0442\u0430\u0440\u0448\u0438\u0445 \u0442\u043e\u0432\u0430\u0440\u0438\u0449\u0435\u0439 \u2014 \u0431\u043e\u043b\u0435\u0435 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f \u0433\u043b\u0443\u0431\u043e\u043a\u0438\u0435 \u0437\u043d\u0430\u043d\u0438\u044f \u0441\u0435\u043b\u0435\u043d\u0438\u0443\u043c\u0430 \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0430\u0431\u043e\u0442\u044b.<\/p>\n<h3>\u0411\u043e\u043d\u0443\u0441 \u2014 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c Allure<\/h3>\n<p>\u041a\u0430\u043a proof of concept \u043b\u0451\u0433\u043a\u043e\u0441\u0442\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u043e\u0432\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u0432\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0441\u0435\u0439\u0447\u0430\u0441 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c Allure-\u043e\u0442\u0447\u0451\u0442\u044b \u0432 \u043d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b.<\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c Allure:<\/p>\n<pre><code class=\"python\">pip install allure-pytest<\/code><\/pre>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043a\u043b\u0430\u0441\u0441 Locator:<\/p>\n<details class=\"spoiler\">\n<summary>locator.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">def click(self):     with allure.step(f\"Click on {self.name}\"):         self._webelement_required()         self.webelement.click()  def input(self, value: str):     with allure.step(f\"Input '{value}' in {self.name}\"):         self._webelement_required()         self.webelement.send_keys(value)  def wait_for_disappear(self):     with allure.step(f\"Wait for disappear {self.name}\"):         WebDriverWait(Driver(), 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, self.xpath)))<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0442\u0435\u0441\u0442\u044b \u0441 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 \u043e\u0442\u0447\u0451\u0442\u0430:<\/p>\n<pre><code class=\"python\">pytest --alluredir=.\/allure_results<\/code><\/pre>\n<p>\u0412\u043e\u0441\u0445\u0438\u0442\u0438\u043c\u0441\u044f \u043d\u0430\u0448\u0438\u043c \u043e\u0442\u0447\u0451\u0442\u043e\u043c:<\/p>\n<pre><code class=\"python\">allure serve allure_results\/<\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/bb5\/1ed\/744\/bb51ed744eac18c3a3603fd0e4b49735.png\" width=\"1600\" height=\"625\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bb5\/1ed\/744\/bb51ed744eac18c3a3603fd0e4b49735.png\"\/><\/figure>\n<p>\u0412\u044b\u0448\u043b\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e, \u043d\u0435 \u0442\u0430\u043a \u043b\u0438?<\/p>\n<p>\u0412 \u043a\u043e\u043d\u0446\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0441\u0432\u043e\u0439 \u0433\u0438\u0442\u0445\u0430\u0431 \u0441 \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0440\u0430\u0437\u0432\u0438\u0442\u044b\u043c (\u0438 \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u043c) \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c \u2014 <a href=\"https:\/\/github.com\/giant-whale\/selenoid-python-framework\"><u>https:\/\/github.com\/giant-whale\/selenoid-python-framework<\/u><\/a><\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u0432\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438.<\/p>\n<p>P.S. \u0420\u043e\u043c\u0430, \u0442\u0435\u043f\u0435\u0440\u044c \u0442\u044b \u043c\u043e\u0436\u0435\u0448\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u043a\u0438\u043d\u0443\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u044d\u0442\u0443 \u0441\u0442\u0430\u0442\u044c\u044e \u0441\u0432\u043e\u0438\u043c \u043a\u043e\u043b\u043b\u0435\u0433\u0430\u043c, \u0441\u043e\u043c\u043d\u0435\u0432\u0430\u044e\u0449\u0438\u043c\u0441\u044f \u0432 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0441\u0442\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/786000\/\"> https:\/\/habr.com\/ru\/articles\/786000\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041a\u043e\u043b\u044f, \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c 7 \u043b\u0435\u0442, \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u2014 6 \u043b\u0435\u0442. \u0422\u0430\u043a \u0443\u0436 \u0441\u043b\u043e\u0436\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u043d\u0435 \u043e\u0441\u043e\u0431\u043e \u043b\u044e\u0431\u043b\u044e WebUI-\u0442\u0435\u0441\u0442\u044b, \u043d\u043e \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u043d\u0438 \u0443 \u043c\u0435\u043d\u044f \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442\u0441\u044f \u043b\u0443\u0447\u0448\u0435 \u0432\u0441\u0435\u0433\u043e.<\/p>\n<p>\u0412 \u043e\u0434\u0438\u043d \u0434\u0435\u043d\u044c \u043c\u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043d\u0438\u043b \u043c\u043e\u0439 \u0434\u0440\u0443\u0433 \u0420\u043e\u043c\u0430 \u0438 \u0441\u043a\u0430\u0437\u0430\u043b: \u00ab\u041a\u043e\u043b\u044f, \u043f\u043e\u043c\u043d\u0438\u0448\u044c \u0442\u044b \u0443 \u043d\u0430\u0441 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u044b \u0434\u0435\u043b\u0430\u043b \u0441 \u0441\u0435\u043b\u0435\u043d\u0438\u0443\u043c\u043e\u043c? \u041f\u043e\u043c\u043e\u0433\u0438 \u043c\u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a \u0436\u0435 \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u00bb.<\/p>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0432 \u0420\u043e\u043c\u0435 \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0441\u0432\u043e\u0439 \u0433\u0438\u0442\u0445\u0430\u0431 \u0438 \u0443\u0441\u0442\u0440\u043e\u0438\u0432 \u043f\u0430\u0440\u0443 \u0441\u043e\u0437\u0432\u043e\u043d\u043e\u0432 \u0441 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438, \u0447\u0442\u043e \u0438 \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u044f \u0434\u043e\u0431\u0438\u043b\u0441\u044f \u043e\u0442 \u043d\u0435\u0433\u043e \u0437\u0430\u0432\u0435\u0442\u043d\u043e\u0433\u043e \u00ab\u042f \u043f\u043e\u043d\u044f\u043b\u00bb.\u00a0<\/p>\n<p>\u0421\u043f\u0443\u0441\u0442\u044f \u0434\u0432\u0430 \u0434\u043d\u044f \u0420\u043e\u043c\u0430 \u043f\u043e\u0437\u0432\u043e\u043d\u0438\u043b \u0441\u043d\u043e\u0432\u0430: \u00ab\u041a\u043e\u043b\u044f, \u044f \u0432\u0441\u0451 \u043f\u043e\u043d\u044f\u043b, \u0430 \u0432\u043e\u0442 \u0440\u0435\u0431\u044f\u0442\u0430 \u0432 \u043c\u043e\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u2014 \u043d\u0435\u0442. \u041f\u043e\u043c\u043e\u0433\u0438 \u043c\u043d\u0435 \u043e\u0431\u044a\u044f\u0441\u043d\u0438\u0442\u044c \u0438\u043c\u00bb.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e\u0442 \u0442\u043e\u043c, \u043a\u0430\u043a:<\/p>\n<ul>\n<li>\n<p>\u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043d\u0438\u0437\u0438\u0442\u044c \u0437\u0430\u0442\u0440\u0430\u0442\u044b \u043d\u0430 \u0438\u0445 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u043a\u0430\u0447\u0430\u0442\u044c\u0441\u044f \u0432 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u044c \u0434\u0435\u043d\u044c\u0433\u0438 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0435\u0440\u0432\u044b \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u043e\u0432.<\/p>\n<\/li>\n<\/ul>\n<h2>\u0427\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0442\u0430\u0442\u044c\u0438<\/h2>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0430 \u044d\u0432\u043e\u043b\u044e\u0446\u0438\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0435\u0433\u043e:<\/p>\n<ul>\n<li>\n<p>Python;<\/p>\n<\/li>\n<li>\n<p>Pytest;<\/p>\n<\/li>\n<li>\n<p>Selenium (\u0432\u0435\u0440\u0441\u0438\u0438 3).<\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 Selenium \u0432\u0435\u0440\u0441\u0438\u0438 3.* \u043e\u0431\u0443\u0441\u043b\u043e\u0432\u043b\u0435\u043d\u043e \u0442\u0435\u043c, \u0447\u0442\u043e Selenium 4.*-\u0432\u0435\u0431\u0434\u0440\u0430\u0439\u0432\u0435\u0440 \u043d\u0435 \u0434\u0440\u0443\u0436\u0438\u0442 \u0441 \u0441\u0438\u043d\u0433\u043b\u0442\u043e\u043d\u043e\u043c. \u041d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434, \u043f\u0440\u0438\u0447\u0438\u043d \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0441 3 \u043d\u0430 4 \u043d\u0435\u0442 \u0441\u043e\u0432\u0441\u0435\u043c \u2014 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 Selenium Grid, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e \u0432\u0441\u0451\u043c \u043f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442 Selenoid. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0436\u0435\u043b\u0430\u043d\u0438\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0435-\u043d\u043e\u0432\u043e\u0435, \u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 Playwright \u2014 \u0441 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c \u043a\u043e\u0434 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u043c\u043e\u0436\u043d\u043e \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u043f\u043e\u0434 \u043d\u0435\u0433\u043e.<\/p>\n<p>\u0414\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u043c \u0442\u0430\u043a:<\/p>\n<ol>\n<li>\n<p>\u043f\u0438\u0448\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u043a\u043e\u0434;<\/p>\n<\/li>\n<li>\n<p>\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u043c, \u043f\u043e\u0447\u0435\u043c\u0443 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u043b\u043e\u0445\u043e;<\/p>\n<\/li>\n<li>\n<p>\u0443\u043b\u0443\u0447\u0448\u0430\u0435\u043c \u043a\u043e\u0434;<\/p>\n<\/li>\n<li>\n<p>GOTO 1.<\/p>\n<\/li>\n<\/ol>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0441\u0451 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u044d\u0442\u043e\u0442 \u043d\u0430\u0431\u043e\u0440 \u0441\u0442\u0430\u0442\u0435\u0439 \u2014 <a href=\"https:\/\/habr.com\/ru\/articles\/248559\/\">Selenium \u0434\u043b\u044f Python. \u0413\u043b\u0430\u0432\u0430 1. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \/ \u0425\u0430\u0431\u0440 (habr.com)<\/a><\/p>\n<p>\u0421\u0442\u0430\u0442\u044c\u044f \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u0430 \u043d\u0430 \u043e\u043f\u044b\u0442\u043d\u043e\u0433\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0437\u043d\u0430\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0441\u043b\u043e\u0432\u0430, \u043a\u0430\u043a xpath, selenium webdriver, \u0438 \u0443\u0436\u0435 \u0438\u043c\u0435\u043b \u043e\u043f\u044b\u0442 \u0441 Pytest, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043f\u0440\u043e\u043f\u0443\u0449\u0443 \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f \u043e\u0441\u043d\u043e\u0432.<\/p>\n<h3>\u041f\u0438\u0448\u0435\u043c \u0442\u0435\u0441\u0442\u044b<\/h3>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0432\u043e\u0437\u044c\u043c\u0451\u043c \u0434\u0432\u0430 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f:<\/p>\n<ol>\n<li>\n<p>\u0437\u0430\u0439\u0442\u0438 \u043d\u0430 <a href=\"http:\/\/habr.com\">habr.com<\/a>, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043c\u044b \u0435\u0441\u0442\u044c \u043d\u0430 \u0445\u0430\u0431\u0440\u0435;<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u0439\u0442\u0438 \u043d\u0430 <a href=\"http:\/\/habr.com\">habr.com<\/a>, \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0444\u0440\u0430\u0437\u0435 \u201cozon tech\u201d, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0432\u0441\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0444\u0440\u0430\u0437\u0443 \u201cozon tech\u201d.<\/p>\n<\/li>\n<\/ol>\n<p>\u0417\u0434\u0435\u0441\u044c \u0431\u0443\u0434\u0435\u0442 \u043c\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u0441\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<details class=\"spoiler\">\n<summary>requirements.txt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">selenium==3.141.0 pytest==7.4.3 urllib3&lt;=2.0<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0417\u0430\u043c\u0435\u0442\u043a\u0430 \u2014 requirements.txt \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u043e\u043c\u044f\u043d\u0443\u0442 \u0432\u0441\u0435\u0433\u043e \u0434\u0432\u0430 \u0440\u0430\u0437\u0430. \u0424\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<br \/>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 urllib3 &lt;= 2.0 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 selenium webdriver.<br \/>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 <code>pip install -r requirements.txt<\/code>.<\/p>\n<details class=\"spoiler\">\n<summary>driver.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver import Firefox   class Driver(Firefox):     def __init__(self, *args, **kwargs):         super().__init__(*args, **kwargs)         self.implicitly_wait(5)  # default time waiting for a locator<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>conftest.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import pytest  from driver import Driver   @pytest.fixture() def browser() -> Driver:     driver = Driver(executable_path=\"path\/to\/driver\")     driver.set_window_size(1920, 1080)     driver.get(\"https:\/\/habr.com\/ru\/all\")      yield driver      driver.quit()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>test_ozon.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait  from driver import Driver   def test_main_page(browser: Driver):     browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header__logo\")]')     browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header-user-menu__search\")]')     browser.find_element_by_xpath('\/\/section[@id=\"news_block_1\"]')   def test_search(browser: Driver):     search_string = \"ozon tech\"      open_search = browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header-user-menu__search\")]')     open_search.click()      search_input = browser.find_element_by_xpath('\/\/div[contains(@class, \"tm-search__input\")]\/\/input')     search_input.send_keys(search_string)      search_button = browser.find_element_by_xpath('\/\/div[contains(@class, \"tm-search__input\")]\/\/span')     search_button.click()      WebDriverWait(browser, 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, '\/\/div[contains(@class, \"placeholder-wrapper\")]')))      browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header__logo\")]')     browser.find_element_by_xpath('\/\/a[contains(@class, \"tm-header-user-menu__search\")]')      articles = browser.find_elements_by_xpath('\/\/div[@class=\"tm-articles-list\"]\/article')     assert articles, \"empty articles list\"      errors = []     for article in articles:         if search_string not in article.text.lower():             errors.append(f\"'{search_string}' not found in '{article.text.lower()}'\")      assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c PageObject<\/h3>\n<p>\u0422\u0435\u0441\u0442\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b, \u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043a\u0430\u043a-\u0442\u043e \u043d\u0435 \u0442\u0430\u043a, \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u043e\u0435 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u0438\u0437 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043c\u044b \u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043e \u0442\u0435\u0441\u0442\u0430\u0445 \u0434\u043b\u044f \u0432\u0435\u0431\u0430, \u044d\u0442\u043e\u0442 \u043f\u0430\u0442\u0442\u0435\u0440\u043d \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f PageObject \u2014 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0438 \u0435\u0451 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u0435\u0442\u043e\u0434\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0441\u0442\u044c \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0442\u0443\u0434\u0430 \u043f\u0430\u0440\u0443 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b:<\/p>\n<details class=\"spoiler\">\n<summary>pages\/main_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from driver import Driver   class MainPage:     header_logo_xpath = '\/\/a[contains(@class, \"tm-header__logo\")]'     header_search_xpath = '\/\/a[contains(@class, \"tm-header-user-menu__search\")]'     search_input_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/input'     search_button_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/span'     news_block_xpath = '\/\/section[@id=\"news_block_1\"]'      @staticmethod     def verify_page(driver: Driver):         driver.find_element_by_xpath(MainPage.header_logo_xpath)         driver.find_element_by_xpath(MainPage.header_search_xpath)         driver.find_element_by_xpath(MainPage.news_block_xpath)      @staticmethod     def open_search(driver: Driver):         open_search = driver.find_element_by_xpath(MainPage.header_search_xpath)         open_search.click()      @staticmethod     def input_search(driver: Driver, search: str):         search_input = driver.find_element_by_xpath(MainPage.search_input_xpath)         search_input.send_keys(search)      @staticmethod     def press_search_button(driver: Driver):         search_button = driver.find_element_by_xpath(MainPage.search_button_xpath)         search_button.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/search_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait  from driver import Driver   class SearchPage:     header_logo_xpath = '\/\/a[contains(@class, \"tm-header__logo\")]'     header_search_xpath = '\/\/a[contains(@class, \"tm-header-user-menu__search\")]'     article_xpath = '\/\/div[@class=\"tm-articles-list\"]\/article'     preview_xpath = '\/\/div[contains(@class, \"placeholder-wrapper\")]'      @staticmethod     def wait_for_loader_off(driver: Driver):         WebDriverWait(driver, 10).until_not(expected_conditions.presence_of_element_located((By.XPATH, SearchPage.preview_xpath)))      @staticmethod     def verify_page(driver: Driver, expected_string_in_results: str):         SearchPage.wait_for_loader_off(driver)          driver.find_element_by_xpath(SearchPage.header_logo_xpath)         driver.find_element_by_xpath(SearchPage.header_search_xpath)          articles = driver.find_elements_by_xpath(SearchPage.article_xpath)         assert articles, \"empty articles list\"          errors = []         for article in articles:             if expected_string_in_results not in article.text.lower():                 errors.append(f\"'{expected_string_in_results}' not found in '{article.text.lower()}'\")          assert not errors, errors<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u043c \u043d\u0430\u0448\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043a \u0442\u0435\u0441\u0442\u0430\u043c:<\/p>\n<details class=\"spoiler\">\n<summary>test_ozon.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from driver import Driver from pages.main_page import MainPage from pages.search_page import SearchPage   def test_main_page(browser: Driver):     MainPage.verify_page(browser)   def test_search(browser: Driver):     search_string = \"ozon tech\"      MainPage.open_search(browser)     MainPage.input_search(browser, search_string)     MainPage.press_search_button(browser)     SearchPage.verify_page(browser, search_string)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u0441\u0442\u044b \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0449\u0435, \u043d\u0435 \u0442\u0430\u043a \u043b\u0438?<\/p>\n<h3>\u041f\u0440\u0438\u043a\u043e\u043b\u0430\u0447\u0438\u0432\u0430\u0435\u043c PageElement<\/h3>\n<p>\u0421\u0442\u0430\u043b\u043e \u043b\u0443\u0447\u0448\u0435, \u043d\u043e \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0441\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c. \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u0435, \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0445\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014\u00a0 \u0434\u043b\u044f Header.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u043c PageElement \u2014 \u043e\u0431\u044b\u0447\u043d\u043e \u043e\u043d \u0440\u044f\u0434\u043e\u043c \u0441 PageObject, \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0435\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 Header, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043d\u0435\u0433\u043e \u043e\u0431\u0449\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b.<\/p>\n<details class=\"spoiler\">\n<summary>components\/header.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from driver import Driver   class ComponentHeader:     header_logo_xpath = '\/\/a[contains(@class, \"tm-header__logo\")]'     header_search_xpath = '\/\/a[contains(@class, \"tm-header-user-menu__search\")]'      @staticmethod     def verify_component(driver: Driver):         driver.find_element_by_xpath(ComponentHeader.header_logo_xpath)         driver.find_element_by_xpath(ComponentHeader.header_search_xpath)      @staticmethod     def open_search(driver: Driver):         open_search = driver.find_element_by_xpath(ComponentHeader.header_search_xpath)         open_search.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.<\/p>\n<details class=\"spoiler\">\n<summary>pages\/main_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from components.header import ComponentHeader from driver import Driver   class MainPage:     Header = ComponentHeader      search_input_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/input'     search_button_xpath = '\/\/div[contains(@class, \"tm-search__input\")]\/\/span'     news_block_xpath = '\/\/section[@id=\"news_block_1\"]'      @staticmethod     def verify_page(driver: Driver):         MainPage.Header.verify_component(driver)         driver.find_element_by_xpath(MainPage.news_block_xpath)      @staticmethod     def input_search(driver: Driver, search: str):         search_input = driver.find_element_by_xpath(MainPage.search_input_xpath)         search_input.send_keys(search)      @staticmethod     def press_search_button(driver: Driver):         search_button = driver.find_element_by_xpath(MainPage.search_button_xpath)         search_button.click()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>pages\/search_page.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import<\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-365133","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/365133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=365133"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/365133\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=365133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=365133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=365133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}