• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

使用selenium + pytest + allure做WBE UI自动化

武飞扬头像
王大可 Ü
帮助1

目录结构

依然使用了常见的po 模式

  • 目录结构
    • config
      • config.ini 修改目标路由与相关配置
    • elements
      • 抽象页面空间元素
    • method
      • 抽象页面方法
    • TestCase
      • 测试用例
    • utils
      • Assert 自定义断言
      • chromedriver 浏览器驱动 需匹配版本 非linux 环境无视
      • Driver 初始化driver
      • graphical 图片相对定位
      • log 日志
      • seleniumBase api封装
      • seleniumOpt opt定义

初始化Driver

根据判断运行环境 选择不同的chrome driver 或者使用 ChromeDriverManager 自动下载

class Driver:

    @classmethod
    def driver(cls):
        try:
            # select local or docker
            # if platform == "Darwin":
            # url = Config().get_conf("dockerHub", "url")
            # driver = webdriver.Remote(url, desired_capabilities=capabilities(),
            #                           options=options())
            # # else:
            if platform.system() == "Linux":
                executable_path = os.path.join(os.path.dirname(__file__), "chromedriver")
                driver = webdriver.Chrome(executable_path=executable_path, options=options())
            else:
                executable_path = ChromeDriverManager().install()
                driver = webdriver.Chrome(executable_path=executable_path, options=options())
            driver.implicitly_wait(20)
            log.info(f"driver:{driver.name}")
            return driver
        except BaseException as e:
            log.error(e)
            raise e
学新通

自定义driveroptions 配置

def options():
    """
    浏览器设置
    :return:
    """
    _options = ChromeOptions()
    # # 设置chrome为手机界面
    # mobileEmulation = Config().get_conf("seleniumOpt", "mobileEmulation")
    # if mobileEmulation == 'true':
    #     mobile_emulation = {"deviceName": "Galaxy S5"}
    #     _options.add_experimental_option("mobileEmulation", mobile_emulation)
    if platform.system() == 'Darwin':
        pass
    else:
        _options.add_argument('--headless')
        _options.add_argument('--no-sandbox')
        _options.add_argument('--disable-gpu')
        _options.add_argument('--disable-dev-shm-usage')

    return _options

学新通

Base类

封装常用API 把driver 实例化传入
查找元素尽量使用显示等待 expected_conditions
使用了Faker 可随意生成测试数据
查找元素失败 使用allure 上传截图

class SeleniumBaseConfig:
    faker = Faker(locale="zh_CN")
    Assert = Assert
    log = get_log()

    def __init__(self, driver: webdriver):
        """
        :param driver: 驱动
        """
        self.driver = driver

    def find_element(self, locator: tuple, timeout=5):
        """
        find_element
        :param locator: (xpath,xxx)(id,xxx)
        :param timeout: timeout
        :return:
        """
        try:
            element = WebDriverWait(self.driver, timeout).until(EC.presence_of_element_located(locator))
            self.log.info(f"[{self.find_element.__name__}]  locator: {locator} element: {str(element)}")
            return element
        except WebDriverException as e:
            self.log.error(e)
            allure.attach(self.get_png(), "异常截图", allure.attachment_type.PNG)
            allure.attach("未找到: {}, 详细信息如下: {}".format(locator, e))
            raise e

学新通

自定Assert

很常规 没什么可说的

class Assert:

    @staticmethod
    def assert_unequal(exp, res):
        try:
            log.info("ASSERT UNEQUAL: EXPECT IS [{}] RESULT IS [{}]".format(exp, res))
            assert exp != res
            return True
        except AssertionError as e:
            log.error("ASSERT UNEQUAL ERROR: EXPECT IS [{}] RESULT IS [{}]".format(exp, res))
            raise e

关于页面元素

根据op 依旧对页面抽象成类 并继承SeleniumBaseConfig
元素类1一对1配套

class RegisterMethod(SeleniumBaseConfig):


    def __init__(self, driver):
        super().__init__(driver)

    def register(self, mobile: str):
        """
        注册
        :param mobile
        :return:
        """
        self.send_keys(RegisterLogin.INPUT_NUMBER, mobile)
        self.click(RegisterLogin.NEXT_STEP_BUTTON)
        time.sleep(1)

对应元素类

class Register:
    # ================== 公共 ================
    # 下一步
    NEXT_STEP_BUTTON = ("xpath", "//button")
    # 返回
    RETURN = ('xpath', '//p[@class="return-prev-step"]')
    # input 错误信息
    INPUT_ERROR_INFO = ("xpath", '//p[@class="default-input-error"]')

    # ================== home界面 ================
    CREATE_BUTTON = ("xpath", "//div[text()='创建']")  # 创建button
    JOIN_BUTTON = ("xpath", "//div[text()='加入']")  # 创建button

    INPUT_TEAM_NAME = ("xpath", '//input[@placeholder="请输入团队名称"]')  # 输入团队名称
    INPUT_YOUR_NAME = ("xpath", '//input[@placeholder="请输入你的姓名"]')  # 输入个人名称

学新通

测试用例


	@pytest.mark.P3
    @allure.title("创建新团队:姓名输入汉字、英文、数字字符")
    @pytest.mark.flaky(reruns=1, reruns_delay=2)
    def test_create_team_username(self):
        """
        姓名输入汉字、英文、数字字符
        1.register
        2.create team
        3.create_team_username
        """
        pics = []
        self.driver.register(self.mobile)
        exp = "true"
        res = self.driver.check_button_disabled()
        self.driver.allure_report(self.test_create_team_username, pics, exp, res)
        self.driver.Assert.assert_equal(exp, res)
学新通

Config 配置

一般配置host 等数据 使用ConfigParser读取写入
config.ini

[loginParam]
username = username
password = 123456

[domain]
cm = xx
class Config:

	def get_conf(self, title, value):
        """
        read .ini
        :param title:
        :param value:
        :return:
        """
        log.info("{} : {}:{}".format(self.get_conf.__name__, title, value))
        return self.config.get(title, value)

    def set_conf(self, title, value, text):
        """
        change .ini
        :param title:
        :param value:
        :param text:
        :return:
        """

        log.info("{} : {}:{}:{}".format(self.set_conf.__name__, title, value, text))

        self.config.set(title, value, text)
        with open(self.config_path, 'w ') as f:
            return self.config.write(f)
学新通

main 启动

使用sys.argv 获取命令行自定义参数

if len(sys.argv) > 3:
        runCase = sys.argv[1]  # P1 P2 P3 ALL  指定运行的用例等级
        if runCase != "ALL":
            case_path = f"-m={runCase}"
        xml = sys.argv[2] # 报告路径
        if xml:
            xml_report_path = xml
        BUILD_NUMBER = sys.argv[3]

    args = ["-s", "-v", "-n=auto", case_path, '--alluredir', xml_report_path, '--clean-alluredir']

    pytest.main(args=args)

图片相对定位 获得坐标

场景

canvas 页面 无元素可定位

使用

先截取想要点击的位置图片 保存到本地
find_me 传入driver 对象
通过 center_xcenter_y 属性方法 获得相对坐标

class GraphicalLocator():

    def __init__(self, img_path):
        self.locator = img_path
        # x, y position in pixels counting from left, top corner
        self.x = None
        self.y = None
        self.img = cv2.imread(img_path)
        self.height = self.img.shape[0]
        self.width = self.img.shape[1]
        self.threshold = None

    @property
    def center_x(self):
        return self.x   int(self.width / 2) if self.x and self.width else None

    @property
    def center_y(self): return self.y   int(self.height / 2) if self.y and self.height else None

    def find_me(self, drv):  # Clear last found coordinates
        self.x = self.y = None
        # Get current screenshot of a web page
        scr = drv.get_screenshot_as_png()
        # Convert img to BytesIO
        scr = Image.open(BytesIO(scr))
        # Convert to format accepted by OpenCV
        scr = numpy.asarray(scr, dtype=numpy.float32).astype(numpy.uint8)
        # Convert image from BGR to RGB format
        scr = cv2.cvtColor(scr, cv2.COLOR_BGR2RGB)

        # Image matching works only on gray images
        # (color conversion from RGB/BGR to GRAY scale)
        img_match = cv2.minMaxLoc(
            cv2.matchTemplate(cv2.cvtColor(scr, cv2.COLOR_RGB2GRAY),
                              cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY),
                              cv2.TM_CCOEFF_NORMED))
        # Calculate position of found element
        self.x = img_match[3][0]
        self.y = img_match[3][1]

        # From full screenshot crop part that matches template image
        scr_crop = scr[self.y:(self.y   self.height),
                   self.x:(self.x   self.width)]

        # Calculate colors histogram of both template# and matching images and compare them
        scr_hist = cv2.calcHist([scr_crop], [0, 1, 2], None,
                                [8, 8, 8], [0, 256, 0, 256, 0, 256])
        img_hist = cv2.calcHist([self.img], [0, 1, 2], None,
                                [8, 8, 8], [0, 256, 0, 256, 0, 256])
        comp_hist = cv2.compareHist(img_hist, scr_hist,
                                    cv2.HISTCMP_CORREL)

        # Save treshold matches of: graphical image and image histogram
        self.threshold = {'shape': round(img_match[1], 2), 'histogram': round(comp_hist, 2)}

        # Return image with blue rectangle around match
        return cv2.rectangle(scr, (self.x, self.y),
                             (self.x   self.width, self.y   self.height),
                             (0, 0, 255), 2)
学新通

pytest

pytest 有很多好用的功能、比如失败重跑、多进程并行用例、fixture 测试
用例参数化等等
详见 gitHUB-SeleniumUIAuto 中的 readme

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcfegj
系列文章
更多 icon
同类精品
更多 icon
继续加载