diff --git a/Dockerfile b/Dockerfile index d40e022..ab9dc96 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,4 +57,4 @@ RUN pipenv install --python=$(which python3.6) ENTRYPOINT [ "pipenv", "run" ] -CMD ["pytest", "--driver", "Firefox", "--variables", "variables.json", "--html", "report.html"] +CMD ["pytest", "--driver", "Firefox", "--base-url", "http://redash:5000", "--verify-base-url", "--variables", "variables.json", "--html", "report.html"] diff --git a/Pipfile b/Pipfile index 0f788dc..aba5cce 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,7 @@ name = "pypi" [packages] pytest = "*" +pytest-base-url = "*" pytest-selenium = "*" pypom = "*" pytest-variables = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 4b6c586..a41f132 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,20 @@ { "_meta": { "hash": { - "sha256": "d6131fc6bb32a8212eeebf2348c457d8e2b3ee816f4efb0603666c850ffd8da6" + "sha256": "965c019e00fe38782f6b122d2615c37c079098f1084feaab10a760063b171b3f" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.1", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "17.5.0", + "platform_system": "Darwin", + "platform_version": "Darwin Kernel Version 17.5.0: Fri Apr 13 19:32:32 PDT 2018; root:xnu-4570.51.2~1/RELEASE_X86_64", + "python_full_version": "3.6.1", + "python_version": "3.6", + "sys_platform": "darwin" }, "pipfile-spec": 6, "requires": { @@ -18,52 +31,52 @@ "default": { "attrs": { "hashes": [ - "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", - "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450" + "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", + "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" ], - "version": "==17.4.0" + "version": "==18.1.0" }, "certifi": { "hashes": [ - "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", - "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0", + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7" ], "version": "==2018.4.16" }, "chardet": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" ], "version": "==3.0.4" }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" ], "version": "==2.6" }, "more-itertools": { "hashes": [ - "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", + "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44" ], "version": "==4.1.0" }, "pluggy": { "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", - "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" + "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5", + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" ], "version": "==0.6.0" }, "py": { "hashes": [ - "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", - "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" + "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a", + "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881" ], "version": "==1.5.3" }, @@ -72,15 +85,13 @@ "sha256:ea1d3b0297fdeccc47d034a005e6cd16e206ad32258aeffbca47f0c516a63082", "sha256:ece35948abcb65f4dc230bd7464323bc83e7c02d35aaaeea53fd728effc0df14" ], - "index": "pypi", "version": "==2.0.0" }, "pytest": { "hashes": [ - "sha256:54713b26c97538db6ff0703a12b19aeaeb60b5e599de542e7fca0ec83b9038e8", - "sha256:829230122facf05a5f81a6d4dfe6454a04978ea3746853b2b84567ecf8e5c526" + "sha256:829230122facf05a5f81a6d4dfe6454a04978ea3746853b2b84567ecf8e5c526", + "sha256:54713b26c97538db6ff0703a12b19aeaeb60b5e599de542e7fca0ec83b9038e8" ], - "index": "pypi", "version": "==3.5.1" }, "pytest-base-url": { @@ -95,13 +106,12 @@ "sha256:23bf611f3048decc1cf5281238574155d07190e0d1370f91318eec6b8031a6c1", "sha256:bbb6cb7936bc4944cd8122f72e9981157149714648d3596ec1ff2b2ed9baeac1" ], - "index": "pypi", "version": "==1.17.0" }, "pytest-metadata": { "hashes": [ - "sha256:2d495b61542cb25dfc52fbf40c7a02220c7c127b7ba8974e6c72d6c9593c547a", - "sha256:ec37c48f44e7973cc6d06b36a148d3a3432e5dda8b8a40239fb52099b202907f" + "sha256:ec37c48f44e7973cc6d06b36a148d3a3432e5dda8b8a40239fb52099b202907f", + "sha256:2d495b61542cb25dfc52fbf40c7a02220c7c127b7ba8974e6c72d6c9593c547a" ], "version": "==1.7.0" }, @@ -110,7 +120,6 @@ "sha256:050a357a8bc9d38241052a277a5da3d67ff4801a807e8918113a88defee11c97", "sha256:b3c36a212d0b5a3ba61fccbf9f754aa2286840dc2ae910a04b67c72685a1bca7" ], - "index": "pypi", "version": "==1.12.0" }, "pytest-variables": { @@ -118,7 +127,6 @@ "sha256:59c00b95779657532ac5f8209b28b5d447c8b4bc4210c1d6bdf9a42aa201f9b0", "sha256:7808b77b643b9f8a24f1ee1c32132648b1c62ab93956f20fe101dde66db6d09a" ], - "index": "pypi", "version": "==1.7.1" }, "requests": { @@ -130,15 +138,15 @@ }, "selenium": { "hashes": [ - "sha256:2b6f018e55f50e9c67a67caec2f73f806f72c162fb38cf3ea79e0a8f6506bf56", - "sha256:5841fb30c3965866220c34d16de8e3d091e2833fcac385160a63db0c3522a297" + "sha256:1372101ad23798462038481f92ba1c7fab8385c788b05da6b44318f10ea52422", + "sha256:b8a2630fd858636c894960726ca3c94d8277e516ea3a9d81614fb819a5844764" ], - "version": "==3.11.0" + "version": "==3.12.0" }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" ], "version": "==1.11.0" }, @@ -151,8 +159,8 @@ }, "zope.component": { "hashes": [ - "sha256:1b29aa65413f6dda29e64e2352a6aa13d9ba70078f6b91f328573488788d531c", - "sha256:2776ab93945c8df3781fdcc2126dc7080e9a1d64fef5f76a21550473cbb505bf" + "sha256:2776ab93945c8df3781fdcc2126dc7080e9a1d64fef5f76a21550473cbb505bf", + "sha256:1b29aa65413f6dda29e64e2352a6aa13d9ba70078f6b91f328573488788d531c" ], "version": "==4.4.1" }, @@ -165,15 +173,15 @@ }, "zope.interface": { "hashes": [ - "sha256:21506674d30c009271fe68a242d330c83b1b9d76d62d03d87e1e9528c61beea6", - "sha256:3d184aff0756c44fff7de69eb4cd5b5311b6f452d4de28cb08343b3f21993763", "sha256:467d364b24cb398f76ad5e90398d71b9325eb4232be9e8a50d6a3b3c7a1c8789", - "sha256:57c38470d9f57e37afb460c399eb254e7193ac7fb8042bd09bdc001981a9c74c", + "sha256:3d184aff0756c44fff7de69eb4cd5b5311b6f452d4de28cb08343b3f21993763", + "sha256:21506674d30c009271fe68a242d330c83b1b9d76d62d03d87e1e9528c61beea6", + "sha256:f41037260deaacb875db250021fe883bf536bf6414a4fd25b25059b02e31b120", "sha256:9ada83f4384bbb12dedc152bcdd46a3ac9f5f7720d43ac3ce3e8e8b91d733c10", - "sha256:a1daf9c5120f3cc6f2b5fef8e1d2a3fb7bbbb20ed4bfdc25bc8364bc62dcf54b", - "sha256:e6b77ae84f2b8502d99a7855fa33334a1eb6159de45626905cb3e454c023f339", "sha256:e881ef610ff48aece2f4ee2af03d2db1a146dc7c705561bd6089b2356f61641f", - "sha256:f41037260deaacb875db250021fe883bf536bf6414a4fd25b25059b02e31b120" + "sha256:e6b77ae84f2b8502d99a7855fa33334a1eb6159de45626905cb3e454c023f339", + "sha256:a1daf9c5120f3cc6f2b5fef8e1d2a3fb7bbbb20ed4bfdc25bc8364bc62dcf54b", + "sha256:57c38470d9f57e37afb460c399eb254e7193ac7fb8042bd09bdc001981a9c74c" ], "version": "==4.5.0" } diff --git a/docker-compose.ui-tests.yml b/docker-compose.ui-tests.yml new file mode 100644 index 0000000..079002b --- /dev/null +++ b/docker-compose.ui-tests.yml @@ -0,0 +1,11 @@ +# This configuration file is for **development** setup. For production, refer to +# docker-compose.production.yml. +version: '2' +services: + ui-tests: + image: mozilla/redash-ui-tests:latest + environment: + REDASH_SERVER_URL: http://redash:5000 + links: + - "server:redash" + entrypoint: ./docker-entrypoint.sh diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cbdcd00 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +version: '2' +services: + ui-tests: + build: . + environment: + REDASH_SERVER_URL: http://redash:5000 + links: + - "server:redash" + entrypoint: ./docker-entrypoint.sh + server: + image: redash/redash:latest + command: dev_server + depends_on: + - postgres + - redis + ports: + - "5000:5000" + environment: + PYTHONUNBUFFERED: 0 + REDASH_LOG_LEVEL: "INFO" + REDASH_REDIS_URL: "redis://redis:6379/0" + REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres" + REDASH_GOOGLE_CLIENT_ID: "dummy" + REDASH_GOOGLE_CLIENT_SECRET: "dummy" + restart: always + worker: + image: redash/redash:latest + command: scheduler + volumes_from: + - server + depends_on: + - server + environment: + PYTHONUNBUFFERED: 0 + REDASH_LOG_LEVEL: "INFO" + REDASH_REDIS_URL: "redis://redis:6379/0" + REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres" + QUEUES: "queries,scheduled_queries,celery" + WORKERS_COUNT: 2 + redis: + image: redis:3.0-alpine + restart: unless-stopped + postgres: + image: postgres:9.5.6-alpine + # The following turns the DB into less durable, but gains significant performance improvements for the tests run (x3 + # improvement on my personal machine). We should consider moving this into a dedicated Docker Compose configuration for + # tests. + command: "postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=OFF" + restart: unless-stopped diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..bf7906e --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +case "$1" in + ui_test) + exec pipenv run pytest --driver=Firefox --base-url=http://redash:5000 --verify-base-url --variables=variables.json --html=report.html + ;; + *) + exec "$@" + ;; +esac diff --git a/pages/login.py b/pages/login.py index 8354a03..41632c9 100644 --- a/pages/login.py +++ b/pages/login.py @@ -32,7 +32,7 @@ class LoginPage(Page): """Return the profile dropdown element.""" element = self.wait.until(expected.visibility_of_element_located(( By.CSS_SELECTOR, - ".dropdown--profile__username", + ".dropdown .dropdown--profile__username", ))) return element.text diff --git a/setup-docker.sh b/setup-docker.sh new file mode 100755 index 0000000..5045614 --- /dev/null +++ b/setup-docker.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +docker-compose -f docker-compose.yml run --rm server create_db +docker-compose -f docker-compose.yml run --rm postgres psql -h postgres -U postgres -c "create database tests" +wget localhost:5000/setup --post-data="name=Ashley McTest&email=ashley@example.com&password=REPLACE ME&org_name=default" -O /dev/null diff --git a/tests/conftest.py b/tests/conftest.py index 698c8c8..0902998 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,9 @@ import os import attr import pytest +import requests +from requests.packages.urllib3.util.retry import Retry +from requests.adapters import HTTPAdapter from pages.login import LoginPage @@ -18,7 +21,22 @@ class User: email = attr.ib(type=str) -@pytest.fixture(name='org') +@pytest.fixture(scope='session', autouse=True) +def _verify_url(request, base_url, user, org): + """Verifies the base URL. + + This will ping the base url until it returns a 200. + """ + verify = request.config.option.verify_base_url + if base_url and verify: + session = requests.Session() + retries = Retry(backoff_factor=0.1, + status_forcelist=[500, 502, 503, 504]) + session.mount(base_url, HTTPAdapter(max_retries=retries)) + session.get(base_url, verify=False) + + +@pytest.fixture(name='org', scope='session') def fixture_org(): """Return the slug of an org.""" return 'default' @@ -30,34 +48,14 @@ def fixture_unknown_user(variables, org): return User(**variables[org]['users']['unknown']) -@pytest.fixture(name='user') +@pytest.fixture(name='user', scope='session') def fixture_user(variables, org): """Return a registered user.""" return User(**variables[org]['users']['ashley']) -@pytest.fixture(name='server_url') -def fixture_server_url(request): - """Return the URL to the Redash server.""" - return request.config.option.server_url - - @pytest.fixture(name='login_page') -def fixture_login_page(selenium, server_url, org): +def fixture_login_page(selenium, base_url, org): """Return a page object model for the login page.""" - login_page = LoginPage(selenium, server_url, org=org) + login_page = LoginPage(selenium, base_url, org=org) return login_page.open() - - -def pytest_addoption(parser): - """Add custom options to pytest.""" - group = parser.getgroup('redash') - - group.addoption( - '--server-url', - action='store', - dest='server_url', - type=str, - default=os.getenv('REDASH_SERVER_URL', 'http://localhost:5000'), - help="URL to the Redash Server", - ) diff --git a/tests/test_login.py b/tests/test_login.py index adc9bfa..2b6fbe0 100644 --- a/tests/test_login.py +++ b/tests/test_login.py @@ -2,7 +2,10 @@ """UI tests for the login page.""" +import pytest + +@pytest.mark.nondestructive def test_login_wrong_user_credentials(login_page, unknown_user): """Test for a failed login attempt.""" assert login_page.title == 'Login to Redash' @@ -13,6 +16,7 @@ def test_login_wrong_user_credentials(login_page, unknown_user): assert login_page.title == 'Login to Redash' +@pytest.mark.nondestructive def test_login(login_page, user): """Test for a successful login attempt.""" assert login_page.title == 'Login to Redash'