From 6ff8c430cfb894abe8caef73975053344d079615 Mon Sep 17 00:00:00 2001 From: Arif Nizami Date: Wed, 6 Nov 2024 21:06:25 -0600 Subject: [PATCH 1/7] implemented cypress e2e --- .gitignore | 3 ++ cucumber.config.js | 5 +++ cypress.config.ts | 24 ++++++++++++ cypress/README.md | 5 +++ cypress/e2e/features/homepage.feature | 11 ++++++ cypress/fixtures/example.json | 5 +++ cypress/pages/authPage.js | 16 ++++++++ cypress/pages/homePage.js | 12 ++++++ cypress/support/commands.ts | 37 +++++++++++++++++++ cypress/support/e2e.ts | 20 ++++++++++ .../support/step_definitions/homepageSteps.js | 10 +++++ .../support/step_definitions/loginSteps.js | 24 ++++++++++++ package.json | 7 +++- tsconfig.json | 2 +- 14 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 cucumber.config.js create mode 100644 cypress.config.ts create mode 100644 cypress/README.md create mode 100644 cypress/e2e/features/homepage.feature create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/pages/authPage.js create mode 100644 cypress/pages/homePage.js create mode 100644 cypress/support/commands.ts create mode 100644 cypress/support/e2e.ts create mode 100644 cypress/support/step_definitions/homepageSteps.js create mode 100644 cypress/support/step_definitions/loginSteps.js diff --git a/.gitignore b/.gitignore index b31b9e9..45f0784 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ postgres-data /prisma/customerQueries.js # Package Lock package-lock.json + +#Cypress env +cypress.env.json diff --git a/cucumber.config.js b/cucumber.config.js new file mode 100644 index 0000000..a2a9c1c --- /dev/null +++ b/cucumber.config.js @@ -0,0 +1,5 @@ +const { defineConfig } = require("@badeball/cypress-cucumber-preprocessor"); + +module.exports = defineConfig({ + stepDefinitions: "cypress/support/step_definitions/**/*.{js,ts}", // Include both JS and TS files +}); diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000..a869f99 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "cypress"; +import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; +import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; +import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild"; + +export default defineConfig({ + e2e: { + baseUrl: "http://localhost:3000", + async setupNodeEvents(on, config) { + await addCucumberPreprocessorPlugin(on, config); + + on("file:preprocessor", createBundler({ + plugins: [createEsbuildPlugin(config)], + })); + + return config; + }, + specPattern: "cypress/e2e/features/**/*.feature", // Path for feature files + }, + env: { + AUTH0_USERNAME: process.env.CYPRESS_AUTH0_USERNAME, + AUTH0_PASSWORD: process.env.CYPRESS_AUTH0_PASSWORD, + }, +}); diff --git a/cypress/README.md b/cypress/README.md new file mode 100644 index 0000000..ec9513c --- /dev/null +++ b/cypress/README.md @@ -0,0 +1,5 @@ +# Gherkin Documentation +[Gherkin Documentation](https://cucumber.io/docs/gherkin/reference/) + +# Cypress Wiki +[Cypress E2E Testing](https://docs.cypress.io/app/end-to-end-testing/writing-your-first-end-to-end-test) \ No newline at end of file diff --git a/cypress/e2e/features/homepage.feature b/cypress/e2e/features/homepage.feature new file mode 100644 index 0000000..a19b5aa --- /dev/null +++ b/cypress/e2e/features/homepage.feature @@ -0,0 +1,11 @@ +Feature: Homepage Load + Scenario: User loads the homepage + Given I am on the homepage + Then I should see the homepage title + +Feature: User Login + Scenario: User logs in successfully + Given I am on the homepage + When the user clicks the login button + Then the user should be redirected to the Auth0 login page + And the user logs in with the test credentials diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/pages/authPage.js b/cypress/pages/authPage.js new file mode 100644 index 0000000..233bb35 --- /dev/null +++ b/cypress/pages/authPage.js @@ -0,0 +1,16 @@ +class AuthPage { + enterUsername(username) { + cy.get('input[name="username"]').type(username); // Adjust the selector if needed + } + + enterPassword(password) { + cy.get('input[name="password"]').type(password); // Adjust the selector if needed + } + + submit() { + cy.get('button[type="submit"]').click(); // Adjust the selector if needed + } + } + + export default new AuthPage(); + \ No newline at end of file diff --git a/cypress/pages/homePage.js b/cypress/pages/homePage.js new file mode 100644 index 0000000..fde0170 --- /dev/null +++ b/cypress/pages/homePage.js @@ -0,0 +1,12 @@ +class HomePage { + visit() { + cy.visit("/"); + } + + verifyTitle() { + cy.title().should("include", "WCOA Rides"); + } + } + + export default new HomePage(); + \ No newline at end of file diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 0000000..698b01a --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } \ No newline at end of file diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 0000000..f80f74f --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/cypress/support/step_definitions/homepageSteps.js b/cypress/support/step_definitions/homepageSteps.js new file mode 100644 index 0000000..735c9aa --- /dev/null +++ b/cypress/support/step_definitions/homepageSteps.js @@ -0,0 +1,10 @@ +import { Given, Then } from "@badeball/cypress-cucumber-preprocessor"; +import HomePage from "../../pages/homePage"; + +Given("I am on the homepage", () => { + HomePage.visit(); +}); + +Then("I should see the homepage title", () => { + HomePage.verifyTitle(); +}); diff --git a/cypress/support/step_definitions/loginSteps.js b/cypress/support/step_definitions/loginSteps.js new file mode 100644 index 0000000..2ac11ef --- /dev/null +++ b/cypress/support/step_definitions/loginSteps.js @@ -0,0 +1,24 @@ +import { Given, When, Then } from "@badeball/cypress-cucumber-preprocessor"; +import HomePage from "../../pages/homePage"; +import AuthPage from "../../pages/authPage"; + +const username = Cypress.env("AUTH0_USERNAME"); +const password = Cypress.env("AUTH0_PASSWORD"); + +Given("I am on the homepage", () => { + HomePage.visit(); +}); + +When("the user clicks the login button", () => { + HomePage.clickLogin(); +}); + +Then("the user should be redirected to the Auth0 login page", () => { + cy.url().should("include", "auth0"); +}); + +Then("the user logs in with the test credentials", () => { + AuthPage.enterUsername(username); + AuthPage.enterPassword(password); + AuthPage.submit(); +}); diff --git a/package.json b/package.json index e42d110..ad8a25f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "postinstall": "prisma generate", - "lint": "eslint ." + "lint": "eslint .", + "cypress:open": "cypress open" }, "lint-staged": { "*.{js,jsx,ts,tsx,mjs}": [ @@ -45,6 +46,8 @@ "ws": "^8.18.0" }, "devDependencies": { + "@badeball/cypress-cucumber-preprocessor": "^21.0.2", + "@bahmutov/cypress-esbuild-preprocessor": "^2.2.3", "@eslint/compat": "^1.2.0", "@types/node": "^20.11.30", "@types/react": "^18.2.73", @@ -54,6 +57,8 @@ "@typescript-eslint/parser": "^8.9.0", "autoprefixer": "^10.4.20", "copy-webpack-plugin": "^12.0.2", + "cypress": "^13.15.1", + "esbuild": "^0.24.0", "eslint": "^9.12.0", "eslint-config-next": "^14.2.15", "eslint-config-prettier": "^9.1.0", diff --git a/tsconfig.json b/tsconfig.json index 32a7c38..a04da1f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,6 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/page.jsx", "app/loginPage/page.jsx", "app/api/auth/[authType]/route.js"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/page.jsx", "app/loginPage/page.jsx", "app/api/auth/[authType]/route.js", "cypress/**/*.ts"], "exclude": ["node_modules"] } From c20ee6ac0d5b841bf1c37ac851fbb0787d40b078 Mon Sep 17 00:00:00 2001 From: Arif Nizami Date: Wed, 6 Nov 2024 22:47:45 -0600 Subject: [PATCH 2/7] added login and logout tests --- cypress/e2e/features/homepage.feature | 13 +++-- cypress/pages/authPage.js | 51 ++++++++++++++----- cypress/pages/homePage.js | 8 +++ .../support/step_definitions/homepageSteps.js | 49 +++++++++++++++++- .../support/step_definitions/loginSteps.js | 24 --------- 5 files changed, 101 insertions(+), 44 deletions(-) delete mode 100644 cypress/support/step_definitions/loginSteps.js diff --git a/cypress/e2e/features/homepage.feature b/cypress/e2e/features/homepage.feature index a19b5aa..090912d 100644 --- a/cypress/e2e/features/homepage.feature +++ b/cypress/e2e/features/homepage.feature @@ -1,11 +1,14 @@ Feature: Homepage Load - Scenario: User loads the homepage - Given I am on the homepage - Then I should see the homepage title -Feature: User Login Scenario: User logs in successfully Given I am on the homepage When the user clicks the login button Then the user should be redirected to the Auth0 login page - And the user logs in with the test credentials + And the user logs in with valid credentials + Then the user is authenticated and redirected back to the dashboard + + Scenario: User logs out successfully + Given the user is logged in + When the user clicks the logout button + Then the user should be logged out and see the login button + And the user should not be able to access the dashboard diff --git a/cypress/pages/authPage.js b/cypress/pages/authPage.js index 233bb35..e848754 100644 --- a/cypress/pages/authPage.js +++ b/cypress/pages/authPage.js @@ -1,16 +1,41 @@ +import HomePage from "./homePage"; + class AuthPage { - enterUsername(username) { - cy.get('input[name="username"]').type(username); // Adjust the selector if needed - } - enterPassword(password) { - cy.get('input[name="password"]').type(password); // Adjust the selector if needed - } - - submit() { - cy.get('button[type="submit"]').click(); // Adjust the selector if needed - } + enterUsername(username) { + cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", { args: { username } }, ({ username }) => { + cy.get('input[name="username"]').type(username); + }); } - - export default new AuthPage(); - \ No newline at end of file + + enterPassword(password) { + cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", { args: { password } }, ({ password }) => { + cy.get('input[name="password"]').type(password); + }); + } + + submit() { + cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { + cy.get('button[type="submit"]').click(); + }); + } + + login() { + const username = Cypress.env("AUTH0_USERNAME"); + const password = Cypress.env("AUTH0_PASSWORD"); + + cy.session("authSession", () => { + HomePage.visit(); + cy.wait(1000); + HomePage.clickLogin(); + cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { + cy.url().should("include", "auth0"); + }); + this.enterUsername(username); + this.enterPassword(password); + this.submit(); + }); + } +} + +export default new AuthPage(); diff --git a/cypress/pages/homePage.js b/cypress/pages/homePage.js index fde0170..05581bc 100644 --- a/cypress/pages/homePage.js +++ b/cypress/pages/homePage.js @@ -6,6 +6,14 @@ class HomePage { verifyTitle() { cy.title().should("include", "WCOA Rides"); } + + clickLogin() { + cy.contains('button', "Log In").click(); + } + + clickLogout() { + cy.contains('button', "Log Out").click(); + } } export default new HomePage(); diff --git a/cypress/support/step_definitions/homepageSteps.js b/cypress/support/step_definitions/homepageSteps.js index 735c9aa..77b6434 100644 --- a/cypress/support/step_definitions/homepageSteps.js +++ b/cypress/support/step_definitions/homepageSteps.js @@ -1,10 +1,55 @@ -import { Given, Then } from "@badeball/cypress-cucumber-preprocessor"; +import { Given, When, Then } from "@badeball/cypress-cucumber-preprocessor"; import HomePage from "../../pages/homePage"; +import AuthPage from "../../pages/authPage"; + +const username = Cypress.env("AUTH0_USERNAME"); +const password = Cypress.env("AUTH0_PASSWORD"); Given("I am on the homepage", () => { HomePage.visit(); }); -Then("I should see the homepage title", () => { +When("the user clicks the login button", () => { + cy.wait(1000); + HomePage.clickLogin(); +}); + +Then("the user should be redirected to the Auth0 login page", () => { + cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { + cy.url().should("include", "auth0"); + }); +}); + +Then("the user logs in with valid credentials", () => { + AuthPage.enterUsername(username); + AuthPage.enterPassword(password); + AuthPage.submit(); +}); + +Then("the user is authenticated and redirected back to the dashboard", () => { + cy.wait(1000); + HomePage.verifyTitle(); +}); + +Given("the user is logged in", () => { + AuthPage.login(); +}); + +When("the user clicks the logout button", () => { + HomePage.visit(); + cy.wait(1000); + HomePage.clickLogout(); +}); + +Then("the user should be logged out and see the login button", () => { HomePage.verifyTitle(); + cy.contains('button', 'Log In'); + +}); + +Then("the user should not be able to access the dashboard", () => { + cy.visit("/dashboardEmployee"); + cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { + cy.url().should("include", "auth0"); + }); }); diff --git a/cypress/support/step_definitions/loginSteps.js b/cypress/support/step_definitions/loginSteps.js deleted file mode 100644 index 2ac11ef..0000000 --- a/cypress/support/step_definitions/loginSteps.js +++ /dev/null @@ -1,24 +0,0 @@ -import { Given, When, Then } from "@badeball/cypress-cucumber-preprocessor"; -import HomePage from "../../pages/homePage"; -import AuthPage from "../../pages/authPage"; - -const username = Cypress.env("AUTH0_USERNAME"); -const password = Cypress.env("AUTH0_PASSWORD"); - -Given("I am on the homepage", () => { - HomePage.visit(); -}); - -When("the user clicks the login button", () => { - HomePage.clickLogin(); -}); - -Then("the user should be redirected to the Auth0 login page", () => { - cy.url().should("include", "auth0"); -}); - -Then("the user logs in with the test credentials", () => { - AuthPage.enterUsername(username); - AuthPage.enterPassword(password); - AuthPage.submit(); -}); From b4fa754e9ca5014b07c1f4559bbd672c8b2e0ae3 Mon Sep 17 00:00:00 2001 From: Arif Nizami Date: Thu, 7 Nov 2024 16:32:39 -0600 Subject: [PATCH 3/7] setup e2e database and npm run test:e2e command --- .gitignore | 1 + cypress.config.ts | 1 + .../support/step_definitions/homepageSteps.js | 1 + docker-compose.e2e.yml | 32 +++++++++++++++++++ middleware.ts | 4 +-- package.json | 14 ++++++-- prisma/seed.e2e.js | 24 ++++++++++++++ 7 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 docker-compose.e2e.yml create mode 100644 prisma/seed.e2e.js diff --git a/.gitignore b/.gitignore index 45f0784..c04c6fb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env*.local .env +.env*.test # vercel .vercel diff --git a/cypress.config.ts b/cypress.config.ts index a869f99..4615449 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -20,5 +20,6 @@ export default defineConfig({ env: { AUTH0_USERNAME: process.env.CYPRESS_AUTH0_USERNAME, AUTH0_PASSWORD: process.env.CYPRESS_AUTH0_PASSWORD, + POSTGRES_PRISMA_URL: process.env.POSTGRES_PRISMA_URL, }, }); diff --git a/cypress/support/step_definitions/homepageSteps.js b/cypress/support/step_definitions/homepageSteps.js index 77b6434..177a36e 100644 --- a/cypress/support/step_definitions/homepageSteps.js +++ b/cypress/support/step_definitions/homepageSteps.js @@ -29,6 +29,7 @@ Then("the user logs in with valid credentials", () => { Then("the user is authenticated and redirected back to the dashboard", () => { cy.wait(1000); HomePage.verifyTitle(); + cy.url().should("include", "dashboard"); }); Given("the user is logged in", () => { diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml new file mode 100644 index 0000000..cd744e5 --- /dev/null +++ b/docker-compose.e2e.yml @@ -0,0 +1,32 @@ +version: '3.8' +services: + e2e_postgres: + image: postgres:latest + container_name: e2e_postgres_db + ports: + - "5433:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: e2e_test_db + volumes: + - e2e_postgres_data:/var/lib/postgresql/data + e2e_websockify: + image: kamehb/websockify + restart: unless-stopped + command: "0.0.0.0:6433 e2e_postgres:5433" + ports: + - "6433:6432" + depends_on: + - e2e_postgres + volumes: + - e2e-websockify-data:/opt/websockify/data + - e2e-websockify-config:/opt/websockify/config + +volumes: + e2e_postgres_data: + driver: local + e2e-websockify-data: + driver: local + e2e-websockify-config: + driver: local diff --git a/middleware.ts b/middleware.ts index 3cc05f8..0a31074 100644 --- a/middleware.ts +++ b/middleware.ts @@ -6,12 +6,12 @@ import { PrismaNeon } from '@prisma/adapter-neon'; import { Pool, neonConfig } from '@neondatabase/serverless'; import { jwtVerify, importX509 } from 'jose'; -const localDatabasePort = 6432; +const localDatabasePort = process.env.WEBSOCKET_PORT || 6432;; const localDatabaseHost = 'localhost'; neonConfig.fetchEndpoint = `http://${localDatabaseHost}:${localDatabasePort}`; neonConfig.useSecureWebSocket = false; // SET TO TRUE IN PROD neonConfig.pipelineConnect = false; -neonConfig.wsProxy = 'localhost:6432'; +neonConfig.wsProxy = `localhost:${localDatabasePort}`; const neon = new Pool({ connectionString: process.env.POSTGRES_PRISMA_URL }); const adapter = new PrismaNeon(neon); const client = new PrismaClient({ adapter }); diff --git a/package.json b/package.json index ad8a25f..51b4d8c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "start": "next start", "postinstall": "prisma generate", "lint": "eslint .", - "cypress:open": "cypress open" + "prisma:seede2e": "node prisma/seed.e2e.js", + "dev:test": "env-cmd -f .env.test next dev", + "post-e2e": "docker compose -f docker-compose.e2e.yml down -v", + "test:e2e": "docker compose -f docker-compose.e2e.yml down -v && docker compose -f docker-compose.e2e.yml up -d e2e_postgres && cross-env sleep 2 && env-cmd -f .env.test npx prisma db push && env-cmd -f .env.test npm run prisma:seede2e && concurrently --kill-others --success first \"npm run dev:test\" \"wait-on http://localhost:3000 && npx cypress open\" && npm run post-e2e" }, "lint-staged": { "*.{js,jsx,ts,tsx,mjs}": [ @@ -56,8 +59,12 @@ "@typescript-eslint/eslint-plugin": "^8.9.0", "@typescript-eslint/parser": "^8.9.0", "autoprefixer": "^10.4.20", + "concurrently": "^9.1.0", "copy-webpack-plugin": "^12.0.2", + "cross-env": "^7.0.3", + "cross-os": "^1.5.0", "cypress": "^13.15.1", + "env-cmd": "^10.1.0", "esbuild": "^0.24.0", "eslint": "^9.12.0", "eslint-config-next": "^14.2.15", @@ -70,9 +77,10 @@ "prettier": "^3.3.3", "prisma": "^5.15.0", "tailwindcss": "^3.4.13", - "typescript": "^5.6.3" + "typescript": "^5.6.3", + "wait-on": "^8.0.1" }, "overrides": { "eslint": "$eslint" } -} +} \ No newline at end of file diff --git a/prisma/seed.e2e.js b/prisma/seed.e2e.js new file mode 100644 index 0000000..dc80ed3 --- /dev/null +++ b/prisma/seed.e2e.js @@ -0,0 +1,24 @@ +const { PrismaClient } = require('@prisma/client'); +const prisma = new PrismaClient(); + +async function main() { + await prisma.user.create({ + data: { + email: "e2ewcoatestuser@utdallas.edu", + firstName: "E2E", + lastName: "TEST", + phone: "0000000000", + }, + }); + + // Other Seed Data goes here +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); From 1a5773da58838a2dbc33ccfc1aa7b723cb6af722 Mon Sep 17 00:00:00 2001 From: Arif Nizami Date: Thu, 5 Dec 2024 15:04:33 -0600 Subject: [PATCH 4/7] updated seede2e --- prisma/seed.e2e.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/seed.e2e.js b/prisma/seed.e2e.js index dc80ed3..259742a 100644 --- a/prisma/seed.e2e.js +++ b/prisma/seed.e2e.js @@ -2,7 +2,7 @@ const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); async function main() { - await prisma.user.create({ + await prisma.admin.create({ data: { email: "e2ewcoatestuser@utdallas.edu", firstName: "E2E", From 82cab2349020bcc3e75b41aa85b0b574217fb566 Mon Sep 17 00:00:00 2001 From: Arif Nizami Date: Thu, 5 Dec 2024 15:42:37 -0600 Subject: [PATCH 5/7] made login function a cypress command and fixed linting --- cucumber.config.js | 4 +- cypress.config.ts | 21 ++++---- cypress/pages/authPage.js | 52 +++++++------------ cypress/pages/homePage.js | 31 +++++------ cypress/support/commands.ts | 29 ++++++++++- cypress/support/cypress.d.ts | 12 +++++ cypress/support/e2e.ts | 4 +- .../support/step_definitions/homepageSteps.js | 43 ++++++++------- middleware.ts | 2 +- 9 files changed, 110 insertions(+), 88 deletions(-) create mode 100644 cypress/support/cypress.d.ts diff --git a/cucumber.config.js b/cucumber.config.js index a2a9c1c..5bb09d3 100644 --- a/cucumber.config.js +++ b/cucumber.config.js @@ -1,5 +1,5 @@ -const { defineConfig } = require("@badeball/cypress-cucumber-preprocessor"); +const { defineConfig } = require('@badeball/cypress-cucumber-preprocessor'); module.exports = defineConfig({ - stepDefinitions: "cypress/support/step_definitions/**/*.{js,ts}", // Include both JS and TS files + stepDefinitions: 'cypress/support/step_definitions/**/*.{js,ts}', // Include both JS and TS files }); diff --git a/cypress.config.ts b/cypress.config.ts index 4615449..dd75891 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,21 +1,24 @@ -import { defineConfig } from "cypress"; -import createBundler from "@bahmutov/cypress-esbuild-preprocessor"; -import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor"; -import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild"; +import { defineConfig } from 'cypress'; +import createBundler from '@bahmutov/cypress-esbuild-preprocessor'; +import { addCucumberPreprocessorPlugin } from '@badeball/cypress-cucumber-preprocessor'; +import createEsbuildPlugin from '@badeball/cypress-cucumber-preprocessor/esbuild'; export default defineConfig({ e2e: { - baseUrl: "http://localhost:3000", + baseUrl: 'http://localhost:3000', async setupNodeEvents(on, config) { await addCucumberPreprocessorPlugin(on, config); - on("file:preprocessor", createBundler({ - plugins: [createEsbuildPlugin(config)], - })); + on( + 'file:preprocessor', + createBundler({ + plugins: [createEsbuildPlugin(config)], + }) + ); return config; }, - specPattern: "cypress/e2e/features/**/*.feature", // Path for feature files + specPattern: 'cypress/e2e/features/**/*.feature', // Path for feature files }, env: { AUTH0_USERNAME: process.env.CYPRESS_AUTH0_USERNAME, diff --git a/cypress/pages/authPage.js b/cypress/pages/authPage.js index e848754..55c7ce0 100644 --- a/cypress/pages/authPage.js +++ b/cypress/pages/authPage.js @@ -1,41 +1,27 @@ -import HomePage from "./homePage"; - -class AuthPage { - - enterUsername(username) { - cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", { args: { username } }, ({ username }) => { - cy.get('input[name="username"]').type(username); - }); +export class AuthPage { + static enterUsername(username) { + cy.origin( + 'https://dev-5qwmxmrqa1bl88h4.us.auth0.com', + { args: { username } }, + ({ username }) => { + cy.get('input[name="username"]').type(username); + } + ); } - enterPassword(password) { - cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", { args: { password } }, ({ password }) => { - cy.get('input[name="password"]').type(password); - }); + static enterPassword(password) { + cy.origin( + 'https://dev-5qwmxmrqa1bl88h4.us.auth0.com', + { args: { password } }, + ({ password }) => { + cy.get('input[name="password"]').type(password); + } + ); } - submit() { - cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { + static submit() { + cy.origin('https://dev-5qwmxmrqa1bl88h4.us.auth0.com', () => { cy.get('button[type="submit"]').click(); }); } - - login() { - const username = Cypress.env("AUTH0_USERNAME"); - const password = Cypress.env("AUTH0_PASSWORD"); - - cy.session("authSession", () => { - HomePage.visit(); - cy.wait(1000); - HomePage.clickLogin(); - cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { - cy.url().should("include", "auth0"); - }); - this.enterUsername(username); - this.enterPassword(password); - this.submit(); - }); - } } - -export default new AuthPage(); diff --git a/cypress/pages/homePage.js b/cypress/pages/homePage.js index 05581bc..981c2b3 100644 --- a/cypress/pages/homePage.js +++ b/cypress/pages/homePage.js @@ -1,20 +1,17 @@ -class HomePage { - visit() { - cy.visit("/"); - } - - verifyTitle() { - cy.title().should("include", "WCOA Rides"); - } +export class HomePage { + static visit() { + cy.visit('/'); + } + + static verifyTitle() { + cy.title().should('include', 'WCOA Rides'); + } - clickLogin() { - cy.contains('button', "Log In").click(); - } + static clickLogin() { + cy.contains('button', 'Log In').click(); + } - clickLogout() { - cy.contains('button', "Log Out").click(); - } + static clickLogout() { + cy.contains('button', 'Log Out').click(); } - - export default new HomePage(); - \ No newline at end of file +} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 698b01a..6dc7c25 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -1,4 +1,5 @@ /// + // *********************************************** // This example commands.ts shows you how to // create various custom commands and overwrite @@ -31,7 +32,31 @@ // login(email: string, password: string): Chainable // drag(subject: string, options?: Partial): Chainable // dismiss(subject: string, options?: Partial): Chainable -// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// visit(originalFn: CommandOriginalFn, +// url: string, options: Partial): Chainable // } // } -// } \ No newline at end of file +// } + +Cypress.Commands.add('login', () => { + const username = Cypress.env('AUTH0_USERNAME'); + const password = Cypress.env('AUTH0_PASSWORD'); + + cy.session('authSession', () => { + cy.visit('/'); + cy.wait(1000); + cy.contains('button', 'Log In').click(); + cy.origin('https://dev-5qwmxmrqa1bl88h4.us.auth0.com', () => { + cy.url().should('include', 'auth0'); + }); + cy.origin( + 'https://dev-5qwmxmrqa1bl88h4.us.auth0.com', + { args: { username, password } }, + ({ username, password }) => { + cy.get('input[name="username"]').type(username); + cy.get('input[name="password"]').type(password); + cy.get('button[type="submit"]').click(); + } + ); + }); +}); diff --git a/cypress/support/cypress.d.ts b/cypress/support/cypress.d.ts new file mode 100644 index 0000000..7f128b9 --- /dev/null +++ b/cypress/support/cypress.d.ts @@ -0,0 +1,12 @@ +/// + +declare global { + // eslint-disable-next-line no-unused-vars + namespace Cypress { + interface Chainable { + login(): Chainable; + } + } +} + +export {}; // This is important to avoid issues with module augmentation diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index f80f74f..598ab5f 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; // Alternatively you can use CommonJS syntax: -// require('./commands') \ No newline at end of file +// require('./commands') diff --git a/cypress/support/step_definitions/homepageSteps.js b/cypress/support/step_definitions/homepageSteps.js index 177a36e..728416d 100644 --- a/cypress/support/step_definitions/homepageSteps.js +++ b/cypress/support/step_definitions/homepageSteps.js @@ -1,56 +1,55 @@ -import { Given, When, Then } from "@badeball/cypress-cucumber-preprocessor"; -import HomePage from "../../pages/homePage"; -import AuthPage from "../../pages/authPage"; +import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'; +import HomePage from '../../pages/homePage'; +import AuthPage from '../../pages/authPage'; -const username = Cypress.env("AUTH0_USERNAME"); -const password = Cypress.env("AUTH0_PASSWORD"); +const username = Cypress.env('AUTH0_USERNAME'); +const password = Cypress.env('AUTH0_PASSWORD'); -Given("I am on the homepage", () => { +Given('I am on the homepage', () => { HomePage.visit(); }); -When("the user clicks the login button", () => { +When('the user clicks the login button', () => { cy.wait(1000); HomePage.clickLogin(); }); -Then("the user should be redirected to the Auth0 login page", () => { - cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { - cy.url().should("include", "auth0"); +Then('the user should be redirected to the Auth0 login page', () => { + cy.origin('https://dev-5qwmxmrqa1bl88h4.us.auth0.com', () => { + cy.url().should('include', 'auth0'); }); }); -Then("the user logs in with valid credentials", () => { +Then('the user logs in with valid credentials', () => { AuthPage.enterUsername(username); AuthPage.enterPassword(password); AuthPage.submit(); }); -Then("the user is authenticated and redirected back to the dashboard", () => { +Then('the user is authenticated and redirected back to the dashboard', () => { cy.wait(1000); HomePage.verifyTitle(); - cy.url().should("include", "dashboard"); + cy.url().should('include', 'dashboard'); }); -Given("the user is logged in", () => { - AuthPage.login(); +Given('the user is logged in', () => { + cy.login(); }); -When("the user clicks the logout button", () => { +When('the user clicks the logout button', () => { HomePage.visit(); cy.wait(1000); HomePage.clickLogout(); }); -Then("the user should be logged out and see the login button", () => { +Then('the user should be logged out and see the login button', () => { HomePage.verifyTitle(); cy.contains('button', 'Log In'); - }); -Then("the user should not be able to access the dashboard", () => { - cy.visit("/dashboardEmployee"); - cy.origin("https://dev-5qwmxmrqa1bl88h4.us.auth0.com", () => { - cy.url().should("include", "auth0"); +Then('the user should not be able to access the dashboard', () => { + cy.visit('/dashboardEmployee'); + cy.origin('https://dev-5qwmxmrqa1bl88h4.us.auth0.com', () => { + cy.url().should('include', 'auth0'); }); }); diff --git a/middleware.ts b/middleware.ts index c79a642..9c6496e 100644 --- a/middleware.ts +++ b/middleware.ts @@ -6,7 +6,7 @@ import { PrismaNeon } from '@prisma/adapter-neon'; import { Pool, neonConfig } from '@neondatabase/serverless'; import { jwtVerify, importX509 } from 'jose'; -const localDatabasePort = process.env.WEBSOCKET_PORT || 6432;; +const localDatabasePort = process.env.WEBSOCKET_PORT || 6432; const localDatabaseHost = 'localhost'; neonConfig.fetchEndpoint = `http://${localDatabaseHost}:${localDatabasePort}`; neonConfig.useSecureWebSocket = false; // SET TO TRUE IN PROD From 327d8c2da211b56a8ae4eef3f42f8cc345d734d2 Mon Sep 17 00:00:00 2001 From: Arif Nizami Date: Thu, 5 Dec 2024 16:29:46 -0600 Subject: [PATCH 6/7] updated e2e test command to support windows --- package.json | 4 ++-- wait.mjs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 wait.mjs diff --git a/package.json b/package.json index 51b4d8c..c112277 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "prisma:seede2e": "node prisma/seed.e2e.js", "dev:test": "env-cmd -f .env.test next dev", "post-e2e": "docker compose -f docker-compose.e2e.yml down -v", - "test:e2e": "docker compose -f docker-compose.e2e.yml down -v && docker compose -f docker-compose.e2e.yml up -d e2e_postgres && cross-env sleep 2 && env-cmd -f .env.test npx prisma db push && env-cmd -f .env.test npm run prisma:seede2e && concurrently --kill-others --success first \"npm run dev:test\" \"wait-on http://localhost:3000 && npx cypress open\" && npm run post-e2e" + "test:e2e": "docker compose -f docker-compose.e2e.yml down -v && docker compose -f docker-compose.e2e.yml up -d e2e_postgres && cross-env node wait.mjs && env-cmd -f .env.test npx prisma db push && env-cmd -f .env.test npm run prisma:seede2e && concurrently --kill-others --success first \"npm run dev:test\" \"wait-on http://localhost:3000 && npx cypress open\" && npm run post-e2e" }, "lint-staged": { "*.{js,jsx,ts,tsx,mjs}": [ @@ -83,4 +83,4 @@ "overrides": { "eslint": "$eslint" } -} \ No newline at end of file +} diff --git a/wait.mjs b/wait.mjs new file mode 100644 index 0000000..463e410 --- /dev/null +++ b/wait.mjs @@ -0,0 +1,8 @@ +// wait.mjs +const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +(async () => { + console.log('Sleeping for 2 seconds...'); + await sleep(2000); // Sleep for 2 seconds + console.log('Awoke after 2 seconds!'); +})(); From 1f2b1612b229c6aa00dc5b65a18612ad71e7532a Mon Sep 17 00:00:00 2001 From: Arif Nizami Date: Thu, 5 Dec 2024 16:32:48 -0600 Subject: [PATCH 7/7] removed console --- wait.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wait.mjs b/wait.mjs index 463e410..2ee59e4 100644 --- a/wait.mjs +++ b/wait.mjs @@ -1,8 +1,6 @@ // wait.mjs -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); (async () => { - console.log('Sleeping for 2 seconds...'); - await sleep(2000); // Sleep for 2 seconds - console.log('Awoke after 2 seconds!'); + await sleep(2000); // Sleep for 2 seconds })();