完美解决Python 3.0环境 robotframework的appiumlibrary run-on-failure 功能无法使用的问题
大家都知道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了。