mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 18:37:33 -04:00
Merge branch 'main' of https://github.com/GSA/notifications-admin into 1269-content-update-for-login-gov-settings
This commit is contained in:
@@ -82,7 +82,7 @@ dead-code:
|
|||||||
.PHONY: e2e-test
|
.PHONY: e2e-test
|
||||||
e2e-test: export NEW_RELIC_ENVIRONMENT=test
|
e2e-test: export NEW_RELIC_ENVIRONMENT=test
|
||||||
e2e-test: ## Run end-to-end integration tests; note that --browser webkit isn't currently working
|
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
|
poetry run pytest -vv --browser chromium --browser firefox tests/end_to_end
|
||||||
|
|
||||||
.PHONY: js-lint
|
.PHONY: js-lint
|
||||||
js-lint: ## Run javascript linting scanners
|
js-lint: ## Run javascript linting scanners
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ from app.formatters import (
|
|||||||
format_datetime_human,
|
format_datetime_human,
|
||||||
format_datetime_normal,
|
format_datetime_normal,
|
||||||
format_datetime_relative,
|
format_datetime_relative,
|
||||||
|
format_datetime_scheduled_notification,
|
||||||
format_datetime_table,
|
format_datetime_table,
|
||||||
format_day_of_week,
|
format_day_of_week,
|
||||||
format_delta,
|
format_delta,
|
||||||
@@ -551,6 +552,7 @@ def add_template_filters(application):
|
|||||||
format_datetime,
|
format_datetime,
|
||||||
format_datetime_24h,
|
format_datetime_24h,
|
||||||
format_datetime_normal,
|
format_datetime_normal,
|
||||||
|
format_datetime_scheduled_notification,
|
||||||
format_datetime_table,
|
format_datetime_table,
|
||||||
valid_phone_number,
|
valid_phone_number,
|
||||||
linkable_name,
|
linkable_name,
|
||||||
|
|||||||
@@ -583,3 +583,9 @@ details form {
|
|||||||
#countdown-container {
|
#countdown-container {
|
||||||
display: none; // Hide the countdown timer
|
display: none; // Hide the countdown timer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.placeholder, .placeholder-conditional {
|
||||||
|
background-color: #face00;
|
||||||
|
border: 1px solid #face00;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
|||||||
+21
-3
@@ -22,7 +22,7 @@ from notifications_utils.recipients import InvalidPhoneError, validate_phone_num
|
|||||||
from notifications_utils.take import Take
|
from notifications_utils.take import Take
|
||||||
|
|
||||||
from app.utils.csv import get_user_preferred_timezone
|
from app.utils.csv import get_user_preferred_timezone
|
||||||
from app.utils.time import parse_dt, parse_naive_dt
|
from app.utils.time import parse_naive_dt
|
||||||
|
|
||||||
|
|
||||||
def apply_html_class(tags, html_file):
|
def apply_html_class(tags, html_file):
|
||||||
@@ -93,16 +93,34 @@ def format_datetime_normal(date):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def format_datetime_scheduled_notification(date):
|
||||||
|
# e.g. April 09, 2024 at 04:00 PM US/Eastern.
|
||||||
|
# Everything except scheduled notifications, the time is always "now".
|
||||||
|
# Scheduled notifications are the exception to the rule.
|
||||||
|
# Here we are formating and displaying the datetime without converting datetime to a different timezone.
|
||||||
|
|
||||||
|
datetime_obj = parse_naive_dt(date)
|
||||||
|
|
||||||
|
format_time_without_tz = datetime_obj.replace(tzinfo=timezone.utc).strftime(
|
||||||
|
"%I:%M %p"
|
||||||
|
)
|
||||||
|
return "{} at {} {}".format(
|
||||||
|
format_date_normal(date), format_time_without_tz, get_user_preferred_timezone()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def format_datetime_table(date):
|
def format_datetime_table(date):
|
||||||
# example: 03-18-2024 at 04:53 PM, intended for datetimes in tables
|
# example: 03-18-2024 at 04:53 PM, intended for datetimes in tables
|
||||||
return "{} at {}".format(format_date_numeric(date), format_time_12h(date))
|
return "{} at {}".format(format_date_numeric(date), format_time_12h(date))
|
||||||
|
|
||||||
|
|
||||||
def format_time_12h(date):
|
def format_time_12h(date):
|
||||||
date = parse_dt(date)
|
date = parse_naive_dt(date)
|
||||||
|
|
||||||
preferred_tz = pytz.timezone(get_user_preferred_timezone())
|
preferred_tz = pytz.timezone(get_user_preferred_timezone())
|
||||||
return date.astimezone(preferred_tz).strftime("%I:%M %p")
|
return (
|
||||||
|
date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%I:%M %p")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def format_datetime_relative(date):
|
def format_datetime_relative(date):
|
||||||
|
|||||||
+12
-21
@@ -470,7 +470,7 @@ def send_one_off_step(service_id, template_id, step_index):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _check_messages(service_id, template_id, upload_id, preview_row):
|
def _check_messages(service_id, template_id, upload_id, preview_row, **kwargs):
|
||||||
try:
|
try:
|
||||||
# The happy path is that the job doesn’t already exist, so the
|
# The happy path is that the job doesn’t already exist, so the
|
||||||
# API will return a 404 and the client will raise HTTPError.
|
# API will return a 404 and the client will raise HTTPError.
|
||||||
@@ -510,11 +510,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
|
|||||||
show_recipient=False,
|
show_recipient=False,
|
||||||
email_reply_to=email_reply_to,
|
email_reply_to=email_reply_to,
|
||||||
sms_sender=sms_sender,
|
sms_sender=sms_sender,
|
||||||
)
|
**kwargs,
|
||||||
simplifed_template = get_template(
|
|
||||||
db_template,
|
|
||||||
current_service,
|
|
||||||
show_recipient=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
allow_list = []
|
allow_list = []
|
||||||
@@ -535,7 +531,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
|
|||||||
allow_list = None
|
allow_list = None
|
||||||
recipients = RecipientCSV(
|
recipients = RecipientCSV(
|
||||||
contents,
|
contents,
|
||||||
template=template or simplifed_template,
|
template=template,
|
||||||
max_initial_rows_shown=50,
|
max_initial_rows_shown=50,
|
||||||
max_errors_shown=50,
|
max_errors_shown=50,
|
||||||
guestlist=allow_list,
|
guestlist=allow_list,
|
||||||
@@ -569,9 +565,6 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
|
|||||||
|
|
||||||
if preview_row < len(recipients) + 2:
|
if preview_row < len(recipients) + 2:
|
||||||
template.values = recipients[preview_row - 2].recipient_and_personalisation
|
template.values = recipients[preview_row - 2].recipient_and_personalisation
|
||||||
simplifed_template.values = recipients[
|
|
||||||
preview_row - 2
|
|
||||||
].recipient_and_personalisation
|
|
||||||
elif preview_row > 2:
|
elif preview_row > 2:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
@@ -599,7 +592,6 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
|
|||||||
service_id, template.id, db_template["version"], original_file_name
|
service_id, template.id, db_template["version"], original_file_name
|
||||||
),
|
),
|
||||||
template_id=template_id,
|
template_id=template_id,
|
||||||
simplifed_template=simplifed_template,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -658,7 +650,9 @@ def check_messages(service_id, template_id, upload_id, row_index=2):
|
|||||||
@user_has_permissions("send_messages", restrict_admin_usage=True)
|
@user_has_permissions("send_messages", restrict_admin_usage=True)
|
||||||
def preview_job(service_id, template_id, upload_id, row_index=2):
|
def preview_job(service_id, template_id, upload_id, row_index=2):
|
||||||
session["scheduled_for"] = request.form.get("scheduled_for", "")
|
session["scheduled_for"] = request.form.get("scheduled_for", "")
|
||||||
data = _check_messages(service_id, template_id, upload_id, row_index)
|
data = _check_messages(
|
||||||
|
service_id, template_id, upload_id, row_index, force_hide_sender=True
|
||||||
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"views/check/preview.html",
|
"views/check/preview.html",
|
||||||
@@ -825,11 +819,11 @@ def send_one_off_to_myself(service_id, template_id):
|
|||||||
def check_notification(service_id, template_id):
|
def check_notification(service_id, template_id):
|
||||||
return render_template(
|
return render_template(
|
||||||
"views/notifications/check.html",
|
"views/notifications/check.html",
|
||||||
**_check_notification(service_id, template_id),
|
**_check_notification(service_id, template_id, show_recipient=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _check_notification(service_id, template_id, exception=None):
|
def _check_notification(service_id, template_id, exception=None, **kwargs):
|
||||||
db_template = current_service.get_template_with_user_permission_or_403(
|
db_template = current_service.get_template_with_user_permission_or_403(
|
||||||
template_id, current_user
|
template_id, current_user
|
||||||
)
|
)
|
||||||
@@ -842,13 +836,9 @@ def _check_notification(service_id, template_id, exception=None):
|
|||||||
template = get_template(
|
template = get_template(
|
||||||
db_template,
|
db_template,
|
||||||
current_service,
|
current_service,
|
||||||
show_recipient=True,
|
|
||||||
email_reply_to=email_reply_to,
|
email_reply_to=email_reply_to,
|
||||||
sms_sender=sms_sender,
|
sms_sender=sms_sender,
|
||||||
)
|
**kwargs,
|
||||||
simplifed_template = get_template(
|
|
||||||
db_template,
|
|
||||||
current_service,
|
|
||||||
)
|
)
|
||||||
placeholders = fields_to_fill_in(template)
|
placeholders = fields_to_fill_in(template)
|
||||||
|
|
||||||
@@ -874,7 +864,6 @@ def _check_notification(service_id, template_id, exception=None):
|
|||||||
back_link_from_preview=back_link_from_preview,
|
back_link_from_preview=back_link_from_preview,
|
||||||
choose_time_form=choose_time_form,
|
choose_time_form=choose_time_form,
|
||||||
**(get_template_error_dict(exception) if exception else {}),
|
**(get_template_error_dict(exception) if exception else {}),
|
||||||
simplifed_template=simplifed_template,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -924,7 +913,9 @@ def preview_notification(service_id, template_id):
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"views/notifications/preview.html",
|
"views/notifications/preview.html",
|
||||||
**_check_notification(service_id, template_id),
|
**_check_notification(
|
||||||
|
service_id, template_id, show_recipient=False, force_hide_sender=True
|
||||||
|
),
|
||||||
scheduled_for=session["scheduled_for"],
|
scheduled_for=session["scheduled_for"],
|
||||||
recipient=recipient,
|
recipient=recipient,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
|
|
||||||
{{ page_header('Preview') }}
|
{{ page_header('Preview') }}
|
||||||
<div>
|
<div>
|
||||||
<p class="sms-message-scheduler">Scheduled: {{ scheduled_for |format_datetime_normal if scheduled_for else 'Now'}}</p>
|
<p class="sms-message-scheduler">Scheduled: {{ scheduled_for |format_datetime_scheduled_notification if scheduled_for else 'Now'}}</p>
|
||||||
<p class="sms-message-file-name">File: {{original_file_name}}</p>
|
<p class="sms-message-file-name">File: {{original_file_name}}</p>
|
||||||
<p class="sms-message-template">Template: {{template.name}}</p>
|
<p class="sms-message-template">Template: {{template.name}}</p>
|
||||||
<p class="sms-message-sender" >From: {{ template.sender }}</p>
|
<p class="sms-message-sender" >From: {{ template.sender }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 id="{{ file_contents_header_id }}">Message</h2>
|
<h2 id="{{ file_contents_header_id }}">Message</h2>
|
||||||
<div class="preview-message"> {{ simplifed_template|string }}</div>
|
<div class="preview-message"> {{ template|string }}</div>
|
||||||
{% if not request.args.from_test %}
|
{% if not request.args.from_test %}
|
||||||
<h2>Recipients list</h2>
|
<h2>Recipients list</h2>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -43,14 +43,14 @@
|
|||||||
{{ page_header('Preview') }}
|
{{ page_header('Preview') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<p class="sms-message-scheduler">Scheduled: {{ scheduled_for |format_datetime_normal if scheduled_for else 'Now'}}</p>
|
<p class="sms-message-scheduler">Scheduled: {{ scheduled_for |format_datetime_scheduled_notification if scheduled_for else 'Now'}}</p>
|
||||||
<p class="sms-message-template">Template: {{template.name}}</p>
|
<p class="sms-message-template">Template: {{template.name}}</p>
|
||||||
<p class="sms-message-sender" >From: {{ template.sender }}</p>
|
<p class="sms-message-sender" >From: {{ template.sender }}</p>
|
||||||
<p class="sms-message-sender" >To: {{ recipient }}</p>
|
<p class="sms-message-sender" >To: {{ recipient }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 id="{{ file_contents_header_id }}">Message</h2>
|
<h2 id="{{ file_contents_header_id }}">Message</h2>
|
||||||
<div class="preview-message"> {{ simplifed_template|string }}</div>
|
<div class="preview-message"> {{ template|string }}</div>
|
||||||
|
|
||||||
<div class="js-stick-at-bottom-when-scrolling">
|
<div class="js-stick-at-bottom-when-scrolling">
|
||||||
<form method="post" enctype="multipart/form-data" action="{{url_for(
|
<form method="post" enctype="multipart/form-data" action="{{url_for(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ def get_template(
|
|||||||
redact_missing_personalisation=False,
|
redact_missing_personalisation=False,
|
||||||
email_reply_to=None,
|
email_reply_to=None,
|
||||||
sms_sender=None,
|
sms_sender=None,
|
||||||
|
force_hide_sender=False,
|
||||||
):
|
):
|
||||||
if "email" == template["template_type"]:
|
if "email" == template["template_type"]:
|
||||||
return EmailPreviewTemplate(
|
return EmailPreviewTemplate(
|
||||||
@@ -33,7 +34,7 @@ def get_template(
|
|||||||
prefix=service.name,
|
prefix=service.name,
|
||||||
show_prefix=service.prefix_sms,
|
show_prefix=service.prefix_sms,
|
||||||
sender=sms_sender,
|
sender=sms_sender,
|
||||||
show_sender=bool(sms_sender),
|
show_sender=bool(sms_sender) and not force_hide_sender,
|
||||||
show_recipient=show_recipient,
|
show_recipient=show_recipient,
|
||||||
redact_missing_personalisation=redact_missing_personalisation,
|
redact_missing_personalisation=redact_missing_personalisation,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,8 +22,3 @@ def is_less_than_days_ago(date_from_db, number_of_days):
|
|||||||
|
|
||||||
def parse_naive_dt(dt):
|
def parse_naive_dt(dt):
|
||||||
return parser.parse(dt, ignoretz=True)
|
return parser.parse(dt, ignoretz=True)
|
||||||
|
|
||||||
|
|
||||||
def parse_dt(dt):
|
|
||||||
# Parse datetime without ignoring the timezone
|
|
||||||
return parser.parse(dt)
|
|
||||||
|
|||||||
Generated
+36
-36
@@ -171,41 +171,41 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "boto3"
|
name = "boto3"
|
||||||
version = "1.29.6"
|
version = "1.34.79"
|
||||||
description = "The AWS SDK for Python"
|
description = "The AWS SDK for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">= 3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "boto3-1.29.6-py3-none-any.whl", hash = "sha256:f4d19e01d176c3a5a05e4af733185ff1891b08a3c38d4a439800fa132aa6e9be"},
|
{file = "boto3-1.34.79-py3-none-any.whl", hash = "sha256:265b0b4865e8c07e27abb32a31d2bd9129bb009b1d89ca0783776ec084886123"},
|
||||||
{file = "boto3-1.29.6.tar.gz", hash = "sha256:d1d0d979a70bf9b0b13ae3b017f8523708ad953f62d16f39a602d67ee9b25554"},
|
{file = "boto3-1.34.79.tar.gz", hash = "sha256:139dd2d94eaa0e3213ff37ba7cf4cb2e3823269178fe8f3e33c965f680a9ddde"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
botocore = ">=1.32.6,<1.33.0"
|
botocore = ">=1.34.79,<1.35.0"
|
||||||
jmespath = ">=0.7.1,<2.0.0"
|
jmespath = ">=0.7.1,<2.0.0"
|
||||||
s3transfer = ">=0.7.0,<0.8.0"
|
s3transfer = ">=0.10.0,<0.11.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "botocore"
|
name = "botocore"
|
||||||
version = "1.32.7"
|
version = "1.34.79"
|
||||||
description = "Low-level, data-driven core of boto 3."
|
description = "Low-level, data-driven core of boto 3."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">= 3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "botocore-1.32.7-py3-none-any.whl", hash = "sha256:58b33d02cafa23461c8a9d211b30e8cded992380a84de409379fd02811fa3e11"},
|
{file = "botocore-1.34.79-py3-none-any.whl", hash = "sha256:a42a014d3dbaa9ef123810592af69f9e55b456c5be3ac9efc037325685519e83"},
|
||||||
{file = "botocore-1.32.7.tar.gz", hash = "sha256:c6795c731b04c8e3635588c44cfd1a4462fc5987859195522c96812cf3eceff9"},
|
{file = "botocore-1.34.79.tar.gz", hash = "sha256:6b59b0f7de219d383a2a633f6718c2600642ebcb707749dc6c67a6a436474b7a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
jmespath = ">=0.7.1,<2.0.0"
|
jmespath = ">=0.7.1,<2.0.0"
|
||||||
python-dateutil = ">=2.1,<3.0.0"
|
python-dateutil = ">=2.1,<3.0.0"
|
||||||
urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}
|
urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
crt = ["awscrt (==0.19.17)"]
|
crt = ["awscrt (==0.19.19)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cachecontrol"
|
name = "cachecontrol"
|
||||||
@@ -925,13 +925,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "govuk-bank-holidays"
|
name = "govuk-bank-holidays"
|
||||||
version = "0.13"
|
version = "0.14"
|
||||||
description = "Tool to load UK bank holidays from GOV.UK"
|
description = "Tool to load UK bank holidays from GOV.UK"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
files = [
|
files = [
|
||||||
{file = "govuk-bank-holidays-0.13.tar.gz", hash = "sha256:ffb6ac050701cb850fd2f08eb9c7c91753090f00525e69cebec4f869f6f0de41"},
|
{file = "govuk-bank-holidays-0.14.tar.gz", hash = "sha256:ce85102423b72908957d25981f616494729686515d5d66c09a1d35a354ce20a6"},
|
||||||
{file = "govuk_bank_holidays-0.13-py3-none-any.whl", hash = "sha256:2b91901be492235c4160a64d56d4d1945f6ca7f11c1ea4277395981866c35bef"},
|
{file = "govuk_bank_holidays-0.14-py3-none-any.whl", hash = "sha256:da485c4a40c6c874c925916e492e3f20b807cffba7eed5f07fb69327aef6b10b"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1602,7 +1602,7 @@ requests = ">=2.0.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notifications-utils"
|
name = "notifications-utils"
|
||||||
version = "0.4.0"
|
version = "0.4.5"
|
||||||
description = ""
|
description = ""
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "^3.12.2"
|
python-versions = "^3.12.2"
|
||||||
@@ -1613,8 +1613,8 @@ develop = false
|
|||||||
async-timeout = "^4.0.2"
|
async-timeout = "^4.0.2"
|
||||||
bleach = "^6.1.0"
|
bleach = "^6.1.0"
|
||||||
blinker = "^1.6.2"
|
blinker = "^1.6.2"
|
||||||
boto3 = "^1.28.66"
|
boto3 = "^1.34.77"
|
||||||
botocore = "^1.31.66"
|
botocore = "^1.34.79"
|
||||||
cachetools = "^5.3.0"
|
cachetools = "^5.3.0"
|
||||||
certifi = "^2024.2.2"
|
certifi = "^2024.2.2"
|
||||||
cffi = "^1.16.0"
|
cffi = "^1.16.0"
|
||||||
@@ -1624,7 +1624,7 @@ cryptography = "^42.0.4"
|
|||||||
flask = "^2.3.2"
|
flask = "^2.3.2"
|
||||||
flask-redis = "^0.4.0"
|
flask-redis = "^0.4.0"
|
||||||
geojson = "^3.0.1"
|
geojson = "^3.0.1"
|
||||||
govuk-bank-holidays = "^0.13"
|
govuk-bank-holidays = "^0.14"
|
||||||
idna = "^3.6"
|
idna = "^3.6"
|
||||||
itsdangerous = "^2.1.2"
|
itsdangerous = "^2.1.2"
|
||||||
jinja2 = "^3.1.3"
|
jinja2 = "^3.1.3"
|
||||||
@@ -1633,16 +1633,16 @@ markupsafe = "^2.1.5"
|
|||||||
mistune = "==0.8.4"
|
mistune = "==0.8.4"
|
||||||
numpy = "^1.24.2"
|
numpy = "^1.24.2"
|
||||||
ordered-set = "^4.1.0"
|
ordered-set = "^4.1.0"
|
||||||
phonenumbers = "^8.13.32"
|
phonenumbers = "^8.13.34"
|
||||||
pycparser = "^2.21"
|
pycparser = "^2.21"
|
||||||
python-dateutil = "^2.8.2"
|
python-dateutil = "^2.8.2"
|
||||||
python-json-logger = "^2.0.7"
|
python-json-logger = "^2.0.7"
|
||||||
pytz = "^2023.3"
|
pytz = "^2024.1"
|
||||||
pyyaml = "^6.0"
|
pyyaml = "^6.0"
|
||||||
redis = "^5.0.3"
|
redis = "^5.0.3"
|
||||||
regex = "^2023.12.25"
|
regex = "^2023.12.25"
|
||||||
requests = "^2.31.0"
|
requests = "^2.31.0"
|
||||||
s3transfer = "^0.7.0"
|
s3transfer = "^0.10.1"
|
||||||
shapely = "^2.0.1"
|
shapely = "^2.0.1"
|
||||||
six = "^1.16.0"
|
six = "^1.16.0"
|
||||||
smartypants = "^2.0.1"
|
smartypants = "^2.0.1"
|
||||||
@@ -1654,7 +1654,7 @@ werkzeug = "^3.0.1"
|
|||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/GSA/notifications-utils.git"
|
url = "https://github.com/GSA/notifications-utils.git"
|
||||||
reference = "HEAD"
|
reference = "HEAD"
|
||||||
resolved_reference = "4cf526bc1fd9532507936c174418dbd3be52c925"
|
resolved_reference = "7d1d2e9bb3791316231e97433c71da6a70c4d2ab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
@@ -1781,13 +1781,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phonenumbers"
|
name = "phonenumbers"
|
||||||
version = "8.13.33"
|
version = "8.13.34"
|
||||||
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "phonenumbers-8.13.33-py2.py3-none-any.whl", hash = "sha256:f2d653268ece55a4f3752d9cda4be6f7465f298e6d028d522aedda13cf057201"},
|
{file = "phonenumbers-8.13.34-py2.py3-none-any.whl", hash = "sha256:bc0bb5d3bab29e28549194f6bf57cb3ca03c3dd84238af12674fe24031631bda"},
|
||||||
{file = "phonenumbers-8.13.33.tar.gz", hash = "sha256:991f2619f0593b36b674c345af47944ec4bae526b353cf53d707e662087be63b"},
|
{file = "phonenumbers-8.13.34.tar.gz", hash = "sha256:7c2676be07b7d0f74411e275e0660380a0ec3ee0d359f070d719424bd2c5f62e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2348,13 +2348,13 @@ unidecode = ["Unidecode (>=1.1.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2023.4"
|
version = "2024.1"
|
||||||
description = "World timezone definitions, modern and historical"
|
description = "World timezone definitions, modern and historical"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"},
|
{file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
|
||||||
{file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"},
|
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2624,20 +2624,20 @@ diagram = ["matplotlib (>=3.0.0)", "pydot (>=1.3.0)", "tqdm (>=v4.31.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "s3transfer"
|
name = "s3transfer"
|
||||||
version = "0.7.0"
|
version = "0.10.1"
|
||||||
description = "An Amazon S3 Transfer Manager"
|
description = "An Amazon S3 Transfer Manager"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">= 3.7"
|
python-versions = ">= 3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "s3transfer-0.7.0-py3-none-any.whl", hash = "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a"},
|
{file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"},
|
||||||
{file = "s3transfer-0.7.0.tar.gz", hash = "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"},
|
{file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
botocore = ">=1.12.36,<2.0a.0"
|
botocore = ">=1.33.2,<2.0a.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"]
|
crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
@@ -2935,4 +2935,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12.2"
|
python-versions = "^3.12.2"
|
||||||
content-hash = "fb81af6421e07a1640bd784f2e1934237a064b30705a0c02e6f06415493e05ff"
|
content-hash = "55d894a50ac58f68283ea1428a7c721e54bdb1d78839756d57a1dd705c728c09"
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ flask-basicauth = "~=0.2"
|
|||||||
flask-login = "^0.6"
|
flask-login = "^0.6"
|
||||||
flask-talisman = "*"
|
flask-talisman = "*"
|
||||||
flask-wtf = "^1.2"
|
flask-wtf = "^1.2"
|
||||||
govuk-bank-holidays = "==0.13"
|
govuk-bank-holidays = "^0.14"
|
||||||
gunicorn = {version = "==21.2.0", extras = ["eventlet"]}
|
gunicorn = {version = "==21.2.0", extras = ["eventlet"]}
|
||||||
humanize = "~=4.9"
|
humanize = "~=4.9"
|
||||||
itsdangerous = "~=2.1"
|
itsdangerous = "~=2.1"
|
||||||
@@ -33,7 +33,7 @@ pyexcel-xlsx = "==0.6.0"
|
|||||||
openpyxl = "==3.0.10"
|
openpyxl = "==3.0.10"
|
||||||
pyproj = "==3.6.1"
|
pyproj = "==3.6.1"
|
||||||
python-dotenv = "==1.0.1"
|
python-dotenv = "==1.0.1"
|
||||||
pytz = "~=2023.4"
|
pytz = "^2024.1"
|
||||||
rtreelib = "==0.2.0"
|
rtreelib = "==0.2.0"
|
||||||
werkzeug = "^3.0.1"
|
werkzeug = "^3.0.1"
|
||||||
wtforms = "~=3.1"
|
wtforms = "~=3.1"
|
||||||
|
|||||||
@@ -979,7 +979,7 @@ def test_upload_valid_csv_shows_preview_and_table(
|
|||||||
'<td class="table-field-left-aligned"> <div> A </div> </td>',
|
'<td class="table-field-left-aligned"> <div> A </div> </td>',
|
||||||
(
|
(
|
||||||
'<td class="table-field-left-aligned"> '
|
'<td class="table-field-left-aligned"> '
|
||||||
'<div> '
|
"<div> "
|
||||||
"<ul> "
|
"<ul> "
|
||||||
"<li>foo</li> <li>foo</li> <li>foo</li> "
|
"<li>foo</li> <li>foo</li> <li>foo</li> "
|
||||||
"</ul> "
|
"</ul> "
|
||||||
@@ -992,7 +992,7 @@ def test_upload_valid_csv_shows_preview_and_table(
|
|||||||
'<td class="table-field-left-aligned"> <div> B </div> </td>',
|
'<td class="table-field-left-aligned"> <div> B </div> </td>',
|
||||||
(
|
(
|
||||||
'<td class="table-field-left-aligned"> '
|
'<td class="table-field-left-aligned"> '
|
||||||
'<div> '
|
"<div> "
|
||||||
"<ul> "
|
"<ul> "
|
||||||
"<li>foo</li> <li>foo</li> <li>foo</li> "
|
"<li>foo</li> <li>foo</li> <li>foo</li> "
|
||||||
"</ul> "
|
"</ul> "
|
||||||
@@ -1005,7 +1005,7 @@ def test_upload_valid_csv_shows_preview_and_table(
|
|||||||
'<td class="table-field-left-aligned"> <div> C </div> </td>',
|
'<td class="table-field-left-aligned"> <div> C </div> </td>',
|
||||||
(
|
(
|
||||||
'<td class="table-field-left-aligned"> '
|
'<td class="table-field-left-aligned"> '
|
||||||
'<div> '
|
"<div> "
|
||||||
"<ul> "
|
"<ul> "
|
||||||
"<li>foo</li> <li>foo</li> "
|
"<li>foo</li> <li>foo</li> "
|
||||||
"</ul> "
|
"</ul> "
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
|
||||||
|
E2E_TEST_URI = os.getenv("NOTIFY_E2E_TEST_URI")
|
||||||
|
|
||||||
|
|
||||||
|
def _setup(page):
|
||||||
|
# Prepare for adding a new service later in the test.
|
||||||
|
current_date_time = datetime.datetime.now()
|
||||||
|
new_service_name = "E2E Federal Test Service {now} - {browser_type}".format(
|
||||||
|
now=current_date_time.strftime("%m/%d/%Y %H:%M:%S"),
|
||||||
|
browser_type=page.context.browser.browser_type.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
# Check the page title exists and matches what we expect.
|
||||||
|
expect(page).to_have_title(re.compile("Choose service"))
|
||||||
|
|
||||||
|
# Check for the sign in heading.
|
||||||
|
sign_in_heading = page.get_by_role("heading", name="Choose service")
|
||||||
|
expect(sign_in_heading).to_be_visible()
|
||||||
|
|
||||||
|
# Retrieve some prominent elements on the page for testing.
|
||||||
|
add_service_button = page.get_by_role(
|
||||||
|
"button", name=re.compile("Add a new service")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(add_service_button).to_be_visible()
|
||||||
|
|
||||||
|
existing_service_link = page.get_by_role("link", name=new_service_name)
|
||||||
|
|
||||||
|
# Check to see if the service was already created - if so, we should fail.
|
||||||
|
# TODO: Figure out how to make this truly isolated, and/or work in a
|
||||||
|
# delete service workflow.
|
||||||
|
expect(existing_service_link).to_have_count(0)
|
||||||
|
|
||||||
|
# Click on add a new service.
|
||||||
|
add_service_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# Check for the sign in heading.
|
||||||
|
about_heading = page.get_by_role("heading", name="About your service")
|
||||||
|
expect(about_heading).to_be_visible()
|
||||||
|
|
||||||
|
# Retrieve some prominent elements on the page for testing.
|
||||||
|
service_name_input = page.locator('xpath=//input[@name="name"]')
|
||||||
|
add_service_button = page.get_by_role("button", name=re.compile("Add service"))
|
||||||
|
|
||||||
|
expect(service_name_input).to_be_visible()
|
||||||
|
expect(add_service_button).to_be_visible()
|
||||||
|
|
||||||
|
# Fill in the form.
|
||||||
|
service_name_input.fill(new_service_name)
|
||||||
|
|
||||||
|
# Click on add service.
|
||||||
|
add_service_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# Check for the service name title and heading.
|
||||||
|
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))
|
||||||
|
|
||||||
|
return new_service_name
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_template(page):
|
||||||
|
|
||||||
|
current_service_link = page.get_by_text("Current service")
|
||||||
|
expect(current_service_link).to_be_visible()
|
||||||
|
current_service_link.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
send_messages_button = page.get_by_role("link", name="Send messages")
|
||||||
|
expect(send_messages_button).to_be_visible()
|
||||||
|
send_messages_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
create_template_button = page.get_by_role("button", name="New template")
|
||||||
|
expect(create_template_button).to_be_visible()
|
||||||
|
create_template_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
start_with_a_blank_template_radio = page.get_by_text("Start with a blank template")
|
||||||
|
expect(start_with_a_blank_template_radio).to_be_visible()
|
||||||
|
start_with_a_blank_template_radio.click()
|
||||||
|
|
||||||
|
continue_button = page.get_by_role("button", name="Continue")
|
||||||
|
|
||||||
|
# continue_button = page.get_by_text("Continue")
|
||||||
|
expect(continue_button).to_be_visible()
|
||||||
|
continue_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
template_name_input = page.get_by_text("Template name")
|
||||||
|
expect(template_name_input).to_be_visible()
|
||||||
|
template_name = str(uuid.uuid4())
|
||||||
|
template_name_input.fill(template_name)
|
||||||
|
message_input = page.get_by_role("textbox", name="Message")
|
||||||
|
expect(message_input).to_be_visible()
|
||||||
|
message = "Test message for e2e test"
|
||||||
|
message_input.fill(message)
|
||||||
|
|
||||||
|
save_button = page.get_by_text("Save")
|
||||||
|
expect(save_button).to_be_visible()
|
||||||
|
save_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
use_this_template_button = page.get_by_text("Use this template")
|
||||||
|
expect(use_this_template_button).to_be_visible()
|
||||||
|
use_this_template_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
use_my_phone_number_link = page.get_by_text("Use my phone number")
|
||||||
|
expect(use_my_phone_number_link).to_be_visible()
|
||||||
|
use_my_phone_number_link.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
preview_button = page.get_by_text("Preview")
|
||||||
|
expect(preview_button).to_be_visible()
|
||||||
|
preview_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# We are not going to send the message for this test, we just want to confirm
|
||||||
|
# that the template has been created and we are now seeing the message from the
|
||||||
|
# template in the preview.
|
||||||
|
assert "Test message for e2e test" in page.content()
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_new_template(authenticated_page):
|
||||||
|
page = authenticated_page
|
||||||
|
|
||||||
|
_setup(page)
|
||||||
|
|
||||||
|
create_new_template(page)
|
||||||
|
|
||||||
|
_teardown(page)
|
||||||
|
|
||||||
|
|
||||||
|
def _teardown(page):
|
||||||
|
page.click("text='Settings'")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
page.click("text='Delete this service'")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
page.click("text='Yes, delete'")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
# Check the page title exists and matches what we expect.
|
||||||
|
expect(page).to_have_title(re.compile("Choose service"))
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
|
||||||
|
E2E_TEST_URI = os.getenv("NOTIFY_E2E_TEST_URI")
|
||||||
|
|
||||||
|
|
||||||
|
def _setup(page):
|
||||||
|
# Prepare for adding a new service later in the test.
|
||||||
|
current_date_time = datetime.datetime.now()
|
||||||
|
new_service_name = "E2E Federal Test Service {now} - {browser_type}".format(
|
||||||
|
now=current_date_time.strftime("%m/%d/%Y %H:%M:%S"),
|
||||||
|
browser_type=page.context.browser.browser_type.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
# Check the page title exists and matches what we expect.
|
||||||
|
expect(page).to_have_title(re.compile("Choose service"))
|
||||||
|
|
||||||
|
# Check for the sign in heading.
|
||||||
|
sign_in_heading = page.get_by_role("heading", name="Choose service")
|
||||||
|
expect(sign_in_heading).to_be_visible()
|
||||||
|
|
||||||
|
# Retrieve some prominent elements on the page for testing.
|
||||||
|
add_service_button = page.get_by_role(
|
||||||
|
"button", name=re.compile("Add a new service")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(add_service_button).to_be_visible()
|
||||||
|
|
||||||
|
existing_service_link = page.get_by_role("link", name=new_service_name)
|
||||||
|
|
||||||
|
# Check to see if the service was already created - if so, we should fail.
|
||||||
|
# TODO: Figure out how to make this truly isolated, and/or work in a
|
||||||
|
# delete service workflow.
|
||||||
|
expect(existing_service_link).to_have_count(0)
|
||||||
|
|
||||||
|
# Click on add a new service.
|
||||||
|
add_service_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# Check for the sign in heading.
|
||||||
|
about_heading = page.get_by_role("heading", name="About your service")
|
||||||
|
expect(about_heading).to_be_visible()
|
||||||
|
|
||||||
|
# Retrieve some prominent elements on the page for testing.
|
||||||
|
service_name_input = page.locator('xpath=//input[@name="name"]')
|
||||||
|
add_service_button = page.get_by_role("button", name=re.compile("Add service"))
|
||||||
|
|
||||||
|
expect(service_name_input).to_be_visible()
|
||||||
|
expect(add_service_button).to_be_visible()
|
||||||
|
|
||||||
|
# Fill in the form.
|
||||||
|
service_name_input.fill(new_service_name)
|
||||||
|
|
||||||
|
# Click on add service.
|
||||||
|
add_service_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# Check for the service name title and heading.
|
||||||
|
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))
|
||||||
|
|
||||||
|
return new_service_name
|
||||||
|
|
||||||
|
|
||||||
|
def handle_no_existing_template_case(page):
|
||||||
|
"""
|
||||||
|
This is the test path for local development. Developers have their own
|
||||||
|
databases and typically they have service or two already created, which
|
||||||
|
means they won't see the "Example text message template" which is done
|
||||||
|
as an introduction. Instead they will see "Create your first template".
|
||||||
|
Note that this test goes all the way through sending and downloading the
|
||||||
|
report and verifying the phone number, etc. in the report. This is because
|
||||||
|
developer systems are fully connected to AWS.
|
||||||
|
"""
|
||||||
|
|
||||||
|
create_template_button = page.get_by_text("Create your first template")
|
||||||
|
expect(create_template_button).to_be_visible()
|
||||||
|
create_template_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
new_template_button = page.get_by_text("New template")
|
||||||
|
expect(new_template_button).to_be_visible()
|
||||||
|
new_template_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
start_with_a_blank_template_radio = page.get_by_text("Start with a blank template")
|
||||||
|
expect(start_with_a_blank_template_radio).to_be_visible()
|
||||||
|
start_with_a_blank_template_radio.click()
|
||||||
|
|
||||||
|
continue_button = page.get_by_role("button", name="Continue")
|
||||||
|
|
||||||
|
# continue_button = page.get_by_text("Continue")
|
||||||
|
expect(continue_button).to_be_visible()
|
||||||
|
continue_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
template_name_input = page.get_by_text("Template name")
|
||||||
|
expect(template_name_input).to_be_visible()
|
||||||
|
template_name = str(uuid.uuid4())
|
||||||
|
template_name_input.fill(template_name)
|
||||||
|
message_input = page.get_by_role("textbox", name="Message")
|
||||||
|
expect(message_input).to_be_visible()
|
||||||
|
message = "Test message"
|
||||||
|
message_input.fill(message)
|
||||||
|
|
||||||
|
save_button = page.get_by_text("Save")
|
||||||
|
expect(save_button).to_be_visible()
|
||||||
|
save_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
use_this_template_button = page.get_by_text("Use this template")
|
||||||
|
expect(use_this_template_button).to_be_visible()
|
||||||
|
use_this_template_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
use_my_phone_number_link = page.get_by_text("Use my phone number")
|
||||||
|
expect(use_my_phone_number_link).to_be_visible()
|
||||||
|
use_my_phone_number_link.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
preview_button = page.get_by_text("Preview")
|
||||||
|
expect(preview_button).to_be_visible()
|
||||||
|
preview_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
send_button = page.get_by_role("button", name="Send")
|
||||||
|
expect(send_button).to_be_visible()
|
||||||
|
send_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
dashboard_button = page.get_by_text("Dashboard")
|
||||||
|
expect(dashboard_button).to_be_visible()
|
||||||
|
dashboard_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
download_link = page.get_by_text("Download")
|
||||||
|
expect(download_link).to_be_visible()
|
||||||
|
|
||||||
|
# Start waiting for the download
|
||||||
|
with page.expect_download() as download_info:
|
||||||
|
# Perform the action that initiates download
|
||||||
|
download_link.click()
|
||||||
|
download = download_info.value
|
||||||
|
# Wait for the download process to complete and save the downloaded file somewhere
|
||||||
|
download.save_as("download_test_file")
|
||||||
|
f = open("download_test_file", "r")
|
||||||
|
|
||||||
|
content = f.read()
|
||||||
|
f.close()
|
||||||
|
# We don't want to wait 5 minutes to get a response from AWS about the message we sent
|
||||||
|
# So we are using this invalid phone number the e2e_test_user signed up with (12025555555)
|
||||||
|
# to shortcircuit the sending process. Our phone number validator will insta-fail the
|
||||||
|
# message and it won't be sent, but the report will still be generated, which is all
|
||||||
|
# we care about here.
|
||||||
|
assert (
|
||||||
|
"Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time"
|
||||||
|
in content
|
||||||
|
)
|
||||||
|
assert "12025555555" in content
|
||||||
|
assert "one-off-e2e_test_user" in content
|
||||||
|
os.remove("download_test_file")
|
||||||
|
|
||||||
|
|
||||||
|
def handle_existing_template_case(page):
|
||||||
|
"""
|
||||||
|
This is the test path used in the Github action. Right now the e2e tests
|
||||||
|
are not connected to AWS, so we can't actually send messages. Hence, the
|
||||||
|
test stops when it verifies that the 'Send' button exists on the page. In
|
||||||
|
the future we would like to change the way e2e tests run so they run against
|
||||||
|
staging, in which case either the code in this method can be uncommented,
|
||||||
|
or perhaps this path will be unnecessary (because staging is not completely clean)
|
||||||
|
and we will just use the same path as for local development.
|
||||||
|
"""
|
||||||
|
existing_template_link = page.get_by_text("Example text message template")
|
||||||
|
expect(existing_template_link).to_be_visible()
|
||||||
|
existing_template_link.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
continue_button = page.get_by_role("button", name="Continue")
|
||||||
|
|
||||||
|
# continue_button = page.get_by_text("Continue")
|
||||||
|
expect(continue_button).to_be_visible()
|
||||||
|
continue_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
continue_button = page.get_by_role("button", name="Continue")
|
||||||
|
|
||||||
|
# continue_button = page.get_by_text("Continue")
|
||||||
|
expect(continue_button).to_be_visible()
|
||||||
|
continue_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# day_of_week_input = page.locator('xpath=//input[@name="day of week"]')
|
||||||
|
# day_of_week_input = page.get_by_text("day of week")
|
||||||
|
day_of_week_input = page.get_by_role("textbox", name="day of week")
|
||||||
|
expect(day_of_week_input).to_be_visible()
|
||||||
|
day_of_week_input.fill("Monday")
|
||||||
|
|
||||||
|
continue_button = page.get_by_role("button", name="Continue")
|
||||||
|
|
||||||
|
# continue_button = page.get_by_text("Continue")
|
||||||
|
expect(continue_button).to_be_visible()
|
||||||
|
continue_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
color_input = page.get_by_role("textbox", name="color")
|
||||||
|
expect(color_input).to_be_visible()
|
||||||
|
color_input.fill("Green")
|
||||||
|
|
||||||
|
# continue_button = page.get_by_text("Continue")
|
||||||
|
expect(continue_button).to_be_visible()
|
||||||
|
continue_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
preview_button = page.get_by_text("Preview")
|
||||||
|
expect(preview_button).to_be_visible()
|
||||||
|
preview_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
send_button = page.get_by_role("button", name="Send")
|
||||||
|
expect(send_button).to_be_visible()
|
||||||
|
# send_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
# page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# dashboard_button = page.get_by_text("Dashboard")
|
||||||
|
# expect(dashboard_button).to_be_visible()
|
||||||
|
# dashboard_button.click()
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
# page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# download_link = page.get_by_text("Download")
|
||||||
|
# expect(download_link).to_be_visible()
|
||||||
|
|
||||||
|
# Start waiting for the download
|
||||||
|
# with page.expect_download() as download_info:
|
||||||
|
# Perform the action that initiates download
|
||||||
|
# download_link.click()
|
||||||
|
# download = download_info.value
|
||||||
|
# Wait for the download process to complete and save the downloaded file somewhere
|
||||||
|
# download.save_as("download_test_file")
|
||||||
|
# f = open("download_test_file", "r")
|
||||||
|
|
||||||
|
# content = f.read()
|
||||||
|
# f.close()
|
||||||
|
# We don't want to wait 5 minutes to get a response from AWS about the message we sent
|
||||||
|
# So we are using this invalid phone number the e2e_test_user signed up with (12025555555)
|
||||||
|
# to shortcircuit the sending process. Our phone number validator will insta-fail the
|
||||||
|
# message and it won't be sent, but the report will still be generated, which is all
|
||||||
|
# we care about here.
|
||||||
|
# assert (
|
||||||
|
# "Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time"
|
||||||
|
# in content
|
||||||
|
# )
|
||||||
|
# assert "12025555555" in content
|
||||||
|
# assert "one-off-e2e_test_user" in content
|
||||||
|
# os.remove("download_test_file")
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_message_from_existing_template(authenticated_page):
|
||||||
|
page = authenticated_page
|
||||||
|
|
||||||
|
_setup(page)
|
||||||
|
|
||||||
|
if page.get_by_text("Create your first template").count() > 0:
|
||||||
|
handle_no_existing_template_case(page)
|
||||||
|
else:
|
||||||
|
handle_existing_template_case(page)
|
||||||
|
|
||||||
|
_teardown(page)
|
||||||
|
|
||||||
|
|
||||||
|
def _teardown(page):
|
||||||
|
page.click("text='Settings'")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
page.click("text='Delete this service'")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
page.click("text='Yes, delete'")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
page.wait_for_load_state("domcontentloaded")
|
||||||
|
|
||||||
|
# Check to make sure that we've arrived at the next page.
|
||||||
|
# Check the page title exists and matches what we expect.
|
||||||
|
expect(page).to_have_title(re.compile("Choose service"))
|
||||||
Reference in New Issue
Block a user