Skip to content

Commit

Permalink
Feat/branding UI tests (#1773)
Browse files Browse the repository at this point in the history
* feat(branding): add new placeholder pages

* style(branding): add info icon

* feat(branding): add new routes/logic for branding

* feat(branding): add feature flag to allow branding settings link to flow to new feature

* feat(branding): add new form for GOC branding selection

* chore(branding): re-gen index.css

* chore: formatting

* feat(back navigation): add js helper to make back links all work without keeping track

* feat(branding form model): simplify model so it can be used in both the GOC and pool pages

* feat(branding preview): add flexibility to preview current branding or arbitrary branding

* feat(preview): add a partial using shadow dom to isolate and display email template preview

* feat(branding): move display of branding into its own partial

* feat(view branding): add preview option to settings page

* feat(goc branding): use branding partial

* feat(pool): add ability to select from other branding in org; add empty state;

* feat(preview): add preview page

* feat(branding request): get the page structure ready

* feat(view branding): add preview to settings

* chore: formatting

* chore: formatting

* chore: regen css

* chore(branding request): add WTF form for branding request page

* feat(branding request): add form to page

* feat(branding request): experiment with building a better file upload component

* chore: regen css

* feat(branding_request): make a proper js component; refactor page to suit

* feat(request_branding): add backend code i missed

* a11y(branding_request): focus on preview after file upload

* feat(branding_request): make request post work

* chore(css/forms): update css and use form macros

* chore: formatting

* style(radio image): update padding

* feat(request submit): fix up confirmation page/forms

* chore: regen css

* chore: formatting

* test(branding): add tests for `/edit-branding` and `/review-pool`

* chore: remove unused component

* feat(test-ids): update some macros to accept testid param

* feat(branding): add testids to ui elements

* chore: formatting

* feat(branding request page): add page model for requesting a new brand

* feat(test branding request): add fixtures

* feat(test branding request): add tests

* feat(login): add command to wrap login into session so it can persist when possible

* chore(refactor): add folder for branding tests, move files around

* feat(a11y test): add a11y tests for all new branding routes

* chore: remove unused imports

* secure(request_branding): encode URI before using it

* fix(pool): clean up forms; implement latest content/design

* a11y(select-input): divs arent allowed inside labels

* style(branding-settings): update/simplify as per latest design

* style(preview): update per latest design

* style(branding): update as per latest design

* chore: regen css

* chore: formatting

* Add tests for edit branding defaults only for now

* test(test_headers): update test with new CSP

* Exclude all variations of cypress .env files

* Fix and add to tests

- Added tests for navigation between edit-branding and branding template preview page
- Added tests to verify that the FR/EN-first branding logos appear
  correctly on the template preview page
- Added data-testids to various components
- Added BrandingSettings page components and actions

* chore: formatting

* fix(branding): update failing tests

* fix: add placeholder for missing translations

* fix(a11y): fix floating quotation mark that breaks html validation

* WIP Add tests for review-pool

- Small refactor on file name
- Added some more testids
- Started some groundwork for expanding tests on the service settings
  page

* feat(request branding): update tests per new functionality

* chore: rename test suites

* Add more tests for review-pool

* (WIP)Added barebones Redis client

- Only working locally at the moment. Need to add env variables for different environments

* Finish review-pool tests

* Apply suggestions from code review

Co-authored-by: Andrew <[email protected]>

* Fix review-pool tests

- Added more data-testids
- Make a couple tests less reliant on page content
- Improve logging around Redis tasks

* Fix edit-branding ui tests

- added additional data-testid

* Fix branding a11y

- Add /branding page to the list of branding routes for a11y

* Formatting

* Use admin flagged account to clear cache

- Since the github CI runner does not have direct access to redis, the previous method of connecting directly to redis to clear the cache is not viable.
- Use admin user to navigate to the admin panel to clear the cache when we test the review pool page for empty and non-empty lists

* Use env to fetch admin user

* chore: upgrade cypress

* a11y(branding): fix up a few a11y issues discovered by the tests

* test(branding): update test-ids for error messages

* Merge main

* chore: formatting

* chore: update tests

* chore: remov redis integration

* fix: update cypress config

* chore: remove more redis stuff

* chore: fixing up references and stuff; bringing back all.js since this will be a nightmare to merge upstream

* chore: update how branding tests do login to align with new paradigm using test isolation

* chore: formatting

* chore: update cypress config, use correct service id

* chore: fix missing things

* testing: reducing # of tests to see if that allows them to pass

* fix: get rid of hostname reference as its not needed and actually wrong in the review env

* fix: remove bad hostname reference

---------

Co-authored-by: wbanks <[email protected]>
Co-authored-by: William B <[email protected]>
  • Loading branch information
3 people authored Jun 6, 2024
1 parent 71d85ae commit 2c8a473
Show file tree
Hide file tree
Showing 36 changed files with 866 additions and 164 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jinja_templates/
scripts/searchresults.csv
dump.rdb

cypress.env.json
cypress*env*json*
node_modules/
tests_cypress/cypress/videos/
tests_cypress/cypress/screenshots/
2 changes: 1 addition & 1 deletion app/assets/javascripts/branding_request.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions app/templates/components/empty-list.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{% macro empty_list(heading, message, img="emptyFlower", link=None, linkText="") %}
<div class="flex items-center border-dashed border-1 border-gray-400 p-8 sm:bg-{{img}} sm:bg-right bg-empty-state">
{% macro empty_list(heading, message, img="emptyFlower", link=None, linkText="", attributes="") %}
<div class="flex items-center border-dashed border-1 border-gray-400 p-8 sm:bg-{{img}} sm:bg-right bg-empty-state" data-testid='empty-list'>
<div class="w-full sm:w-2/3" tabindex="0">
<div class="text-title text-gray-700 mb-6">{{ heading }}</div>
<p class="mb-0 text-base text-gray-800">{{ message }}</p>
{% if link %}
<a class="inline-block mt-6 text-base font-bold" href="{{ link }}">{{ linkText }}</a>
<a class="inline-block mt-6 text-base font-bold" {% if attributes %} {{ attributes }} {% endif %} href="{{ link }}">{{ linkText }}</a>
{% endif %}
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions app/templates/components/select-input.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
field, hint, disable, option_hints, hide_legend, collapsible_opts, legend_style, input, is_page_heading, use_aria_labelledby, testid
) %}
{% for option in field %}
{{ select_input(option, disable, option_hints, input=input, option_conditionals=option_conditionals) }}
{{ select_input(option, disable, option_hints, input=input, option_conditionals=option_conditionals, testid=option.data) }}
{% endfor %}
{% endcall %}
{% endmacro %}
Expand Down Expand Up @@ -65,14 +65,14 @@
</fieldset>
</div>
{% endmacro %}
{% macro select_input(option, disable=[], option_hints={}, data_target=None, as_list_item=False, input="radio", option_conditionals={}) %}
{% macro select_input(option, disable=[], option_hints={}, data_target=None, as_list_item=False, input="radio", option_conditionals={}, testid=None) %}
{% set has_conditional = option_conditionals[option.data] %}
{% if as_list_item %}
<li class="multiple-choice" {% if data_target %} data-target="{{ data_target }}" {% endif %}>
{% else %}
<div class="multiple-choice{{ ' has-conditional' if has_conditional}}" {% if data_target %} data-target="{{ data_target }}" {% endif %}>
{% endif %}
<input id="{{ option.id }}" name="{{ option.name }}" type="{{ input }}" value="{{ option.data }}" {% if option.data in disable %} disabled {% endif %} {% if option.checked %} checked {% endif %}>
<input id="{{ option.id }}" name="{{ option.name }}" type="{{ input }}" value="{{ option.data }}" {% if testid %} data-testid="{{ testid }}" {% endif %} {% if option.data in disable %} disabled {% endif %} {% if option.checked %} checked {% endif %}>
<label class="block-label" for="{{ option.id }}">
{{ option.label.text.split("||")[0] if '||' in option.label.text else option.label.text }}
{% if '||' in option.label.text %}
Expand All @@ -88,11 +88,11 @@
</label>
{% if has_conditional %}
<div class="conditional">
{{ textbox(
{{ textbox(
option_conditionals[option.data],
label=_(option_conditionals[option.data].label.text)|capitalize,
hint=hint_txt if optional_placeholder else None,
width='w-full',
label=_(option_conditionals[option.data].label.text)|capitalize,
hint=hint_txt if optional_placeholder else None,
width='w-full',
) }}
</div>
{% endif %}
Expand Down
4 changes: 2 additions & 2 deletions app/templates/views/email-branding/_preview.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="max-w-5xl">
<div class="py-0 px-0 px-gutterHalf box-border border border-gray-300 overflow-x-auto" id="template_preview">
<div class="py-0 px-0 px-gutterHalf box-border border border-gray-300 overflow-x-auto" data-testid="template-preview" id="template_preview" tabindex="0">
{% if '/_email' in request.base_url %}
<link href="https://fonts.googleapis.com/css?family=Flow+Block&display=swap" rel="stylesheet" />
{% endif %}
<template shadowrootmode="open">

<style nonce="{{ request_nonce }}">
:host(#template_preview) h2, :host(#template_preview) p {
font-family: "Flow Block", system-ui !important;
Expand Down
2 changes: 1 addition & 1 deletion app/templates/views/email-branding/branding-pool.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<div class="grid-row contain-floats">
<div class="py-0 px-0 px-gutterHalf box-border">
{% if logos|length == 0 %}
{{ empty_list(_('There are no other logos yet'), _('To add another logo, make a request'), 'emptyBirdCurious', url_for('main.create_branding_request', service_id=current_service.id), _('Request a new logo')) }}
{{ empty_list(_('There are no other logos yet'), _('To add another logo, make a request'), 'emptyBirdCurious', url_for('main.create_branding_request', service_id=current_service.id), _('Request a new logo'), attributes="data-testid=goto-request") }}
{% else %}
{% call form_wrapper() %}
{{ radios(form.pool_branding, hide_legend=True, testid="pool_branding") }}
Expand Down
4 changes: 2 additions & 2 deletions app/templates/views/email-branding/branding-request.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ <h2 class="heading-medium">{{ _('Prepare your logo') }}</h2>
</span>
{% endif %}

<div class="form-group">
<span class="form-group">
{{ form.file(**{ 'class': 'file-upload-field', 'accept': 'image/png', 'data_testid': 'brand_image', 'aria-labelledby': 'file-description', 'data-error-msg': _('You must select a file to continue') }) }}

<span aria-hidden="true" id="file-upload-button" class="button button-secondary font-normal"> {{ _('Select a file') }} </span>
</div>
</span>
</label>

<div class="mt-6 ml-2 pl-6 border-l-4 border-gray-200 preview font-normal relative" data-testid="brand_fileinfo">
Expand Down
2 changes: 1 addition & 1 deletion app/templates/views/service-settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ <h2 class="heading-small p-0 m-0">{{ _('Your service is in trial mode') }}</h2>
change_txt,
url_for(branding_route, service_id=current_service.id),
permissions=['manage_service'],
attributes="",
attributes="data-testid=edit-email-branding",
for=txt
)}}
{% endcall %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<div class="py-0 px-0 px-gutterHalf box-border">
<p>{{ _('This preview shows how your logo will appear to recipients') }}</p>
{% include 'views/email-branding/_preview.html' %}
<a href="{{ url_for('main.edit_branding_settings', service_id=current_service.id) }}" class="button mt-12">
<a href="{{ url_for('main.edit_branding_settings', service_id=current_service.id) }}" data-testid="change-logo" class="button mt-12">
{{ _('Change logo') }}
</a>
</div>
Expand Down
10 changes: 9 additions & 1 deletion tests_cypress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ let STAGING = {
Simulated: ['[email protected]', '[email protected]', '[email protected]'],
SimulatedPhone: ['+16132532222', '+16132532223', '+16132532224']
},
Organisations: {
'DEFAULT_ORG_ID': '4eef762f-383d-4068-81ca-c2c5c186eb16',
'NO_CUSTOM_BRANDING_ORG_ID': '4eef762f-383d-4068-81ca-c2c5c186eb16'
},
ReplyTos: {
Default: '24e5288d-8bfa-4ad4-93aa-592c11a694cd',
Second: '797865c4-788b-4184-91ae-8e45eb07e40b'
Expand All @@ -41,7 +45,7 @@ let LOCAL = {
},
Services: {
Notify: 'd6aa2c68-a2d9-4437-ab19-3ae8eb202553',
Cypress: '73209498-d078-42ff-82dc-57176c52ddde'
Cypress: '5c8a0501-2aa8-433a-ba51-cefb8063ab93'
},
Templates: {
'FILE_ATTACH_TEMPLATE_ID': 'e52acc48-dcb9-4f70-81cf-b87d0ceaef1b',
Expand All @@ -59,6 +63,10 @@ let LOCAL = {
Simulated: ['[email protected]', '[email protected]', '[email protected]'],
SimulatedPhone: ['+16132532222', '+16132532223', '+16132532224']
},
Organisations: {
'DEFAULT_ORG_ID': 'ff9e5ddd-926f-4ae2-bc87-f5104262ca17',
'NO_CUSTOM_BRANDING_ORG_ID': '39b3230e-300a-42f4-bfb7-40b20b704d44'
},
ReplyTos: {
Default: '8c2a9b22-8fec-4ad9-bca8-658abbb7406e',
Second: 'fc4d2266-5594-47d0-8056-7bef62d59177'
Expand Down
6 changes: 4 additions & 2 deletions tests_cypress/cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ module.exports = defineConfig({
});

const emailAccount = await EmailAccount()

on('task', {
log (message) { // for debugging
log(message) { // for debugging
console.log(message)
return null
},
// Email Account ///
getLastEmail() {
return emailAccount.getLastEmail()
},
Expand All @@ -34,7 +36,7 @@ module.exports = defineConfig({
},
createEmailAccount() {
return emailAccount.createEmailAccount();
}
},
});

on('before:browser:launch', (browser = {}, launchOptions) => {
Expand Down
33 changes: 33 additions & 0 deletions tests_cypress/cypress/Notify/Admin/Pages/AllPages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import LoginPage from "./LoginPage";
import TwoFactorPage from "./TwoFactorPage";
import DashboardPage from "./DashboardPage";
import AddRecipientsPage from "./AddRecipientsPage";
import Navigation from "./Navigation";
import TemplatesPage from "./TemplatesPage";
import AccountsPage from "./AccountsPage";
import CreateAccountPage from "./CreateAccountPage";
import RequestBranding from "./branding/RequestBranding";
import EditBranding from "./branding/EditBrandingPage";
import BrandingSettings from "./branding/BrandingSettingsPage"
import ServiceSettingsPage from "./ServiceSettingsPage";
import ReviewPoolPage from "./branding/ReviewPoolPage";
import PreviewBrandingPage from "./branding/PreviewBrandingPage";
import ClearCachePage from "./admin/ClearCachePage";

export default {
LoginPage,
TwoFactorPage,
DashboardPage,
AddRecipientsPage,
Navigation,
TemplatesPage,
AccountsPage,
CreateAccountPage,
RequestBranding,
EditBranding,
BrandingSettings,
ServiceSettingsPage,
ReviewPoolPage,
PreviewBrandingPage,
ClearCachePage,
}
24 changes: 24 additions & 0 deletions tests_cypress/cypress/Notify/Admin/Pages/ServiceSettingsPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Parts of the page a user can interact with
let Components = {
// Service

// Emails
EditEmailBrandingLink: () => cy.getByTestId("edit-email-branding")

// Text messages

// Platform admin settings
};

let Actions = {
ClickChangeEmailBrandingLink: () => {
Components.EditEmailBrandingLink().click();
}
};

let ServiceSettingsPage = {
Components,
...Actions
};

export default ServiceSettingsPage;
54 changes: 54 additions & 0 deletions tests_cypress/cypress/Notify/Admin/Pages/admin/ClearCachePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Parts of the page a user can interact with
let Components = {
UserCache: () => cy.getByTestId('user'),
ServiceCache: () => cy.getByTestId('service'),
TemplateCache: () => cy.getByTestId('template'),
EmailBrandingCache: () => cy.getByTestId('email_branding'),
LetterBrandingCache: () => cy.getByTestId('letter_branding'),
OrganisationCache: () => cy.getByTestId('organisation'),
GcArticlesCache: () => cy.getByTestId('gc-articles'),
ClearCacheButton: () => cy.get('[data-button-id="btn"]')
};

let Actions = {
// TODO: Could probably just select the fieldset and iterate over the input
// elements until we find the requested cache to clear. Flip side is
// is you have to know what the test-id's are before hand.
SelectCacheToClear: (cache) => {
switch (cache) {
case 'user':
Components.UserCache().check();
break;
case 'service':
Components.ServiceCache().check();
break;
case 'template':
Components.TemplateCache().check();
break;
case 'email_branding':
Components.EmailBrandingCache().check();
break;
case 'letter_branding':
Components.LetterBrandingCache().check();
break;
case 'organisation':
Components.OrganisationCache().check();
break;
case 'gc-articles':
Components.GcArticlesCache().check();
break;
default:
throw new Error('Invalid cache type');
}
},
ClickClearCacheButton: () => {
Components.ClearCacheButton().click();
}
};

let ClearCachePage = {
Components,
...Actions
};

export default ClearCachePage;
12 changes: 12 additions & 0 deletions tests_cypress/cypress/Notify/Admin/Pages/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import Navigation from "./Navigation";
import TemplatesPage from "./TemplatesPage";
import AccountsPage from "./AccountsPage";
import CreateAccountPage from "./CreateAccountPage";
import BrandingSettingsPage from "./branding/BrandingSettingsPage";
import EditBrandingPage from "./branding/EditBrandingPage";
import PreviewBrandingPage from "./branding/PreviewBrandingPage";
import RequestBrandingPage from "./branding/RequestBrandingPage";
import ReviewPoolPage from "./branding/ReviewPoolPage";
import ServiceSettingsPage from "./ServiceSettingsPage";

export default {
LoginPage,
Expand All @@ -16,4 +22,10 @@ export default {
TemplatesPage,
AccountsPage,
CreateAccountPage,
BrandingSettingsPage,
EditBrandingPage,
PreviewBrandingPage,
RequestBrandingPage,
ReviewPoolPage,
ServiceSettingsPage,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
let Components = {
ChangeLogo: () => cy.getByTestId("change-logo"),
// Email jinja template uses a shadowroot so we have to treat this one differently
TemplatePreview: () => cy.getByTestId("template-preview").shadow().find('img'),
StatusBanner: () => cy.get('div[role="status"]'),
BackButton: () => cy.getByTestId("go-back")
}

let Actions = {
ChooseDifferentLogo: () => {
Components.ChangeLogo().click();
},
GoBack: () => {
Components.BackButton().click();
}
}

let BrandingSettingsPage = {
Components,
...Actions
};

export default BrandingSettingsPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Parts of the page a user can interact with
let Components = {
BrandGocEn: () => cy.getByTestId('__FIP-EN__'),
BrandGocFr: () => cy.getByTestId('__FIP-FR__'),
BrandFieldset: () => cy.getByTestId('goc_branding'),
BrandPoolLink: () => cy.getByTestId('goto-pool'),
ChangeBranding: () => cy.getByTestId('change_branding'),
ErrorMessage: () => cy.get('[class="error-message"]'),
BackLink: () => cy.getByTestId('go-back'),
SubmitButton: () => cy.getByTestId('submit'),
};

let Actions = {
SelectDefaultBranding: (lang) => {
lang === 'en' ? Components.BrandGocEn().check() : Components.BrandGocFr().check();
},
ClickBrandPool: () => {
Components.BrandPoolLink().click();
},
ClickBackLink: () => {
Components.BackLink().click();
},
Submit: () => {
Components.SubmitButton().click();
},
};

let EditBrandingPage = {
Components,
...Actions
};

export default EditBrandingPage;
Loading

0 comments on commit 2c8a473

Please sign in to comment.