Appium 完美解决 Python 3.0 环境 robotframework 的 appiumlibrary run-on-failure 功能无法使用的问题

matthewD · 2019年03月02日 · 55 次阅读

大家都知道 RobotFramework 的第三方测试库非常多,极其好用。引入 AppiumLibrary 库,被大多数公司作为 app 自动化测试的框架。

但是 AppiumLibrary 对于 Python3.0 的支持目前还是实验阶段。鉴于 python2.7 将在 2020 年不再更新,笔者尝试着使用 python3.0 来大家基于 appium 的自动化测试框架。

经过笔者测试,发现大部分功能还是 OK 的,但是在 run-on-failure 功能上,发现 appiumlibrary 并没有按照预期的那样在出错时执行设定好的命令。那么这个是什么原因导致的呢?别着急,我们先来看一下库的代码:

在根目录下的init.py 文件:

def __init__(self, timeout=5, run_on_failure='Capture Page Screenshot'):
        """AppiumLibrary can be imported with optional arguments.

        ``timeout`` is the default timeout used to wait for all waiting actions.
        It can be later set with `Set Appium Timeout`.

        ``run_on_failure`` specifies the name of a keyword (from any available
        libraries) to execute when a AppiumLibrary keyword fails.

        By default `Capture Page Screenshot` will be used to take a screenshot of the current page.
        Using the value `No Operation` will disable this feature altogether. See
        `Register Keyword To Run On Failure` keyword for more information about this
        functionality.

        Examples:
        | Library | AppiumLibrary | 10 | # Sets default timeout to 10 seconds                                                                             |
        | Library | AppiumLibrary | timeout=10 | run_on_failure=No Operation | # Sets default timeout to 10 seconds and does nothing on failure           |
        """
        for base in AppiumLibrary.__bases__:
            base.__init__(self)
        self.set_appium_timeout(timeout)
        self.register_keyword_to_run_on_failure(run_on_failure)

我们可以看出,在引入 appiumLibrary 库的时候其实默认调用了 register_keyword_to_run_on_failure 方法,而且这个方法的参数是有默认值的,也就是 Capture Page Screenshot,那么为什么脚本在出错时没有调用呢?

首先我就想到了进入 register_keyword_to_run_on_failure 这个方法去看源代码,然后并没有发现什么有用的东西。这时候我们进行分析:register_keyword_to_run_on_failure 在 init 方法的时候调用也就是说所有的 keyword 在失败时都会调用后面的方法,这个是怎样实现的?

所以我们继续分析,进入 appiumLibrary 的 keywords 文件夹,我发现,所有的 keyword 的类都继承于 keywordgroup。这让我自然而然的进入了 keywordgroup 中:

import sys
import inspect
try:
    from decorator import decorator
except SyntaxError:  # decorator module requires Python/Jython 2.4+
    decorator = None
if sys.platform == 'cli':
    decorator = None  # decorator module doesn't work with IronPython 2.6


def _run_on_failure_decorator(method, *args, **kwargs):
    try:
        return method(*args, **kwargs)
    except Exception as err:
        self = args[0]
        if hasattr(self, '_run_on_failure'):
            self._run_on_failure()
        raise err


class KeywordGroupMetaClass(type):
    def __new__(cls, clsname, bases, dict):
        if decorator:
            for name, method in dict.items():
                if not name.startswith('_') and inspect.isroutine(method):
                    dict[name] = decorator(_run_on_failure_decorator, method)
        return type.__new__(cls, clsname, bases, dict)


class KeywordGroup(object):
    __metaclass__ = KeywordGroupMetaClass

不难看出 KeywordGroupMetaClass 作为元类,限定了类方法中所有的 method 都套上了一个_run_on_failure_decorator 的装饰器。重点关注这行代码的使用方法:

class KeywordGroup(object):
    __metaclass__ = KeywordGroupMetaClass

不难发现,这个是 python2 的写法,在 python3 中这样使用,不会报错,也不会有任何作用。

在 python3 中使用元类的方法是:

class MyList(list, metaclass=ListMetaclass):
    pass

为了兼容 python2 和 python3,我们需要使用以下方法(参考http://python-future.org/compatible_idioms.html):

# Python 2 and 3:
from six import with_metaclass
# or
from future.utils import with_metaclass

class Form(with_metaclass(FormType, BaseForm)):
    pass

我们把这段代码进行如下修改:

from six import with_metaclass
class KeywordGroup(with_metaclass(KeywordGroupMetaClass, object)):
  pass

经过测试,这样就可以既在 python2 也可以在 python3 work 了。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册