From 493b2cde6e3407c570d2e9c6baa72ee6fd34fbae Mon Sep 17 00:00:00 2001 From: Douglas Camata <douglas.camata@nexedi.com> Date: Wed, 26 Oct 2016 15:23:22 +0200 Subject: [PATCH] jstestnode: added support for running tests with Selenium Remote and Appium It uses Appium, which provides a Selenium WebDriver compatible API to remotely control an iOS (or Android) simulator. This way we can run tests in both mobile OSes without big changes to the current test code and infrastructure. This allows user to customize in the test suite module on which system they want to run the tests (Firefox or iOS) through the slapos parameters. In iOS, for example, it's possible to change the iOS version and it's required that the user give the SauceLabs credentials in form of user:apikey using the `appium_server_auth` parameter. An example of parameters to use in a test suite: ``` { "mariadb": { "relaxed-writes": true, "mariadb-relaxed-writes": true, "test-database-amount": 30 }, "target": "iOS", "target-version": "9.3", "target-device": "iPhone Simulator", "target-browser": "Safari", "appium-server-auth": "username:auth_token", "test-suite" : "jio" , "test-url": "jio-repository.git/test/tests.html" } ``` --- slapos/recipe/erp5testnode/__init__.py | 1 + .../erp5testnode/template/erp5testnode.cfg.in | 1 + .../erp5testnode/template/httpd.conf.in | 3 +- software/jstestnode/instance.cfg.in | 6 +- software/jstestnode/runTestSuite.in | 62 ++++++++++++++++--- software/jstestnode/software.cfg | 4 +- 6 files changed, 63 insertions(+), 14 deletions(-) diff --git a/slapos/recipe/erp5testnode/__init__.py b/slapos/recipe/erp5testnode/__init__.py index 534fdf369..2b4dc3630 100644 --- a/slapos/recipe/erp5testnode/__init__.py +++ b/slapos/recipe/erp5testnode/__init__.py @@ -58,6 +58,7 @@ class Recipe(GenericBaseRecipe): "\npath_list = %s" % ",".join(software_path_list) CONFIG['computer_id'] = self.buildout['slap-connection']['computer-id'] CONFIG['server_url'] = self.buildout['slap-connection']['server-url'] + CONFIG['frontend_url'] = self.buildout['testnode-frontend']['connection-secure_access'] configuration_file = self.createFile( self.options['configuration-file'], self.substituteTemplate( diff --git a/slapos/recipe/erp5testnode/template/erp5testnode.cfg.in b/slapos/recipe/erp5testnode/template/erp5testnode.cfg.in index 4c4126d86..83da16ea7 100644 --- a/slapos/recipe/erp5testnode/template/erp5testnode.cfg.in +++ b/slapos/recipe/erp5testnode/template/erp5testnode.cfg.in @@ -18,6 +18,7 @@ httpd_port = %(httpd_port)s httpd_software_access_port = %(httpd_software_access_port)s computer_id = %(computer_id)s server_url = %(server_url)s +frontend_url = %(frontend_url)s # Binaries git_binary = %(git_binary)s diff --git a/slapos/recipe/erp5testnode/template/httpd.conf.in b/slapos/recipe/erp5testnode/template/httpd.conf.in index 52e458184..5152816ce 100644 --- a/slapos/recipe/erp5testnode/template/httpd.conf.in +++ b/slapos/recipe/erp5testnode/template/httpd.conf.in @@ -76,8 +76,7 @@ SSLProxyEngine On Listen [%(ip)s]:%(software_access_port)s <VirtualHost *:%(software_access_port)s> SSLEngine on - RewriteRule (.*) http://[%(ip)s]:%(software_access_port)s/VirtualHostBase/https/[%(ip)s]:%(software_access_port)s/VirtualHostRoot/$1 [L,P] - DocumentRoot "%(testnode_software_directory)s" + DocumentRoot "%(testnode_srv_directory)s" <Directory /> Options FollowSymLinks IndexOptions FancyIndexing diff --git a/software/jstestnode/instance.cfg.in b/software/jstestnode/instance.cfg.in index fe159fdb7..fb10076ff 100644 --- a/software/jstestnode/instance.cfg.in +++ b/software/jstestnode/instance.cfg.in @@ -2,7 +2,6 @@ parts = nginx-service runTestSuite-instance - eggs-directory = ${buildout:eggs-directory} develop-eggs-directory = ${buildout:develop-eggs-directory} offline = true @@ -36,6 +35,7 @@ framebuffer = $${:srv}/framebuffer recipe = slapos.recipe.template url = ${template-runTestSuite:output} output = $${directory:bin}/runTestSuite +buildout-directory = $${buildout:directory} mode = 0700 [firefox-instance] @@ -76,7 +76,7 @@ output = $${directory:etc}/nginx.cfg mode = 0600 access_log = $${directory:log}/nginx-access.log error_log = $${directory:log}/nginx-error.log -ip = $${instance-parameter:ipv6-random} +ip = $${instance-parameters:ipv6-random} port = 9443 ssl_key = $${directory:ssl}/nginx.key ssl_csr = $${directory:ssl}/nginx.csr @@ -85,7 +85,7 @@ ssl_crt = $${directory:ssl}/nginx.crt ################################# # SlapOS service ################################# -[instance-parameter] +[instance-parameters] recipe = slapos.cookbook:slapconfiguration computer = $${slap_connection:computer_id} partition = $${slap_connection:partition_id} diff --git a/software/jstestnode/runTestSuite.in b/software/jstestnode/runTestSuite.in index a0b4421f2..77714e469 100644 --- a/software/jstestnode/runTestSuite.in +++ b/software/jstestnode/runTestSuite.in @@ -31,9 +31,43 @@ def main(): parser.add_argument('--node_quantity', help='ignored', type=int) parser.add_argument('--master_url', help='The Url of Master controling many suites') + parser.add_argument('--frontend_url', + help='The url of frontend of the test suite') + parser.add_argument('--target', + help='Target OS to run tests on', + type=str) + parser.add_argument('--target_version', + help='Target OS version to use', + type=str,) + parser.add_argument('--target_browser', + help='The desired browser of the target OS to be used. Example: Firefox if target is Android.', + type=str,) + parser.add_argument('--target_device', + help='The desired device running the target OS. Example: iPad Simulator, if target is iOS.', + type=str,) + parser.add_argument('--appium_server_auth', + help='Combination of user and token to access SauceLabs service. (i.e. user:token)', + type=str) args = parser.parse_args() + import json + parsed_parameters = json.loads('$${instance-parameters:configuration._}') + + + if not getattr(args, 'target', None): + args.target = parsed_parameters.get('target', 'firefox') + if not getattr(args, 'test_suite', None): + args.test_suite = parsed_parameters.get('test-suite') + if not getattr(args, 'target_version', None): + args.target_version = parsed_parameters.get('target-version') + if not getattr(args, 'appium_server_auth', None): + args.appium_server_auth = parsed_parameters.get('appium-server-auth') + if not getattr(args, 'target_browser', None): + args.target_browser = parsed_parameters.get('target-browser') + if not getattr(args, 'target_device', None): + args.target_device = parsed_parameters.get('target-device') + try: test_suite_title = args.test_suite_title or args.test_suite test_suite = args.test_suite @@ -51,8 +85,25 @@ def main(): ########################## # Run all tests ########################## - firefox_binary = webdriver.firefox.firefox_binary.FirefoxBinary(firefox_path=FIREFOX_EXECUTABLE) - browser = webdriver.Firefox(firefox_binary=firefox_binary) + if args.target == 'firefox': + firefox_binary = webdriver.firefox.firefox_binary.FirefoxBinary(firefox_path=FIREFOX_EXECUTABLE) + browser = webdriver.Firefox(firefox_binary=firefox_binary) + else: + capabilities = { + 'platformName': args.target, + 'platformVersion': args.target_version, + 'deviceName': args.target_device, + 'browserName': args.target_browser + + } + if not args.appium_server_auth: + raise RuntimeError('--appium_server_auth is required.') + appium_url = "http://%s@ondemand.saucelabs.com/wd/hub" % (args.appium_server_auth) + browser = webdriver.Remote(appium_url, capabilities) + + full_path = '$${runTestSuite-instance:buildout-directory}/software_release/parts/%s' % parsed_parameters['test-url'] + full_path = full_path.split('srv')[-1] + url = "%s%s" % (args.frontend_url, full_path) agent = browser.execute_script("return navigator.userAgent") print agent @@ -65,9 +116,9 @@ def main(): html_parser = etree.HTMLParser(recover=True) body = etree.fromstring(browser.page_source.encode('UTF-8'), html_parser) + browser.quit() browser.title.encode('UTF-8') - browser.quit() print ' '.join(body.xpath('//*[@id="qunit-testresult"]//text()')) @@ -99,7 +150,7 @@ def main(): node_title = args.test_node_title, test_title = test_suite_title, project_title = args.project_title) - if test_result is None: + if test_result is None or not hasattr(args, 'master_url'): return # report test results while 1: @@ -123,8 +174,5 @@ def main(): # XXX: inform test node master of error raise EnvironmentError(result) - finally: - browser.quit() - if __name__ == "__main__": main() diff --git a/software/jstestnode/software.cfg b/software/jstestnode/software.cfg index 15c3efeb2..d7faa94bf 100644 --- a/software/jstestnode/software.cfg +++ b/software/jstestnode/software.cfg @@ -25,7 +25,7 @@ parts = [instance] recipe = slapos.recipe.template -md5sum = 25a9c895fff279b71b0dbbad6647181b +md5sum = 605c1f62f93bbb77bb5f3b485882d4f2 url = ${:_profile_base_location_}/instance.cfg.in output = ${buildout:directory}/instance.cfg mode = 0644 @@ -107,7 +107,7 @@ mode = 0644 [template-runTestSuite] recipe = slapos.recipe.template url = ${:_profile_base_location_}/runTestSuite.in -md5sum = 88e820d30b36ecca9b45242ce4a52039 +md5sum = da89bddca28899023b67eb9757cb94f4 output = ${buildout:directory}/runTestSuite.in mode = 0644 -- 2.30.9