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