merge from main

This commit is contained in:
Kenneth Kehl
2024-02-26 11:10:37 -08:00
8 changed files with 246 additions and 158 deletions

View File

@@ -7,12 +7,12 @@ permissions:
env:
NOTIFY_ENVIRONMENT: test
NEW_RELIC_CONFIG_FILE: newrelic.ini
NEW_RELIC_ENVIRONMENT: test
FLASK_APP: application.py
WERKZEUG_DEBUG_PIN: off
REDIS_ENABLED: 0
NODE_VERSION: 16.15.1
AWS_US_TOLL_FREE_NUMBER: "+18556438890"
ADMIN_BASE_URL: http://localhost:6012
jobs:
build:
@@ -44,29 +44,97 @@ jobs:
run: npm test
- name: Run py tests with coverage
run: poetry run coverage run --omit=*/notifications_utils/* -m pytest --maxfail=10 --ignore=tests/end_to_end tests/
# - name: Run E2E tests
# run: poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end
# env:
# NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
# NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
# NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
# NOTIFY_E2E_TEST_URI: ${{ secrets.NOTIFY_E2E_TEST_URI }}
- name: Check coverage threshold
run: poetry run coverage report --fail-under=90
# - name: Health check
# run: |
# response=$(curl -url ${{secrets.NOTIFY_E2E_TEST_URI}}_status)
# if grep -q "ok" <<< "$response"; then
# echo "Health check passed"
# else
# echo "Health check failed"
# exit 1
# fi
# env:
# NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
# NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
# NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
# NOTIFY_E2E_TEST_URI: ${{ secrets.NOTIFY_E2E_TEST_URI }}
end-to-end-tests:
permissions:
checks: write
pull-requests: write
contents: write
runs-on: ubuntu-latest
services:
postgres:
image: postgres
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: test_notification_api
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 6379 on service container to the host
- 6379:6379
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- uses: jwalton/gh-find-current-pr@v1
id: findPr
- name: Clone API
uses: actions/checkout@v3
with:
repository: GSA/notifications-api
path: 'notifications-api'
- name: Install API dependencies
working-directory: 'notifications-api'
run: make bootstrap
env:
DATABASE_URL: postgresql://user:password@localhost:5432/test_notification_api
SQLALCHEMY_DATABASE_TEST_URI: postgresql://user:password@localhost:5432/test_notification_api
REDIS_URL: redis://localhost:6379
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
NOTIFY_ENVIRONMENT: development
- name: Run API server
working-directory: 'notifications-api'
run: make run-procfile &
env:
DATABASE_URL: postgresql://user:password@localhost:5432/test_notification_api
SQLALCHEMY_DATABASE_TEST_URI: postgresql://user:password@localhost:5432/test_notification_api
REDIS_URL: redis://localhost:6379
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
NOTIFY_ENVIRONMENT: development
- name: Run Admin server
run: make run-flask &
env:
# API_HOST_NAME: https://notify-api-staging.app.cloud.gov
API_HOST_NAME: http://localhost:6011
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
ADMIN_CLIENT_USERNAME: notify-admin
NOTIFY_ENVIRONMENT: e2etest
NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
NOTIFY_E2E_TEST_URI: http://localhost:6012
- name: Run E2E tests
# Run the E2E tests against the code found in this PR.
# run: poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end
# --browser webkit doesn't work at this time.
run: make e2e-test
# Debugging for now to troubleshoot a connectivity issue to the local servers
# run: curl --request GET --url "http://localhost:6012"
env:
NOTIFY_ENVIRONMENT: e2etest
NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
NOTIFY_E2E_TEST_URI: http://localhost:6012
validate-new-relic-config:
runs-on: ubuntu-latest
@@ -76,6 +144,7 @@ jobs:
- uses: ./.github/actions/setup-project
- name: Validate NewRelic config
env:
NEW_RELIC_CONFIG_FILE: newrelic.ini
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
# Need to set a NEW_RELIC_ENVIRONMENT with monitor_mode: true
NEW_RELIC_ENVIRONMENT: staging

View File

@@ -80,8 +80,8 @@ dead-code:
.PHONY: e2e-test
e2e-test: export NEW_RELIC_ENVIRONMENT=test
e2e-test: ## Run end-to-end integration tests
poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end
e2e-test: ## Run end-to-end integration tests; note that --browser webkit isn't currently working
poetry run pytest -v --browser chromium --browser firefox tests/end_to_end
.PHONY: js-lint
js-lint: ## Run javascript linting scanners

View File

@@ -82,8 +82,7 @@ class Config(object):
"IBAN": "GB33BUKB20201555555555",
"swift": "ABCDEF12",
"notify_billing_email_addresses": [
"generic@digital.cabinet-office.gov.uk",
"first.last@digital.cabinet-office.gov.uk",
"tts-benefits-studio@gsa.gov",
],
}
@@ -153,6 +152,34 @@ class Staging(Production):
HEADER_COLOUR = "#00ff00" # $green
class E2ETest(Staging):
"""
An environment config that is intended to operate as if it were in the
staging environment but with the configuration of the development and test
environments so the E2E tests work.
"""
# Borrowed from development environment
DEBUG = True
SESSION_COOKIE_SECURE = False
SESSION_PROTECTION = None
HTTP_PROTOCOL = "http"
ASSET_DOMAIN = ""
ASSET_PATH = "/static/"
# Borrowed from test environment
TESTING = True
WTF_CSRF_ENABLED = False
# buckets - mirror staging
CSV_UPLOAD_BUCKET = cloud_config.s3_credentials(
"notify-api-csv-upload-bucket-staging"
)
LOGO_UPLOAD_BUCKET = cloud_config.s3_credentials(
"notify-admin-logo-upload-bucket-staging"
)
class Demo(Staging):
HEADER_COLOUR = "#6F72AF" # $mauve
@@ -174,6 +201,7 @@ class Scanning(Production):
configs = {
"development": Development,
"test": Test,
"e2etest": E2ETest,
"scanning": Scanning,
"staging": Staging,
"demo": Demo,

View File

@@ -1,7 +1,6 @@
import copy
import json
import os
import re
from contextlib import contextmanager
from datetime import date, datetime, timedelta
from unittest.mock import Mock, PropertyMock
@@ -529,7 +528,7 @@ def mock_update_service(mocker):
"sms_sender",
"permissions",
]
}
},
)
return {"data": service}
@@ -2355,7 +2354,7 @@ def client_request(logged_in_client, mocker, service_one): # noqa (C901 too com
_test_page_title=True,
_test_for_elements_without_class=True,
_optional_args="",
**endpoint_kwargs
**endpoint_kwargs,
):
return ClientRequest.get_url(
url_for(endpoint, **(endpoint_kwargs or {})) + _optional_args,
@@ -2374,7 +2373,7 @@ def client_request(logged_in_client, mocker, service_one): # noqa (C901 too com
_expected_redirect=None,
_test_page_title=True,
_test_for_elements_without_class=True,
**endpoint_kwargs
**endpoint_kwargs,
):
resp = logged_in_client.get(
url,
@@ -2416,7 +2415,7 @@ def client_request(logged_in_client, mocker, service_one): # noqa (C901 too com
_follow_redirects=False,
_expected_redirect=None,
_content_type=None,
**endpoint_kwargs
**endpoint_kwargs,
):
return ClientRequest.post_url(
url_for(endpoint, **(endpoint_kwargs or {})),
@@ -2475,7 +2474,7 @@ def client_request(logged_in_client, mocker, service_one): # noqa (C901 too com
_expected_status=302,
_optional_args="",
_content_type=None,
**endpoint_kwargs
**endpoint_kwargs,
):
return ClientRequest.post_response_from_url(
url_for(endpoint, **(endpoint_kwargs or {})) + _optional_args,
@@ -3521,89 +3520,6 @@ def mock_get_invited_org_user_by_id(mocker, sample_org_invite):
)
def login_for_end_to_end_testing(browser):
# Open a new page and go to the staging site.
context = browser.new_context()
page = context.new_page()
page.goto(os.getenv("NOTIFY_E2E_TEST_URI"))
sign_in_button = page.get_by_role("link", name="Sign in")
# Test trying to sign in.
sign_in_button.click()
# Wait for the next page to fully load.
page.wait_for_load_state("domcontentloaded")
# Check for the sign in form elements.
# NOTE: Playwright cannot find input elements by role and recommends using
# get_by_label() instead; however, hidden form elements do not have
# labels associated with them, hence the XPath!
# See https://playwright.dev/python/docs/api/class-page#page-get-by-label
# and https://playwright.dev/python/docs/locators#locate-by-css-or-xpath
# for more information.
email_address_input = page.get_by_label("Email address")
password_input = page.get_by_label("Password")
continue_button = page.get_by_role("button", name=re.compile("Continue"))
# Sign in to the site.
email_address_input.fill(os.getenv("NOTIFY_E2E_TEST_EMAIL"))
password_input.fill(os.getenv("NOTIFY_E2E_TEST_PASSWORD"))
continue_button.click()
# Wait for the next page to fully load.
page.wait_for_load_state("domcontentloaded")
# Check for the sign in form elements.
# NOTE: Playwright cannot find input elements by role and recommends using
# get_by_label() instead; however, hidden form elements do not have
# labels associated with them, hence the XPath!
# See https://playwright.dev/python/docs/api/class-page#page-get-by-label
# and https://playwright.dev/python/docs/locators#locate-by-css-or-xpath
# for more information.
# mfa_input = page.get_by_label('Text message code')
# continue_button = page.get_by_role('button', name=re.compile('Continue'))
# # Enter MFA code and continue.
# TODO: Revisit this at a later point in time.
# totp = pyotp.TOTP(
# os.getenv('MFA_TOTP_SECRET'),
# digits=int(os.getenv('MFA_TOTP_LENGTH'))
# )
# mfa_input.fill(totp.now())
# continue_button.click()
# page.wait_for_load_state('domcontentloaded')
# # Save storage state into the file.
# auth_state_path = os.path.join(
# os.getenv('NOTIFY_E2E_AUTH_STATE_PATH'),
# 'state.json'
# )
# context.storage_state(path=auth_state_path)
@pytest.fixture(scope="session")
def end_to_end_context(browser):
context = browser.new_context()
return context
@pytest.fixture(scope="session")
def end_to_end_authenticated_context(browser):
# Create and load a previously authenticated context for Playwright E2E
# tests.
login_for_end_to_end_testing(browser)
auth_state_path = os.path.join(
os.getenv("NOTIFY_E2E_AUTH_STATE_PATH"), "state.json"
)
context = browser.new_context(storage_state=auth_state_path)
return context
@pytest.fixture()
def fake_markdown_file():
input = "#Test"

View File

View File

@@ -0,0 +1,105 @@
import os
import re
import pytest
E2E_TEST_URI = os.getenv("NOTIFY_E2E_TEST_URI")
def login_for_end_to_end_testing(browser):
# Open a new page and go to the staging site.
context = browser.new_context()
page = context.new_page()
page.goto(f"{E2E_TEST_URI}/")
sign_in_button = page.get_by_role("link", name="Sign in")
# Test trying to sign in.
sign_in_button.click()
# Wait for the next page to fully load.
page.wait_for_load_state("domcontentloaded")
# Check for the sign in form elements.
# NOTE: Playwright cannot find input elements by role and recommends using
# get_by_label() instead; however, hidden form elements do not have
# labels associated with them, hence the XPath!
# See https://playwright.dev/python/docs/api/class-page#page-get-by-label
# and https://playwright.dev/python/docs/locators#locate-by-css-or-xpath
# for more information.
email_address_input = page.get_by_label("Email address")
password_input = page.get_by_label("Password")
continue_button = page.get_by_role("button", name=re.compile("Continue"))
# Sign in to the site.
email_address_input.fill(os.getenv("NOTIFY_E2E_TEST_EMAIL"))
password_input.fill(os.getenv("NOTIFY_E2E_TEST_PASSWORD"))
continue_button.click()
# Wait for the next page to fully load.
page.wait_for_load_state("domcontentloaded")
# Check for the sign in form elements.
# NOTE: Playwright cannot find input elements by role and recommends using
# get_by_label() instead; however, hidden form elements do not have
# labels associated with them, hence the XPath!
# See https://playwright.dev/python/docs/api/class-page#page-get-by-label
# and https://playwright.dev/python/docs/locators#locate-by-css-or-xpath
# for more information.
# mfa_input = page.get_by_label('Text message code')
# continue_button = page.get_by_role('button', name=re.compile('Continue'))
# # Enter MFA code and continue.
# TODO: Revisit this at a later point in time.
# totp = pyotp.TOTP(
# os.getenv('MFA_TOTP_SECRET'),
# digits=int(os.getenv('MFA_TOTP_LENGTH'))
# )
# mfa_input.fill(totp.now())
# continue_button.click()
# page.wait_for_load_state('domcontentloaded')
# Save storage state into the file.
auth_state_path = os.path.join(
os.getenv("NOTIFY_E2E_AUTH_STATE_PATH"), "state.json"
)
context.storage_state(path=auth_state_path)
@pytest.fixture(scope="session")
def end_to_end_context(browser):
context = browser.new_context()
return context
@pytest.fixture(scope="session")
def end_to_end_authenticated_context(browser):
# Create and load a previously authenticated context for Playwright E2E
# tests.
# login_for_end_to_end_testing(browser)
auth_state_path = os.path.join(
os.getenv("NOTIFY_E2E_AUTH_STATE_PATH"), "state.json"
)
context = browser.new_context(storage_state=auth_state_path)
return context
@pytest.fixture(scope="session")
def authenticated_page(end_to_end_context):
# Open a new page and go to the staging site.
page = end_to_end_context.new_page()
page.goto(f"{E2E_TEST_URI}/")
sign_in_button = page.get_by_role("link", name="Sign in")
# Sign in to the site - E2E test accounts are set to flow through.
sign_in_button.click()
# Wait for the next page to fully load.
page.wait_for_load_state("domcontentloaded")
return page

View File

@@ -4,33 +4,13 @@ import re
from playwright.sync_api import expect
def _bypass_sign_in(end_to_end_context):
# Open a new page and go to the staging site.
page = end_to_end_context.new_page()
page.goto(os.getenv("NOTIFY_E2E_TEST_URI"))
sign_in_button = page.get_by_role("link", name="Sign in")
# Test trying to sign in. Because we are loading the email and password
sign_in_button.click()
# Wait for the next page to fully load.
page.wait_for_load_state("domcontentloaded")
return page
E2E_TEST_URI = os.getenv("NOTIFY_E2E_TEST_URI")
def test_add_new_service_workflow(end_to_end_context):
# page = end_to_end_context.new_page()
page = _bypass_sign_in(end_to_end_context)
page.goto(os.getenv("NOTIFY_E2E_TEST_URI"))
def test_add_new_service_workflow(authenticated_page, end_to_end_context):
page = authenticated_page
page.goto(f"{E2E_TEST_URI}/")
# sign_in_button = page.get_by_role("link", name="Sign in")
#
# Test trying to sign in. Because we are loading the email and password
# sign_in_button.click()
#
# Wait for the next page to fully load.
page.wait_for_load_state("domcontentloaded")
@@ -41,9 +21,7 @@ def test_add_new_service_workflow(end_to_end_context):
browser_type=end_to_end_context.browser.browser_type.name,
)
accounts_uri = "{}accounts".format(os.getenv("NOTIFY_E2E_TEST_URI"))
page.goto(accounts_uri)
page.goto(f"{E2E_TEST_URI}/accounts")
# Check to make sure that we've arrived at the next page.
page.wait_for_load_state("domcontentloaded")
@@ -82,24 +60,13 @@ def test_add_new_service_workflow(end_to_end_context):
# Retrieve some prominent elements on the page for testing.
service_name_input = page.locator('xpath=//input[@name="name"]')
federal_radio_button = page.locator('xpath=//input[@value="federal"]')
state_radio_button = page.locator('xpath=//input[@value="state"]')
other_radio_button = page.locator('xpath=//input[@value="other"]')
add_service_button = page.get_by_role("button", name=re.compile("Add service"))
expect(service_name_input).to_be_visible()
expect(federal_radio_button).to_be_visible()
expect(state_radio_button).to_be_visible()
expect(other_radio_button).to_be_visible()
expect(add_service_button).to_be_visible()
# Fill in the form.
service_name_input.fill(new_service_name)
expect(federal_radio_button).to_be_enabled()
# Trying to click directly on the radio button resulted in a "not in viewport error" and this is the
# suggested workaround. Googling, the reason seems to be that there might be some (invisible?) css positioned
# above the radio button itself.
page.click("text='Federal government'")
# Click on add service.
add_service_button.click()
@@ -108,7 +75,7 @@ def test_add_new_service_workflow(end_to_end_context):
page.wait_for_load_state("domcontentloaded")
# Check for the service name title and heading.
service_heading = page.get_by_text(new_service_name)
service_heading = page.get_by_text(new_service_name, exact=True)
expect(service_heading).to_be_visible()
expect(page).to_have_title(re.compile(new_service_name))

View File

@@ -3,11 +3,13 @@ import re
from playwright.sync_api import expect
E2E_TEST_URI = os.getenv("NOTIFY_E2E_TEST_URI")
def test_landing_page(end_to_end_context):
# Open a new page and go to the staging site.
page = end_to_end_context.browser.new_page()
page.goto(os.getenv("NOTIFY_E2E_TEST_URI"))
page.goto(f"{E2E_TEST_URI}/")
# Check to make sure that we've arrived at the next page.
page.wait_for_load_state("domcontentloaded")
@@ -17,7 +19,8 @@ def test_landing_page(end_to_end_context):
# Retrieve some prominent elements on the page for testing.
main_header = page.get_by_role(
"heading", name="Send text messages to your participants"
"heading",
name="Reach people where they are with government-powered text messages",
)
sign_in_button = page.get_by_role("link", name="Sign in")
benefits_studio_email = page.get_by_role("link", name="tts-benefits-studio@gsa.gov")
@@ -51,7 +54,7 @@ def test_landing_page(end_to_end_context):
# def test_sign_in_and_mfa_pages(end_to_end_context):
# # Open a new page and go to the staging site.
# page = end_to_end_context.new_page()
# page.goto(os.getenv("NOTIFY_E2E_TEST_URI"))
# page.goto(f"{E2E_TEST_URI}/")
# print(f"test_sign_in_and_mfa_pages initial {page}")
#
# sign_in_button = page.get_by_role("link", name="Sign in")