Skip to content

Commit

Permalink
add e2e test with cypress
Browse files Browse the repository at this point in the history
  • Loading branch information
alan2207 committed Jul 4, 2021
1 parent d85179b commit 1d31ab5
Show file tree
Hide file tree
Showing 14 changed files with 964 additions and 48 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

# testing
/coverage
/cypress/videos
/cypress/screenshots

# production
/build
Expand Down
1 change: 1 addition & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions cypress/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
root: true,
plugins: ['eslint-plugin-cypress'],
env: { 'cypress/globals': true },
};
5 changes: 5 additions & 0 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
225 changes: 225 additions & 0 deletions cypress/integration/smoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
const faker = require('faker');

describe('smoke', () => {
it('should handle normal app flow', () => {
const user = {
firstName: faker.internet.userName(),
lastName: faker.internet.userName(),
email: faker.internet.email(),
password: faker.internet.password(),
teamName: faker.company.companyName(),
};

const discussion = {
title: faker.company.catchPhrase(),
body: faker.lorem.sentence(),
};

// registration:
cy.visit('http://localhost:3000/auth/register');

cy.findByRole('textbox', {
name: /first name/i,
}).type(user.firstName);
cy.findByRole('textbox', {
name: /last name/i,
}).type(user.lastName);
cy.findByRole('textbox', {
name: /email address/i,
}).type(user.email);
cy.findByLabelText(/password/i).type(user.password);

cy.findByRole('textbox', {
name: /team name/i,
}).type(user.teamName);

cy.findByRole('button', {
name: /register/i,
}).click();

cy.findByRole('heading', {
name: `Welcome ${user.firstName} ${user.lastName}`,
}).should('exist');

cy.findByRole('link', {
name: /discussions/i,
}).click();

// create discussion:
cy.findByRole('button', {
name: /create discussion/i,
}).click();

cy.findByRole('dialog').within(() => {
cy.findByRole('textbox', {
name: /title/i,
}).type(discussion.title);
cy.findByRole('textbox', {
name: /body/i,
}).type(discussion.body);
cy.findByRole('button', {
name: /submit/i,
}).click();
});
cy.findByRole('alert', {
name: /discussion created/i,
}).within(() => {
cy.findByText(/discussion created/i).should('exist');
cy.findByRole('button').click();
});
cy.findByRole('dialog').should('not.exist');

cy.wait(200);

// visit discussion page:
cy.findByRole('row', {
name: `${discussion.title} View Delete`,
}).within(() => {
cy.findByRole('link', {
name: /view/i,
}).click();
});

cy.findByRole('heading', {
name: discussion.title,
}).should('exist');

// update

cy.findByRole('button', {
name: /update discussion/i,
}).click();

const updatedDiscussion = {
title: faker.company.catchPhrase(),
body: faker.lorem.sentence(),
};

cy.findByRole('dialog').within(() => {
cy.findByRole('textbox', {
name: /title/i,
})
.clear()
.type(updatedDiscussion.title);
cy.findByRole('textbox', {
name: /body/i,
})
.clear()
.type(updatedDiscussion.body);
cy.findByRole('button', {
name: /submit/i,
}).click();
});
cy.findByRole('alert', {
name: /discussion updated/i,
}).within(() => {
cy.findByText(/discussion updated/i).should('exist');
cy.findByRole('button').click();
});

cy.findByRole('heading', {
name: updatedDiscussion.title,
}).should('exist');

// create comment

const comment = {
body: faker.lorem.sentence(),
};

cy.findByRole('button', {
name: /create comment/i,
}).click();

cy.findByRole('dialog').within(() => {
cy.findByRole('textbox', {
name: /body/i,
}).type(comment.body, { force: true }); // for some reason it requires force to be set to true

cy.findByRole('button', {
name: /submit/i,
}).click();
});
cy.findByRole('alert', {
name: /comment created/i,
}).within(() => {
cy.findByText(/comment created/i).should('exist');
cy.findByRole('button').click();
});

cy.findByRole('list', {
name: 'comments',
}).within(() => {
cy.findByText(comment.body).should('exist');
});

cy.wait(200);

// delete comment

cy.findByRole('list', {
name: 'comments',
}).within(() => {
cy.findByRole('listitem', {
name: `comment-${comment.body}-0`,
}).within(() => {
cy.findByRole('button', {
name: /delete comment/i,
}).click();
});
});

cy.findByRole('dialog').within(() => {
cy.findByRole('button', {
name: /delete/i,
}).click();
});

cy.findByRole('alert', {
name: /comment deleted/i,
}).within(() => {
cy.findByText(/comment deleted/i).should('exist');
cy.findByRole('button').click();
});

cy.findByRole('list', {
name: 'comments',
}).within(() => {
cy.findByText(comment.body).should('not.exist');
});

// go back to discussions list

cy.findByRole('link', {
name: /discussions/i,
}).click();

cy.wait(200);

// delete discussion:
cy.findByRole('row', {
name: `${updatedDiscussion.title} View Delete`,
}).within(() => {
cy.findByRole('button', {
name: 'Delete',
}).click();
});

cy.findByRole('dialog').within(() => {
cy.findByRole('button', {
name: /delete/i,
}).click();
});

cy.findByRole('alert', {
name: /discussion deleted/i,
}).within(() => {
cy.findByText(/discussion deleted/i).should('exist');
cy.findByRole('button').click();
});

cy.findByRole('row', {
name: `${updatedDiscussion.title} View Delete`,
}).should('not.exist');
});
});
22 changes: 22 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// 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 {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}
26 changes: 26 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ***********************************************
// 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 '@testing-library/cypress/add-commands';
22 changes: 22 additions & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ***********************************************************
// This example support/index.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.js using ES2015 syntax:
import './commands';

// Alternatively you can use CommonJS syntax:
// require('./commands')

cy.faker = require('faker');
8 changes: 8 additions & 0 deletions cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "@testing-library/cypress"]
},
"include": ["**/*.ts"]
}
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@testing-library/user-event": "^12.1.10",
"@types/faker": "^5.5.6",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/node": "^16.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"axios": "^0.21.1",
Expand Down Expand Up @@ -81,6 +81,7 @@
"@storybook/node-logger": "^6.3.2",
"@storybook/preset-create-react-app": "^3.1.7",
"@storybook/react": "^6.3.2",
"@testing-library/cypress": "^7.0.6",
"@testing-library/react-hooks": "^7.0.0",
"@types/dompurify": "^2.2.2",
"@types/jsonwebtoken": "^8.5.1",
Expand All @@ -90,8 +91,10 @@
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
"autoprefixer": "^9",
"cypress": "^7.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.4.0",
"eslint-plugin-cypress": "^2.11.3",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jest-dom": "^3.9.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
Expand All @@ -108,6 +111,7 @@
"postcss": "^7",
"prettier": "^2.3.0",
"react-test-renderer": "^17.0.2",
"start-server-and-test": "^1.12.5",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"tsconfig-paths-webpack-plugin": "^3.5.1"
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/Notifications/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const Notification = ({
leaveTo="opacity-0"
>
<div className="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden">
<div className="p-4">
<div className="p-4" role="alert" aria-label={title}>
<div className="flex items-start">
<div className="flex-shrink-0">{icons[type]}</div>
<div className="ml-3 w-0 flex-1 pt-0.5">
Expand Down
2 changes: 1 addition & 1 deletion src/features/comments/components/CommentsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const CommentsList = ({ discussionId }: CommentsListProps) => {
<ul aria-label="comments" className="flex flex-col space-y-3">
{commentsQuery.data.map((comment, index) => (
<li
aria-label={`comment-${comment.id || index}`}
aria-label={`comment-${comment.body}-${index}`}
key={comment.id || index}
className="w-full bg-white shadow-sm p-4"
>
Expand Down
4 changes: 3 additions & 1 deletion src/test/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { JWT_SECRET } from '@/config';

import { db } from './db';

const isTesting = process.env.NODE_ENV === 'test' || ((window as any).Cypress as any);

export const delayedResponse = createResponseComposition(undefined, [
context.delay(process.env.NODE_ENV === 'test' ? 0 : 1000),
context.delay(isTesting ? 0 : 1000),
]);

export const hash = (str: string) => {
Expand Down
Loading

0 comments on commit 1d31ab5

Please sign in to comment.