前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自动化测试的一些技巧

自动化测试的一些技巧

作者头像
赵云龙龙
发布2021-07-15 14:46:54
5370
发布2021-07-15 14:46:54
举报
文章被收录于专栏:python爱好部落python爱好部落

嗯,似乎好久没上来写文章了。接近两个月没怎么动笔了,惭愧惭愧! 想想这两个月也没干什么,主要是为了生计。一个公司好不好,最主要是要能生存,有现金流。最近自己最大的体会,就是内卷太厉害,各行各业,各种卷。 OK,废话少说。今天来聊一聊如何搭建一个自动化框架。 老生常谈的话题。 我们写代码的时候,为了方便维护,管理以及扩展啥的,需要搭建一个框架。 那么这个框架该如何搭建呢? 以mobile UI为例,看看需要搭建到什么程度。 首先需要支持不同的平台。Android,IOS都能支持。最好是一套代码共用。 然后模拟器,真机也能够支持。 还能够支持多机同步,异步的跑case. 同步跑可以测兼容性,异步跑可以节省运行的时间。 支持数据驱动; 支持CICD. 看起来似乎复杂,其实也不难。

下面来列一列一些小技巧,这里主要是以appium和pytest为主。 不同路径的调用,当用同一套代码来实现自动化的时候,可以用同一个文件,通过用platfrom这个参数来实现来实现。 也可以同一个文件名,函数名,不同的模块来时间,有点像name space。 Importlib模块与import都可以通过过字符串来导入另外一个模块。 Importlib是python的一个库,通过导入importlib,调用import_module()方法,传入用户想要获取的模块对应的路径字符串,即可获取一个,模块module,module可以调用这个test模块下的所有属性和方法。 import是python的一个内置方法,直接调用import()即可获取一个模块.

定位元素的方法

代码语言:javascript
复制
from selenium.webdriver.common.by import By
import selenium.common
from selenium.webdriver.support.wait import WebDriverWait
import selenium.webdriver.support.expected_conditions as EC


class MobileBy(By):
    IOS_PREDICATE = '-ios predicate string'
    IOS_UIAUTOMATION = '-ios uiautomation'
    IOS_CLASS_CHAIN = '-ios class chain'
    ANDROID_UIAUTOMATOR = '-android uiautomator'
    ANDROID_VIEWTAG = '-android viewtag'
    ANDROID_DATA_MATCHER = '-android datamatcher'
    ANDROID_VIEW_MATCHER = '-android viewmatcher'
    WINDOWS_UI_AUTOMATION = '-windows uiautomation'
    ACCESSIBILITY_ID = 'accessibility id'
    IMAGE = '-image'
    CUSTOM = '-custom'

class BasicPage(object):
    def __init__(self, driver):
        self.driver = driver

    def find_element(self, *loc):
        try:
            # 元素可见时,返回查找到的元素;以下入参为元组的元素,需要加*
            # WebDriverWait(self.driver, TIME_OUT_S).until()
            WebDriverWait(self.driver, WAIT_LONG_TIME_S).until(EC.presence_of_element_located(loc))
            return self.driver.find_element(*loc)

        except selenium.common.exceptions.NoSuchElementException:
            logging.warning('Can not find element: %s' % loc[1])
            raise

    def find_elements(self, *loc):
        try:
            # WebDriverWait(self.driver, TIME_OUT_S).until(lambda driver: driver.find_elements(*loc))
            WebDriverWait(self.driver, WAIT_LONG_TIME_S).until(EC.visibility_of_element_located(loc))
            return self.driver.find_elements(*loc)

        except selenium.common.exceptions.NoSuchElementException:
            logging.warning('Can not find element: %s' % loc[1])
            raise

我们可以用find_element这种形式。

一些手势的处理:

代码语言:javascript
复制
 def long_press(self, *loc):
        action1 = TouchAction(self.driver)
        element = self.find_element(*loc)
        action1.long_press(el=element, duration=DURATION_MS).wait(WAIT_TIME_MS).perform()

    def press(self, *loc):
        action1 = TouchAction(self.driver)
        element = self.find_element(*loc)
        action1.press(element).perform()

    def swipe(self, oritation="up", ratio=0.75, duration=DURATION_MS):
        x = self.driver.get_window_size()["width"]
        y = self.driver.get_window_size()["height"]

        def down():
            start_y = 0.2
            stop_y = ratio

            x1 = int(x * 0.5)
            y1 = int(y * start_y)
            x2 = int(x * 0.5)
            y2 = int(y * stop_y)
            self.driver.swipe(x1, y1, x2, y2, duration)

        def up():
            stop_y = 0.2
            start_y = ratio

            x1 = int(x * 0.5)
            y1 = int(y * start_y)
            x2 = int(x * 0.5)
            y2 = int(y * stop_y)
            self.driver.swipe(x1, y1, x2, y2, duration)

        def left():
            start_x = 0.2
            stop_x = ratio

            x1 = int(x * start_x)
            y1 = int(y * 0.5)
            x2 = int(x * stop_x)
            y2 = int(y * 0.5)
            self.driver.swipe(x1, y1, x2, y2, duration)

        def right():
            stop_x = 0.25
            start_x = ratio

            x1 = int(x * start_x)
            y1 = int(y * 0.5)
            x2 = int(x * stop_x)
            y2 = int(y * 0.5)
            self.driver.swipe(x1, y1, x2, y2, duration)

        actions = {
            "down": down,
            "up": up,
            "left": left,
            "right": right
        }[oritation]

        actions()

    def scroll(self, start_element, stop_element, duration=DURATION_MS):
        self.driver.scroll(start_element, stop_element, duration)

元素状态的判断:

代码语言:javascript
复制
    def get_text(self, *loc):
        try:
            words = self.find_element(*loc).text
            return words

        except:
            logging.warning('Can not find element: %s' % loc[1])
            return ""

    def is_element_exist_in_page(self, text):
        if text in self.driver.page_source:
            return True

        else:
            return False

    def is_element_exist(self, *loc):
        try:
            WebDriverWait(self.driver, WAIT_SHORT_TIME_S, 0.5).until(EC.presence_of_element_located(loc))
            return True
        except:
            return False

    def is_element_enable(self, *loc):

        if self.find_element(*loc).is_enabled() == True:
            return True
        else:
            return False

判断元素是否可用 is_displayed/is_enabled/is_selected 对弹出框的处理:

代码语言:javascript
复制
    def is_toast_exist(self, text, poll_frequency=0.1):

        '''is toast exist, return True or False

         :Agrs:

          - driver - 传driver

         - text   - 页面上看到的文本内容

         - timeout - 最大超时时间,默认30s

         - poll_frequency  - 间隔查询时间,默认0.5s查询一次

        :Usage:

         is_toast_exist(driver, "看到的内容")

        '''

        try:

            toast_loc = (MobileBy.XPATH, ".//*[contains(@text,'%s')]" % (text))

            WebDriverWait(self.driver, TIME_OUT_S, poll_frequency).until(EC.presence_of_element_located(toast_loc))

            return True

        except:
            return False

    def get_toast_text(self, timeout=20, poll_frequency=0.1):
        '''
        定位toast元素,获取text属性
        :param driver: driver实例对象
        :param timeout: 元素定位超时时间
        :param poll_frequency: 查询频率
        :return: toast文本内容
        '''
        toast_loc = (By.XPATH, '//*[@class="android.widget.Toast"]')
        try:
            toast = WebDriverWait(self.driver, timeout, poll_frequency).until(EC.presence_of_element_located(toast_loc))
            toast_text = toast.get_attribute('text')
            return toast_text
        except Exception as e:
            return e

    def accept_dialouge(self):
        '''
        if dialouge has allow
        '''
        self.driver.switch_to.alert.accept()

    def accept_pop_up(self, *loc):
        '''
        if dialouge has OK or accept, Continue, can be locate easy
        '''
        try:
            time.sleep(WAIT_LITTLE_TIME_S)
            loc = self.find_element(*loc)
            if loc:
                loc.click()
            else:
                pass
        except:
            pass

    def accept_permission_popup(self, text):
        '''
        if dialouge has accept or other, hard to be located but need to use text
        '''
        for i in range(MAX_TRY_TIMES):
            loc = (MobileBy.XPATH, "//*[@text='{}']".format(text))
            try:
                e = WebDriverWait(self.driver, WAIT_SHORT_TIME_S, 0.5).until(EC.presence_of_element_located(loc))
                e.click()
            except:
                pass

一些基本操作:

代码语言:javascript
复制
    def relaunch_app(self):
        self.driver.background_app(WAIT_LITTLE_TIME_S)

    def set_orientation(self, orientation="LANDSCAPE"):
        if self.get_current_orientation() != "LANDSCAPE" and orientation == "LANDSCAPE":
            self.driver.orientation = "LANDSCAPE"
        if self.get_current_orientation() != "PORTRAIT" and orientation == "PORTRAIT":
            self.driver.orientation = "PORTRAIT"

    def set_random_orientation(self):
        if self.get_current_orientation() == "LANDSCAPE":
            self.driver.orientation = "PORTRAIT"
        if self.get_current_orientation() == "PORTRAIT":
            self.driver.orientation = "LANDSCAPE"

    def get_current_orientation(self):
        orientation = self.driver.orientation
        return orientation

    def current_activity(self):  # this function only support for Android
        activity = self.driver.current_activity
        return activity

    def wait_activity(self, activity, timeout, interval=1):

        """
        Wait for an activity: block until target activity presents
          or time out.

          This is an Android-only method.

          :Agrs:
           - activity - target activity
           - timeout - max wait time, in seconds
          - interval - sleep interval between retries, in seconds
         """

        try:
            WebDriverWait(self, timeout, interval).until(self.current_activity == activity)

            return True
        except:

            return False

    def current_context(self):
        context = self.driver.current_context
        return context

    def all_contexts(self):
        contexts = self.driver.contexts
        return contexts

    def switch_to_webview(self):
        webview = self.all_contexts()[1]
        self.driver.switch_to.context(webview)

    def switch_to_native(self):
        self.driver.switch_to.context('NATIVE_APP')

自己定义的一些个方法:

代码语言:javascript
复制
    def navigate(self, locate="home"):

        home_loc = {
            "android": (MobileBy.ID, "main_tab_id_home"),
            "ios": (MobileBy.IOS_PREDICATE, 'label == "Home"')
        }[plantform]

        lesson_loc = {
            "android": (MobileBy.ID, "main_tab_id_course"),
            "ios": (MobileBy.IOS_PREDICATE, 'label == "Lesson"')
        }[plantform]

        class_loc = {
            "android": (MobileBy.ID, "main_tab_id_book"),
            "ios": (MobileBy.IOS_PREDICATE, 'label == "Class"')
        }[plantform]

        me_loc = {
            "android": (MobileBy.ID, "main_tab_id_attend"),
            "ios": (MobileBy.IOS_PREDICATE, 'label == "Me"')
        }[plantform]

        change_course_loc = {
            "android": (MobileBy.ID, "changeCourse"),
            "ios": (MobileBy.IOS_PREDICATE, 'name == "Change course" AND value == "Change course"')
        }[plantform]

        setting_dot_loc = {
            "android": (MobileBy.ID, "settings_icon_expanded"),
            "ios": (MobileBy.IOS_PREDICATE, 'label == "extraMenu"')
        }[plantform]

        def go_to_home_page():
            self.find_element(*home_loc).click()
            time.sleep(WAIT_LITTLE_TIME_S)

        def go_to_lesson_page():
            self.find_element(*lesson_loc).click()
            time.sleep(WAIT_SHORT_TIME_S)

        def go_to_class_page():
            self.find_element(*class_loc).click()
            time.sleep(WAIT_SHORT_TIME_S)

        def go_to_me_page():
            self.find_element(*me_loc).click()
            time.sleep(WAIT_SHORT_TIME_S)

        def go_to_change_course_page():
            go_to_lesson_page()
            self.find_element(*change_course_loc).click()
            time.sleep(WAIT_SHORT_TIME_S)

        go_to = {
            "home": go_to_home_page,
            "lesson": go_to_lesson_page,
            "class": go_to_class_page,
            "me": go_to_me_page,
            "changecourse": go_to_change_course_page
            # "settings": go_to_setting_page(),
            # "account": go_to_account_page(),
        }[locate]
        go_to()

conftest.py里面定义一下:

代码语言:javascript
复制
import pytest
import importlib
lesson_action=importlib.import_module("actions.{}.lessonpage".format(plantform))
course_action=importlib.import_module("actions.{}.coursepage".format(plantform))
activity_action=importlib.import_module("actions.{}.activitypage".format(plantform))



driver = None


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    '''
    获取每个用例状态的钩子函数
    :param item:
    :param call:
    :return:
    '''
    # 获取钩子方法的调用结果
    outcome = yield
    rep = outcome.get_result()
    # 仅仅获取用例call 执行结果是失败的情况, 不包含 setup/teardown
    if rep.when == "call" and rep.failed:
        mode = "a" if os.path.exists("failures") else "w"
        with open("failures", mode) as f:
            # let's also access a fixture for the fun of it
            if "tmpdir" in item.fixturenames:
                extra = " (%s)" % item.funcargs["tmpdir"]
            else:
                extra = ""
            f.write(rep.nodeid + extra + "\n")
        # 添加allure报告截图
        if hasattr(driver, "get_screenshot_as_png"):
            with allure.step('添加失败截图...'):
                allure.attach(driver.get_screenshot_as_png(), "失败截图", allure.attachment_type.PNG)

@pytest.fixture(scope="module")
def driver():
    global drivers
    desired_caps = {}

    if plantform == "android":
        yaml_path = os.path.join(config_path, "android.yaml")
        device_name, plat_version = device_info().get_singal_device_info
        desired_caps = get_yaml_data(yaml_path)["capability"]
        print(desired_caps)
        desired_caps['platformVersion'] = plat_version
        desired_caps['deviceName'] = device_name

        print(desired_caps)
    if plantform == "ios":
        yaml_path = os.path.join(config_path, "ios.yaml")

        if device_type == "real":
            udid = iosdevice().Get_UUID
            device_name=iosdevice().Get_Device_Name
            device_version=iosdevice().Get_Device_information
            desired_caps = get_yaml_data(yaml_path)["real_caps"]
            print(desired_caps)
            desired_caps['platformVersion'] = device_version
            desired_caps['deviceName'] = device_name
            desired_caps['udid'] = udid

            print(desired_caps)

        if device_type == "simulator":
            desired_caps = get_yaml_data(yaml_path)["simulator_caps"]
            print(desired_caps)


    driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
    yield driver

其他的一些操作,辅助操作,写写好,就一个比较完备的框架就能实现了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 python粉丝团 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档