Repository: echobind/bisonapp
Pull request #268 (2022-07-07 16:04:16)

chore: upgrade to cypress 10
## Changes

- Upgrades to Cypress 10 which contained a few breaking changes: https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-version-10-0
   -  The configuration files (`cypress.config.ts`,`cypress/support/`, `cypress/plugins/` were automatically migrated by cypress during the upgrade process.
-  Converts our cypress tests from `.js` files to `.ts`
-  Fixes flaky tests for login page due to running assertions before the API request finishes
-  Even before these changes, there were quite a few TypeScript issues with our Cypress set up due to misconfiguration in our tsconfig files. I think this might have been the reason we were using `js` files. This should resolve those.

## Checklist

- [x] Requires dependency update?
- [x] Generating a new app works


Patch:
diff --git a/packages/create-bison-app/tasks/copyFiles.js b/packages/create-bison-app/tasks/copyFiles.js
index 8082a60..6f8f3ac 100644
--- a/packages/create-bison-app/tasks/copyFiles.js
+++ b/packages/create-bison-app/tasks/copyFiles.js
@@ -94,7 +94,7 @@ async function copyFiles({ variables, targetFolder }) {
         ".tool-versions",
         "codegen.yml",
         "constants.ts",
-        "cypress.json",
+        "cypress.config.ts",
         "jest.config.js",
         "next-env.d.ts",
         "prettier.config.js",
diff --git a/packages/create-bison-app/template/.github/workflows/main.js.yml.ejs b/packages/create-bison-app/template/.github/workflows/main.js.yml.ejs
index 0581fe2..f31441f 100644
--- a/packages/create-bison-app/template/.github/workflows/main.js.yml.ejs
+++ b/packages/create-bison-app/template/.github/workflows/main.js.yml.ejs
@@ -76,7 +76,7 @@ jobs:
 
       # E2E Tests via Cypress
       - name: Run Cypress E2E Tests
-        uses: cypress-io/github-action@v2
+        uses: cypress-io/github-action@v4
         with:
           # build: yarn build
           start: yarn test:server
diff --git a/packages/create-bison-app/template/cypress.config.ts b/packages/create-bison-app/template/cypress.config.ts
new file mode 100644
index 0000000..489c47e
--- /dev/null
+++ b/packages/create-bison-app/template/cypress.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'cypress';
+
+import { setupNodeEvents } from './cypress/plugins';
+
+export default defineConfig({
+  fixturesFolder: false,
+  e2e: {
+    setupNodeEvents,
+    baseUrl: 'http://localhost:3001',
+    specPattern: 'tests/e2e/**/*.cy.{ts,tsx}',
+    excludeSpecPattern: ['tsconfig.json'],
+  },
+});
diff --git a/packages/create-bison-app/template/cypress.json b/packages/create-bison-app/template/cypress.json
deleted file mode 100644
index aebaefb..0000000
--- a/packages/create-bison-app/template/cypress.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "baseUrl": "http://localhost:3001",
-  "integrationFolder": "tests/e2e",
-  "fixturesFolder": false,
-  "ignoreTestFiles": ["tsconfig.json"]
-}
diff --git a/packages/create-bison-app/template/cypress/plugins/index.ts b/packages/create-bison-app/template/cypress/plugins/index.ts
index 29d15a8..ef2a4e6 100644
--- a/packages/create-bison-app/template/cypress/plugins/index.ts
+++ b/packages/create-bison-app/template/cypress/plugins/index.ts
@@ -1,6 +1,3 @@
-/// <reference types="cypress" />
-
-// import 'cypress';
 import path from 'path';
 
 import { User } from '@prisma/client';
@@ -10,27 +7,11 @@ import { LOGIN_TOKEN_KEY } from '@/constants';
 import { resetDB, disconnect, setupDB, graphQLRequest } from '@/tests/helpers';
 import * as Factories from '@/tests/factories';
 
-declare global {
-  // eslint-disable-next-line
-  namespace Cypress {
-    interface Chainable<Subject = any> {
-      task<T>(event: string, arg?: any, options?: Partial<Loggable & Timeoutable>): Chainable<T>;
-    }
-  }
+export interface LoginTaskObject {
+  token: string;
 }
 
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
-// This function is called when a project is opened or re-opened (e.g. due to
-// the project's config changing)
+type FactoryNames = keyof typeof Factories extends `${infer T}Factory` ? T : never;
 
 if (process.env.CYPRESS_LOCAL) {
   console.log('--- WARNING --- RUNNING CYPRESS IN LOCAL MODE... database will not be cleaned');
@@ -43,15 +24,9 @@ if (process.env.CYPRESS_LOCAL) {
   require('dotenv').config({ path: envPath });
 }
 
-/**
- * @type {Cypress.PluginConfig}
- */
-export default (on, _config) => {
-  // `on` is used to hook into various events Cypress emits
-  // `config` is the resolved Cypress config
-
-  on('before:run', () => {
-    return setupDB();
+export const setupNodeEvents: Cypress.ConfigOptions['setupNodeEvents'] = (on, _config) => {
+  on('before:run', async () => {
+    await setupDB();
   });
 
   on('task', {
@@ -68,11 +43,12 @@ export default (on, _config) => {
     disconnectDB: () => {
       return disconnect();
     },
-    factory: ({ name, attrs }) => {
+    factory: ({ name, attrs }: { name: FactoryNames, attrs: any }) => {
       const Factory = Factories[`${name}Factory`];
+
       return Factory.create(attrs);
     },
-    login: (attrs: Pick<User, 'email' | 'password'>): Promise<LoginTaskObject> => {
+    login: async (attrs: Pick<User, 'email' | 'password'>): Promise<LoginTaskObject> => {
       const { email, password } = attrs;
       const variables = { email, password };
 
@@ -84,17 +60,10 @@ export default (on, _config) => {
         }
       `;
 
-      return graphQLRequest({ query, variables }).then((response) => {
-        const { token } = response.body.data.login;
-
-        cookies().set(LOGIN_TOKEN_KEY, token);
-
-        return { token };
-      });
+      const response = await graphQLRequest({ query, variables });
+      const { token } = response.body.data.login;
+      cookies().set(LOGIN_TOKEN_KEY, token);
+      return { token };
     },
   });
 };
-
-export interface LoginTaskObject {
-  token: string;
-}
diff --git a/packages/create-bison-app/template/cypress/support/commands.ts b/packages/create-bison-app/template/cypress/support/commands.ts
index fbffb8d..b990136 100644
--- a/packages/create-bison-app/template/cypress/support/commands.ts
+++ b/packages/create-bison-app/template/cypress/support/commands.ts
@@ -1,3 +1,10 @@
+/// <reference types="cypress" />
+// ***********************************************
+// For comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+
 import '@testing-library/cypress/add-commands';
 
 import { Prisma, User } from '@prisma/client';
@@ -6,29 +13,14 @@ import { LOGIN_TOKEN_KEY } from '@/constants';
 import { LoginTaskObject } from '@/cypress/plugins';
 
 declare global {
-  // eslint-disable-next-line
   namespace Cypress {
     interface Chainable {
       login: typeof login;
+      createUserAndLogin: typeof createUserAndLogin;
     }
   }
 }
 
-// -- 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) => { ... })
-
 /**
  *  Handles logging a user in via email and password.
  *  This should be used to login in future e2e tests instead of the login form.
@@ -46,13 +38,13 @@ function login(attrs: Pick<User, 'email' | 'password'>) {
  *  Handles creating and logging in a user with a set of attributes
  *  This should be used to login in future e2e tests instead of the login form.
  */
-function createUserAndLogin(args: Prisma.UserCreateInput) {
+function createUserAndLogin(args: Partial<Prisma.UserCreateInput> = {}) {
   const attrs = {
     ...args,
     password: args?.password || 'abcd1234',
   };
 
-  return cy.task('factory', { name: 'User', attrs }).then((user: User) => {
+  return cy.task<User>('factory', { name: 'User', attrs }).then((user) => {
     return login({ email: user.email, password: attrs.password }).then(() => user);
   });
 }
diff --git a/packages/create-bison-app/template/cypress/support/index.ts b/packages/create-bison-app/template/cypress/support/e2e.ts
similarity index 92%
rename from packages/create-bison-app/template/cypress/support/index.ts
rename to packages/create-bison-app/template/cypress/support/e2e.ts
index 310c761..e6ce68a 100644
--- a/packages/create-bison-app/template/cypress/support/index.ts
+++ b/packages/create-bison-app/template/cypress/support/e2e.ts
@@ -1,5 +1,5 @@
 // ***********************************************************
-// This example support/index.js is processed and
+// This example support/e2e.ts is processed and
 // loaded automatically before your test files.
 //
 // This is a great place to put global configuration and
diff --git a/packages/create-bison-app/template/cypress/tsconfig.json b/packages/create-bison-app/template/cypress/tsconfig.json
index 21c2f87..7c94e29 100644
--- a/packages/create-bison-app/template/cypress/tsconfig.json
+++ b/packages/create-bison-app/template/cypress/tsconfig.json
@@ -3,12 +3,8 @@
   "compilerOptions": {
     "target": "es2015",
     "lib": ["es5", "dom"],
-    "types": ["cypress", "@testing-library/cypress", "node"],
-    "module": "commonjs",
-    "sourceMap": false,
-    "inlineSourceMap": true,
-    "noImplicitAny": false
+    "types": ["cypress", "@testing-library/cypress", "node"]
   },
-  "include": ["**/*.ts"],
+  "include": ["**/*.ts", "../cypress.config.ts"],
   "exclude": []
 }
diff --git a/packages/create-bison-app/template/package.json.ejs b/packages/create-bison-app/template/package.json.ejs
index 6216dde..3244255 100644
--- a/packages/create-bison-app/template/package.json.ejs
+++ b/packages/create-bison-app/template/package.json.ejs
@@ -97,10 +97,10 @@
     "commander": "^8.1.0",
     "concurrently": "^7.1.0",
     "cross-env": "^7.0.3",
-    "cypress": "^9.6.0",
+    "cypress": "^10.3.0",
     "eslint": "^8.18.0",
     "eslint-config-next": "^12.1.6",
-    "eslint-plugin-cypress": "^2.11.1",
+    "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-echobind": "^0.3.0",
     "eslint-plugin-import": "^2.26.0",
     "eslint-plugin-prettier": "^4.0.0",
diff --git a/packages/create-bison-app/template/tsconfig.json b/packages/create-bison-app/template/tsconfig.json
index f80df6d..849bc87 100644
--- a/packages/create-bison-app/template/tsconfig.json
+++ b/packages/create-bison-app/template/tsconfig.json
@@ -45,6 +45,8 @@
   ],
   "exclude": [
     "node_modules",
-    "cypress"
+    "cypress",
+    "cypress.config.ts",
+    "**/*.cy.ts"
   ]
 }


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b81a0a0..ebba065 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -107,7 +107,7 @@ jobs:
         run: yarn test:ci
 
       - name: Run bison app E2E tests
-        uses: cypress-io/github-action@v2
+        uses: cypress-io/github-action@v4
         with:
           start: yarn test:server
           wait-on: "http://localhost:3001"
diff --git a/packages/create-bison-app/template/tests/e2e/login.test.js b/packages/create-bison-app/template/tests/e2e/login.cy.ts
similarity index 74%
rename from packages/create-bison-app/template/tests/e2e/login.test.js
rename to packages/create-bison-app/template/tests/e2e/login.cy.ts
index dafe3c4..8991d0f 100644
--- a/packages/create-bison-app/template/tests/e2e/login.test.js
+++ b/packages/create-bison-app/template/tests/e2e/login.cy.ts
@@ -1,15 +1,20 @@
+import { User } from '@prisma/client';
+
 describe('Login', () => {
   describe('with an email that doesnt exist', () => {
     it('shows an error', () => {
       const email = 'nowayshouldIexist@wee.net';
       const password = 'test1234';
 
+      cy.intercept('POST', '/api/graphql').as('loginMutation');
       cy.visit('/');
       cy.findByText(/login/i).click();
 
+      cy.location('pathname').should('equal', '/login');
       cy.findByLabelText(/email/i).type(email);
       cy.findByLabelText(/password/i).type(password);
       cy.findAllByRole('button', { name: /login/i }).click();
+      cy.wait('@loginMutation');
 
       cy.findByText(/is invalid/i).should('exist');
     });
@@ -20,7 +25,8 @@ describe('Login', () => {
       const attrs = { password: 'superawesome' };
 
       // note: async/await breaks cypress 😭
-      cy.task('factory', { name: 'User', attrs }).then((user) => {
+      cy.task<User>('factory', { name: 'User', attrs }).then((user) => {
+        cy.intercept('POST', '/api/graphql').as('loginMutation');
         cy.visit('/');
         cy.findByText(/login/i).click();
 
@@ -28,6 +34,7 @@ describe('Login', () => {
         cy.findByLabelText(/email/i).type(user.email);
         cy.findByLabelText(/password/i).type(attrs.password);
         cy.findAllByRole('button', { name: /login/i }).click();
+        cy.wait('@loginMutation');
 
         cy.findByText(/logout/i).should('exist');
         cy.location('pathname').should('equal', '/');
diff --git a/packages/create-bison-app/template/tests/e2e/logout.test.js b/packages/create-bison-app/template/tests/e2e/logout.cy.ts
similarity index 100%
rename from packages/create-bison-app/template/tests/e2e/logout.test.js
rename to packages/create-bison-app/template/tests/e2e/logout.cy.ts
diff --git a/packages/create-bison-app/template/tests/e2e/tsconfig.json b/packages/create-bison-app/template/tests/e2e/tsconfig.json
index 5481021..688e4a1 100644
--- a/packages/create-bison-app/template/tests/e2e/tsconfig.json
+++ b/packages/create-bison-app/template/tests/e2e/tsconfig.json
@@ -1,4 +1,7 @@
 {
   "extends": "../../cypress/tsconfig.json",
-  "include": ["../**/*.ts"]
-}
+  "compilerOptions": {
+    "isolatedModules": false
+  },
+  "include": ["../../cypress/**/*.ts", "../../cypress.config.ts", "../**/*.ts"]
+}
\ No newline at end of file
diff --git a/packages/create-bison-app/test/tasks/copyFiles.test.js b/packages/create-bison-app/test/tasks/copyFiles.test.js
index e455e5e..7592f9f 100644
--- a/packages/create-bison-app/test/tasks/copyFiles.test.js
+++ b/packages/create-bison-app/test/tasks/copyFiles.test.js
@@ -80,7 +80,7 @@ describe("copyFiles", () => {
         ".tool-versions",
         "codegen.yml",
         "constants.ts",
-        "cypress.json",
+        "cypress.config.ts",
         "jest.config.js",
         "next-env.d.ts",
         "prettier.config.js",
@@ -158,7 +158,7 @@ describe("copyFiles", () => {
     });
 
     it("copies e2e tests", async () => {
-      const files = [".eslintrc.json", "login.test.js"];
+      const files = [".eslintrc.json", "login.cy.ts", "logout.cy.ts", "tsconfig.json"];
 
       files.forEach((file) => {
         const filePath = path.join(targetFolder, "tests", "e2e", file);