From eff7ab7308146cb465ee4fabe5b5bdfe9d5987ca Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Fri, 25 Nov 2016 23:24:03 +0100 Subject: [PATCH] Fixes Chromedriver tests under Windows, by @yahman72 (from his commit: webdriver process management) PEP8 Compliant. (untouch test/run_tests.py) --- test/resources/testserver/testserver.py | 52 +++++- test/run_tests.py | 236 ++++++++++++++---------- 2 files changed, 186 insertions(+), 102 deletions(-) diff --git a/test/resources/testserver/testserver.py b/test/resources/testserver/testserver.py index c67ca3c4a..51c85da5e 100644 --- a/test/resources/testserver/testserver.py +++ b/test/resources/testserver/testserver.py @@ -4,17 +4,25 @@ import BaseHTTPServer import httplib import os +import socket class StoppableHttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): """http request handler with QUIT stopping the server""" + def setup(self): + SimpleHTTPServer.SimpleHTTPRequestHandler.setup(self) + self.rfile.close() + self.wfile.close() + self.rfile = HttpEchoer(self.connection._sock, 'rb', self.rbufsize) + self.wfile = HttpEchoer(self.connection._sock, 'wb', self.wbufsize) + def do_QUIT(self): """send 200 OK response, and set server.stop to True""" self.send_response(200) self.end_headers() self.server.stop = True - + def do_POST(self): # We could also process paremeters here using something like below. # length = self.headers['Content-Length'] @@ -63,7 +71,11 @@ def send_head(self): self.send_response(200) self.send_header("Content-type", ctype) fs = os.fstat(f.fileno()) - self.send_header("Content-Length", str(fs[6])) + # chrome hangs if this server runs on windows + # it probably waits for more data -> os.fstat() does not return correct + # length? We are responding with HTTP 1.0 = we do not need + # content-length header: + # self.send_header("Content-Length", str(fs[6])) self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) self.end_headers() return f @@ -78,27 +90,53 @@ def serve_forever(self): while not self.stop: self.handle_request() + +class HttpEchoer(socket._fileobject): + # echoes all HTTP traffic to a file if + # the server output is set to a file in run_tests.py + def read(self, size=-1): + data = super(HttpEchoer, self).read(size) + sys.stdout.write("-->: %s" % data) + sys.stdout.flush() + return data + + def readline(self, size=-1): + data = super(HttpEchoer, self).readline(size) + sys.stdout.write("-->: %s" % data) + sys.stdout.flush() + return data + + def readlines(self, sizehint=0): + data = super(HttpEchoer, self).readlines(sizehint) + sys.stdout.write("-->: %s" % data) + sys.stdout.flush() + return data + + def write(self, data): + sys.stdout.write("<--: %s" % data) + sys.stdout.flush() + super(HttpEchoer, self).write(data) + + def stop_server(port=7000): """send QUIT request to http server running on localhost:""" conn = httplib.HTTPConnection("localhost:%d" % port) conn.request("QUIT", "/") conn.getresponse() + def start_server(port=7000): import os os.chdir(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), '..')) server = StoppableHttpServer(('', port), StoppableHttpRequestHandler) server.serve_forever() - - + if __name__ == '__main__': import sys - if len(sys.argv) != 2 or sys.argv[1] not in [ 'start', 'stop' ]: + if len(sys.argv) != 2 or sys.argv[1] not in ['start', 'stop']: print('usage: %s start|stop' % sys.argv[0]) sys.exit(1) if sys.argv[1] == 'start': start_server() else: stop_server() - - diff --git a/test/run_tests.py b/test/run_tests.py index 4de5cf7d7..72eb989c9 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -1,117 +1,163 @@ #!/usr/bin/env python + +"""Script to run Selenium2Library acceptance tests. + +Tests are executed using Robot Framework and results verified automatically +afterwards using `robotstatuschecker` tool. The tool can be installed using +`pip install robotstatuschecker` and more information about it can be found +from https://github.com/robotframework/statuschecker/. Notice that initially +some tests fail. + +Usage: + + run_tests.py interpreter browser [options] + +Arguments: + + interpreter: Any Python interpreter supported by the library (e.g. `python`, + `jython`, `c:\\Python27\\python.exe`) + browser: Any browser supported by the library (e.g. `chrome`, `firefox`) + options: Additional command line options passed to Robot Framework + +Examples: + + run_tests.py python chrome + run_tests.py jython c:\\Python35\\python.exe --test "Click element" +""" + from __future__ import print_function -import env + +from contextlib import contextmanager import os import sys -from subprocess import Popen, call -from tempfile import TemporaryFile +import subprocess + +from robot import rebot_cli +try: + import robotstatuschecker +except ImportError: + sys.exit('Required `robotstatuschecker` not installed.\n' + 'Install it with `pip install robotstatuschecker`.') +import env from run_unit_tests import run_unit_tests -ROBOT_ARGS = [ - '--doc', 'SeleniumSPacceptanceSPtestsSPwithSP%(browser)s', - '--outputdir', '%(outdir)s', - '--variable', 'browser:%(browser)s', - '--variable', 'pyversion:%(pyVersion)s', - '--escape', 'space:SP', - '--report', 'none', - '--log', 'none', - #'--suite', 'Acceptance.Keywords.Textfields', + +ROBOT_OPTIONS = [ + '--doc', 'Selenium2Library acceptance tests with {browser}', + '--outputdir', env.RESULTS_DIR, + '--variable', 'BROWSER:{browser}', + '--variable', 'PY_VERSION:{py_version}', + '--report', 'NONE', + '--log', 'NONE', '--loglevel', 'DEBUG', - '--pythonpath', '%(pythonpath)s', - '--noncritical', 'known_issue_-_%(pyVersion)s', - '--noncritical', 'known_issue_-_%(browser)s', + '--pythonpath', os.pathsep.join([env.SRC_DIR, env.TEST_LIBS_DIR]), ] -REBOT_ARGS = [ - '--outputdir', '%(outdir)s', - '--name', '%(browser)sSPAcceptanceSPTests', - '--escape', 'space:SP', +REBOT_OPTIONS = [ + '--outputdir', env.RESULTS_DIR, '--critical', 'regression', '--noncritical', 'inprogress', - '--noncritical', 'known_issue_-_%(pyVersion)s', - '--noncritical', 'known_issue_-_%(browser)s', + '--noncritical', 'known issue {browser}', ] -ARG_VALUES = {'outdir': env.RESULTS_DIR, 'pythonpath': ':'.join((env.SRC_DIR, env.TEST_LIBS_DIR))} - -def acceptance_tests(interpreter, browser, args): - ARG_VALUES['browser'] = browser.replace('*', '') - ARG_VALUES['pyVersion'] = interpreter + sys.version[:3] - start_http_server() - runner = {'python': 'pybot', 'jython': 'jybot', 'ipy': 'ipybot'}[interpreter] - if os.sep == '\\': - runner += '.bat' - execute_tests(runner, args) - stop_http_server() - return process_output(args) - -def start_http_server(): - server_output = TemporaryFile() - Popen(['python', env.HTTP_SERVER_FILE ,'start'], - stdout=server_output, stderr=server_output) - -def execute_tests(runner, args): + + +def unit_tests(): + print('Running unit tests') + failures = run_unit_tests() + if failures: + print('\nUnit tests failed! Not running acceptance tests.') + sys.exit(failures) + + +def acceptance_tests(interpreter, browser, options): if not os.path.exists(env.RESULTS_DIR): os.mkdir(env.RESULTS_DIR) - command = [runner] + [arg % ARG_VALUES for arg in ROBOT_ARGS] + args + [env.ACCEPTANCE_TEST_DIR] - print() - print('Starting test execution with command:\n' + ' '.join(command)) + with http_server(): + execute_tests(interpreter, browser, options) + failures = process_output(browser, options) + if failures: + print('\n{} critical test{} failed.' + .format(failures, 's' if failures != 1 else '')) + else: + print('\nAll critical tests passed.') + sys.exit(failures) + + +@contextmanager +def http_server(): + serverlog = open(os.path.join(env.RESULTS_DIR, 'serverlog.txt'), 'w') + process = subprocess.Popen(['python', env.HTTP_SERVER_FILE, 'start'], + stdout=serverlog, stderr=subprocess.STDOUT) + try: + yield + finally: + subprocess.call(['python', env.HTTP_SERVER_FILE, 'stop']) + process.wait() + serverlog.close() + + +def execute_tests(interpreter, browser, cli_options): + runner = [interpreter, '-m', 'robot.run'] + options = [opt.format(browser=browser, + py_version=interpreter + sys.version[:3]) + for opt in ROBOT_OPTIONS] + if env.TRAVIS: + options.extend(get_travis_conf(browser)) + command = runner + options + cli_options + [env.ACCEPTANCE_TEST_DIR] + log_start(command) syslog = os.path.join(env.RESULTS_DIR, 'syslog.txt') - call(command, shell=os.sep=='\\', env=dict(os.environ, ROBOT_SYSLOG_FILE=syslog)) + # Running tests as an external process, not using `robot_cli`, to allow + # using different interpreter that is used for running this script. + subprocess.call(command, env=dict(os.environ, ROBOT_SYSLOG_FILE=syslog)) -def stop_http_server(): - call(['python', env.HTTP_SERVER_FILE, 'stop']) -def process_output(args): - print() - if _has_robot_27(): - call(['python', os.path.join(env.RESOURCES_DIR, 'statuschecker.py'), - os.path.join(env.RESULTS_DIR, 'output.xml')]) - rebot = 'rebot' if os.sep == '/' else 'rebot.bat' - rebot_cmd = [rebot] + [ arg % ARG_VALUES for arg in REBOT_ARGS ] + args + \ - [os.path.join(ARG_VALUES['outdir'], 'output.xml') ] - print() - print('Starting output processing with command:\n' + ' '.join(rebot_cmd)) - rc = call(rebot_cmd, env=os.environ) - if rc == 0: - print('All critical tests passed') +def get_travis_conf(browser): + conf = [ + '--variable', 'SAUCE_USERNAME:{}'.format(env.SAUCE_USERNAME), + '--variable', 'SAUCE_ACCESS_KEY:{}'.format(env.SAUCE_ACCESS_KEY) + ] + if browser == 'firefox': + conf.extend([ + '--variable', + 'REMOTE_URL:http://{}:{}@ondemand.saucelabs.com:80/wd/hub'.format( + env.SAUCE_USERNAME, env.SAUCE_ACCESS_KEY + ) + ]) else: - print('%d critical test%s failed' % (rc, 's' if rc != 1 else '')) - return rc + conf.extend([ + '--variable', + 'DESIRED_CAPABILITIES:build:{0}-{1},tunnel-identifier:{0}'.format( + env.TRAVIS_JOB_NUMBER, browser + ) + ]) + return conf -def _has_robot_27(): - try: - from robot.result import ExecutionResult - except: - return False - return True - -def _exit(rc): - sys.exit(rc) -def _help(): - print('Usage: python run_tests.py python|jython browser [options]') +def log_start(command_list): + command = subprocess.list2cmdline(command_list) + for hidden in [env.SAUCE_USERNAME, env.SAUCE_ACCESS_KEY]: + if hidden: + command = command.replace(hidden, '*' * len(hidden)) print() - print('See README.txt for details.') - return 255 + print('Starting test execution with command:') + print(command) -def _run_unit_tests(): - print('Running unit tests') - failures = run_unit_tests() - if failures != 0: - print('\n%d unit tests failed - not running acceptance tests!' % failures) - else: - print('All unit tests passed') - return failures - - -if __name__ == '__main__': - if not len(sys.argv) > 2: - _exit(_help()) - unit_failures = _run_unit_tests() - if unit_failures: - _exit(unit_failures) - interpreter = sys.argv[1] - browser = sys.argv[2].lower() - args = sys.argv[3:] - if browser != 'unit': - _exit(acceptance_tests(interpreter, browser, args)) + +def process_output(browser, cli_options): + print('Verifying results...') + output = os.path.join(env.RESULTS_DIR, 'output.xml') + robotstatuschecker.process_output(output, verbose=False) + options = [opt.format(browser=browser) for opt in REBOT_OPTIONS] + try: + rebot_cli(options + cli_options + [output]) + except SystemExit as exit: + return exit.code + + +if __name__ == '__main__': + if len(sys.argv) < 3 or '--help' in sys.argv: + sys.exit(__doc__) + unit_tests() + acceptance_tests(interpreter=sys.argv[1], + browser=sys.argv[2].lower(), + options=sys.argv[3:])