From c8c7b52c8c1890f49a3101d02ae119e635726eb6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ji=C5=99=C3=AD=20Helebrant?= <jiri.helebrant@nic.cz>
Date: Mon, 8 Aug 2016 18:08:24 +0200
Subject: [PATCH] Rewrite tests to Nightwatch + Selenium. Improvements over old
 python-selenium interface: - actually works with up-to-date browsers - works
 with chromium without installing shady extensions - code is much cleaner and
 shorter (like 4 times shorter), tests run faster

---
 run_tests.sh                  | 29 ++++++++++++----
 tests/.gitignore              |  1 +
 tests/DevicesTableLoads.py    | 21 ------------
 tests/Help.js                 | 11 ++++++
 tests/HelpIndexBuilds.py      | 21 ------------
 tests/Map.js                  | 10 ++++++
 tests/MapLayersLoad.py        | 22 ------------
 tests/Monkey.py               | 63 -----------------------------------
 tests/OperatorsTableLoads.py  | 21 ------------
 tests/PersonalHistoryLoads.py | 37 --------------------
 tests/ProjectBar.js           | 10 ++++++
 tests/ProjectsBarLoads.py     | 21 ------------
 tests/ResultDetail.js         | 13 ++++++++
 tests/SpeedChartDraws.py      | 53 -----------------------------
 tests/Stats.js                | 26 +++++++++++++++
 tests/StatsTableLoads.py      | 21 ------------
 tests/nightwatch.json         | 46 +++++++++++++++++++++++++
 17 files changed, 139 insertions(+), 287 deletions(-)
 create mode 100644 tests/.gitignore
 delete mode 100644 tests/DevicesTableLoads.py
 create mode 100644 tests/Help.js
 delete mode 100644 tests/HelpIndexBuilds.py
 create mode 100644 tests/Map.js
 delete mode 100644 tests/MapLayersLoad.py
 delete mode 100644 tests/Monkey.py
 delete mode 100644 tests/OperatorsTableLoads.py
 delete mode 100644 tests/PersonalHistoryLoads.py
 create mode 100644 tests/ProjectBar.js
 delete mode 100644 tests/ProjectsBarLoads.py
 create mode 100644 tests/ResultDetail.js
 delete mode 100644 tests/SpeedChartDraws.py
 create mode 100644 tests/Stats.js
 delete mode 100644 tests/StatsTableLoads.py
 create mode 100644 tests/nightwatch.json

diff --git a/run_tests.sh b/run_tests.sh
index 58628aa9..6bc44b4b 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -1,16 +1,31 @@
 #!/usr/bin/env bash
 
-cd output && python -m http.server 9000 &
-
 if curl -s http://localhost:4444 | grep Jetty > /dev/null; then
     echo "Using Selenium at localhost:4444."
 else
     echo "Selenium not running.";
+    echo "Please start Selenium server at localhost:4444 (java -jar selenium-server-standalone-X.X.X.jar)";
+    exit 1;
+fi
+
+NIGHTWATCH_BIN=$(which nightwatch)
+if [[ -x "$NIGHTWATCH_BIN" ]]; then
+    echo "Using Nightwatch from $NIGHTWATCH_BIN"
+else
+    echo "Nightwatch not found in your PATH. Install guide can be found at http://nightwatchjs.org/guide#install-nightwatch.";
     exit 1;
 fi
 
-for i in tests/*py; do
-    echo "\n\n";
-    echo "Running ${i}:";
-    python2 ${i};
-done
+PWD=$(pwd)
+
+mkdir /tmp/netmetr-test
+cp -r output /tmp/netmetr-test/netmetr-proto
+cd /tmp/netmetr-test/ && python -m http.server 9000 > /dev/null 2>&1 &
+
+cd $PWD
+
+cd tests
+nightwatch
+nightwatch --env chrome
+
+pkill -f "python.*http.server 9000"
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 00000000..27a3afbb
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1 @@
+reports
diff --git a/tests/DevicesTableLoads.py b/tests/DevicesTableLoads.py
deleted file mode 100644
index 73c5f203..00000000
--- a/tests/DevicesTableLoads.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import webdriver
-import unittest
-from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
-
-
-class DevicesTableLoads(unittest.TestCase):
-    def setUp(self):
-        binary = FirefoxBinary("/usr/bin/firefox-bin")
-        self.driver = webdriver.Firefox(firefox_binary=binary)
-        self.driver.implicitly_wait(10)
-
-    def test_devices_table_loads(self):
-        self.driver.get("http://localhost:9000/cs/statistiky.html")
-        self.assertTrue(self.driver.find_element_by_css_selector("#devices td.sorting_1"))
-
-    def tearDown(self):
-        self.driver.close()
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/Help.js b/tests/Help.js
new file mode 100644
index 00000000..d4422611
--- /dev/null
+++ b/tests/Help.js
@@ -0,0 +1,11 @@
+module.exports = {
+  "Help index builds" : function (browser) {
+    browser
+      .url(browser.launchUrl + "napoveda.html")
+      .waitForElementVisible("body")
+      .click("div.toc ul:first-child li:first-child")
+      .pause(1000)
+      .waitForElementVisible("div.toc ul:first-child li:first-child li:first-child a span")
+      .end();
+  }
+};
diff --git a/tests/HelpIndexBuilds.py b/tests/HelpIndexBuilds.py
deleted file mode 100644
index e9b02926..00000000
--- a/tests/HelpIndexBuilds.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import webdriver
-import unittest
-from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
-
-
-class HelpIndexBuilds(unittest.TestCase):
-    def setUp(self):
-        binary = FirefoxBinary("/usr/bin/firefox-bin")
-        self.driver = webdriver.Firefox(firefox_binary=binary)
-        self.driver.implicitly_wait(10)
-
-    def test_help_index_builds(self):
-        self.driver.get("http://localhost:9000/cs/napoveda.html")
-        self.assertTrue(self.driver.find_element_by_css_selector("div.toc ul li a span"))
-
-    def tearDown(self):
-        self.driver.close()
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/Map.js b/tests/Map.js
new file mode 100644
index 00000000..fe63b0e0
--- /dev/null
+++ b/tests/Map.js
@@ -0,0 +1,10 @@
+module.exports = {
+  "Heatmap layer loads" : function (browser) {
+    browser
+      .url(browser.launchUrl + "mapa.html")
+      .waitForElementVisible("body")
+      .waitForElementNotVisible(".loader-overlay")
+      .waitForElementVisible(".leaflet-layer[style~='z-index: 3'] .leaflet-tile-loaded:first-child")
+      .end();
+  }
+};
diff --git a/tests/MapLayersLoad.py b/tests/MapLayersLoad.py
deleted file mode 100644
index 62bb121a..00000000
--- a/tests/MapLayersLoad.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import webdriver
-import unittest
-from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
-
-
-class MapLayersLoad(unittest.TestCase):
-    def setUp(self):
-        binary = FirefoxBinary("/usr/bin/firefox-bin")
-        self.driver = webdriver.Firefox(firefox_binary=binary)
-        self.driver.implicitly_wait(10)
-
-    def test_map_layers_load(self):
-        self.driver.get("http://localhost:9000/cs/mapa.html")
-        self.assertTrue("img.leaflet-tile-loaded")
-        self.assertTrue(self.driver.find_element_by_css_selector("div.leaflet-control-layers-overlays > label > span"))
-
-    def tearDown(self):
-        self.driver.close()
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/Monkey.py b/tests/Monkey.py
deleted file mode 100644
index d6a9f44a..00000000
--- a/tests/Monkey.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import selenium, webdriver
-from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
-from urlparse import parse_qs, urlparse
-from os import system
-import time
-import thread
-
-base_url = "http://localhost:9000/netmetr/"
-pages = [
-    "index.html",
-    "mapa.html",
-    "aplikace.html",
-    "moje.html",
-    "statistiky.html",
-    "open-data.html",
-    "napoveda.html",
-    ]
-
-
-class httpHandler(BaseHTTPRequestHandler):
-    def do_GET(self):
-        self.send_response(200)
-        self.send_header('Content-type', 'application/json')
-        self.send_header('Access-Control-Allow-Origin', '*')
-        self.end_headers()
-        q = parse_qs(urlparse(self.path).query)
-        print "  " + q['msg'][0]
-        return
-
-    def log_message(self, format, *args):
-        return
-
-
-def release_monkeys(page):
-    print page
-    driver.get(base_url + page)
-    driver.execute_script("$.getScript('//cdnjs.cloudflare.com/ajax/libs/gremlins.js/0.1.0/" +
-                          "gremlins.min.js').done(function(){gremlins.createHorde().unleash();})")
-    time.sleep(11)
-
-
-def run_error_logger(port):
-    try:
-        server = HTTPServer(('', port), httpHandler)
-        server.serve_forever()
-    except KeyboardInterrupt:
-        server.socket.close()
-
-thread.start_new_thread(run_error_logger, (4445,))
-
-system("sed -i \"s~<head>~<head><script>var req=new XMLHttpRequest;(function(){console.error=" +
-       "function(m){req.open('GET','http://localhost:4445?msg='+m);req.send()}})();" +
-       "window.onerror=function (m,u,l){req.open('GET','http://localhost:4445?msg='+" +
-       "u.replace(/.*theme/,'')+':'+l+': '+m);req.send()};</script>~\" netmetr/*.html")
-
-driver = webdriver.Chrome()
-
-for page in pages:
-    release_monkeys(page)
-
-driver.quit()
-system("sed -i \"s~<head>.*charset~<head><meta charset~\" netmetr/*.html")
diff --git a/tests/OperatorsTableLoads.py b/tests/OperatorsTableLoads.py
deleted file mode 100644
index 3ba34855..00000000
--- a/tests/OperatorsTableLoads.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import webdriver
-import unittest
-from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
-
-
-class DevicesTableLoads(unittest.TestCase):
-    def setUp(self):
-        binary = FirefoxBinary("/usr/bin/firefox-bin")
-        self.driver = webdriver.Firefox(firefox_binary=binary)
-        self.driver.implicitly_wait(10)
-
-    def test_devices_table_loads(self):
-        self.driver.get("http://localhost:9000/cs/statistiky.html")
-        self.assertTrue(self.driver.find_element_by_css_selector("#operators td.sorting_1"))
-
-    def tearDown(self):
-        self.driver.close()
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/PersonalHistoryLoads.py b/tests/PersonalHistoryLoads.py
deleted file mode 100644
index 798c69fd..00000000
--- a/tests/PersonalHistoryLoads.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import selenium
-import unittest
-import time
-import re
-
-
-class OperatorsTableLoads(unittest.TestCase):
-    def setUp(self):
-        self.verificationErrors = []
-        self.selenium = selenium("localhost", 4444, "*chrome", "http://localhost:9000/")
-        self.selenium.start()
-
-    def test_history_table_loads(self):
-        sel = self.selenium
-        sel.open("/netmetr/moje.html")
-        sel.wait_for_page_to_load("30000")
-        time.sleep(1)
-        sel.type("id=sync-code", "9a12c5b843be")
-        time.sleep(1)
-        sel.click("//button[@id='send-sync-code']")
-        for i in range(10):
-            try:
-                if sel.is_element_present("css=#stats td.sorting_1"):
-                    break
-            except:
-                pass
-            time.sleep(1)
-        else:
-            self.fail("time out")
-
-    def tearDown(self):
-        self.selenium.stop()
-        self.assertEqual([], self.verificationErrors)
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/ProjectBar.js b/tests/ProjectBar.js
new file mode 100644
index 00000000..d1e7f332
--- /dev/null
+++ b/tests/ProjectBar.js
@@ -0,0 +1,10 @@
+module.exports = {
+  "Project bar loads" : function (browser) {
+    browser
+      .url(browser.launchUrl + "napoveda.html")
+      .resizeWindow(1000, 800)
+      .waitForElementVisible("body")
+      .waitForElementVisible("#tb-projects-bar")
+      .end();
+  }
+};
diff --git a/tests/ProjectsBarLoads.py b/tests/ProjectsBarLoads.py
deleted file mode 100644
index 0d386a0c..00000000
--- a/tests/ProjectsBarLoads.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import webdriver
-import unittest
-from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
-
-
-class DevicesTableLoads(unittest.TestCase):
-    def setUp(self):
-        binary = FirefoxBinary("/usr/bin/firefox-bin")
-        self.driver = webdriver.Firefox(firefox_binary=binary)
-        self.driver.implicitly_wait(10)
-
-    def test_devices_table_loads(self):
-        self.driver.get("http://localhost:9000/cs/statistiky.html")
-        self.assertTrue(self.driver.find_element_by_css_selector("#tb-projects-bar"))
-
-    def tearDown(self):
-        self.driver.close()
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/ResultDetail.js b/tests/ResultDetail.js
new file mode 100644
index 00000000..3a2bbaba
--- /dev/null
+++ b/tests/ResultDetail.js
@@ -0,0 +1,13 @@
+module.exports = {
+  "Speed chart draws" : function (browser) {
+    browser
+      .url(browser.launchUrl + "statistiky.html")
+      .waitForElementVisible("body")
+      .waitForElementNotVisible(".loader-overlay")
+      .click("#results tr:first-child td.datetime a")
+      .waitForElementVisible("body")
+      .waitForElementNotVisible(".loader-overlay")
+      .waitForElementVisible("#placeholder-dl canvas.flot-overlay")
+      .end();
+  }
+};
diff --git a/tests/SpeedChartDraws.py b/tests/SpeedChartDraws.py
deleted file mode 100644
index b68ad4f3..00000000
--- a/tests/SpeedChartDraws.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import selenium
-import unittest
-import time
-import re
-
-
-class SpeedChartsDraw(unittest.TestCase):
-    def setUp(self):
-        self.verificationErrors = []
-        self.selenium = selenium("localhost", 4444, "*chrome", "http://localhost:9000/")
-        self.selenium.start()
-
-    def test_speed_charts_draw(self):
-        sel = self.selenium
-
-        sel.open("/netmetr/statistiky.html")
-        sel.wait_for_page_to_load("30000")
-        sel.run_script("window.scrollTo(0, document.body.scrollHeight);")
-
-        for i in range(10):
-            try:
-                if sel.is_element_present("css=table#stats td.datetime a"):
-                    break
-            except:
-                pass
-            time.sleep(1)
-        else:
-            self.fail("Timed out when loading results table.")
-
-        sel.click("css=table#stats td.datetime a")
-
-        for i in range(10):
-            try:
-                if sel.is_element_present("css=table#detail-speed"):
-                    break
-            except:
-                pass
-            time.sleep(1)
-        else:
-            self.fail("Timed out when loading result detail.")
-
-        try:
-            self.failUnless(sel.is_element_present("css=canvas.flot-overlay"))
-        except AssertionError, e:
-            self.verificationErrors.append(str(e))
-
-    def tearDown(self):
-        self.selenium.stop()
-        self.assertEqual([], self.verificationErrors)
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/Stats.js b/tests/Stats.js
new file mode 100644
index 00000000..e998898b
--- /dev/null
+++ b/tests/Stats.js
@@ -0,0 +1,26 @@
+module.exports = {
+  "Devices table loads" : function (browser) {
+    browser
+      .url(browser.launchUrl + "statistiky.html")
+      .waitForElementVisible("body")
+      .waitForElementNotVisible(".loader-overlay")
+      .waitForElementVisible("#devices tr:first-child td.sorting_1")
+      .end();
+  },
+  "Operators table loads" : function (browser) {
+    browser
+      .url(browser.launchUrl + "statistiky.html")
+      .waitForElementVisible("body")
+      .waitForElementNotVisible(".loader-overlay")
+      .waitForElementVisible("#operators tr:first-child td.sorting_1")
+      .end();
+  },
+  "Stats table loads" : function (browser) {
+    browser
+      .url(browser.launchUrl + "statistiky.html")
+      .waitForElementVisible("body")
+      .waitForElementNotVisible(".loader-overlay")
+      .waitForElementVisible("#operators tr:first-child td.sorting_1")
+      .end();
+  }
+};
diff --git a/tests/StatsTableLoads.py b/tests/StatsTableLoads.py
deleted file mode 100644
index 41011a30..00000000
--- a/tests/StatsTableLoads.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-from selenium import webdriver
-import unittest
-from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
-
-
-class DevicesTableLoads(unittest.TestCase):
-    def setUp(self):
-        binary = FirefoxBinary("/usr/bin/firefox-bin")
-        self.driver = webdriver.Firefox(firefox_binary=binary)
-        self.driver.implicitly_wait(10)
-
-    def test_devices_table_loads(self):
-        self.driver.get("http://localhost:9000/cs/statistiky.html")
-        self.assertTrue(self.driver.find_element_by_css_selector("#stats td.sorting_1"))
-
-    def tearDown(self):
-        self.driver.close()
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/nightwatch.json b/tests/nightwatch.json
new file mode 100644
index 00000000..6ec046d3
--- /dev/null
+++ b/tests/nightwatch.json
@@ -0,0 +1,46 @@
+{
+    "src_folders": ["."],
+    "output_folder": "reports",
+    "custom_commands_path": "",
+    "custom_assertions_path": "",
+    "page_objects_path": "",
+    "globals_path": "",
+
+    "selenium": {
+        "start_process": false,
+        "server_path": "",
+        "log_path": "",
+        "host": "127.0.0.1",
+        "port": 4444,
+        "cli_args": {
+            "webdriver.chrome.driver": ""
+        }
+    },
+
+    "test_settings": {
+        "default": {
+            "launch_url": "http://localhost:9000/netmetr-proto/cs/",
+            "selenium_port": 4444,
+            "selenium_host": "localhost",
+            "silent": true,
+            "screenshots": {
+                "enabled": false,
+                "path": ""
+            },
+            "desiredCapabilities": {
+                "browserName": "firefox",
+                "javascriptEnabled": true
+            },
+            "globals": {
+                "waitForConditionTimeout": 3000
+            }
+        },
+
+        "chrome": {
+            "desiredCapabilities": {
+                "browserName": "chrome",
+                "javascriptEnabled": true
+            }
+        }
+    }
+}
-- 
GitLab