关于PO模型遇到的一个问题

背景:基于pytest+poium实现UI自动化

问题:先看下我们基于PO思想封装的一个页面类,每个行为我们都把它处理为一个类方法,再看用例层,传入全局browser(全局浏览器驱动实例)给类Search_baidu,执行用例的时候会报一个错误:
AttributeError: ‘NoneType’ object has no attribute ‘find_elements’

  1. # 页面类
  2. from poium import Page, NewPageElement
  3. class Search_baidu(Page):
  4. """百度首页页面元素定位"""
  5. @classmethod
  6. def input_search(cls, search_key):
  7. input_text = NewPageElement(css='input[name="wd"]', describe="输入搜索关键词")
  8. input_text.send_keys(search_key)
  9. @classmethod
  10. def click_search(cls):
  11. click_btn = NewPageElement(css='input[class$="s_btn"]', describe="点击搜索")
  12. click_btn.click()
  1. # 用例层
  2. """使用poium库实现百度搜索功能"""
  3. import pytest
  4. from page_handle.baidu_demo.baidu_search import Search_baidu
  5. class TestBaidu:
  6. """百度搜素"""
  7. def test_baidu_search_case2(self, browser, baidu_url):
  8. page_baidu = Search_baidu(browser)
  9. page_baidu.get(baidu_url)
  10. page_baidu.input_search('pytest')
  11. page_baidu.click_search()

原因:基于上面报的错误,直接追踪到NewPageElement类,看下部分源码发现,NewPageElement类其实是一个数据描述符,通过get(),拿到一个browser实例,再回到我们的的页面类,NewPageElement代理非类属性,所以无法触发get(),也就无法拿到browser实例,只有代理另一个类的类属性才能触发get()

  1. # NewPageElement类部分源码
  2. class NewPageElement(object):
  3. """
  4. new Page element class
  5. """
  6. def __init__(self, timeout=5, describe="undefined", index=0, **kwargs):
  7. self.timeout = timeout
  8. self.index = index
  9. self.desc = describe
  10. if not kwargs:
  11. raise ValueError("Please specify a locator")
  12. if len(kwargs) > 1:
  13. raise ValueError("Please specify only one locator")
  14. self.kwargs = kwargs
  15. self.k, self.v = next(iter(kwargs.items()))
  16. if self.k not in LOCATOR_LIST.keys():
  17. raise FindElementTypesError("Element positioning of type '{}' is not supported.".format(self.k))
  18. def __get__(self, instance, owner):
  19. if instance is None:
  20. return None
  21. Browser.driver = instance.driver
  22. return self
  23. def __set__(self, instance, value):
  24. self.__get__(instance, instance.__class__)
  25. self.send_keys(value)
  26. def __find_element(self, elem):
  27. """
  28. Find if the element exists.
  29. """
  30. logging.info("Browser.driver调试!!: {}".format(Browser.driver))
  31. for i in range(self.timeout):
  32. elems = Browser.driver.find_elements(by=elem[0], value=elem[1])
  33. if len(elems) == 1:
  34. logging.info("Find element: {by}={value} ".format(
  35. by=elem[0], value=elem[1]))
  36. break
  37. elif len(elems) > 1:
  38. logging.info("Find {n} elements through: {by}={value}".format(
  39. n=len(elems), by=elem[0], value=elem[1]))
  40. break
  41. else:
  42. sleep(1)
  43. else:
  44. error_msg = "Find 0 elements through: {by}={value}".format(by=elem[0], value=elem[1])
  45. logging.error(error_msg)
  46. raise NoSuchElementException(error_msg)

解决问题:因为NewPageElement是数据描述符,把描述符定义为这个类的类属性,则解决问题
备注:更多关于描述符知识点见python描述符
_

设置浏览器启动语言

有时候开启浏览器需要特别的语言版本,比如,英文的,中文的。
测试时候发现,机器默认启动的是系统语言对应的浏览器,我们的被测网站的中文的,而系统是英文的,开启的浏览器就是英文的,提交的某些数据按照英文的格式提交了,导致数据报错,那么我们就需要设置浏览器启动语言。
Chrome 需要通过ChromeOptions来解决,设置如下参数(—lang=zh-CN):

  1. def web_driver():
  2. """
  3. 定义浏览器驱动
  4. :return:
  5. """
  6. driver = None
  7. chrome_options = CH_Options()
  8. if Config.DRIVER_TYPE == 'chrome':
  9. # 界面模式
  10. chrome_options.add_argument('--ignore-certificate-errors')
  11. driver = webdriver.Chrome(options=chrome_options)
  12. elif Config.DRIVER_TYPE == 'chrome_headless':
  13. # 无头模式
  14. chrome_options.add_argument('--headless')
  15. chrome_options.add_argument('--lang=zh-CN') # 设置chrome启动语言为中文
  16. chrome_options.add_argument('--disable-gpu')
  17. chrome_options.add_argument('--window-size=1680x1050')
  18. chrome_options.add_argument('--ignore-certificate-errors')
  19. driver = webdriver.Chrome(options=chrome_options)
  20. else:
  21. raise NameError("driver 浏览器驱动定义错误!")
  22. driver.maximize_window()
  23. driver.implicitly_wait(10)
  24. return driver

记一次关于如何启动麦克风权限

image.png
chrome启动参数中增加 use-fake-device-for-media-stream、use-fake-ui-for-media-stream

  1. def web_driver():
  2. """
  3. 定义浏览器驱动
  4. :return:
  5. """
  6. chrome_options = CH_Options()
  7. if Config.DRIVER_TYPE == 'chrome':
  8. # 界面模式
  9. chrome_options.add_argument('--ignore-certificate-errors') # 忽略与证书相关的错误
  10. chrome_options.add_argument("allow-file-access-from-files") # 允许打开本地文件
  11. # --use-fake-device-for-media-stream 使用假设备进行MediaStream替换实际的摄像头和麦克风
  12. # --use-fake-ui-for-media-stream 通过选择媒体流的默认设备来绕过媒体流信息量。与--use-fake-device-for-media-stream一起使用
  13. chrome_options.add_argument("use-fake-device-for-media-stream")
  14. chrome_options.add_argument("use-fake-ui-for-media-stream")
  15. driver = webdriver.Chrome(options=chrome_options)
  16. elif Config.DRIVER_TYPE == 'chrome_headless':
  17. # 无头模式
  18. chrome_options.add_argument('--headless')
  19. chrome_options.add_argument('--lang=zh-CN') # 设置chrome启动语言为中文
  20. chrome_options.add_argument('--disable-gpu')
  21. chrome_options.add_argument('--window-size={}'.format(screen_size()))
  22. chrome_options.add_argument('--ignore-certificate-errors')
  23. driver = webdriver.Chrome(options=chrome_options)
  24. logger.info("当前系统分辨率为{}".format(screen_size()))
  25. else:
  26. raise NameError("driver 浏览器驱动定义错误!")
  27. driver.maximize_window()
  28. driver.implicitly_wait(10)
  29. return driver