test harness for selenium testing
This commit is contained in:
parent
78af50fcf0
commit
63f94deb40
2
.gitignore
vendored
2
.gitignore
vendored
@ -45,5 +45,5 @@ config/dev.ini
|
|||||||
# coverage output
|
# coverage output
|
||||||
.coverage
|
.coverage
|
||||||
|
|
||||||
# selenium
|
# selenium testing
|
||||||
browserstacklocal
|
browserstacklocal
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
[default]
|
[default]
|
||||||
PGDATABASE = atat_selenium
|
PGDATABASE = atat_selenium
|
||||||
REDIS_URI = redis://redishost:6379
|
|
||||||
CRL_DIRECTORY = tests/fixtures/crl
|
CRL_DIRECTORY = tests/fixtures/crl
|
||||||
WTF_CSRF_ENABLED = false
|
WTF_CSRF_ENABLED = false
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
norecursedirs = .venv .git node_modules
|
norecursedirs = .venv .git node_modules
|
||||||
env =
|
env =
|
||||||
D:FLASK_ENV=test
|
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
|
||||||
|
@ -45,8 +45,8 @@ echo "starting BrowserStack local client..."
|
|||||||
BSL_ID=$!
|
BSL_ID=$!
|
||||||
|
|
||||||
# run example selenium script that fetches the home page
|
# run example selenium script that fetches the home page
|
||||||
echo "running selenium example script"
|
echo "running selenium tests"
|
||||||
pipenv run python selenium_example.py
|
pipenv run pytest tests/acceptance -s
|
||||||
|
|
||||||
# kill BrowserStackLocal
|
# kill BrowserStackLocal
|
||||||
kill $BSL_ID
|
kill $BSL_ID
|
||||||
|
@ -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")
|
|
0
tests/acceptance/__init__.py
Normal file
0
tests/acceptance/__init__.py
Normal file
67
tests/acceptance/conftest.py
Normal file
67
tests/acceptance/conftest.py
Normal file
@ -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()
|
109
tests/acceptance/live_server.py
Normal file
109
tests/acceptance/live_server.py
Normal file
@ -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()
|
3
tests/acceptance/test_basic.py
Normal file
3
tests/acceptance/test_basic.py
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user