diff --git a/tests_cypress/README.md b/tests_cypress/README.md new file mode 100644 index 0000000000..4a408a7ce7 --- /dev/null +++ b/tests_cypress/README.md @@ -0,0 +1,50 @@ +# Notify + Cypress 🎉 + +## Setup +This folder contains Cypress tests suites. In order to run them, you'll need to install cypress and its dependencies. If you're running inside the dev container, rebuild your dev container to get the necessary packages. + +## Running the tests +### In your devcontainer +There are some issues getting the cypress UI to launch within the devcontainer. For now, you can run the headless tests inside the dev container but if you want to launch the cypress UI you will need to do that outside of the dev container. + +There are 3 helper scripts in `package.json` to run 2 of the test suites. Run these from the `tests_cypress/` folder: +- `npm run cypress`: this will open Cypress with its UI and you can choose any test suite to run +- `npm run a11y`: this will run the accessibility tests in headless mode using the electron browser +- `npm run ci`: this will run the headless CI tests in headless mode using the electron browser + +### Outside of your devcontainer +To launch the cypress UI, where you can choose your test suite and visually debug and inspect tests, run (from the `tests_cypress/` folder): +- `npm run cypress`: this will open the cypress UI where you can choose which tests to run and in which browser + +### Local installation +To install cypress locally, use the following command, from the `tests_cypress/` folder: +```bash +npm install +npx cypress install +``` + +## Configuration +- `cypress.env.json`: this file contains sensitive items like api keys and passphrases that you'll need to run the tests. You'll need to add the file `cypress.env.json` into the `tests_cypress/` folder and its contents can be found in 1password. +- `config.js`: this file contains non-sensitive items like template ids and hostnames that you'll need to run the tests + +### `cypress.env.json` contents +| key | description | +| -------------------------- | ----------------------------------------------- | +| ADMIN_SECRET | Secret admin uses to authenticate against API | +| ADMIN_USERNAME | Username admin uses to authenticate against API | +| NOTIFY_USER | Notify user used by the tests | +| NOTIFY_PASSWORD | Password of NOTIFY_USER (deprecated) | +| IMAP_PASSWORD | IMAP password of gmail account for NOTIFY_USER | +| CYPRESS_AUTH_USER_NAME | Username for the Cypress auth client | +| CYPRESS_AUTH_CLIENT_SECRET | Secret for the Cypress auth client | +| NOTIFY_USER | Actual notify user CDS email account | +| NOTIFY_PASSWORD | Password of NOTIFY_USER (deprecated) | +| CYPRESS_USER_PASSWORD | Password for the Cypress user | +| IMAP_PASSWORD | IMAP password of gmail account for NOTIFY_USER | + +### Target environment 🎯 +The tests are configured to run against the staging environment by default. To run the tests against your local environment, you'll need to create a local service and API keys and store these values in your config. You will also need to update the `ConfigToUse` variable in `config.js` file: +```js +const ConfigToUse = { ...config.COMMON, ...config.LOCAL }; +``` + diff --git a/tests_cypress/config.js b/tests_cypress/config.js index a2f7682ff6..1fc49aa0fb 100644 --- a/tests_cypress/config.js +++ b/tests_cypress/config.js @@ -1,3 +1,13 @@ +let COMMON = { + Services: { + Cypress: 'd4e8a7f4-2b8a-4c9a-8b3f-9c2d4e8a7f4b' + }, + Templates: { + 'SMOKE_TEST_EMAIL': 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + 'SMOKE_TEST_SMS': 'e4b8f8d0-6a3b-4b9e-8c2b-1f2d3e4a5b6c', + }, +}; + let STAGING = { CONFIG_NAME: "STAGING", Hostnames: { @@ -5,35 +15,6 @@ let STAGING = { Admin: 'https://staging.notification.cdssandbox.xyz', DDAPI: 'https://api.document.staging.notification.cdssandbox.xyz', }, - Services: { - Notify: 'd6aa2c68-a2d9-4437-ab19-3ae8eb202553', - Cypress: '5c8a0501-2aa8-433a-ba51-cefb8063ab93' - }, - Templates: { - 'FILE_ATTACH_TEMPLATE_ID': '7246c71e-3d60-458b-96af-af17a5b07659', - 'SIMPLE_EMAIL_TEMPLATE_ID': '939dafde-1b60-47f0-a6d5-c9080d92a4a8', - 'VARIABLES_EMAIL_TEMPLATE_ID': '1101a00a-11b7-4036-865c-add43fcff7c9', - 'SMOKE_TEST_EMAIL': '5e26fae6-3565-44d5-bfed-b18680b6bd39', - 'SMOKE_TEST_EMAIL_BULK': '04145882-0f21-4d57-940d-69883fc23e77', - 'SMOKE_TEST_EMAIL_ATTACH': 'bf85def8-01b4-4c72-98a8-86f2bc10f2a4', - 'SMOKE_TEST_EMAIL_LINK': '37924e87-038d-48b8-b122-f6dddefd56d5', - 'SMOKE_TEST_SMS': '16cae0b3-1d44-47ad-a537-fd12cc0646b6' - }, - Users: { - Team: ['andrew.leith+bannertest@cds-snc.ca'], - NonTeam: ['person@example.com'], - Simulated: ['simulate-delivered-2@notification.canada.ca', 'simulate-delivered-3@notification.canada.ca', 'success@simulator.amazonses.com'], - 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' - }, - viewports: [320, 375, 640, 768] }; let LOCAL = { @@ -43,43 +24,15 @@ let LOCAL = { Admin: 'http://localhost:6012', DDAPI: 'http://localhost:7000', }, - Services: { - Notify: 'd6aa2c68-a2d9-4437-ab19-3ae8eb202553', - Cypress: '5c8a0501-2aa8-433a-ba51-cefb8063ab93' - }, - Templates: { - 'FILE_ATTACH_TEMPLATE_ID': 'e52acc48-dcb9-4f70-81cf-b87d0ceaef1b', - 'SIMPLE_EMAIL_TEMPLATE_ID': '0894dc6c-1b07-465e-91f0-aa76f202a83f', - 'VARIABLES_EMAIL_TEMPLATE_ID': 'fa00aa13-87fd-4bc7-9349-ba9270347055', - 'SMOKE_TEST_EMAIL': '08673acf-fef1-408d-8ce7-7809907595b2', - 'SMOKE_TEST_EMAIL_BULK': 'efbd319b-4de8-41c7-850f-93ec0490d3c2', - 'SMOKE_TEST_EMAIL_ATTACH': 'cbd5307f-8662-4cea-9b8e-3bc672bf005c', - 'SMOKE_TEST_EMAIL_LINK': '94cce202-b171-440f-b0c1-734368ca9494', - 'SMOKE_TEST_SMS': 'a9fff158-a745-417a-b1ec-ceebcba6614f' - }, - Users: { - Team: ['william.banks+admin@cds-snc.ca'], - NonTeam: ['person@example.com'], - Simulated: ['simulate-delivered-2@notification.canada.ca', 'simulate-delivered-3@notification.canada.ca', 'success@simulator.amazonses.com'], - 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' - }, - viewports: [320, 375, 640, 768] }; const config = { + COMMON, STAGING, LOCAL, }; // choose which config to use here -const ConfigToUse = config.STAGING; +const ConfigToUse = { ...config.COMMON, ...config.STAGING }; module.exports = ConfigToUse; diff --git a/tests_cypress/cypress.config.js b/tests_cypress/cypress.config.js index fd79f394ca..74dcbeadfd 100644 --- a/tests_cypress/cypress.config.js +++ b/tests_cypress/cypress.config.js @@ -1,7 +1,8 @@ var config = require('./config'); const { defineConfig } = require("cypress"); -const EmailAccount = require("./cypress/plugins/email-account") +const EmailAccount = require("./cypress/plugins/email-account"); +const CreateAccount = require("./cypress/plugins/create-account"); const htmlvalidate = require("cypress-html-validate/plugin"); module.exports = defineConfig({ @@ -28,8 +29,8 @@ module.exports = defineConfig({ return null }, // Email Account /// - getLastEmail() { - return emailAccount.getLastEmail() + getLastEmail(emailAddress) { + return emailAccount.getLastEmail(emailAddress) }, deleteAllEmails() { return emailAccount.deleteAllEmails() @@ -37,9 +38,18 @@ module.exports = defineConfig({ fetchEmail(acct) { return emailAccount.fetchEmail(acct) }, - createEmailAccount() { - return emailAccount.createEmailAccount(); + createAccount({ baseUrl, username, secret }) { + if (global.acct) { + return global.acct; + } else { + let acct = CreateAccount(baseUrl, username, secret); + global.acct = acct; + return acct + } }, + getUserName() { + return global.acct; + } }); on('before:browser:launch', (browser = {}, launchOptions) => { diff --git a/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js b/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js index 448b07f562..60ab0c1604 100644 --- a/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js +++ b/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js @@ -19,7 +19,7 @@ let Actions = { }, Login: (email, password, agreeToTerms=true) => { cy.clearCookie(ADMIN_COOKIE); // clear auth cookie - cy.task('deleteAllEmails'); // purge email inbox to make getting the 2fa code easier + // cy.task('deleteAllEmails'); // purge email inbox to make getting the 2fa code easier // login with username and password cy.visit(LoginPage.URL); @@ -29,7 +29,7 @@ let Actions = { // get email 2fa code recurse( - () => cy.task('getLastEmail', {} ), // Cypress commands to retry + () => cy.task('getLastEmail', email), // Cypress commands to retry Cypress._.isObject, // keep retrying until the task returns an object { log: true, diff --git a/tests_cypress/cypress/Notify/Admin/Pages/TemplatesPage.js b/tests_cypress/cypress/Notify/Admin/Pages/TemplatesPage.js index 6f1307aa82..0ac2a2a96f 100644 --- a/tests_cypress/cypress/Notify/Admin/Pages/TemplatesPage.js +++ b/tests_cypress/cypress/Notify/Admin/Pages/TemplatesPage.js @@ -39,6 +39,9 @@ let Actions = { cy.contains('a', template_name).first().click(); cy.contains('h1', template_name).should('be.visible'); }, + SelectTemplateById: (service_id, template_id) => { + cy.get(`a[href="/services/${service_id}/templates/${template_id}"]`).click(); + }, GotoAddRecipients: () => { Components.YesAddRecipients().click(); //cy.contains('h1', 'Add recipients').should('be.visible'); diff --git a/tests_cypress/cypress/Notify/Admin/Pages/TouPrompt.js b/tests_cypress/cypress/Notify/Admin/Pages/TouPrompt.js index aa80431386..2b5ab73363 100644 --- a/tests_cypress/cypress/Notify/Admin/Pages/TouPrompt.js +++ b/tests_cypress/cypress/Notify/Admin/Pages/TouPrompt.js @@ -12,7 +12,11 @@ let Actions = { AgreeToTerms: () => { TouPrompt.Components.Terms().scrollTo('bottom', { ensureScrollable: false }); Components.DismissButton().click(); - cy.url().should('include', '/accounts'); + // depending on how many services a user has, the app either goes to the /accounts page + // or to a specific service page - either means success + cy.url().should('match', /\/(accounts|services)/); + cy.get('h1').should('not.contain', 'Before you continue'); + }, }; diff --git a/tests_cypress/cypress/Notify/NotifyAPI.js b/tests_cypress/cypress/Notify/NotifyAPI.js index d79977b5ed..a2f98611b4 100644 --- a/tests_cypress/cypress/Notify/NotifyAPI.js +++ b/tests_cypress/cypress/Notify/NotifyAPI.js @@ -15,7 +15,7 @@ const Utilities = { return jwt.jws.JWS.sign("HS256", JSON.stringify(headers), JSON.stringify(claims), Cypress.env('ADMIN_SECRET')); }, GenerateID: (length = 10) => { - const nanoid = customAlphabet('1234567890abcdef-_', length) + const nanoid = customAlphabet('1234567890abcdef', length) return nanoid() } }; diff --git a/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js b/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js index 504a832503..ef0344a946 100644 --- a/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js +++ b/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js @@ -82,7 +82,7 @@ const pages = [ describe(`A11Y - App pages [${config.CONFIG_NAME}]`, () => { for (const page of pages) { it(`${page.name}`, () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.a11yScan(page.route, { a11y: true, htmlValidate: true, diff --git a/tests_cypress/cypress/e2e/admin/branding/a11y.cy.js b/tests_cypress/cypress/e2e/admin/branding/a11y.cy.js index a29898c772..5f336d03db 100644 --- a/tests_cypress/cypress/e2e/admin/branding/a11y.cy.js +++ b/tests_cypress/cypress/e2e/admin/branding/a11y.cy.js @@ -13,7 +13,7 @@ describe("Branding A11Y", () => { // stop the recurring dashboard fetch requests cy.intercept("GET", "**/dashboard.json", {}); - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); }); // perform a11yScan on all pages in the branding_pages array diff --git a/tests_cypress/cypress/e2e/admin/branding/branding-settings.cy.js b/tests_cypress/cypress/e2e/admin/branding/branding-settings.cy.js index 8b39a101d8..c99f6e7c63 100644 --- a/tests_cypress/cypress/e2e/admin/branding/branding-settings.cy.js +++ b/tests_cypress/cypress/e2e/admin/branding/branding-settings.cy.js @@ -9,7 +9,7 @@ describe("Branding settings", () => { // stop the recurring dashboard fetch requests cy.intercept("GET", "**/dashboard.json", {}); - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/service-settings`); }); diff --git a/tests_cypress/cypress/e2e/admin/branding/edit-branding.cy.js b/tests_cypress/cypress/e2e/admin/branding/edit-branding.cy.js index be87beb392..0372b11faf 100644 --- a/tests_cypress/cypress/e2e/admin/branding/edit-branding.cy.js +++ b/tests_cypress/cypress/e2e/admin/branding/edit-branding.cy.js @@ -11,7 +11,7 @@ describe("Edit Branding", () => { // stop the recurring dashboard fetch requests cy.intercept("GET", "**/dashboard.json", {}); - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/edit-branding`); }); diff --git a/tests_cypress/cypress/e2e/admin/branding/request-branding.cy.js b/tests_cypress/cypress/e2e/admin/branding/request-branding.cy.js index 2f22d3ce30..099b6c90be 100644 --- a/tests_cypress/cypress/e2e/admin/branding/request-branding.cy.js +++ b/tests_cypress/cypress/e2e/admin/branding/request-branding.cy.js @@ -8,7 +8,7 @@ describe("Branding request", () => { beforeEach(() => { // stop the recurring dashboard fetch requests cy.intercept("GET", "**/dashboard.json", {}); - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/branding-request`); }); diff --git a/tests_cypress/cypress/e2e/admin/branding/review-pool.cy.js b/tests_cypress/cypress/e2e/admin/branding/review-pool.cy.js index 2247e3ef27..175cc32887 100755 --- a/tests_cypress/cypress/e2e/admin/branding/review-pool.cy.js +++ b/tests_cypress/cypress/e2e/admin/branding/review-pool.cy.js @@ -9,7 +9,7 @@ import { describe("Review Pool", () => { beforeEach(() => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/review-pool`); }); context("General page functionality", () => { diff --git a/tests_cypress/cypress/e2e/admin/components/remaining_messages_summary.cy.js b/tests_cypress/cypress/e2e/admin/components/remaining_messages_summary.cy.js index 13095a7bba..ae9d435d26 100644 --- a/tests_cypress/cypress/e2e/admin/components/remaining_messages_summary.cy.js +++ b/tests_cypress/cypress/e2e/admin/components/remaining_messages_summary.cy.js @@ -64,6 +64,7 @@ describe("Remaining Messages Summary Component", () => { it("shows thousands separator in FR for today’s remaining", () => { cy.get("#header-lang").click(); + cy.get("html").should("have.attr", "lang", "fr"); cy.visit(PageURL); RMS.Below() .find('*[data-testid="rms"]') @@ -82,6 +83,7 @@ describe("Remaining Messages Summary Component", () => { it("shows thousands separator in FR for the year’s remaining", () => { cy.get("#header-lang").click(); + cy.get("html").should("have.attr", "lang", "fr"); cy.visit(PageURL); RMS.Below() .find('*[data-testid="rms"]') diff --git a/tests_cypress/cypress/e2e/admin/login.cy.js b/tests_cypress/cypress/e2e/admin/login.cy.js index 841a98f218..7d61cac772 100644 --- a/tests_cypress/cypress/e2e/admin/login.cy.js +++ b/tests_cypress/cypress/e2e/admin/login.cy.js @@ -32,7 +32,7 @@ describe("Basic login", () => { }); it("displays notify service page", () => { - cy.visit(`/services/${config.Services.Notify}`); + cy.visit(`/services/${config.Services.Cypress}`); cy.contains("h1", "Dashboard").should("be.visible"); }); }); diff --git a/tests_cypress/cypress/e2e/admin/menu/disclosure_menu.cy.js b/tests_cypress/cypress/e2e/admin/menu/disclosure_menu.cy.js index d0f44e79fc..8bddd841eb 100644 --- a/tests_cypress/cypress/e2e/admin/menu/disclosure_menu.cy.js +++ b/tests_cypress/cypress/e2e/admin/menu/disclosure_menu.cy.js @@ -134,7 +134,7 @@ describe("Mobile menu", () => { }); it("Re-focus on the menu button when the menu closes (signed in)", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit("/"); MainMenu.OpenMenu(); diff --git a/tests_cypress/cypress/e2e/admin/platform_admin/template-categories.cy.js b/tests_cypress/cypress/e2e/admin/platform_admin/template-categories.cy.js index f04ab9d49c..5fb7e8f546 100644 --- a/tests_cypress/cypress/e2e/admin/platform_admin/template-categories.cy.js +++ b/tests_cypress/cypress/e2e/admin/platform_admin/template-categories.cy.js @@ -9,7 +9,7 @@ import { Admin } from "../../../Notify/NotifyAPI"; describe("Template Categories", () => { beforeEach(() => { - cy.login(Cypress.env("NOTIFY_ADMIN_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.loginAsPlatformAdmin(); cy.visit(`/template-categories`); }); diff --git a/tests_cypress/cypress/e2e/admin/qualtrics.cy.js b/tests_cypress/cypress/e2e/admin/qualtrics.cy.js index 9e11c676ca..265a74f04c 100644 --- a/tests_cypress/cypress/e2e/admin/qualtrics.cy.js +++ b/tests_cypress/cypress/e2e/admin/qualtrics.cy.js @@ -17,7 +17,7 @@ describe("Qualtrics", () => { }); it("survey button appears and survey opens", () => { - cy.visit(`/services/${config.Services.Notify}`); + cy.visit(`/services/${config.Services.Cypress}`); cy.contains("h1", "Dashboard").should("be.visible"); cy.get("#QSIFeedbackButton-btn").should("be.visible"); // qualtrics survey button cy.get("#QSIFeedbackButton-btn").click(); // click the button diff --git a/tests_cypress/cypress/e2e/admin/service-settings.cy.js b/tests_cypress/cypress/e2e/admin/service-settings.cy.js index f6a79aa261..bd75c7c501 100644 --- a/tests_cypress/cypress/e2e/admin/service-settings.cy.js +++ b/tests_cypress/cypress/e2e/admin/service-settings.cy.js @@ -8,7 +8,7 @@ describe("Service Settings", () => { // stop the recurring dashboard fetch requests cy.intercept("GET", "**/dashboard.json", {}); - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit( config.Hostnames.Admin + @@ -30,7 +30,7 @@ describe("Platform Admin Service Settings", () => { beforeEach(() => { // stop the recurring dashboard fetch requests cy.intercept("GET", "**/dashboard.json", {}); - cy.login(Cypress.env("NOTIFY_ADMIN_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit( config.Hostnames.Admin + `/services/${config.Services.Cypress}/service-settings`, diff --git a/tests_cypress/cypress/e2e/admin/sign_out/sign_out.cy.js b/tests_cypress/cypress/e2e/admin/sign_out/sign_out.cy.js index 9d45b2df5b..5f253c5bdf 100644 --- a/tests_cypress/cypress/e2e/admin/sign_out/sign_out.cy.js +++ b/tests_cypress/cypress/e2e/admin/sign_out/sign_out.cy.js @@ -16,7 +16,7 @@ describe("Sign out", () => { it("Redirects to session timeout page when logged in (multiple pages)", () => { ["/home", "/features"].forEach((page) => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); vistPageAndFastForwardTime(page); // asserts diff --git a/tests_cypress/cypress/e2e/admin/sitemap/sitemap.cy.js b/tests_cypress/cypress/e2e/admin/sitemap/sitemap.cy.js index 32a44f2045..e0ae27d470 100644 --- a/tests_cypress/cypress/e2e/admin/sitemap/sitemap.cy.js +++ b/tests_cypress/cypress/e2e/admin/sitemap/sitemap.cy.js @@ -54,7 +54,7 @@ describe(`Sitemap`, () => { }); it("Does display the 'You' group when logged in", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(path); cy.getByTestId("sitemap-group").contains("Your GC Notify"); @@ -75,13 +75,13 @@ describe(`Sitemap`, () => { cy.get('a[href="/sitemap"]').should("be.visible"); }); it("Has the sitemap link on app pages when logged in", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit("/activity"); cy.get(`#${sitemap_footer_id}`).should("be.visible"); }); it("Has the sitemap link on GCA pages when logged in", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit("/features"); cy.get('a[href="/sitemap"]').should("be.visible"); diff --git a/tests_cypress/cypress/e2e/admin/template-categories.cy.js b/tests_cypress/cypress/e2e/admin/template-categories.cy.js index 88c0f73057..4e265ccc1b 100644 --- a/tests_cypress/cypress/e2e/admin/template-categories.cy.js +++ b/tests_cypress/cypress/e2e/admin/template-categories.cy.js @@ -5,7 +5,7 @@ import { TemplatesPage as Page } from "../../Notify/Admin/Pages/all"; describe("Template categories", () => { beforeEach(() => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/templates`); }); diff --git a/tests_cypress/cypress/e2e/admin/template-filters.cy.js b/tests_cypress/cypress/e2e/admin/template-filters.cy.js index ee675f55fd..d36204e34d 100644 --- a/tests_cypress/cypress/e2e/admin/template-filters.cy.js +++ b/tests_cypress/cypress/e2e/admin/template-filters.cy.js @@ -9,13 +9,18 @@ const types = { }; const categories = { - en: ["Test"], - fr: ["Test"], + en: ["Other"], + fr: ["Autre"], +}; + +const catEmpty = { + en: "Test", + fr: "Test", }; describe("Template filters", () => { beforeEach(() => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); }); ["en", "fr"].forEach((lang) => { @@ -162,7 +167,7 @@ describe("Template filters", () => { Page.ToggleFilters(); Page.ApplyTypeFilter(types[lang][1]); - Page.ApplyCategoryFilter(categories[lang][0]); + Page.ApplyCategoryFilter(catEmpty[lang]); // Empty state should be visible Page.Components.EmptyState().should("be.visible"); diff --git a/tests_cypress/cypress/e2e/admin/template/create-template.cy.js b/tests_cypress/cypress/e2e/admin/template/create-template.cy.js index 4e75c291aa..6d846b1c93 100644 --- a/tests_cypress/cypress/e2e/admin/template/create-template.cy.js +++ b/tests_cypress/cypress/e2e/admin/template/create-template.cy.js @@ -7,7 +7,7 @@ import { Admin } from "../../../Notify/NotifyAPI"; describe("Create Template", () => { context("FF_TEMPLATE_CATEGORY - ON", () => { it("Process type should be null and the category process type should be used if non-admin", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/templates`); TemplatesPage.CreateTemplate(); TemplatesPage.SelectTemplateType("email"); @@ -39,10 +39,7 @@ describe("Create Template", () => { }); it("Process type should be null and the category process type should be used if admin", () => { - cy.login( - Cypress.env("NOTIFY_ADMIN_USER"), - Cypress.env("NOTIFY_PASSWORD"), - ); + cy.loginAsPlatformAdmin(); cy.visit(`/services/${config.Services.Cypress}/templates`); TemplatesPage.CreateTemplate(); TemplatesPage.SelectTemplateType("email"); diff --git a/tests_cypress/cypress/e2e/admin/template/edit-template.cy.js b/tests_cypress/cypress/e2e/admin/template/edit-template.cy.js index 18d1848a4f..8609ffc8b6 100644 --- a/tests_cypress/cypress/e2e/admin/template/edit-template.cy.js +++ b/tests_cypress/cypress/e2e/admin/template/edit-template.cy.js @@ -4,18 +4,6 @@ import config from "../../../../config"; import { TemplatesPage as Page } from "../../../Notify/Admin/Pages/all"; import { Admin } from "../../../Notify/NotifyAPI"; -// TODO: dont hardcode these -const templates = { - smoke_test_email: { - name: "SMOKE_TEST_EMAIL", - id: "5e26fae6-3565-44d5-bfed-b18680b6bd39", - }, - smoke_test_email_attach: { - name: "SMOKE_TEST_EMAIL_ATTACH", - id: "bf85def8-01b4-4c72-98a8-86f2bc10f2a4", - }, -}; - const categories = { OTHER: "Other", AUTH: "Authentication", @@ -28,21 +16,21 @@ describe("Edit template", () => { // Override the process_type -> new process type should be saved for an existing template it("Should allow platform admin to override process type", () => { // login as admin - cy.login( - Cypress.env("NOTIFY_ADMIN_USER"), - Cypress.env("NOTIFY_PASSWORD"), - ); + cy.loginAsPlatformAdmin(); cy.visit(`/services/${config.Services.Cypress}/templates`); // set template priority to use TC - Page.SelectTemplate(templates.smoke_test_email.name); + Page.SelectTemplateById( + config.Services.Cypress, + config.Templates.SMOKE_TEST_SMS, + ); Page.EditCurrentTemplate(); Page.SetTemplatePriority("bulk"); Page.SaveTemplate(); // use api to check that it was set Admin.GetTemplate({ - templateId: templates.smoke_test_email.id, + templateId: config.Templates.SMOKE_TEST_SMS, serviceId: config.Services.Cypress, }).then((response) => { console.log("response", response); @@ -56,7 +44,7 @@ describe("Edit template", () => { // use api to check that it was overridden Admin.GetTemplate({ - templateId: templates.smoke_test_email.id, + templateId: config.Templates.SMOKE_TEST_SMS, serviceId: config.Services.Cypress, }).then((response) => { console.log("response", response); @@ -70,7 +58,7 @@ describe("Edit template", () => { // use api to check that it was overridden Admin.GetTemplate({ - templateId: templates.smoke_test_email.id, + templateId: config.Templates.SMOKE_TEST_SMS, serviceId: config.Services.Cypress, }).then((response) => { console.log("response", response); @@ -83,7 +71,7 @@ describe("Edit template", () => { const template_name = "Test Template"; // seed data - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/templates`); Page.CreateTemplate(); Page.SelectTemplateType("email"); @@ -127,14 +115,14 @@ describe("Edit template", () => { it("Should allow platform admin to override process type", () => { // Admin user 1. // login as admin - cy.login( - Cypress.env("NOTIFY_ADMIN_USER"), - Cypress.env("NOTIFY_PASSWORD"), - ); + cy.loginAsPlatformAdmin(); cy.visit(`/services/${config.Services.Cypress}/templates`); // set template priority to use TC - Page.SelectTemplate(templates.smoke_test_email_attach.name); + Page.SelectTemplateById( + config.Services.Cypress, + config.Templates.SMOKE_TEST_EMAIL, + ); Page.EditCurrentTemplate(); Page.Components.TemplateSubject().type("a"); Page.SetTemplatePriority("bulk"); @@ -142,16 +130,15 @@ describe("Edit template", () => { // use api to check that it was set Admin.GetTemplate({ - templateId: templates.smoke_test_email_attach.id, + templateId: config.Templates.SMOKE_TEST_EMAIL, serviceId: config.Services.Cypress, }).then((response) => { - console.log("response", response); expect(response.body.data.process_type_column).to.equal("bulk"); }); }); it("Should set process_type to null and use category's process_type when non-admin changes a template's category", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/templates`); // Seed data with a template before we start with the test. @@ -184,10 +171,7 @@ describe("Edit template", () => { }); it("Should override process_type when a template has a category and user is admin", () => { - cy.login( - Cypress.env("NOTIFY_ADMIN_USER"), - Cypress.env("NOTIFY_PASSWORD"), - ); + cy.loginAsPlatformAdmin(); cy.visit(`/services/${config.Services.Cypress}/templates`); // Seed data with a template before we start with the test. @@ -219,10 +203,7 @@ describe("Edit template", () => { }); it("Should set the process type to null when a category is changed and user is admin", () => { - cy.login( - Cypress.env("NOTIFY_ADMIN_USER"), - Cypress.env("NOTIFY_PASSWORD"), - ); + cy.loginAsPlatformAdmin(); cy.visit(`/services/${config.Services.Cypress}/templates`); // seed data with a template before we start with the test. diff --git a/tests_cypress/cypress/e2e/admin/template/text-direction.cy.js b/tests_cypress/cypress/e2e/admin/template/text-direction.cy.js index 85ffecd49b..c407bedb9e 100644 --- a/tests_cypress/cypress/e2e/admin/template/text-direction.cy.js +++ b/tests_cypress/cypress/e2e/admin/template/text-direction.cy.js @@ -15,7 +15,7 @@ const templates = { describe("Template text direction", () => { beforeEach(() => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD")); + cy.login(); cy.visit(`/services/${config.Services.Cypress}/templates`); }); diff --git a/tests_cypress/cypress/e2e/admin/tou_prompt.cy.js b/tests_cypress/cypress/e2e/admin/tou_prompt.cy.js index 946b91ef40..8994c0cb17 100644 --- a/tests_cypress/cypress/e2e/admin/tou_prompt.cy.js +++ b/tests_cypress/cypress/e2e/admin/tou_prompt.cy.js @@ -2,7 +2,7 @@ import TouPrompt from "../../Notify/Admin/Pages/TouPrompt"; describe("TOU Prompt", () => { it("should display the prompt when logged in", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD"), false); + cy.login(false); cy.visit("/"); TouPrompt.Components.Prompt().should("be.visible"); @@ -11,7 +11,7 @@ describe("TOU Prompt", () => { }); it("should not display the prompt after being dismissed", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD"), false); + cy.login(false); cy.visit("/"); TouPrompt.Components.Prompt().should("be.visible"); @@ -35,7 +35,7 @@ describe("TOU Prompt", () => { cy.visit("/security"); TouPrompt.Components.Prompt().should("not.exist"); - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD"), false); + cy.login(false); cy.visit("/features"); TouPrompt.Components.Prompt().should("not.exist"); @@ -45,7 +45,7 @@ describe("TOU Prompt", () => { }); it("should display the prompt again when navigating to the page before prompt is dismissed", () => { - cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD"), false); + cy.login(false); cy.visit("/"); TouPrompt.Components.Prompt().should("be.visible"); diff --git a/tests_cypress/cypress/plugins/create-account.js b/tests_cypress/cypress/plugins/create-account.js new file mode 100644 index 0000000000..92ffadc0ea --- /dev/null +++ b/tests_cypress/cypress/plugins/create-account.js @@ -0,0 +1,73 @@ +const http = require('http'); +const https = require('https'); + +// TODO: This duplicates some code in Notify/NotifyAPI.js and should be consolidated +const Utilities = { + CreateJWT: (username, secret) => { + const jwt = require('jsrsasign'); + const claims = { + 'iss': username, + 'iat': Math.round(Date.now() / 1000) + } + + const headers = { alg: "HS256", typ: "JWT" }; + return jwt.jws.JWS.sign("HS256", JSON.stringify(headers), JSON.stringify(claims), secret); + }, + GenerateID: (length = 10) => { + const characters = '0123456789abcdefghijklmnopqrstuvwxyz'; + let result = ''; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } +}; + +const createAccount = async (baseUrl, username, secret) => { + // return a generated id + const token = Utilities.CreateJWT(username, secret); + const generatedUsername = Utilities.GenerateID(10); + const url = `${baseUrl}/cypress/create_user/${generatedUsername}`; + + return new Promise((resolve, reject) => { + const options = { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": 'application/json', + 'Content-Length': 0 + }, + }; + + const getHttpModule = (url) => url.startsWith('https://') ? https : http; + + const req = getHttpModule(url).request(url, options, (res) => { + let data = ''; + + // A chunk of data has been received. + res.on('data', (chunk) => { + data += chunk; + }); + + // The whole response has been received. + res.on('end', () => { + try { + const parsedData = JSON.parse(data); + resolve(parsedData); + } catch (e) { + reject(e); + } + }); + }); + + req.on('error', (error) => { + reject(error); // Reject the promise on request error + }); + + req.end(); + }); +}; + + +module.exports = createAccount; diff --git a/tests_cypress/cypress/plugins/email-account.js b/tests_cypress/cypress/plugins/email-account.js index 93b3ad9f67..0a2ec46b1e 100644 --- a/tests_cypress/cypress/plugins/email-account.js +++ b/tests_cypress/cypress/plugins/email-account.js @@ -10,7 +10,7 @@ const emailAccount = async () => { const emailConfig = { imap: { - user: env.NOTIFY_USER, + user: env.NOTIFY_USER, password: env.IMAP_PASSWORD, host: 'imap.gmail.com', port: 993, @@ -28,8 +28,6 @@ const emailAccount = async () => { * for the Ethereal email account */ async deleteAllEmails() { - // console.debug('Purging the inbox...') - try { const connection = await imaps.connect(emailConfig) @@ -42,12 +40,10 @@ const emailAccount = async () => { const messages = await connection.search(searchCriteria, fetchOptions) if (!messages.length) { - // console.log('Cannot find any emails') // and close the connection to avoid it hanging connection.end() return null } else { - // console.log('There are %d messages, deleting them...', messages.length) // delete all messages const uidsToDelete = messages .filter(message => { @@ -75,55 +71,63 @@ const emailAccount = async () => { * Utility method for getting the last email * for the Ethereal email account */ - async getLastEmail() { - // makes debugging very simple - // console.log('Getting the last email') - + async getLastEmail(emailAddress) { + let connection; try { - const connection = await imaps.connect(emailConfig) + connection = await imaps.connect(emailConfig); // grab up to 50 emails from the inbox - await connection.openBox('INBOX') - const searchCriteria = ['1:50', 'UNDELETED'] + await connection.openBox('INBOX'); + const searchCriteria = ['UNSEEN']; const fetchOptions = { bodies: [''], - } - const messages = await connection.search(searchCriteria, fetchOptions) - // and close the connection to avoid it hanging - connection.end() + struct: true, + }; + const messages = await connection.search(searchCriteria, fetchOptions); if (!messages.length) { - // console.log('Cannot find any emails') - return null + return null; } else { - // console.log('There are %d messages', messages.length) - // grab the last email - const mail = await simpleParser( - messages[messages.length - 1].parts[0].body, - ) - // console.log(mail.subject) - // console.log(mail.text) - + let latestMail = null; + let uidsToDelete = []; + for (const message of messages) { + const mail = await simpleParser(message.parts[0].body); + const to_address = mail.to.value[0].address; + + if (to_address == emailAddress) { + uidsToDelete.push(message.attributes.uid); + latestMail = { + subject: mail.subject, + text: mail.text, + html: mail.html, + }; + } + } - // and returns the main fields - return { - subject: mail.subject, - text: mail.text, - html: mail.html, + try { + if (uidsToDelete.length > 0) { + await connection.deleteMessage(uidsToDelete); + await connection.imap.expunge(); + } + } catch (e) { + console.error('delete error', uidsToDelete, e); } + + return latestMail; } } catch (e) { - // and close the connection to avoid it hanging - // connection.end() - - console.error(e) - return null + console.error(e); + return null; + } finally { + if (connection) { + connection.end(); + } } }, async fetchEmail(acct) { const _config = { imap: { - user: acct.user, + user: acct.user, password: acct.pass, host: "imap.ethereal.email", //'imap.gmail.com', port: 993, @@ -148,20 +152,8 @@ const emailAccount = async () => { connection.end() if (!messages.length) { - // console.log('Cannot find any emails, retrying...') return null } else { - // console.log('There are %d messages', messages.length) - // messages.forEach(function (item) { - // var all = _.find(item.parts, { "which": "" }) - // var id = item.attributes.uid; - // var idHeader = "Imap-Id: "+id+"\r\n"; - // simpleParser(idHeader+all.body, (err, mail) => { - // // access to the whole mail object - // console.log(mail.subject) - // console.log(mail.html) - // }); - // }); // grab the last email const mail = await simpleParser( @@ -185,11 +177,6 @@ const emailAccount = async () => { console.error(e) return null } - }, - async createEmailAccount() { - let testAccount = await nodemailer.createTestAccount(); - // console.log("test account created: ", testAccount); - return testAccount; } } diff --git a/tests_cypress/cypress/support/commands.js b/tests_cypress/cypress/support/commands.js index 6c4dcec387..ec7683582c 100644 --- a/tests_cypress/cypress/support/commands.js +++ b/tests_cypress/cypress/support/commands.js @@ -97,8 +97,19 @@ Cypress.Commands.add('getByTestId', (selector, ...args) => { return cy.get(`[data-testid=${selector}]`, ...args) }); -Cypress.Commands.add('login', (username, password, agreeToTerms = true) => { - cy.session([username, password, agreeToTerms], () => { - LoginPage.Login(username, password, agreeToTerms); +Cypress.Commands.add('login', (agreeToTerms = true) => { + cy.task('createAccount', { baseUrl: config.Hostnames.API, username: Cypress.env('CYPRESS_AUTH_USER_NAME'), secret: Cypress.env('CYPRESS_AUTH_CLIENT_SECRET') }).then((acct) => { + cy.session([acct.regular.email_address, agreeToTerms], () => { + LoginPage.Login(acct.regular.email_address, Cypress.env('CYPRESS_USER_PASSWORD'), agreeToTerms); + }); + }); +}); + + +Cypress.Commands.add('loginAsPlatformAdmin', (agreeToTerms = true) => { + cy.task('createAccount', { baseUrl: config.Hostnames.API, username: Cypress.env('CYPRESS_AUTH_USER_NAME'), secret: Cypress.env('CYPRESS_AUTH_CLIENT_SECRET') }).then((acct) => { + cy.session([acct.admin.email_address, agreeToTerms], () => { + LoginPage.Login(acct.admin.email_address, Cypress.env('CYPRESS_USER_PASSWORD'), agreeToTerms); + }); }); }); diff --git a/tests_cypress/package.json b/tests_cypress/package.json index 2711201a62..1af308a8e6 100644 --- a/tests_cypress/package.json +++ b/tests_cypress/package.json @@ -1,7 +1,8 @@ { "scripts": { "cypress": "ETHEREAL_CACHE=false npx cypress open", - "a11y": "ETHEREAL_CACHE=false npx cypress run --spec 'cypress/e2e/admin/a11y/*'" + "a11y": "ETHEREAL_CACHE=false npx cypress run --spec 'cypress/e2e/admin/a11y/*'", + "ci": "ETHEREAL_CACHE=false npx cypress run --spec 'cypress/e2e/admin/ci.cy.js'" }, "dependencies": { "cypress-axe": "^1.4.0",