Sikuli Selenium Robotfraamework 集成

原文:http://www.8bitavenue.com/2012/04/sikuli-selenium-robot-framework-integration/

1、需要的所有安装包

sikuli

selenium(webdriver)

RobotFramework

python(2.7.2版本)

Java Run Time Installation

Jython

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共同工作。

  1. 创建 [C:\robot] [C:\robot\data] [C:\robot\libs] [C:\robot\suites]

  2. 下载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)
  3. 下载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:])
  4. 复制 C:\Program Files\Sikuli X\sikuli-script.jar 到 C:\robot\libs

  5. C:\robot\libs\sikuli-script.jar加入JAVA_PATH

  6. 运行远端服务 cmd进入c:\robot\libs 然后 Jython.bat SikuliRemoteLibrary.py

  7. 进入RIDE

  8. create 项目

  9. create testsuit

  10. 脚本如下:

    *** 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)

  1. 新建 [C:\robot\arg] 存放要传入的变量

  2. 新建C:\robot\argarg.txt 内容如下:

    –variable user_name:user_name 
    –variable password:pass_word 
    –outputdir ..\out

  3. 新建 C:\robot\run

  4. 创建 run.bat内容如下:

    call pybot.bat –argumentfile C:\robotarg\arg.txt C:\robot\suites\test.txt

  5. 安装Jenkins

  • 下载地址:jenkins.war

  • 运行jenkins java -jar jenkins.war --httpPort=80

Robotframwork集成Jenkins Plugin创建Jenkins 任务
  • 进入http://localhost:8080

  • 点击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测试任务了。