From 78af50fcf0e820f72cd1fb35c836db5ff7135afb Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 1 Oct 2018 09:19:25 -0400 Subject: [PATCH 01/15] sketch of BrowserStack and Selenium testing setup --- .gitignore | 3 +++ Pipfile | 1 + Pipfile.lock | 55 ++++++++++++++++++++++++++++---------------- config/selenium.ini | 5 ++++ script/selenium_test | 52 +++++++++++++++++++++++++++++++++++++++++ selenium_example.py | 26 +++++++++++++++++++++ 6 files changed, 122 insertions(+), 20 deletions(-) create mode 100644 config/selenium.ini create mode 100755 script/selenium_test create mode 100644 selenium_example.py diff --git a/.gitignore b/.gitignore index 37bb5a94..36662aba 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ config/dev.ini # coverage output .coverage + +# selenium +browserstacklocal diff --git a/Pipfile b/Pipfile index 3df91220..bc5ed901 100644 --- a/Pipfile +++ b/Pipfile @@ -34,6 +34,7 @@ factory-boy = "*" pytest-flask = "*" pytest-env = "*" pytest-cov = "*" +selenium = "*" [requires] python_version = "3.6.6" diff --git a/Pipfile.lock b/Pipfile.lock index 474b822c..bf863dc6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c95e31f2315762631fcae7253275e46cbca22bbfd4467cf454e74163743c6ae7" + "sha256": "1e5e6a695229166aaa5e6c427fed07a903766e9b3d24981a19cc8e5ada8db978" }, "pipfile-spec": 6, "requires": { @@ -440,10 +440,10 @@ }, "colorama": { "hashes": [ - "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", - "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" + "sha256:a3d89af5db9e9806a779a50296b5fdb466e281147c2c235e8225ecc6dbf7bbf3", + "sha256:c9b54bebe91a6a803e0772c8561d53f2926bfeb17cd141fbabcb08424086595c" ], - "version": "==0.3.9" + "version": "==0.4.0" }, "coverage": { "hashes": [ @@ -508,10 +508,10 @@ }, "faker": { "hashes": [ - "sha256:74b32991f8e08e4f2f84858b919eca253becfaec4b3fa5fcff7fdbd70d5d78b1", - "sha256:c2ce42dd8361e6d392276006d757532562463c8642b1086709584200b7fd7758" + "sha256:2621643b80a10b91999925cfd20f64d2b36f20bf22136bbdc749bb57d6ffe124", + "sha256:5ed822d31bd2d6edf10944d176d30dc9c886afdd381eefb7ba8b7aad86171646" ], - "version": "==0.9.1" + "version": "==0.9.2" }, "flask": { "hashes": [ @@ -523,10 +523,10 @@ }, "gitdb2": { "hashes": [ - "sha256:87783b7f4a8f6b71c7fe81d32179b3c8781c1a7d6fa0c69bff2f315b00aff4f8", - "sha256:bb4c85b8a58531c51373c89f92163b92f30f81369605a67cd52d1fc21246c044" + "sha256:83361131a1836661a155172932a13c08bda2db3674e4caa32368aa6eb02f38c2", + "sha256:e3a0141c5f2a3f635c7209d56c496ebe1ad35da82fe4d3ec4aaa36278d70648a" ], - "version": "==2.0.4" + "version": "==2.0.5" }, "gitpython": { "hashes": [ @@ -684,11 +684,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:5eff0c9fd652384ecfe730bbcdf3658868725c6928fbf608d9338834d7a974b6", - "sha256:81da9ecf6ca6806a549697529af8ec3ac5b739c13ac14607218e650db1b53131", - "sha256:c67c1c264d8a0d9e1070e9272bacee00f76c81daab7bc4bf09ff991bd1e224a7" + "sha256:646b3401b3b0bb7752100bc9b7aeecb36cb09cdfc63652b5856708b5ba8db7da", + "sha256:82766ffd7397e6661465e20bd1390db0781ca4fbbab4cf6c2578cacdd8b09754", + "sha256:ccad8461b5d912782726af17122113e196085e7e11d57cf0c9b982bf1ab2c7be" ], - "version": "==2.0.5" + "version": "==2.0.6" }, "ptyprocess": { "hashes": [ @@ -699,10 +699,10 @@ }, "py": { "hashes": [ - "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", - "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" ], - "version": "==1.6.0" + "version": "==1.7.0" }, "pygments": { "hashes": [ @@ -778,6 +778,14 @@ ], "version": "==4.2b4" }, + "selenium": { + "hashes": [ + "sha256:ab192cd046164c40fabcf44b47c66c8b12495142f4a69dcc55ea6eeef096e614", + "sha256:fdb6b1143d8899e8a32e358ad05bf5d89a480dbac359dbbd341592aa8696dcd1" + ], + "index": "pypi", + "version": "==3.14.1" + }, "simplegeneric": { "hashes": [ "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" @@ -793,10 +801,10 @@ }, "smmap2": { "hashes": [ - "sha256:0dd53d991af487f9b22774fa89451358da3607c02b9b886a54736c6a313ece0b", - "sha256:dc216005e529d57007ace27048eb336dcecb7fc413cfb3b2f402bb25972b69c6" + "sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde", + "sha256:29a9ffa0497e7f2be94ca0ed1ca1aa3cd4cf25a1f6b4f5f87f74b46ed91d609a" ], - "version": "==2.0.4" + "version": "==2.0.5" }, "stevedore": { "hashes": [ @@ -856,6 +864,13 @@ "markers": "python_version < '3.7' and implementation_name == 'cpython'", "version": "==1.1.0" }, + "urllib3": { + "hashes": [ + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + ], + "version": "==1.23" + }, "watchdog": { "hashes": [ "sha256:965f658d0732de3188211932aeb0bb457587f04f63ab4c1e33eab878e9de961d" diff --git a/config/selenium.ini b/config/selenium.ini new file mode 100644 index 00000000..81029be6 --- /dev/null +++ b/config/selenium.ini @@ -0,0 +1,5 @@ +[default] +PGDATABASE = atat_selenium +REDIS_URI = redis://redishost:6379 +CRL_DIRECTORY = tests/fixtures/crl +WTF_CSRF_ENABLED = false diff --git a/script/selenium_test b/script/selenium_test new file mode 100755 index 00000000..3132a23a --- /dev/null +++ b/script/selenium_test @@ -0,0 +1,52 @@ +#!/bin/bash + +# script/selenium_test: Run selenium tests via BrowserStack + +source "$(dirname "${0}")"/../script/include/global_header.inc.sh + +export FLASK_ENV=selenium + +# create upload directory for app +mkdir uploads | true + +# Fetch postgres settings and set them as ENV vars +source ./script/get_db_settings + +if [ -n "${PGDATABASE}" ]; then + echo "Resetting database ${PGDATABASE}..." + # Reset the db + reset_db "${PGDATABASE}" +else + echo "ERROR: RESET_DB is set, but PGDATABASE is not!" + echo "Skipping database reset..." +fi + +BSL_FILE=BrowserStackLocal +if [[ `uname` == "Darwin" ]]; then + BSL_DOWNLOAD="https://www.browserstack.com/browserstack-local/BrowserStackLocal-darwin-x64.zip" +else + BSL_DOWNLOAD="https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip" +fi + +# Fetch BrowserStackLocal script +if [ -e "${BSL_FILE}" ]; then + echo "BrowserStack file already exists" +else + echo "downloading BrowserStack file" + curl $BSL_DOWNLOAD --output $BSL_FILE.zip + unzip $BSL_FILE.zip -d . + rm $BSL_FILE.zip + chmod u+x $BSL_FILE +fi + +# run BrowserStackLocal in the background +echo "starting BrowserStack local client..." +./$BSL_FILE --key $BROWSERSTACK_TOKEN & +BSL_ID=$! + +# run example selenium script that fetches the home page +echo "running selenium example script" +pipenv run python selenium_example.py + +# kill BrowserStackLocal +kill $BSL_ID diff --git a/selenium_example.py b/selenium_example.py new file mode 100644 index 00000000..d7582aea --- /dev/null +++ b/selenium_example.py @@ -0,0 +1,26 @@ +import os +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +desired_cap = { + 'browser': 'IE', + 'browser_version': '10.0', + 'os': 'Windows', + 'os_version': '7', + 'resolution': '1024x768', + 'browserstack.local': True +} + +print("initializing the webdriver") +driver = webdriver.Remote( + command_executor='http://{}:{}@hub.browserstack.com:80/wd/hub'.format(os.getenv("BROWSERSTACK_EMAIL"), os.getenv("BROWSERSTACK_TOKEN")), + desired_capabilities=desired_cap) + +print("fetching the localhost page") +driver.get("http://localhost:8000") +if not "JEDI" in driver.title: + raise Exception("NO JEDI") +print("this is the page title: {}".format(driver.title)) +driver.quit() +print("exiting") From 63f94deb403d90b6b42a830612bb7daa4e3dbeb8 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 1 Oct 2018 12:05:44 -0400 Subject: [PATCH 02/15] test harness for selenium testing --- .gitignore | 2 +- config/selenium.ini | 1 - pytest.ini | 2 +- script/selenium_test | 4 +- selenium_example.py | 26 -------- tests/acceptance/__init__.py | 0 tests/acceptance/conftest.py | 67 ++++++++++++++++++++ tests/acceptance/live_server.py | 109 ++++++++++++++++++++++++++++++++ tests/acceptance/test_basic.py | 3 + 9 files changed, 183 insertions(+), 31 deletions(-) delete mode 100644 selenium_example.py create mode 100644 tests/acceptance/__init__.py create mode 100644 tests/acceptance/conftest.py create mode 100644 tests/acceptance/live_server.py create mode 100644 tests/acceptance/test_basic.py diff --git a/.gitignore b/.gitignore index 36662aba..58193f90 100644 --- a/.gitignore +++ b/.gitignore @@ -45,5 +45,5 @@ config/dev.ini # coverage output .coverage -# selenium +# selenium testing browserstacklocal diff --git a/config/selenium.ini b/config/selenium.ini index 81029be6..5ad48446 100644 --- a/config/selenium.ini +++ b/config/selenium.ini @@ -1,5 +1,4 @@ [default] PGDATABASE = atat_selenium -REDIS_URI = redis://redishost:6379 CRL_DIRECTORY = tests/fixtures/crl WTF_CSRF_ENABLED = false diff --git a/pytest.ini b/pytest.ini index 91d52803..30767796 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,4 +2,4 @@ norecursedirs = .venv .git node_modules env = D:FLASK_ENV=test -addopts = --cov=atst --cov-report term --cov-fail-under 90 +addopts = --ignore=tests/acceptance/ --cov=atst --cov-report term --cov-fail-under 90 diff --git a/script/selenium_test b/script/selenium_test index 3132a23a..fcf3515a 100755 --- a/script/selenium_test +++ b/script/selenium_test @@ -45,8 +45,8 @@ echo "starting BrowserStack local client..." BSL_ID=$! # run example selenium script that fetches the home page -echo "running selenium example script" -pipenv run python selenium_example.py +echo "running selenium tests" +pipenv run pytest tests/acceptance -s # kill BrowserStackLocal kill $BSL_ID diff --git a/selenium_example.py b/selenium_example.py deleted file mode 100644 index d7582aea..00000000 --- a/selenium_example.py +++ /dev/null @@ -1,26 +0,0 @@ -import os -from selenium import webdriver -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.common.desired_capabilities import DesiredCapabilities - -desired_cap = { - 'browser': 'IE', - 'browser_version': '10.0', - 'os': 'Windows', - 'os_version': '7', - 'resolution': '1024x768', - 'browserstack.local': True -} - -print("initializing the webdriver") -driver = webdriver.Remote( - command_executor='http://{}:{}@hub.browserstack.com:80/wd/hub'.format(os.getenv("BROWSERSTACK_EMAIL"), os.getenv("BROWSERSTACK_TOKEN")), - desired_capabilities=desired_cap) - -print("fetching the localhost page") -driver.get("http://localhost:8000") -if not "JEDI" in driver.title: - raise Exception("NO JEDI") -print("this is the page title: {}".format(driver.title)) -driver.quit() -print("exiting") diff --git a/tests/acceptance/__init__.py b/tests/acceptance/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py new file mode 100644 index 00000000..b19b9855 --- /dev/null +++ b/tests/acceptance/conftest.py @@ -0,0 +1,67 @@ +import os +import pytest +import logging +from logging.handlers import RotatingFileHandler +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +from .live_server import LiveServer + + +@pytest.fixture(scope="session") +def live_app(app): + handler = RotatingFileHandler('log/acceptance.log', maxBytes=10000, backupCount=1) + handler.setLevel(logging.INFO) + app.logger.addHandler(handler) + + runnable = LiveServer(app, port=8943, timeout=10) + runnable.spawn_live_server() + app.server_url = runnable.server_url + + yield app + + runnable.terminate() + + +@pytest.fixture(scope="session") +def browserstack_config(): + return { + "win7_ie10": { + "browser": "IE", + "browser_version": "10.0", + "os": "Windows", + "os_version": "7", + "resolution": "1024x768", + "browserstack.local": True, + }, + "iphone7": { + 'browserName': 'iPhone', + 'device': 'iPhone 7', + 'realMobile': 'true', + 'os_version': '10.3', + "browserstack.local": True, + } + } + + +@pytest.fixture(scope="session") +def driver_builder(browserstack_config): + def build_driver(config_key): + return webdriver.Remote( + command_executor="http://{}:{}@hub.browserstack.com:80/wd/hub".format( + os.getenv("BROWSERSTACK_EMAIL"), os.getenv("BROWSERSTACK_TOKEN") + ), + desired_capabilities=browserstack_config.get(config_key), + ) + + return build_driver + + +@pytest.fixture(scope="session") +def ie10_driver(driver_builder): + driver = driver_builder("win7_ie10") + + yield driver + + driver.quit() diff --git a/tests/acceptance/live_server.py b/tests/acceptance/live_server.py new file mode 100644 index 00000000..2dd46baf --- /dev/null +++ b/tests/acceptance/live_server.py @@ -0,0 +1,109 @@ +import gc +import multiprocessing +import socket +import socketserver +import time +from urllib.parse import urlparse, urljoin + +# This is adapted from flask-testing, https://github.com/jarus/flask-testing +# Inspired by https://docs.djangoproject.com/en/dev/topics/testing/#django.test.LiveServerTestCase +class LiveServer: + def __init__(self, app, port=5000, timeout=5): + self.app = app + self._configured_port = port + self._timeout = timeout + self._port_value = multiprocessing.Value("i", self._configured_port) + + @property + def server_url(self): + return "http://localhost:%s" % self._port_value.value + + def spawn_live_server(self): + self._process = None + port_value = self._port_value + + def worker(app, port): + # Based on solution: http://stackoverflow.com/a/27598916 + # Monkey-patch the server_bind so we can determine the port bound + # by Flask. This handles the case where the port specified is `0`, + # which means that the OS chooses the port. This is the only known + # way (currently) of getting the port out of Flask once we call + # `run`. + original_socket_bind = socketserver.TCPServer.server_bind + + def socket_bind_wrapper(self): + ret = original_socket_bind(self) + + # Get the port and save it into the port_value, so the parent + # process can read it. + (_, port) = self.socket.getsockname() + port_value.value = port + socketserver.TCPServer.server_bind = original_socket_bind + return ret + + socketserver.TCPServer.server_bind = socket_bind_wrapper + app.run(port=port, use_reloader=False) + + self._process = multiprocessing.Process( + target=worker, args=(self.app, self._configured_port) + ) + + self._process.start() + + # We must wait for the server to start listening, but give up + # after a specified maximum timeout + start_time = time.time() + + while True: + elapsed_time = time.time() - start_time + if elapsed_time > self._timeout: + raise RuntimeError( + "Failed to start the server after %d seconds. " % self._timeout + ) + + if self._can_ping_server(): + break + + def _can_ping_server(self): + host, port = self.address + if port == 0: + # Port specified by the user was 0, and the OS has not yet assigned + # the proper port. + return False + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect((host, port)) + except socket.error as e: + success = False + else: + success = True + finally: + sock.close() + + return success + + @property + def address(self): + """ + Gets the server address used to test the connection with a socket. + Respects both the LIVESERVER_PORT config value and overriding server_url + """ + parts = urlparse(self.server_url) + + host = parts.hostname + port = parts.port + + if port is None: + if parts.scheme == "http": + port = 80 + elif parts.scheme == "https": + port = 443 + else: + raise RuntimeError("Unsupported server url scheme: %s" % parts.scheme) + + return host, port + + def terminate(self): + if self._process: + self._process.terminate() diff --git a/tests/acceptance/test_basic.py b/tests/acceptance/test_basic.py new file mode 100644 index 00000000..1c549596 --- /dev/null +++ b/tests/acceptance/test_basic.py @@ -0,0 +1,3 @@ +def test_can_get_title(live_app, ie10_driver): + ie10_driver.get(live_app.server_url) + assert "JEDI" in ie10_driver.title From 0d10ef1b6be297511650d2fbfd26af8edcadbda5 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 1 Oct 2018 13:07:30 -0400 Subject: [PATCH 03/15] refactor selenium driver handling --- tests/acceptance/browsers.py | 18 ++++++++++ tests/acceptance/conftest.py | 65 +++++++++++++++++++--------------- tests/acceptance/test_basic.py | 12 +++++-- 3 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 tests/acceptance/browsers.py diff --git a/tests/acceptance/browsers.py b/tests/acceptance/browsers.py new file mode 100644 index 00000000..f9c30fcd --- /dev/null +++ b/tests/acceptance/browsers.py @@ -0,0 +1,18 @@ +BROWSERSTACK_CONFIG = { + "win7_ie10": { + "browser": "IE", + "browser_version": "10.0", + "os": "Windows", + "os_version": "7", + "resolution": "1024x768", + "browserstack.local": True, + }, + "win10_chrome62": { + "browser": "Chrome", + "browser_version": "62.0", + "os": "Windows", + "os_version": "10", + "resolution": "1024x768", + "browserstack.local": True, + }, +} diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index b19b9855..64e6aa95 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -2,16 +2,18 @@ import os import pytest import logging from logging.handlers import RotatingFileHandler +from collections import Mapping from selenium import webdriver from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from .live_server import LiveServer +from .browsers import BROWSERSTACK_CONFIG @pytest.fixture(scope="session") def live_app(app): - handler = RotatingFileHandler('log/acceptance.log', maxBytes=10000, backupCount=1) + handler = RotatingFileHandler("log/acceptance.log", maxBytes=10000, backupCount=1) handler.setLevel(logging.INFO) app.logger.addHandler(handler) @@ -24,44 +26,49 @@ def live_app(app): runnable.terminate() -@pytest.fixture(scope="session") -def browserstack_config(): - return { - "win7_ie10": { - "browser": "IE", - "browser_version": "10.0", - "os": "Windows", - "os_version": "7", - "resolution": "1024x768", - "browserstack.local": True, - }, - "iphone7": { - 'browserName': 'iPhone', - 'device': 'iPhone 7', - 'realMobile': 'true', - 'os_version': '10.3', - "browserstack.local": True, - } - } +class DriverCollection(Mapping): + """ + Allows access to drivers with dictionary syntax. Keeps track of which ones + have already been initialized. Allows teardown of all existing drivers. + """ + def __init__(self): + self._drivers = {} -@pytest.fixture(scope="session") -def driver_builder(browserstack_config): - def build_driver(config_key): + def __iter__(self): + return iter(self._drivers) + + def __len__(self): + return len(self._drivers) + + def __getitem__(self, name): + if name in self._drivers: + return self._drivers[name] + + elif name in BROWSERSTACK_CONFIG: + self._drivers[name] = self._build_driver(name) + return self._drivers[name] + + else: + raise AttributeError("Driver {} not found".format(name)) + + def _build_driver(self, config_key): return webdriver.Remote( command_executor="http://{}:{}@hub.browserstack.com:80/wd/hub".format( os.getenv("BROWSERSTACK_EMAIL"), os.getenv("BROWSERSTACK_TOKEN") ), - desired_capabilities=browserstack_config.get(config_key), + desired_capabilities=BROWSERSTACK_CONFIG.get(config_key), ) - return build_driver + def teardown(self): + for driver in self._drivers.values(): + driver.quit() @pytest.fixture(scope="session") -def ie10_driver(driver_builder): - driver = driver_builder("win7_ie10") +def drivers(): + driver_collection = DriverCollection() - yield driver + yield driver_collection - driver.quit() + driver_collection.teardown() diff --git a/tests/acceptance/test_basic.py b/tests/acceptance/test_basic.py index 1c549596..b6d68e75 100644 --- a/tests/acceptance/test_basic.py +++ b/tests/acceptance/test_basic.py @@ -1,3 +1,9 @@ -def test_can_get_title(live_app, ie10_driver): - ie10_driver.get(live_app.server_url) - assert "JEDI" in ie10_driver.title +import pytest +from .browsers import BROWSERSTACK_CONFIG + + +@pytest.mark.parametrize("browser_type", BROWSERSTACK_CONFIG.keys()) +def test_can_get_title(browser_type, live_app, drivers): + driver = drivers[browser_type] + driver.get(live_app.server_url) + assert "JEDI" in driver.title From 4a97c1d0fd791f2df9f680cf8b648742df2e6761 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 1 Oct 2018 13:16:17 -0400 Subject: [PATCH 04/15] add readme info about selenium testing --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index bd646be0..264c44aa 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,27 @@ To re-run tests each time a file is changed: pipenv run ptw +### Selenium Tests + +Selenium tests rely on BrowserStack. In order to run the Selenium tests +locally, you need BrowserStack credentials. The user email and key can +be found on the account settings page. To run the selenium tests: + +``` +BROWSERSTACK_TOKEN= BROWSERSTACK_EMAIL= ./script/selenium_test +``` + +The selenium tests are in `tests/acceptance`. This directory is ignored by +pytest for normal test runs. + +The `selenium_test` script manages the setup of a separate database and +launching the BrowserStackLocal client. If you already have the client running +locally, you can run the selenium tests with: + +``` +BROWSERSTACK_TOKEN= BROWSERSTACK_EMAIL= pipenv run pytest tests/acceptance +``` + ## Notes Jinja templates are like mustache templates -- add the From 04b0b1db0d89acef184665438040f017b3170b3f Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 1 Oct 2018 15:57:48 -0400 Subject: [PATCH 05/15] reformat --- .coverage | Bin 0 -> 131072 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .coverage diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..3a4d946bc62031cfd6d43683d57fd8a8740046b9 GIT binary patch literal 131072 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 Date: Tue, 2 Oct 2018 12:59:19 -0400 Subject: [PATCH 06/15] acceptance tests can login to site --- tests/acceptance/conftest.py | 8 +++++ tests/acceptance/test_basic.py | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index 64e6aa95..fd2533a8 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -11,6 +11,14 @@ from .live_server import LiveServer from .browsers import BROWSERSTACK_CONFIG +@pytest.fixture(scope="function", autouse=True) +def session(db, request): + """ + Override base test session + """ + pass + + @pytest.fixture(scope="session") def live_app(app): handler = RotatingFileHandler("log/acceptance.log", maxBytes=10000, backupCount=1) diff --git a/tests/acceptance/test_basic.py b/tests/acceptance/test_basic.py index b6d68e75..2fbea25e 100644 --- a/tests/acceptance/test_basic.py +++ b/tests/acceptance/test_basic.py @@ -1,5 +1,17 @@ import pytest +import requests +from flask import url_for +from urllib.parse import urljoin from .browsers import BROWSERSTACK_CONFIG +from atst.domain.users import Users +import atst.domain.exceptions as exceptions +from tests.test_auth import _login + +import cryptography.x509 as x509 +from cryptography.hazmat.backends import default_backend + + +USER_CERT = "ssl/client-certs/atat.mil.crt" @pytest.mark.parametrize("browser_type", BROWSERSTACK_CONFIG.keys()) @@ -7,3 +19,51 @@ def test_can_get_title(browser_type, live_app, drivers): driver = drivers[browser_type] driver.get(live_app.server_url) assert "JEDI" in driver.title + + +def _get_common_name(cert_path): + with open(USER_CERT, "rb") as cert_file: + cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) + common_names = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) + return common_names[0].value + + +@pytest.fixture(scope="module") +def valid_user_from_cert(): + cn = _get_common_name(USER_CERT) + cn_parts = cn.split(".") + user_info = { + "last_name": cn_parts[0], + "first_name": cn_parts[1], + "dod_id": cn_parts[-1], + "atat_role_name": "developer", + } + return Users.get_or_create_by_dod_id(**user_info) + + +def _valid_login(client, driver): + with open(USER_CERT) as cert: + response = _login(client, cert=cert.read()) + cookie = [cookie for cookie in client.cookie_jar][0] + driver.add_cookie( + { + "domain": None, + "name": cookie.name, + "value": cookie.value, + "path": cookie.path, + "secure": cookie.secure, + "expiry": cookie.expires, + } + ) + + +def test_login(live_app, drivers, client, valid_user_from_cert): + driver = drivers["win10_chrome62"] + driver.get(live_app.server_url) + cookie = _valid_login(client, driver) + requests_page = urljoin( + live_app.server_url, url_for("requests.requests_form_new", screen=1) + ) + driver.get(requests_page) + user = valid_user_from_cert + assert user.last_name in driver.page_source From 586a1eee5db219611e5699c4efcfc4289efc971a Mon Sep 17 00:00:00 2001 From: dandds Date: Tue, 2 Oct 2018 13:58:09 -0400 Subject: [PATCH 07/15] whoops, pytest-flask already has a live-server, thanks pytest-flask --- tests/acceptance/conftest.py | 11 +--- tests/acceptance/live_server.py | 109 -------------------------------- tests/acceptance/test_basic.py | 14 ++-- 3 files changed, 9 insertions(+), 125 deletions(-) delete mode 100644 tests/acceptance/live_server.py diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index fd2533a8..249185ca 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -7,7 +7,6 @@ from selenium import webdriver from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.desired_capabilities import DesiredCapabilities -from .live_server import LiveServer from .browsers import BROWSERSTACK_CONFIG @@ -20,18 +19,12 @@ def session(db, request): @pytest.fixture(scope="session") -def live_app(app): +def app(app): handler = RotatingFileHandler("log/acceptance.log", maxBytes=10000, backupCount=1) handler.setLevel(logging.INFO) app.logger.addHandler(handler) - runnable = LiveServer(app, port=8943, timeout=10) - runnable.spawn_live_server() - app.server_url = runnable.server_url - - yield app - - runnable.terminate() + return app class DriverCollection(Mapping): diff --git a/tests/acceptance/live_server.py b/tests/acceptance/live_server.py deleted file mode 100644 index 2dd46baf..00000000 --- a/tests/acceptance/live_server.py +++ /dev/null @@ -1,109 +0,0 @@ -import gc -import multiprocessing -import socket -import socketserver -import time -from urllib.parse import urlparse, urljoin - -# This is adapted from flask-testing, https://github.com/jarus/flask-testing -# Inspired by https://docs.djangoproject.com/en/dev/topics/testing/#django.test.LiveServerTestCase -class LiveServer: - def __init__(self, app, port=5000, timeout=5): - self.app = app - self._configured_port = port - self._timeout = timeout - self._port_value = multiprocessing.Value("i", self._configured_port) - - @property - def server_url(self): - return "http://localhost:%s" % self._port_value.value - - def spawn_live_server(self): - self._process = None - port_value = self._port_value - - def worker(app, port): - # Based on solution: http://stackoverflow.com/a/27598916 - # Monkey-patch the server_bind so we can determine the port bound - # by Flask. This handles the case where the port specified is `0`, - # which means that the OS chooses the port. This is the only known - # way (currently) of getting the port out of Flask once we call - # `run`. - original_socket_bind = socketserver.TCPServer.server_bind - - def socket_bind_wrapper(self): - ret = original_socket_bind(self) - - # Get the port and save it into the port_value, so the parent - # process can read it. - (_, port) = self.socket.getsockname() - port_value.value = port - socketserver.TCPServer.server_bind = original_socket_bind - return ret - - socketserver.TCPServer.server_bind = socket_bind_wrapper - app.run(port=port, use_reloader=False) - - self._process = multiprocessing.Process( - target=worker, args=(self.app, self._configured_port) - ) - - self._process.start() - - # We must wait for the server to start listening, but give up - # after a specified maximum timeout - start_time = time.time() - - while True: - elapsed_time = time.time() - start_time - if elapsed_time > self._timeout: - raise RuntimeError( - "Failed to start the server after %d seconds. " % self._timeout - ) - - if self._can_ping_server(): - break - - def _can_ping_server(self): - host, port = self.address - if port == 0: - # Port specified by the user was 0, and the OS has not yet assigned - # the proper port. - return False - - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sock.connect((host, port)) - except socket.error as e: - success = False - else: - success = True - finally: - sock.close() - - return success - - @property - def address(self): - """ - Gets the server address used to test the connection with a socket. - Respects both the LIVESERVER_PORT config value and overriding server_url - """ - parts = urlparse(self.server_url) - - host = parts.hostname - port = parts.port - - if port is None: - if parts.scheme == "http": - port = 80 - elif parts.scheme == "https": - port = 443 - else: - raise RuntimeError("Unsupported server url scheme: %s" % parts.scheme) - - return host, port - - def terminate(self): - if self._process: - self._process.terminate() diff --git a/tests/acceptance/test_basic.py b/tests/acceptance/test_basic.py index 2fbea25e..bd703fe9 100644 --- a/tests/acceptance/test_basic.py +++ b/tests/acceptance/test_basic.py @@ -15,9 +15,10 @@ USER_CERT = "ssl/client-certs/atat.mil.crt" @pytest.mark.parametrize("browser_type", BROWSERSTACK_CONFIG.keys()) -def test_can_get_title(browser_type, live_app, drivers): +@pytest.mark.usefixtures('live_server') +def test_can_get_title(browser_type, app, drivers): driver = drivers[browser_type] - driver.get(live_app.server_url) + driver.get(url_for("atst.root", _external=True)) assert "JEDI" in driver.title @@ -57,13 +58,12 @@ def _valid_login(client, driver): ) -def test_login(live_app, drivers, client, valid_user_from_cert): +@pytest.mark.usefixtures('live_server') +def test_login(drivers, client, app, valid_user_from_cert): driver = drivers["win10_chrome62"] - driver.get(live_app.server_url) + driver.get(url_for("atst.root", _external=True)) cookie = _valid_login(client, driver) - requests_page = urljoin( - live_app.server_url, url_for("requests.requests_form_new", screen=1) - ) + requests_page = url_for("requests.requests_form_new", screen=1, _external=True) driver.get(requests_page) user = valid_user_from_cert assert user.last_name in driver.page_source From 5df28ba3313581001009ae6e27c110df31f71d16 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 09:21:21 -0400 Subject: [PATCH 08/15] end-to-end tests should log in via login-dev endpoint --- tests/acceptance/test_basic.py | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/tests/acceptance/test_basic.py b/tests/acceptance/test_basic.py index bd703fe9..a130d1fc 100644 --- a/tests/acceptance/test_basic.py +++ b/tests/acceptance/test_basic.py @@ -5,6 +5,7 @@ from urllib.parse import urljoin from .browsers import BROWSERSTACK_CONFIG from atst.domain.users import Users import atst.domain.exceptions as exceptions +from atst.routes.dev import _DEV_USERS as DEV_USERS from tests.test_auth import _login import cryptography.x509 as x509 @@ -15,7 +16,7 @@ USER_CERT = "ssl/client-certs/atat.mil.crt" @pytest.mark.parametrize("browser_type", BROWSERSTACK_CONFIG.keys()) -@pytest.mark.usefixtures('live_server') +@pytest.mark.usefixtures("live_server") def test_can_get_title(browser_type, app, drivers): driver = drivers[browser_type] driver.get(url_for("atst.root", _external=True)) @@ -42,28 +43,8 @@ def valid_user_from_cert(): return Users.get_or_create_by_dod_id(**user_info) -def _valid_login(client, driver): - with open(USER_CERT) as cert: - response = _login(client, cert=cert.read()) - cookie = [cookie for cookie in client.cookie_jar][0] - driver.add_cookie( - { - "domain": None, - "name": cookie.name, - "value": cookie.value, - "path": cookie.path, - "secure": cookie.secure, - "expiry": cookie.expires, - } - ) - - -@pytest.mark.usefixtures('live_server') +@pytest.mark.usefixtures("live_server") def test_login(drivers, client, app, valid_user_from_cert): - driver = drivers["win10_chrome62"] - driver.get(url_for("atst.root", _external=True)) - cookie = _valid_login(client, driver) - requests_page = url_for("requests.requests_form_new", screen=1, _external=True) - driver.get(requests_page) - user = valid_user_from_cert - assert user.last_name in driver.page_source + driver = drivers["win7_ie10"] + driver.get(url_for("dev.login_dev", _external=True)) + assert "Sign in" not in driver.title From ef041d7e1cb9622993813bebeb68684e559f3f55 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 09:21:36 -0400 Subject: [PATCH 09/15] dont track coverage file --- .coverage | Bin 131072 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .coverage diff --git a/.coverage b/.coverage deleted file mode 100644 index 3a4d946bc62031cfd6d43683d57fd8a8740046b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131072 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 Date: Mon, 15 Oct 2018 10:03:13 -0400 Subject: [PATCH 10/15] catch 500 errors and remove unnecessary acceptance test fixture --- atst/routes/errors.py | 5 +++++ tests/acceptance/conftest.py | 10 ---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/atst/routes/errors.py b/atst/routes/errors.py index 40a00c26..f2fee662 100644 --- a/atst/routes/errors.py +++ b/atst/routes/errors.py @@ -17,4 +17,9 @@ def make_error_pages(app): app.logger.error(e.message) return render_template("error.html", message="Log in Failed"), 401 + @app.errorhandler(Exception) + def exception(e): + app.logger.error(e.message) + return render_template("error.html", message="An Unexpected Error Occurred"), 500 + return app diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index 249185ca..29aa2953 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -1,7 +1,6 @@ import os import pytest import logging -from logging.handlers import RotatingFileHandler from collections import Mapping from selenium import webdriver from selenium.webdriver.common.keys import Keys @@ -18,15 +17,6 @@ def session(db, request): pass -@pytest.fixture(scope="session") -def app(app): - handler = RotatingFileHandler("log/acceptance.log", maxBytes=10000, backupCount=1) - handler.setLevel(logging.INFO) - app.logger.addHandler(handler) - - return app - - class DriverCollection(Mapping): """ Allows access to drivers with dictionary syntax. Keeps track of which ones From cbf188df5f37ae40717c7f3f819b9a2c036ba27f Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 10:04:33 -0400 Subject: [PATCH 11/15] more browserstack info in readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 264c44aa..a0c82d63 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,10 @@ locally, you can run the selenium tests with: BROWSERSTACK_TOKEN= BROWSERSTACK_EMAIL= pipenv run pytest tests/acceptance ``` +The BrowserStack email is the one associated with the account. The token is +available in the BrowserStack profile information page. Go to the dashboard, +then "Account" > "Settings", then the token is under "Local Testing". + ## Notes Jinja templates are like mustache templates -- add the From 4e653a52cfcc0a7baec7bd4ca35ba2b0e94c9680 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 10:06:11 -0400 Subject: [PATCH 12/15] do not disable CSRF for selenium testing --- config/selenium.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/config/selenium.ini b/config/selenium.ini index 5ad48446..054b8ed3 100644 --- a/config/selenium.ini +++ b/config/selenium.ini @@ -1,4 +1,3 @@ [default] PGDATABASE = atat_selenium CRL_DIRECTORY = tests/fixtures/crl -WTF_CSRF_ENABLED = false From 8a14560a9bada9defb546d7bd9f5f6c96c863260 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 10:15:29 -0400 Subject: [PATCH 13/15] trap and kill BrowserStackLocal process in selenium_test script --- script/selenium_test | 1 + 1 file changed, 1 insertion(+) diff --git a/script/selenium_test b/script/selenium_test index fcf3515a..1d583397 100755 --- a/script/selenium_test +++ b/script/selenium_test @@ -43,6 +43,7 @@ fi echo "starting BrowserStack local client..." ./$BSL_FILE --key $BROWSERSTACK_TOKEN & BSL_ID=$! +trap "kill $BSL_ID" SIGTERM SIGINT EXIT # run example selenium script that fetches the home page echo "running selenium tests" From e514aa9a94f8ed2e32f6d62743181bca04fd9cf6 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 10:19:48 -0400 Subject: [PATCH 14/15] disable coverage for acceptance tests --- atst/routes/errors.py | 1 + script/selenium_test | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/atst/routes/errors.py b/atst/routes/errors.py index f2fee662..5b6a2ff2 100644 --- a/atst/routes/errors.py +++ b/atst/routes/errors.py @@ -18,6 +18,7 @@ def make_error_pages(app): return render_template("error.html", message="Log in Failed"), 401 @app.errorhandler(Exception) + # pylint: disable=unused-variable def exception(e): app.logger.error(e.message) return render_template("error.html", message="An Unexpected Error Occurred"), 500 diff --git a/script/selenium_test b/script/selenium_test index 1d583397..b233c069 100755 --- a/script/selenium_test +++ b/script/selenium_test @@ -47,7 +47,7 @@ trap "kill $BSL_ID" SIGTERM SIGINT EXIT # run example selenium script that fetches the home page echo "running selenium tests" -pipenv run pytest tests/acceptance -s +pipenv run pytest tests/acceptance -s --no-cov # kill BrowserStackLocal kill $BSL_ID From 9fcb016f86dd11357c4ac632401f35f316f7af7a Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 15 Oct 2018 10:30:54 -0400 Subject: [PATCH 15/15] formatting --- atst/routes/errors.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/atst/routes/errors.py b/atst/routes/errors.py index 5b6a2ff2..9d70578c 100644 --- a/atst/routes/errors.py +++ b/atst/routes/errors.py @@ -21,6 +21,9 @@ def make_error_pages(app): # pylint: disable=unused-variable def exception(e): app.logger.error(e.message) - return render_template("error.html", message="An Unexpected Error Occurred"), 500 + return ( + render_template("error.html", message="An Unexpected Error Occurred"), + 500, + ) return app