Skip to content

Commit 7761aaf

Browse files
authored
initial commit for cypress framework along with several custom commands and tests (#1)
1 parent d0201b8 commit 7761aaf

9 files changed

+2617
-1
lines changed

.gitignore

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# dependencies
2+
/node_modules
3+
/.pnp
4+
.pnp.js
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
12+
# misc
13+
.DS_Store
14+
.idea
15+
16+
npm-debug.log*
17+
yarn-debug.log*
18+
yarn-error.log*
19+
20+
cypress/videos
21+
cypress/screenshots
22+
23+
# Ignore env local files
24+
.env*.local
25+
cypress.env.json

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,28 @@ Runs the backend using json-server at [http://localhost:4000](http://localhost:4
1818

1919
#### `npm run build`
2020
Builds the app for production to the `build` folder. Bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes. [Deployment info here](https://facebook.github.io/create-react-app/docs/deployment).
21+
22+
## Tests
23+
These tests can be run against `localhost` as per above steps. The tests are using [Cypress](https://www.cypress.io) E2E framework to execute.
24+
25+
## Test Configuration
26+
Env variables are currently configured in `cypress.config.js` and additional configurations can be added as needed.
27+
A `cypress.env.json` file can be created locally that will override values defined in `cypress.config.js` (in the `env` section).
28+
Run configuration scripts can also be modified in `package.json` to run in other browsers or only running specific spec files.
29+
30+
## Running Tests
31+
#### `npm ci`
32+
33+
To pull down latest packages including the Cypress framework.
34+
35+
#### `npm test`
36+
Currently configured to run in headless mode using Chrome browser.
37+
Configuration can be changed to run with other browsers that are installed on the local machine by modifying the `scripts` in `package.json`.
38+
39+
#### `npm run cy-open`
40+
To watch the tests run in a headful browser, this command will open up the Cypress UI application and a locally installed browser. Useful for debugging and observing failures in real time or with the 'rewind' option. Currently configured to use the Firefox browser.
41+
Configuration can be changed to run with other browsers that are installed on the local machine by modifying the `scripts` in `package.json`.
42+
43+
## Developing Tests
44+
Tests are located in `/cypress/e2e`. They are written in Javascript and can be run individually through the UI or in headless terminal.
45+
Custom commands have been created to target common page objects and actions and they can be viewed in `/cypress/support/commands.js`.

cypress.config.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { defineConfig } = require("cypress");
2+
3+
module.exports = defineConfig({
4+
e2e: {
5+
setupNodeEvents(on, config) {
6+
// implement node event listeners here
7+
},
8+
env: {
9+
mainUrl: 'http://localhost:3000',
10+
apiUrl: 'http://localhost:4000'
11+
}
12+
},
13+
});

cypress/e2e/main.cy.js

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/// <reference types="cypress" />
2+
3+
describe('Page Object Checks', () => {
4+
beforeEach(() => {
5+
cy.visit(Cypress.env('mainUrl'));
6+
7+
});
8+
9+
it('loads main search dashboard', () => {
10+
cy.url().should('eq', 'http://localhost:3000/')
11+
cy.title().should('eq', 'Biobot Search Example')
12+
cy.get('.app-logo').should('exist')
13+
cy.get('.app-header').contains('Welcome to the Biobot Test Kit Search App')
14+
cy.get('.search-box-container').should('be.visible');
15+
16+
});
17+
18+
it('should always display label Id placeholder text in the search box', () => {
19+
cy.get('#mui-2-label').should('contain.text', 'Label Id')
20+
cy.get('#mui-2').click()
21+
cy.get('#mui-2-label').should('contain.text', 'Label Id')
22+
cy.get('#mui-2').type('populate')
23+
cy.get('#mui-2-label').should('contain.text', 'Label Id')
24+
cy.get('#mui-2').clear()
25+
cy.get('.app').click()
26+
cy.get('#mui-2-label').should('contain.text', 'Label Id')
27+
28+
});
29+
30+
it('should display search/clear buttons under correct circumstances', () => {
31+
cy.getBySel('SearchIcon').should('exist')
32+
cy.getBySel('CloseIcon').should('not.exist')
33+
cy.get('#mui-2').type('populate')
34+
cy.getBySel('CloseIcon').should('exist')
35+
cy.get('#mui-2').clear()
36+
cy.get('.app').click()
37+
cy.getBySel('CloseIcon').should('not.exist')
38+
39+
});
40+
41+
it('should display caption after populating search results', () => {
42+
cy.addSample();
43+
cy.successModalTrue();
44+
45+
cy.get('caption')
46+
.should('exist')
47+
.contains('Table containing test kit shipping data, use the search bar above to search for your test kit');
48+
49+
});
50+
51+
it('should display id and shipping columns after populating search results', () => {
52+
cy.addSample();
53+
cy.successModalTrue();
54+
cy.get('.MuiTableHead-root')
55+
.should('exist')
56+
.and('contains.text', 'Label Id')
57+
.and('contains.text', 'Shipping Tracking Code');
58+
59+
});
60+
61+
});
62+
63+
64+
describe('Common Scenarios', () => {
65+
beforeEach(() => {
66+
cy.visit(Cypress.env('mainUrl'));
67+
68+
});
69+
70+
it('does exact search for random known id and confirms correct formatting', () => {
71+
72+
cy.sampleByDataJson().then((sample) =>
73+
cy.addSampleBySearch(sample)
74+
);
75+
cy.successModalTrue();
76+
cy.resultsTrue();
77+
cy.regexLabelId();
78+
cy.regexTrackingId();
79+
80+
});
81+
82+
it('does exact search for multiple random known ids and confirms correct formatting', () => {
83+
//this can be modified to change the number of label ids we want to grab or even update it to always run through the entire list.
84+
for (let i = 0; i < 5; i++) {
85+
cy.sampleByDataJson().then((sample) =>
86+
cy.addSampleBySearch(sample)
87+
);
88+
}
89+
cy.successModalTrue();
90+
cy.resultsTrue();
91+
cy.regexLabelId();
92+
cy.regexTrackingId();
93+
94+
});
95+
96+
it('successfully populates a single kit id', () => {
97+
cy.addSample();
98+
cy.successModalTrue();
99+
cy.resultsTrue();
100+
cy.regexLabelId();
101+
cy.regexTrackingId();
102+
103+
});
104+
105+
it('successfully populates multiple kit ids', () => {
106+
//pass in however many we want to populate here.
107+
cy.addSample(5);
108+
cy.successModalTrue();
109+
cy.resultsTrue();
110+
cy.regexLabelId();
111+
cy.regexTrackingId();
112+
113+
});
114+
115+
it('does partial search for known and existing kit id', () => {
116+
cy.addSampleBySearch('007')
117+
cy.successModalTrue();
118+
cy.regexLabelId();
119+
cy.regexTrackingId();
120+
121+
});
122+
123+
it('does partial search for multiple known and existing kit ids', () => {
124+
cy.addSampleBySearch('007')
125+
cy.successModalTrue()
126+
cy.addSampleBySearch('008')
127+
cy.successModalTrue();
128+
cy.regexLabelId();
129+
cy.regexTrackingId();
130+
131+
});
132+
133+
it('dismiss modal after approximately 7 seconds', () => {
134+
cy.addSample();
135+
cy.successModalTrue();
136+
137+
cy.get('.Toastify__toast-container').should('exist')
138+
//normally try to avoid waits with cypress, but the goal here was just to make sure modals will always remain visible for at least 7 seconds.
139+
.wait(7000)
140+
.should('not.exist');
141+
142+
});
143+
144+
it('dimiss modal when user closes manually', () => {
145+
cy.addSample();
146+
cy.successModalTrue();
147+
148+
cy.get('.Toastify__toast-container').should('exist')
149+
cy.get('.Toastify__close-button').click()
150+
cy.get('.Toastify__toast-container').should('not.exist');
151+
152+
});
153+
154+
it('clear button removes all previous results', () => {
155+
cy.addSample(5);
156+
cy.successModalTrue();
157+
cy.addSampleBySearch('007');
158+
cy.resultsTrue();
159+
160+
cy.get('.MuiButtonBase-root').click()
161+
cy.get('.results-container').should('not.exist');
162+
163+
});
164+
165+
});
166+
167+
168+
describe('Exercise API directly', () => {
169+
170+
it('gets an API response from shipping_data label_id', () => {
171+
const labelId = '22-252-1407'
172+
cy.request(Cypress.env('apiUrl') + '/shipping_data?label_id=' + labelId).as('apiReq')
173+
cy.get('@apiReq').then(
174+
(response) => {
175+
expect(response.status).to.eq(200)
176+
expect(response.body[0]).to.have.property('id')
177+
expect(response.body[0]).to.have.property('label_id', labelId)
178+
expect(response.body[0]).to.have.property('shipping_tracking_code');
179+
})
180+
});
181+
182+
});
183+
184+
185+
describe('Error Scenarios', () => {
186+
beforeEach(() => {
187+
cy.visit(Cypress.env('mainUrl'));
188+
189+
});
190+
191+
it('displays error modal for no results found', () => {
192+
cy.get('#mui-2').type('foo').type('{enter}')
193+
cy.errorModalTrue();
194+
195+
});
196+
197+
it('displays error modal when clicking search button without criteria', () => {
198+
//was expecting there to be more built in elements that used the data-testid attribute, but discovered there weren't many as it stands.
199+
//ideally we could add more of these data-testid attributes to help with maintenance.
200+
cy.getBySel('SearchIcon').click()
201+
cy.errorModalTrue();
202+
203+
});
204+
205+
it('should generate and search for common bad strings', () => {
206+
//this can be modified to change the number of bad strings we want to try here or even update it to always run through the entire list.
207+
for (let i = 0; i < 5; i++) {
208+
cy.badString().then((ns) =>
209+
cy.addSampleBySearch(ns)
210+
);
211+
}
212+
});
213+
214+
it('checks for common injections', () => {
215+
//arbitrarily picked a few common injection tests using different quote characters.
216+
cy.get('#mui-2').type(`‘ or 1=1;–`).type('{enter}')
217+
cy.errorModalTrue();
218+
cy.get('#mui-2').clear()
219+
cy.get('#mui-2').type(`“ or 1=1;–`).type('{enter}')
220+
cy.errorModalTrue();
221+
cy.get('#mui-2').clear()
222+
cy.get('#mui-2').type(`‘ or ‘abc‘=‘abc‘;–`).type('{enter}')
223+
cy.errorModalTrue();
224+
cy.get('#mui-2').clear()
225+
cy.get('#mui-2').type(`‘ or ‘ ‘=‘ ‘;–`).type('{enter}')
226+
cy.errorModalTrue();
227+
cy.get('#mui-2').clear()
228+
229+
});
230+
231+
});
232+

cypress/fixtures/example.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "Using fixtures to represent data",
3+
"email": "[email protected]",
4+
"body": "Fixtures are a great way to mock data for responses to routes"
5+
}

0 commit comments

Comments
 (0)