From 0df6d7fd31adbba0e2c50e770b76fdcdfa599898 Mon Sep 17 00:00:00 2001 From: Adrian Cowan Date: Mon, 2 Jan 2023 00:27:36 +1100 Subject: [PATCH] website: more e2e tests for signin Fixed issue where existing sign in test fails due to the existence of dev test login. Added reusable cy.signInWithEmail() to login before testing rest of UI. --- website/cypress.config.js | 7 +++++ website/cypress/e2e/auth/signin.cy.ts | 35 ++++++++++++++++++++-- website/cypress/support/commands.ts | 34 +++++++++++++++++++++ website/cypress/support/e2e.ts | 4 --- website/cypress/support/index.ts | 22 ++++++++++++++ website/package-lock.json | 17 +++++++++++ website/package.json | 1 + website/src/components/Header/UserMenu.tsx | 4 ++- website/src/pages/auth/signin.tsx | 2 +- 9 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 website/cypress/support/index.ts diff --git a/website/cypress.config.js b/website/cypress.config.js index 9610624f..7d391f23 100644 --- a/website/cypress.config.js +++ b/website/cypress.config.js @@ -22,4 +22,11 @@ export default defineConfig({ getCompareSnapshotsPlugin(on, config); }, }, + + env: { + MAILDEV_PROTOCOL: "http", + MAILDEV_HOST: "localhost", + MAILDEV_SMTP_PORT: "1025", + MAILDEV_API_PORT: "1080", + }, }); diff --git a/website/cypress/e2e/auth/signin.cy.ts b/website/cypress/e2e/auth/signin.cy.ts index b6914016..e87d4b88 100644 --- a/website/cypress/e2e/auth/signin.cy.ts +++ b/website/cypress/e2e/auth/signin.cy.ts @@ -1,10 +1,41 @@ +import { faker } from "@faker-js/faker"; + describe("signin flow", () => { it("redirects to a confirmation page on submit of valid email address", () => { cy.visit("/auth/signin"); - cy.get(".chakra-input").type(`test@example.com`); - cy.get(".chakra-stack > .chakra-button").click(); + cy.get('form[data-cy="signin-email"').within(() => { + cy.get(".chakra-input").type(`test@example.com`); + cy.get(".chakra-stack > .chakra-button").click(); + }); cy.url().should("contain", "/auth/verify"); }); + it("emails a login link to the user when signing in with email", () => { + // Use random email to avoid possibility of tests passing just due to other tests or previous runs also causing emails to be sent + const emailAddress = faker.internet.email(); + cy.log("emailAddress", emailAddress); + cy.request("GET", "/api/auth/csrf") + .then((response) => { + const csrfToken = response.body.csrfToken; + cy.request("POST", "/api/auth/signin/email", { + callbackUrl: "/", + email: emailAddress, + csrfToken, + json: "true", + }); + }) + .then((response) => { + cy.signInUsingEmailedLink(emailAddress).then(() => { + cy.get('[data-cy="username"]').should("exist"); + }); + }); + }); + it("shows the logged in users email address if logged in with email", () => { + const emailAddress = "user@example.com"; + cy.signInWithEmail(emailAddress); + // The user will only see the email address if the window is wide enough, not technically required as even when hidden this will find it in the page. + cy.viewport(1920, 1000); + cy.contains('[data-cy="username"]', emailAddress); + }); }); export {}; diff --git a/website/cypress/support/commands.ts b/website/cypress/support/commands.ts index 2ed74fb3..096720d0 100644 --- a/website/cypress/support/commands.ts +++ b/website/cypress/support/commands.ts @@ -36,4 +36,38 @@ // } // } +Cypress.Commands.add("signInUsingEmailedLink", (emailAddress) => { + const mailDevApi = `${Cypress.env("MAILDEV_PROTOCOL")}://${Cypress.env( + "MAILDEV_HOST" + )}:${Cypress.env("MAILDEV_API_PORT")}`; + cy.request( + "GET", + `${mailDevApi}/email?headers.to=${emailAddress.toLowerCase()}` + ).then((response) => { + const emails = response.body; + + // Find and use login link + const loginLink = emails + .pop() + .html.match(/href="[^"]+(\/api\/auth\/callback\/[^"]+?)"/)[1]; + cy.visit(loginLink); + }); +}); + +Cypress.Commands.add("signInWithEmail", (emailAddress) => { + cy.request("GET", "/api/auth/csrf") + .then((response) => { + const csrfToken = response.body.csrfToken; + cy.request("POST", "/api/auth/signin/email", { + callbackUrl: "/", + email: emailAddress, + csrfToken, + json: "true", + }); + }) + .then(() => { + cy.signInUsingEmailedLink(emailAddress); + }); +}); + export {}; diff --git a/website/cypress/support/e2e.ts b/website/cypress/support/e2e.ts index 4c9b7b84..1ed26be7 100644 --- a/website/cypress/support/e2e.ts +++ b/website/cypress/support/e2e.ts @@ -13,12 +13,8 @@ // https://on.cypress.io/configuration // *********************************************************** -// Import commands.js using ES2015 syntax: import "./commands"; import compareSnapshotCommand from "cypress-image-diff-js/dist/command"; compareSnapshotCommand(); -// Alternatively you can use CommonJS syntax: -// require('./commands') - export {}; diff --git a/website/cypress/support/index.ts b/website/cypress/support/index.ts new file mode 100644 index 00000000..80fba7eb --- /dev/null +++ b/website/cypress/support/index.ts @@ -0,0 +1,22 @@ +// load type definitions that come with Cypress module +/// + +declare global { + namespace Cypress { + interface Chainable { + /** + * Custom command to sign in with a given email address + * @example cy.signInWithEmail('user@example.com') + */ + signInWithEmail(emailAddress: string): Chainable; + + /** + * Custom command to sign in with the link emailed to the given email address + * @example cy.signInUsingEmailedLink('user@example.com') + */ + signInUsingEmailedLink(emailAddress: string): Chainable; + } + } +} + +export {}; diff --git a/website/package-lock.json b/website/package-lock.json index 1587c01e..38f7446e 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -44,6 +44,7 @@ "devDependencies": { "@babel/core": "^7.20.7", "@chakra-ui/storybook-addon": "^4.0.16", + "@faker-js/faker": "^7.6.0", "@storybook/addon-actions": "^6.5.15", "@storybook/addon-essentials": "^6.5.15", "@storybook/addon-interactions": "^6.5.15", @@ -3591,6 +3592,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -31099,6 +31110,12 @@ } } }, + "@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", + "dev": true + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", diff --git a/website/package.json b/website/package.json index bc1f3b69..2bd531a0 100644 --- a/website/package.json +++ b/website/package.json @@ -54,6 +54,7 @@ "devDependencies": { "@babel/core": "^7.20.7", "@chakra-ui/storybook-addon": "^4.0.16", + "@faker-js/faker": "^7.6.0", "@storybook/addon-actions": "^6.5.15", "@storybook/addon-essentials": "^6.5.15", "@storybook/addon-interactions": "^6.5.15", diff --git a/website/src/components/Header/UserMenu.tsx b/website/src/components/Header/UserMenu.tsx index c42d8895..cbd71787 100644 --- a/website/src/components/Header/UserMenu.tsx +++ b/website/src/components/Header/UserMenu.tsx @@ -34,7 +34,9 @@ export function UserMenu() { height="40" className="rounded-full" > -

{session.user.name || session.user.email}

+

+ {session.user.name || session.user.email} +

diff --git a/website/src/pages/auth/signin.tsx b/website/src/pages/auth/signin.tsx index 2ead2414..a1af2326 100644 --- a/website/src/pages/auth/signin.tsx +++ b/website/src/pages/auth/signin.tsx @@ -41,7 +41,7 @@ export default function Signin({ csrfToken, providers }) { )} {email && ( -
+