“”” @File: common.py @Description: 核心文件。是浏览器驱动实例的封装,元素操作等。适用于Web自动化 @Author: hailong.chen @Date: 12/11/20 “”” import logging import os import time from datetime import datetime
from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support.select import Select from selenium.webdriver.support.wait import WebDriverWait
logging.basicConfig(level=logging.INFO, format=’%(asctime)s - %(filename)s - %(funcName)s - LINE: %(lineno)s -%(levelname)s - %(message)s’) logger = logging.getLogger(name)
_HIGH_LIGHT = “background: yellow; board: 2px solid red”
_chrome_driver = ‘/Library/Frameworks/Python.framework/Versions/3.9/bin/chromedriver_current’
class BaseWebDriverAgent(object): def init(self, executable_path): try: if executable_path: self.driver = webdriver.Chrome(executable_path=executable_path) else: self.driver = webdriver.Chrome(executable_path=_chrome_driver) except Exception: raise
def get(self, url):
self.driver.get(url)
def refresh(self):
self.driver.refresh()
@property
def window_handles(self):
return self.driver.window_handles
@property
def switch_to(self):
return self.driver.switch_to
def maximize_window(self):
self.driver.maximize_window()
def fullscreen_window(self):
self.driver.fullscreen_window()
def screenshot_as_png(self):
dt = datetime.now().strftime('%Y-%m-%d')
_path_dir = "../../screenshots/%s" % dt
now = datetime.now().strftime('%H:%M:%S')
print("当前的工作目录的绝对路径:" + os.getcwd())
if os.path.exists(_path_dir):
self.driver.get_screenshot_as_file(filename='%s/%s.png' % (_path_dir, now))
else:
try:
os.makedirs(_path_dir)
self.driver.get_screenshot_as_file(filename='%s/%s.png' % (_path_dir, now))
except (IOError, FileExistsError, FileNotFoundError):
raise Exception("截图文件创建异常")
def get_screenshot_as_base64(self):
return self.driver.get_screenshot_as_base64()
def quit(self):
self.driver.quit()
def close(self):
self.driver.close()
class WebDriverAgent(BaseWebDriverAgent): def init(self, executablepath=chrome_driver): super().__init(executable_path)
def find_element(self, locator, value=None, timeout=10):
try:
# 解决 IDE 无法判断 < el > 对象的类型,避免导致引用方法时 IDE 无提示问题(实际可调用)而加入类型注解
el = WebDriverWait(self.driver, timeout).until(
lambda driver: driver.find_element(by=locator, value=value)) # type: WebElement
self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", el, _HIGH_LIGHT)
return el
except (AttributeError, Exception):
self.screenshot_as_png()
logger.info("\n 》》》====== DOM节点 %s \t 在 %s 秒内没找到 ====== 《《《 \n" % ((locator, value), timeout))
def find_elements(self, locator, value, timeout=10):
try:
# 解决 IDE 无法判断 < el > 对象的类型,避免导致引用方法时 IDE 无提示问题(实际可调用)而加入类型注解
els = WebDriverWait(self.driver, timeout).until(
lambda driver: driver.find_elements(by=locator, value=value)) # type: WebElement
for i in range(len(els)):
self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", els[i], _HIGH_LIGHT)
return els
except (AttributeError, Exception):
self.screenshot_as_png()
logger.info("\n 》》》====== DOM节点 %s \t 在 %s 秒内没找到 ====== 《《《 \n" % ((locator, value), timeout))
def find_element_by_css_selector(self, css_selector, timeout=10):
try:
# 解决 IDE 无法判断 < el > 对象的类型,避免导致引用方法时 IDE 无提示问题(实际可调用)而加入类型注解
el = WebDriverWait(self.driver, timeout).until(
lambda driver: driver.find_element_by_css_selector(css_selector)) # type: WebElement
self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", el, _HIGH_LIGHT)
return el
except (AttributeError, Exception):
self.screenshot_as_png()
logger.info("\n 》》》====== DOM节点 %s \t 在 %s 秒内没找到 ====== 《《《 \n" % (css_selector, timeout))
def move_to_element(self, web_element: WebElement):
"""
:param web_element: WebElement
:return: None
"""
try:
ActionChains(self.driver).move_to_element(web_element).perform()
time.sleep(1)
except (AttributeError, Exception):
raise
@staticmethod
def item_select(web_element, text):
"""该方法只对标准下拉框<select> </select>有效"""
s = Select(web_element)
s.select_by_visible_text(text)
def mouse_click(self, web_element):
try:
ActionChains(self.driver).click(web_element).perform()
except (AttributeError, Exception):
raise
def scroll_to_target(self, web_element):
"""用于浏览器内滚动元素到可视窗口以定位元素"""
try:
(self.driver.execute_script("arguments[0].scrollIntoView();", web_element))
time.sleep(1)
except:
raise
def drag_and_drop(self, source_element, target_element):
try:
ActionChains(self.driver).drag_and_drop(source_element, target_element).perform()
except:
raise
def drag_and_drop_by_offset(self, source_element, xoffset, yoffset):
try:
ActionChains(self.driver).drag_and_drop_by_offset(source_element, xoffset, yoffset).perform()
except:
raise
def context_click(self, on_element):
"""鼠标右键单击
"""
try:
ActionChains(self.driver).context_click(on_element).perform()
except:
raise
def double_click(self, on_element):
"""鼠标左键双击"""
try:
ActionChains(self.driver).double_click(on_element).perform()
except:
raise
def get_size(self, web_element: WebElement) -> dict:
"""The size of the element.
i.e {"height": size["height"], "width": size["width"]}
"""
self.move_to_element(web_element)
return web_element.size
def get_rect(self, web_element: WebElement) -> dict:
"""A dictionary with the size and location of the element."""
self.move_to_element(web_element)
return web_element.rect
def get_location(self, web_element: WebElement) -> dict:
"""The location of the element in the renderable canvas."""
self.move_to_element(web_element)
return web_element.location
def get_value_of_css_property(self, web_element: WebElement, property_name):
self.move_to_element(web_element)
return web_element.value_of_css_property(property_name)
def input_text(self, web_element: WebElement, text):
self.move_to_element(web_element)
return web_element.send_keys(text)
def move_by_offset(self, xoffset, yoffset):
return ActionChains(self.driver).move_by_offset(xoffset, yoffset)
def click_and_hold(self, on_element=None):
return ActionChains(self.driver).click_and_hold(on_element)
def release(self, on_element=None):
return ActionChains(self.driver).release(on_element)
<a name="Xjot9"></a>
#### 钩子-定义夹具
> @pytest.fixture
```python
#!/usr/local/bin/py3
# -*- coding:utf-8 -*-
"""
@File: conftest.py
@Description: 工程的核心文件,定义了夹具和测试报告等。
Pytest-hmtl的报表增强,其文件路径、文件名、方法名等名称均是hook实现,不要做更改。详情请通篇阅读
https://pytest-html.readthedocs.io/en/latest/user_guide.html#enhancing-reports ,
https://docs.qameta.io/allure-report/#_attachments_5
@Author: hailong.chen
@Date: 2020/12/26
"""
import time
from datetime import datetime
import allure
from py.xml import html
import pytest
from utils.common import WebDriverAgent
def pytest_html_results_table_header(cells):
cells.insert(1, html.th("Description"))
cells.insert(3, html.th("Datetime", class_="sortable time", col="time"))
cells.pop()
def pytest_html_results_table_row(report, cells):
cells.insert(1, html.td(report.description))
cells.insert(3, html.td(datetime.now().strftime('%D %H:%M:%S'), class_="col-time"))
cells.pop()
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
report = outcome.get_result()
setattr(report, "duration_formatter", "%H:%M:%S.%f")
report.description = str(item.function.__doc__)
extra = getattr(report, "extra", [])
if report.when == "call":
xfail = hasattr(report, "wasxfail")
if (report.skipped and xfail) or (report.failed and not xfail):
# only add additional html on failure
file_name = report.nodeid.replace("::", "_") + ".png"
img = capture_screenshot_as_base64()
if file_name:
html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
'onclick="window.open(this.src)" align="right"/></div>' % img
extra.append(pytest_html.extras.html(html))
# allure 获取截图文件
with allure.step('执行出现失败时截图如下'):
_png = get_screenshot_as_png()
allure.attach(_png, '截图信息', allure.attachment_type.PNG)
report.extra = extra
def pytest_html_results_table_html(report, data):
if report.passed:
del data[:]
data.append(html.div("No log output captured.", class_="empty log"))
driver = None
@pytest.fixture(scope='session', autouse=True)
def browser(request):
global driver
driver = WebDriverAgent()
#driver.get(url)
time.sleep(2)
# 此处是因为浏览器测试驱动出现了白屏,进行一次刷新
driver.refresh()
def end():
driver.quit()
request.addfinalizer(end)
return driver # type: WebDriverAgent
def capture_screenshot_as_base64():
return driver.get_screenshot_as_base64()
def get_screenshot_as_png():
return driver.get_screenshot_as_png()
def pytest_configure(config):
"""
通过 @pytest.mark 调用。用于测试用例管理
e.g @pyetest.mark.bvt 即定义某用例级别为构建认证测试(金丝雀测试)
"""
marker_list = ["bvt", "smoke"]
for markers in marker_list:
config.addinivalue_line("markers", markers)
“”” @File: test_login.py @Description: @Author: hailong.chen @Date: 2020/12/24 “”” import pytest
from page_metadata.website_dom import PageDom
@pytest.mark.usefixtures(‘browser’) class TestLogin: “””website登录业务”””
def test_login(self, browser, acc='*******', password='******'):
"""正常登录"""
browser.find_element(PageDom.LOGIN_ACC[0], PageDom.LOGIN_ACC[1]).send_keys(acc)
browser.find_element(PageDom.LOGIN_PASSWORD[0], PageDom.LOGIN_PASSWORD[1]).send_keys(password)
browser.find_element(PageDom.LOGIN_BTN[0], PageDom.LOGIN_BTN[1]).click()
flag = browser.find_element(PageDom.LOGO_IMG[0], PageDom.LOGO_IMG[1])
assert flag, "登录异常"
---
<a name="YJrAR"></a>
#### 钩子-添加命令与参数、添加自定义markers
> pytest_configure(config)
> pytest_addoption(parser)
> pytest_config.getoption(request)
**如果命令行调用自定义参数时不生效,通常是需要**
> pytest -h 可查看
1. **先行切换到对应注册的 **`**conftest.py**`**的目录,然后调用注册的命令参数 **
**如果命令行调用自定义标记时不生效,解决的办法有两种**
> pytest --markers 可查看
1. **模块名以 test_*.py 或 *_test.py 命名**
2. **先行切换到对应注册的 **`**conftest.py**`**的目录,然后调用注册的命令参数 **
```python
def pytest_configure(config):
"""
注册配置信息,可用于测试用例调度管理。
基于测试发现的规则(cases/website目录下),通过命令行形如 `pytest -m bvt` 或 `pytest -m 'not bvt'` 调用
在 contest.py 所属的目录下,命令调用 `pytest --markers` 可查看有效的标记
e.g @pyetest.mark.bvt 即标记某用例为"bvt"
"""
config.addinivalue_line("markers", "bvt")
config.addinivalue_line("markers", "smoke")
def pytest_addoption(parser):
"""命令参数注册"""
parser.addoption("--env_config", dest="NAME", choices=["default_config", "local_config"],
default="local_config",
help="--env_config参数的选项值只能是'default_config','local_config', 如`--env_config=local_config`")
@pytest.fixture(scope="session")
def env_config(request):
"""获取命令参数的值"""
return request.config.getoption("--env_config")