• 如果按照上面代码运行的话,也没看出有问题,你确认一下 device1 或者 device2 在结束后是否返回

    这里是你的代码

    import time
    import threading
    
    def device1():
        for i in range(10):
            print('device1')
            time.sleep(1)
    
    def device2():
        for i in range(10):
            print('device2')
            time.sleep(0.5)
    
    threads = []
    
    t1 = threading.Thread(target=device1)
    threads.append(t1)
    
    t2 = threading.Thread(target=device2)
    threads.append(t2)
    
    if __name__ == '__main__':
        for t in threads:
            print('start', t)
            t.start()
    
        for t in threads:
            print('join', t)
            t.join()
    
        print('all end')
    

    这里是上面代码对应的打印

  • 我的理解是这样的

    在 macaca 里,webdriver.py 中的 WebDriver 以及 webelement.py 中的 WebElement 这两个类中确实没有直接声明 element_by_xxx 相关的方法,而是通过 add_element_extension_method(WebDriver) 或者 add_element_extension_method(WebElement) 去声明的,所以 element_by_xxx 调用是没问题,但是 PyCharm 不认这个。我想 wd.py 的作者是为了是代码聚合度更高才采用这样的方式的吧

  • 已加

  • 不在

  • 给你一个思路(Macaca+Python)

    https://testerhome.com/topics/7658

  • 是的,封装的目的就是为了降低编写脚本的难度以及增强脚本的可维护性

  • 我理解的分解是这样的,你的目的有两个:
    1、需要滑动查找元素
    2、判断查找到的元素的 text 是否为某个目标值

    所有,按照这个思路就应该分两步,通过诸如这类的方法 find_element_by_swipe_up 获取到 element,然后在用 element.text 去判断。

    macaca 本身并没有封装滑动查找元素,这个通用方法是我自己写的。另外,还写了 wait_string、wait_string_use_and、wait_string_use_or、wait_element_by_accessibility_id、click_element_by_accessibility_id,不过这个要后面在看是否分享。

  • _find_element_by_swipe 是个内部方法,是为了给诸如下面的外部方法调用的(目的是返回一个 element,如果找到就直接返回了,不会多滑动的。在没有找到的时候且未超过滑动次数的时候才滑动,超过滑动次数就直接给异常,这里给异常也是符合 macaca 查找 element 逻辑规则的)

    而你添加这个参数的目的是什么呢?

    def find_element_by_swipe_up(self, using, value, element=None, steps=10, max_swipe=5):
        """
        find element by swipe up
        :param using: The element location strategy.
                      "id","xpath","link text","partial link text","name","tag name","class name","css selector"
        :param value: The value of the location strategy.
        :param element: WebElement of Macaca, if None while swipe window of phone
        :param steps: steps of swipe for Android, The lower the faster
        :param max_swipe: the max times of swipe
        :return: WebElement of Macaca
    
        Raises:
            WebDriverException.
        """
        return self._find_element_by_swipe('up', using, value,
                                           element=element, steps=steps, max_swipe=max_swipe)
    
  • 多谢达峰老师推荐!

  • macaca 怎样左右滑动 at February 27, 2017
  • 要的就是这效果😉

  • 👍

  • 很棒啊!😉

    后面的话,可以把 path = '/Users/zhaozhiquan/automation/iOSSdk/result/' + day + '/screencap'这里面的绝对路径修改一下,这样他人使用你的脚本的时候才不会有问题。

  • 我的报告和截图是放在同一目录下的

    如下,path = ReportPath().get_path() + '\' + screenshot 中的 ReportPath().get_path() 就是报告的存放路径(同时也是截图的存放路径)。通过 raise WebDriverException(message=flag + screenshot(func.qualname_)) 这样的异常,在异常信息中把截图名称给出(用 flag 变量中的'IMAGE:'标记,截图名为 screenshot = name + '-' + date_time + '.PNG')。

    flag = 'IMAGE:'
    
    def _screenshot(name):
        date_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
        screenshot = name + '-' + date_time + '.PNG'
        path = ReportPath().get_path() + '\\' + screenshot
    
        driver = BasePage().get_driver()
        driver.save_screenshot(path)
    
        return screenshot
    
    
    def teststep(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                log.i('\t--> %s', func.__qualname__)
                ret = func(*args, **kwargs)
                return ret
            except WebDriverException:
                log.e('\t<-- %s, %s', func.__qualname__, 'Error')
                raise WebDriverException(message=flag + _screenshot(func.__qualname__))
    
        return wrapper
    

    由于上面的异常信息中已经给出了截图的名称,因此,在 HTMLTestRunner.py 中 image = image[image.find("IMAGE:")+6:(int(image.find("PNG"))+3)] 就可以把截图名拿出来(截图后缀为.PNG)。这样的话,因为报告和截图在同一个目录下,所以这里只需要知道截图名称就可以。

    def _generate_report_test(self, rows, cid, tid, n, t, o, e):
        # e.g. 'pt1.1', 'ft1.1', etc
        has_output = bool(o or e)
        tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
        name = t.id().split('.')[-1]
        doc = t.shortDescription() or ""
        desc = doc and ('%s: %s' % (name, doc)) or name
        tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
    
        # o and e should be byte string because they are collected from stdout and stderr?
        if isinstance(o,str):
            # TODO: some problem with 'string_escape': it escape \n and mess up formating
            # uo = unicode(o.encode('string_escape'))
            uo = e
        else:
            uo = o
        if isinstance(e,str):
            # TODO: some problem with 'string_escape': it escape \n and mess up formating
            # ue = unicode(e.encode('string_escape'))
            ue = e
        else:
            ue = e
    
        script = self.REPORT_TEST_OUTPUT_TMPL % dict(
            id = tid,
            output = saxutils.escape(uo),
            # output=saxutils.escape(uo + ue),
        )
        image = self.REPORT_TEST_OUTPUT_IMAGE % dict(
            screenshot = saxutils.escape(uo)
            # screenshot = saxutils.escape(uo + ue)
        )
        caseid = self.REPORT_TEST_OUTPUT_CASEID % dict(
            case_id = saxutils.escape(uo)
            # case_id = saxutils.escape(uo + ue)
        )
        row = tmpl % dict(
            tid = tid,
            Class = (n == 0 and 'hiddenRow' or 'none'),
            style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
            desc = desc,
            script = script,
            image = image[image.find("IMAGE:")+6:(int(image.find("PNG"))+3)],
            caseid = caseid[caseid.find("case"):(int(caseid.find("case"))+9)],
            status = self.STATUS[n],
        )
        rows.append(row)
        if not has_output:
            return
    
  • macaca 运行报错,其中的坑 at February 24, 2017

    android sdk 的安装路径有空格的话,也会有问题的

  • #28 楼 @cynic 在老家,没带电脑回来,下周一再细聊具体实现

  • #25 楼 @cynic _screenshot 这个方法中有这样一小段 path = ReportPath().get_path() ,这个 path 就是每个设备在跑用例的时候用来放测试报告、日志、截图的路径。图片名加上时间是可以解决覆盖的问题的。

  • #20 楼 @342164796 差不多,我弄的多进程,效果还可以。有需要可以一起探讨

  • #18 楼 @342164796 我在装饰器同时加了打印的,效果不错,可以用这种方式重构来看看

  • 我提供一下我的截图思路(Macaca + HTMLTestRunner):
    1、在元素未找到时截图
    2、在用例失败时截图

    代码如下:
    1、每个测试步骤加上@teststep这个装饰器
    2、每条用例加上@testcase这个装饰器

    def _screenshot(name):
        date_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
        screenshot = name + '-' + date_time + '.PNG'
        path = ReportPath().get_path() + '\\' + screenshot
    
        driver = BasePage().get_driver()
        driver.save_screenshot(path)
    
        return screenshot
    
    def teststep(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                log.i('\t--> %s', func.__qualname__)
                ret = func(*args, **kwargs)
                return ret
            except WebDriverException:
                log.e('\t<-- %s, %s', func.__qualname__, 'Error')
                raise WebDriverException(message=flag + _screenshot(func.__qualname__))
    
        return wrapper
    
    def testcase(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                log.i('--> %s', func.__qualname__)
                ret = func(*args, **kwargs)
                log.i('<-- %s, %s\n', func.__qualname__, 'Success')
                return ret
            except WebDriverException:
                log.e('<-- %s, %s\n', func.__qualname__, 'Error')
                raise WebDriverException
            except AssertionError:
                log.e('<-- %s, %s\n', func.__qualname__, 'Fail')
                raise AssertionError(flag + _screenshot(func.__qualname__))
    
        return wrapper
    

    测试步骤的代码如下:

    @teststep
    def input_account(self, account):
        """以“请输入手机号码”的TEXT为依据"""
        self.driver\
            .element_by_name('请输入手机号码')\
            .clear()\
            .send_keys(account)
    

    测试用例的代码如下:

    @testcase
    def test_Car_MyCarInsurEntry_Func_010(self):
        """我的车险入口验证"""
        self.home_page.click_my()
    
        login = LoginPage()
        if login.wait_page():
            login.input_account(VALID_ACCOUNT.account())
            login.input_password(VALID_ACCOUNT.password())
            login.login()
    
            gesture = GesturePasswordPage()
            if gesture.wait_page():
                gesture.skip()
    
            if self.home_page.wait_page():
                self.home_page.click_my()
    
        my_page = PlatformAppMyPage()
        my_page.wait_page()
        my_page.click_my_car_insurance()
    
        my_car_insurance = MyCarInsurancePage()
        self.assertTrue(my_car_insurance.wait_page())