Initial set up for Ghost Inspector integration tests.

Adds a CircleCI integration for Ghost Inspector
(https://ghostinspector.com), a headless browser testing SaaS. The
README is updated with details about how to run GI locally.

Removes the bootstrap setup for Selenium testing with BrowserStack.
This commit is contained in:
dandds 2019-10-10 09:22:10 -04:00
parent 73a459ea28
commit 7949c64b9b
8 changed files with 101 additions and 172 deletions

View File

@ -135,6 +135,67 @@ jobs:
- run: "docker tag ${AZURE_SERVER_NAME}/atat:atat-${CIRCLE_SHA1} ${AZURE_SERVER_NAME}/atat:latest"
- run: "docker push ${AZURE_SERVER_NAME}/atat:latest"
integration-tests:
docker:
- image: docker:17.05.0-ce-git
steps:
- setup_remote_docker:
version: 18.06.0-ce
- checkout
- run:
name: Set up temporary docker network
command: docker network create atat
- run:
name: Build image
command: docker build . -t atat:latest
- run:
name: Get storage containers
command: docker pull postgres:latest && docker pull redis:latest
- run:
name: Start redis
command: docker run -d --network atat --link redis:redis -p 6379:6379 --name redis redis:latest
- run:
name: Start postgres
command: docker run -d --network atat --link postgres:postgres -p 5432:5432 --name postgres postgres:latest
- run:
name: Start application container
command: |
docker run -d \
-e DISABLE_CRL_CHECK=true \
-e PGHOST=postgres \
-e REDIS_URI=redis://redis:6379 \
-p 8000:8000 \
--network atat \
--name test-atat \
atat:latest \
uwsgi \
--callable app \
--module app \
--plugin python3 \
--virtualenv /opt/atat/atst/.venv \
--http-socket :8000
- run:
name: Wait for containers
command: sleep 3
- run:
name: Create database
command: docker exec postgres createdb -U postgres atat
- run:
name: Apply migrations
command: docker exec test-atat .venv/bin/python .venv/bin/alembic upgrade head
- run:
name: Execute Ghost Inspector test suite
command: |
docker pull ghostinspector/test-runner-standalone:latest
docker run \
-e NGROK_TOKEN=$NGROK_TOKEN \
-e GI_API_KEY=$GI_API_KEY \
-e GI_SUITE=$GI_SUITE \
-e GI_PARAMS_JSON='{}' \
-e APP_PORT="test-atat:8000" \
--network atat \
ghostinspector/test-runner-standalone:latest
workflows:
version: 2
run-tests:
@ -143,9 +204,12 @@ workflows:
- test:
requires:
- app_setup
- azure-build-and-push-image:
- integration-tests:
requires:
- test
- azure-build-and-push-image:
requires:
- integration-tests
filters:
branches:
only:
@ -204,7 +268,7 @@ workflows:
repo: atat
tag: "atat-${CIRCLE_SHA1},latest"
requires:
- test
- integration-tests
filters:
branches:
only:

6
.gitignore vendored
View File

@ -52,10 +52,12 @@ ssl/client-certs/*.srl
# je coverage output
coverage
# selenium testing
# BrowserStack
browserstacklocal
# decompiled CircleCI yaml
local-ci.yml
# python config
.python-version

View File

@ -3,7 +3,7 @@
"files": "^.secrets.baseline$",
"lines": null
},
"generated_at": "2019-10-03T14:34:50Z",
"generated_at": "2019-10-14T19:14:26Z",
"plugins_used": [
{
"base64_limit": 4.5,
@ -40,6 +40,13 @@
"is_verified": false,
"line_number": 156,
"type": "Secret Keyword"
},
{
"hashed_secret": "81b127e2222d9bfc4609053faec85300f7525463",
"is_secret": false,
"is_verified": false,
"line_number": 244,
"type": "Secret Keyword"
}
],
"alembic.ini": [
@ -153,24 +160,6 @@
"type": "Private Key"
}
],
"tests/acceptance/conftest.py": [
{
"hashed_secret": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
"is_secret": false,
"is_verified": false,
"line_number": 48,
"type": "Basic Auth Credentials"
}
],
"tests/fixtures/chain/make-chain.sh": [
{
"hashed_secret": "bad2e396920ce37fe53fc291f90b130d915375fb",
"is_secret": false,
"is_verified": false,
"line_number": 35,
"type": "Secret Keyword"
}
],
"tests/forms/test_validators.py": [
{
"hashed_secret": "260408f687da9094705a841acda9b029563053ee",
@ -194,7 +183,7 @@
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
"is_secret": false,
"is_verified": false,
"line_number": 651,
"line_number": 656,
"type": "Hex High Entropy String"
}
]

View File

@ -220,30 +220,37 @@ To generate coverage reports for the Javascript tests:
yarn test:coverage
### Selenium Tests
### Ghost Inspector 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:
AT-AT uses [Ghost Inpsector](https://app.ghostinspector.com/) for
integration testing. These tests do not run locally as part of the
regular test suite but do run in CI. To run them locally, you will
need the following:
- [docker](https://docs.docker.com/v17.12/install/)
- [circleci CLI tool](https://circleci.com/docs/2.0/local-cli/#installation)
- the prerequisite variable information listed [here](https://ghostinspector.com/docs/integration/circle-ci/)
The version of our CircleCI config (2.1) is incompatible with the
`circleci` tool. First run:
```
BROWSERSTACK_TOKEN=<token> BROWSERSTACK_EMAIL=<email> ./script/selenium_test
circleci config process .circleci/config.yml > local-ci.yml
```
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:
Then run the job:
```
BROWSERSTACK_TOKEN=<token> BROWSERSTACK_EMAIL=<email> pipenv run pytest tests/acceptance
circleci local execute -e GI_SUITE=<SUITE_ID> -e GI_API_KEY=<API KEY> -e NGROK_TOKEN=<NGROK TOKEN> --job integration-tests -c local-ci.yml
```
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".
If the job fails and you want to re-run it, you may receive errors
about running docker containers or the network already existing.
Some version of the following should reset your local docker state:
```
docker container stop redis postgres test-atat; docker container rm redis postgres test-atat ; docker network rm atat
```
## Notes

View File

@ -1,18 +0,0 @@
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,
},
}

View File

@ -1,65 +0,0 @@
import os
import pytest
import logging
from collections import Mapping
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from .browsers import BROWSERSTACK_CONFIG
@pytest.fixture(scope="function", autouse=True)
def session(db, request):
"""
Override base test session
"""
pass
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 = {}
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),
)
def teardown(self):
for driver in self._drivers.values():
driver.quit()
@pytest.fixture(scope="session")
def drivers():
driver_collection = DriverCollection()
yield driver_collection
driver_collection.teardown()

View File

@ -1,50 +0,0 @@
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 atst.routes.dev import _DEV_USERS as DEV_USERS
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())
@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))
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)
@pytest.mark.usefixtures("live_server")
def test_login(drivers, client, app, valid_user_from_cert):
driver = drivers["win7_ie10"]
driver.get(url_for("dev.login_dev", _external=True))
assert "Sign in" not in driver.title