原文:http://www.8bitavenue.com/2012/04/sikuli-selenium-robot-framework-integration/
1、需要的所有安装包
Easy Install setuptools-0.6c11.win32-py2.7.exe
pip 下载后放入C:\Python\Scripts 进入cmd 输入
easy_install pip python ez_setup.py
2、设置环境变量
C:\python27 C:\Python27\Scripts C:\jython2.5.2\bin C:\Program Files (x86)\Java\jre6\bin
3、安装
easy_install robotframework easy_install wxPython easy_install robotframework-ride easy_install robotframework-selenium2library
4、Sikuli selenium和RobotFrameWork开始工作了
按照以下的步骤配置,就可以在RobotFramework中同时应用sikuli和selenium一直写case了。其中seleniumlibrary运行在python环境下,sikuli需要Jython支持才能通过python解析。通过RPC-xml服务使本地python和远端机器运行Jython来实现本地selenium和远端机器的sikuli共同工作。
创建 [C:\robot] [C:\robot\data] [C:\robot\libs] [C:\robot\suites]
下载Python远端服务放到[C:\robot\libs]下robotremoteserver.py googlecode有可能已经被墙了,下面代码可以自行保存成robotremoteserver.py
# Copyright 2008-2014 Nokia Solutions and Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. __version__ = 'devel' import errno import re import select import sys import inspect import traceback from StringIO import StringIO from SimpleXMLRPCServer import SimpleXMLRPCServer from xmlrpclib import Binary try: import signal except ImportError: signal = None try: from collections import Mapping except ImportError: Mapping = dict BINARY = re.compile('[\x00-\x08\x0B\x0C\x0E-\x1F]') NON_ASCII = re.compile('[\x80-\xff]') class RobotRemoteServer(SimpleXMLRPCServer): allow_reuse_address = True _generic_exceptions = (AssertionError, RuntimeError, Exception) _fatal_exceptions = (SystemExit, KeyboardInterrupt) def __init__(self, library, host='127.0.0.1', port=8270, port_file=None, allow_stop=True): """Configure and start-up remote server. :param library: Test library instance or module to host. :param host:Address to listen. Use ``'0.0.0.0'`` to listen to all available interfaces. :param port:Port to listen. Use ``0`` to select a free port automatically. Can be given as an integer or as a string. :param port_file: File to write port that is used. ``None`` means no such file is written. :param allow_stop: Allow/disallow stopping the server using ``Stop Remote Server`` keyword. """ SimpleXMLRPCServer.__init__(self, (host, int(port)), logRequests=False) self._library = library self._allow_stop = allow_stop self._shutdown = False self._register_functions() self._register_signal_handlers() self._announce_start(port_file) self.serve_forever() def _register_functions(self): self.register_function(self.get_keyword_names) self.register_function(self.run_keyword) self.register_function(self.get_keyword_arguments) self.register_function(self.get_keyword_documentation) self.register_function(self.stop_remote_server) def _register_signal_handlers(self): def stop_with_signal(signum, frame): self._allow_stop = True self.stop_remote_server() for name in 'SIGINT', 'SIGTERM', 'SIGHUP': if hasattr(signal, name): signal.signal(getattr(signal, name), stop_with_signal) def _announce_start(self, port_file=None): host, port = self.server_address self._log('Robot Framework remote server at %s:%s starting.' % (host, port)) if port_file: pf = open(port_file, 'w') try: pf.write(str(port)) finally: pf.close() def serve_forever(self): if hasattr(self, 'timeout'): self.timeout = 0.5 elif sys.platform.startswith('java'): self.socket.settimeout(0.5) while not self._shutdown: try: self.handle_request() except (OSError, select.error), err: if err.args[0] != errno.EINTR: raise def stop_remote_server(self): prefix = 'Robot Framework remote server at %s:%s ' % self.server_address if self._allow_stop: self._log(prefix + 'stopping.') self._shutdown = True else: self._log(prefix + 'does not allow stopping.', 'WARN') return self._shutdown def get_keyword_names(self): get_kw_names = getattr(self._library, 'get_keyword_names', None) or \ getattr(self._library, 'getKeywordNames', None) if self._is_function_or_method(get_kw_names): names = get_kw_names() else: names = [attr for attr in dir(self._library) if attr[0] != '_' and self._is_function_or_method(getattr(self._library, attr))] return names + ['stop_remote_server'] def _is_function_or_method(self, item): # Cannot use inspect.isroutine because it returns True for # object().__init__ with Jython and IronPython return inspect.isfunction(item) or inspect.ismethod(item) def run_keyword(self, name, args, kwargs=None): args, kwargs = self._handle_binary_args(args, kwargs or {}) result = {'status': 'FAIL'} self._intercept_std_streams() try: return_value = self._get_keyword(name)(*args, **kwargs) except: exc_type, exc_value, exc_tb = sys.exc_info() self._add_to_result(result, 'error', self._get_error_message(exc_type, exc_value)) self._add_to_result(result, 'traceback', self._get_error_traceback(exc_tb)) self._add_to_result(result, 'continuable', self._get_error_attribute(exc_value, 'CONTINUE'), default=False) self._add_to_result(result, 'fatal', self._get_error_attribute(exc_value, 'EXIT'), default=False) else: try: self._add_to_result(result, 'return', self._handle_return_value(return_value)) except: exc_type, exc_value, _ = sys.exc_info() self._add_to_result(result, 'error', self._get_error_message(exc_type, exc_value)) else: result['status'] = 'PASS' self._add_to_result(result, 'output', self._restore_std_streams()) return result def _handle_binary_args(self, args, kwargs): args = [self._handle_binary_arg(a) for a in args] kwargs = dict([(k, self._handle_binary_arg(v)) for k, v in kwargs.items()]) return args, kwargs def _handle_binary_arg(self, arg): if isinstance(arg, Binary): return arg.data return arg def _add_to_result(self, result, key, value, default=''): if value != default: result[key] = value def get_keyword_arguments(self, name): kw = self._get_keyword(name) if not kw: return [] return self._arguments_from_kw(kw) def _arguments_from_kw(self, kw): args, varargs, kwargs, defaults = inspect.getargspec(kw) if inspect.ismethod(kw): args = args[1:] # drop 'self' if defaults: args, names = args[:-len(defaults)], args[-len(defaults):] args += ['%s=%s' % (n, d) for n, d in zip(names, defaults)] if varargs: args.append('*%s' % varargs) if kwargs: args.append('**%s' % kwargs) return args def get_keyword_documentation(self, name): if name == '__intro__': return inspect.getdoc(self._library) or '' if name == '__init__' and inspect.ismodule(self._library): return '' return inspect.getdoc(self._get_keyword(name)) or '' def _get_keyword(self, name): if name == 'stop_remote_server': return self.stop_remote_server kw = getattr(self._library, name, None) if not self._is_function_or_method(kw): return None return kw def _get_error_message(self, exc_type, exc_value): if exc_type in self._fatal_exceptions: self._restore_std_streams() raise name = exc_type.__name__ message = self._get_message_from_exception(exc_value) if not message: return name if exc_type in self._generic_exceptions \ or getattr(exc_value, 'ROBOT_SUPPRESS_NAME', False): return message return '%s: %s' % (name, message) def _get_message_from_exception(self, value): # UnicodeError occurs below 2.6 and if message contains non-ASCII bytes try: msg = unicode(value) except UnicodeError: msg = ' '.join([self._str(a, handle_binary=False) for a in value.args]) return self._handle_binary_result(msg) def _get_error_traceback(self, exc_tb): # Latest entry originates from this class so it can be removed entries = traceback.extract_tb(exc_tb)[1:] trace = ''.join(traceback.format_list(entries)) return 'Traceback (most recent call last):\n' + trace def _get_error_attribute(self, exc_value, name): return bool(getattr(exc_value, 'ROBOT_%s_ON_FAILURE' % name, False)) def _handle_return_value(self, ret): if isinstance(ret, basestring): return self._handle_binary_result(ret) if isinstance(ret, (int, long, float)): return ret if isinstance(ret, Mapping): return dict([(self._str(key), self._handle_return_value(value)) for key, value in ret.items()]) try: return [self._handle_return_value(item) for item in ret] except TypeError: return self._str(ret) def _handle_binary_result(self, result): if not self._contains_binary(result): return result try: result = str(result) except UnicodeError: raise ValueError("Cannot represent %r as binary." % result) return Binary(result) def _contains_binary(self, result): return (BINARY.search(result) or isinstance(result, str) and sys.platform != 'cli' and NON_ASCII.search(result)) def _str(self, item, handle_binary=True): if item is None: return '' if not isinstance(item, basestring): item = unicode(item) if handle_binary: return self._handle_binary_result(item) return item def _intercept_std_streams(self): sys.stdout = StringIO() sys.stderr = StringIO() def _restore_std_streams(self): stdout = sys.stdout.getvalue() stderr = sys.stderr.getvalue() close = [sys.stdout, sys.stderr] sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ for stream in close: stream.close() if stdout and stderr: if not stderr.startswith(('*TRACE*', '*DEBUG*', '*INFO*', '*HTML*', '*WARN*')): stderr = '*INFO* %s' % stderr if not stdout.endswith('\n'): stdout += '\n' return self._handle_binary_result(stdout + stderr) def _log(self, msg, level=None): if level: msg = '*%s* %s' % (level.upper(), msg) self._write_to_stream(msg, sys.stdout) if sys.__stdout__ is not sys.stdout: self._write_to_stream(msg, sys.__stdout__) def _write_to_stream(self, msg, stream): stream.write(msg + '\n') stream.flush() if __name__ == '__main__': import xmlrpclib def stop(uri): server = test(uri, log_success=False) if server is not None: print 'Stopping remote server at %s.' % uri server.stop_remote_server() def test(uri, log_success=True): server = xmlrpclib.ServerProxy(uri) try: server.get_keyword_names() except: print 'No remote server running at %s.' % uri return None if log_success: print 'Remote server running at %s.' % uri return server def parse_args(args): actions = {'stop': stop, 'test': test} if not args or len(args) > 2 or args[0] not in actions: sys.exit('Usage: python -m robotremoteserver test|stop [uri]') uri = len(args) == 2 and args[1] or 'http://127.0.0.1:8270' if '://' not in uri: uri = 'http://' + uri return actions[args[0]], uri action, uri = parse_args(sys.argv[1:]) action(uri)
下载SikuliRemoteLibrary.py放到[C:\robot\libs]里面 无语了sikuli好像也上去了,代码如下自行保存成SikuliRemoteLibrary
import sys, os from robotremoteserver import RobotRemoteServer # from org.sikuli.script import * class SikuliRemoteLibrary: def __init__(self): self.log = '' # self.SS = Screen() self.PT = Pattern() def count_items_in_directory(self, path): """Returns the number of items in the directory specified by `path`.""" return len([i for i in os.listdir(path) if not i.startswith('.')]) def strings_should_be_equal(self, str1, str2): print "Comparing '%s' to '%s'" % (str1, str2) if str1 != str2: raise AssertionError("Given strings are not equal") # def _wait(self, imgFile, timeOut, similarity): # try: # self.PT = Pattern(imgFile) # self.PT = self.PT.similar(float(similarity)) # self.SS.wait(self.PT, float(timeOut)) # except FindFailed, err: # print "ERR: _wait" # raise AssertionError(err) # def click_object(self, imgFile, timeOut, similarity): # try: # self._wait(imgFile, timeOut, similarity) # self.SS.click(imgFile) # except FindFailed, err: # raise AssertionError("Cannot click [" + imgFile + "]") # def object_exists(self, imgFile, similarity, timeOut): # try: # self._wait(imgFile, timeOut, similarity) # except FindFailed, err: # raise AssertionError("Could not find [" + imgFile + "]") # def type_at_object(self, imgFile, txt, timeOut, similarity): # try: # self._wait(imgFile, timeOut, similarity) # self.SS.type(imgFile, txt) # except FindFailed, err: # raise AssertionError("Cannot type at [" + imgFile + "]") # def paste_at_object(self, imgFile, txt, timeOut, similarity): # try: # self._wait(imgFile, timeOut, similarity) # self.SS.paste(imgFile, txt) # except FindFailed, err: # raise AssertionError("Cannot paste at [" + imgFile + "]") if __name__ == '__main__': SRL = SikuliRemoteLibrary() RobotRemoteServer(SRL, *sys.argv[1:])
复制 C:\Program Files\Sikuli X\sikuli-script.jar 到 C:\robot\libs
C:\robot\libs\sikuli-script.jar加入JAVA_PATH
运行远端服务 cmd进入c:\robot\libs 然后
Jython.bat SikuliRemoteLibrary.py
进入RIDE
create 项目
create testsuit
脚本如下:
*** Settings *** Documentation Integrating Selenium, Sikuli into Robot Framework Test SetupLogin To Yahoo Mail${user_name}${password} Test Teardown Tear Test Down Library Selenium2Library15# Selenium library Library Remotehttp://localhost:${port}# Sikuli Library Screenshot# Taking screenshots when a test fails *** Variables *** ${url}http://mail.yahoo.com# Yahoo mail URL ${browser}ff# Browser ${user_name} user_name# User name ${password} password# Password ${port} 8270# Default port number for the remote server ${data_path} c:\robot\data# Sikuli p_w_picpaths ${similarity} 0.90# Used in Sikuli p_w_picpath comparison ${timeout}10# Time to wait for objects *** Testcases *** login To Yahoo Mail Test Case Wait Until Page Contains Elementyuhead-sform-searchfield${timeout} Input Textyuhead-sform-searchfieldENGLISH Object Exists${data_path}\search_box_left.png${similarity} *** Keywords *** Login To Yahoo Mail [Arguments]${user}${pass} [Documentation]This keyword logs user into Yahoo mail Open Browser${url}${browser} Wait Until Page Contains Elementusername Input Textusername${user} Input Passwordpasswd${pass} Click Button.save Wait Until Page Contains Elementtoolbar Click Object${data_path}\maximize.png Sleep1 Tear Test Down Run Keyword If Test FailedTake Screenshot Close All Browsers
应用sikuli remote library的python脚本如下
import sys from robotremoteserver import RobotRemoteServer from org.sikuli.script import * class SikuliRemoteLibrary: def __init__(self): self.SS = Screen() self.PT = Pattern() def _wait(self, imgFile, timeOut, similarity): try: self.PT = Pattern(imgFile) self.PT = self.PT.similar(float(similarity)) self.SS.wait(self.PT, float(timeOut)) except FindFailed, err: print "ERR: _wait" raise AssertionError(err) def click_object(self, imgFile, timeOut, similarity): try: self._wait(imgFile, timeOut, similarity) self.SS.click(imgFile) except FindFailed, err: raise AssertionError("Cannot click [" + imgFile + "]") def object_exists(self, imgFile, similarity, timeOut): try: self._wait(imgFile, timeOut, similarity) except FindFailed, err: raise AssertionError("Could not find [" + imgFile + "]") def type_at_object(self, imgFile, txt, timeOut, similarity): try: self._wait(imgFile, timeOut, similarity) self.SS.type(imgFile, txt) except FindFailed, err: raise AssertionError("Cannot type at [" + imgFile + "]") def paste_at_object(self, imgFile, txt, timeOut, similarity): try: self._wait(imgFile, timeOut, similarity) self.SS.paste(imgFile, txt) except FindFailed, err: raise AssertionError("Cannot paste at [" + imgFile + "]") if __name__ == '__main__': SRL = SikuliRemoteLibrary() RobotRemoteServer(SRL, *sys.argv[1:])
3、搭建持续集成环境CI(Continuous Intergration)
新建 [C:\robot\arg] 存放要传入的变量
新建C:\robot\argarg.txt 内容如下:
–variable user_name:user_name
–variable password:pass_word
–outputdir ..\out新建 C:\robot\run
创建 run.bat内容如下:
call pybot.bat –argumentfile C:\robotarg\arg.txt C:\robot\suites\test.txt
安装Jenkins
下载地址:jenkins.war
运行jenkins
java -jar jenkins.war --httpPort=80
下载https://code.google.com/p/robotframework-javatools/downloads/detail?name=robotframework.hpi
运行java -jar jenkins.war -httpport=80
进入manage Jenkins
进入Mange Plugins
点击 Advanced
点击Upload Plugins选择robotframework.hpi后点击upload
重启jenkins
点击NewJob
输入 任务名称,选择”Build a free-style software project”
选择 Add build step
点击 Execute windows batch command
输入 :
cd C:\robot\run
call run.bat选择Add post-build action
点击Publish Robot Framework test results
设置”Directory of Robot output” as “..\out”
设置 “Log/Report link” as “log.html”
设置Output xml name” as “output.xml”
设置 “Report html name” as “report.html”
设置 “Log html name” as “log.html”
到现在,我们已经jenkins已经可以开始工作了。确保Sikuliremotelibrary已经运行,现在可以开始build测试任务了。