Skip to content

Commit

Permalink
feat: Add cypress e2e tests for signup and signin (#3490)
Browse files Browse the repository at this point in the history
* feat: Added cypress setup files.

* feat: Added server bootup and initial test run.

* feat: Added e2e tests for signin, signup, and personalization form.

* feat: Added e2e tests for adding a function node.

* feat: Added set node and workflow execution steps.

* feat: Added test id to main sidebar.

* feat: Added test for creating a new workflow.

* feat: Finished test for creating a blank workflow

* chore: Removed screenshots from e2e tests.

* refactor: change e2e tests to per page structure

* feat: add cypress type enchancements

* feat: add typescript for cypress tests

* fix: remove component after merge

* feat: update cypress definitions

* feat: add cypress cleanup task

* refactor: update cypress script names

* ci: add smoke tests to workflow

* chore: remove cypress example files

* feat: update signup flow to be reusable

* fix: fix signup route for cypress page object

* fix: remove cypress reset command

* fix: remove unused imports

* fix: Add unhandled error catcher
  • Loading branch information
alexgrozav authored Nov 8, 2022
1 parent 5d73b6e commit 7764486
Show file tree
Hide file tree
Showing 25 changed files with 2,008 additions and 7 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/ci-master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ jobs:
run: npm run build --if-present

- name: Test
run: npm run test
run:
npm run test

- name: Test E2E
run:
npm run test:e2e:ci:smoke

- name: Lint
run: npm run lint
4 changes: 4 additions & 0 deletions .github/workflows/ci-pull-requests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
- name: Test
run: npm run test

- name: Test E2E
run:
npm run test:e2e:ci:smoke

- name: Fetch base branch for `git diff`
run: git fetch origin ${{ github.event.pull_request.base.ref }}:${{ github.event.pull_request.base.ref }}

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ _START_PACKAGE
nodelinter.config.json
packages/*/package-lock.json
packages/*/.turbo
cypress/videos/*
cypress/screenshots/*
*.swp
12 changes: 12 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { defineConfig } = require("cypress");


module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:5678',
video: false,
screenshotOnRunFailure: false,
experimentalSessionAndOrigin: true,
experimentalInteractiveRunEvents: true,
}
});
4 changes: 4 additions & 0 deletions cypress/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const N8N_AUTH_COOKIE = 'n8n-auth';

export const DEFAULT_USER_EMAIL = 'nathan@n8n.io';
export const DEFAULT_USER_PASSWORD = 'CypressTest123';
23 changes: 23 additions & 0 deletions cypress/e2e/0-smoke.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD} from "../constants";
import {randFirstName, randLastName} from "@ngneat/falso";

const username = DEFAULT_USER_EMAIL;
const password = DEFAULT_USER_PASSWORD;
const firstName = randFirstName();
const lastName = randLastName();

describe('Authentication flow', () => {
it('should sign user up', () => {
cy.signup(username, firstName, lastName, password);
});

it('should sign user in', () => {
cy.on('uncaught:exception', (err, runnable) => {
expect(err.message).to.include('Not logged in');

return false;
})

cy.signin(username, password);
});
});
15 changes: 15 additions & 0 deletions cypress/pages/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IE2ETestPage, IE2ETestPageElement } from "../types";


export class BasePage implements IE2ETestPage {
elements: Record<string, IE2ETestPageElement> = {};
get(id: keyof BasePage['elements'], ...args: unknown[]): ReturnType<IE2ETestPageElement> {
const getter = this.elements[id];

if (!getter) {
throw new Error(`No element with id "${id}" found. Check your page object definition.`);
}

return getter(...args);
}
}
4 changes: 4 additions & 0 deletions cypress/pages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './base';
export * from './signin';
export * from './signup';
export * from './workflows';
11 changes: 11 additions & 0 deletions cypress/pages/signin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BasePage } from "./base";

export class SigninPage extends BasePage {
url = '/signin';
elements = {
form: () => cy.getByTestId('auth-form'),
email: () => cy.getByTestId('email'),
password: () => cy.getByTestId('password'),
submit: () => cy.get('button'),
}
}
13 changes: 13 additions & 0 deletions cypress/pages/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BasePage } from "./base";

export class SignupPage extends BasePage {
url = '/setup';
elements = {
form: () => cy.getByTestId('auth-form'),
email: () => cy.getByTestId('email'),
firstName: () => cy.getByTestId('firstName'),
lastName: () => cy.getByTestId('lastName'),
password: () => cy.getByTestId('password'),
submit: () => cy.get('button'),
}
}
6 changes: 6 additions & 0 deletions cypress/pages/workflows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { BasePage } from "./base";

export class WorkflowsPage extends BasePage {
url = '/workflows';
elements = {}
}
78 changes: 78 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// ***********************************************
// This example commands.js 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) => { ... })

import { WorkflowsPage, SigninPage, SignupPage } from "../pages";
import { N8N_AUTH_COOKIE } from "../constants";


Cypress.Commands.add('getByTestId', (selector, ...args) => {
return cy.get(`[data-test-id="${selector}"]`, ...args)
})

Cypress.Commands.add(
'signin',
(email, password) => {
const signinPage = new SigninPage();
const workflowsPage = new WorkflowsPage();

cy.session([email, password], () => {
cy.visit(signinPage.url);

signinPage.get('form').within(() => {
signinPage.get('email').type(email);
signinPage.get('password').type(password);
signinPage.get('submit').click();
});

// we should be redirected to /workflows
cy.url().should('include', workflowsPage.url);
},
{
validate() {
cy.getCookie(N8N_AUTH_COOKIE).should('exist');
},
});
});

Cypress.Commands.add('signup', (email, firstName, lastName, password) => {
const signupPage = new SignupPage();

cy.visit(signupPage.url);

signupPage.get('form').within(() => {
cy.url().then((url) => {
if (url.endsWith(signupPage.url)) {
signupPage.get('email').type(email);
signupPage.get('firstName').type(firstName);
signupPage.get('lastName').type(lastName);
signupPage.get('password').type(password);
signupPage.get('submit').click();
} else {
cy.log('User already signed up');
}
});
});
})
17 changes: 17 additions & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// ***********************************************************
// This example support/e2e.js 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'

14 changes: 14 additions & 0 deletions cypress/support/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Load type definitions that come with Cypress module
/// <reference types="cypress" />

declare global {
namespace Cypress {
interface Chainable {
getByTestId(selector: string, ...args: (Partial<Loggable & Timeoutable & Withinable & Shadow> | undefined)[]): Chainable<JQuery<HTMLElement>>
signin(email: string, password: string): void;
signup(email: string, firstName: string, lastName: string, password: string): void;
}
}
}

export {};
9 changes: 9 additions & 0 deletions cypress/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type IE2ETestPageElement = (...args: unknown[]) =>
| Cypress.Chainable<JQuery<HTMLElement>>
| Cypress.Chainable<JQuery<HTMLButtonElement>>;

export interface IE2ETestPage {
url?: string;
elements: Record<string, IE2ETestPageElement>;
get(id: string, ...args: unknown[]): ReturnType<IE2ETestPageElement>;
}
Loading

0 comments on commit 7764486

Please sign in to comment.