mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
feat: Add cypress e2e tests for signup and signin (#3490)
* 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
This commit is contained in:
4
cypress/constants.ts
Normal file
4
cypress/constants.ts
Normal file
@@ -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
cypress/e2e/0-smoke.cy.ts
Normal file
23
cypress/e2e/0-smoke.cy.ts
Normal file
@@ -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
cypress/pages/base.ts
Normal file
15
cypress/pages/base.ts
Normal file
@@ -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
cypress/pages/index.ts
Normal file
4
cypress/pages/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './base';
|
||||
export * from './signin';
|
||||
export * from './signup';
|
||||
export * from './workflows';
|
||||
11
cypress/pages/signin.ts
Normal file
11
cypress/pages/signin.ts
Normal file
@@ -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
cypress/pages/signup.ts
Normal file
13
cypress/pages/signup.ts
Normal file
@@ -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
cypress/pages/workflows.ts
Normal file
6
cypress/pages/workflows.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { BasePage } from "./base";
|
||||
|
||||
export class WorkflowsPage extends BasePage {
|
||||
url = '/workflows';
|
||||
elements = {}
|
||||
}
|
||||
78
cypress/support/commands.ts
Normal file
78
cypress/support/commands.ts
Normal file
@@ -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
cypress/support/e2e.ts
Normal file
17
cypress/support/e2e.ts
Normal file
@@ -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
cypress/support/index.ts
Normal file
14
cypress/support/index.ts
Normal file
@@ -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
cypress/types.ts
Normal file
9
cypress/types.ts
Normal file
@@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user