大家都知道 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 了。