Skip to content

Commit 99d828d

Browse files
authored
Add Cypress tests for login and campaign pages (#314)
1 parent 45119b4 commit 99d828d

File tree

14 files changed

+354
-4
lines changed

14 files changed

+354
-4
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,14 @@ ENV/
106106

107107
# PyCharm stuff:
108108
.idea/
109+
110+
# Ignore Cypress example tests
111+
cypress/e2e/1-getting-started/
112+
cypress/e2e/2-advanced-examples/
113+
114+
# Ignore default fixtures unless used
115+
cypress/fixtures/example.json
116+
117+
# Ignore screenshots & videos from Cypress runs
118+
cypress/screenshots/
119+
cypress/videos/

cypress.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
e2e: {
3+
setupNodeEvents(on, config) {
4+
// implement node event listeners here
5+
},
6+
},
7+
};

cypress/support/commands.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// ***********************************************
2+
// This example commands.js shows you how to
3+
// create various custom commands and overwrite
4+
// existing commands.
5+
//
6+
// For more comprehensive examples of custom
7+
// commands please read more here:
8+
// https://on.cypress.io/custom-commands
9+
// ***********************************************
10+
//
11+
//
12+
// -- This is a parent command --
13+
// Cypress.Commands.add('login', (email, password) => { ... })
14+
//
15+
//
16+
// -- This is a child command --
17+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18+
//
19+
//
20+
// -- This is a dual command --
21+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22+
//
23+
//
24+
// -- This will overwrite an existing command --
25+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

cypress/support/e2e.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// ***********************************************************
2+
// This example support/e2e.js is processed and
3+
// loaded automatically before your test files.
4+
//
5+
// This is a great place to put global configuration and
6+
// behavior that modifies Cypress.
7+
//
8+
// You can change the location of this file or turn off
9+
// automatically serving support files with the
10+
// 'supportFile' configuration option.
11+
//
12+
// You can read more here:
13+
// https://on.cypress.io/configuration
14+
// ***********************************************************
15+
16+
// Import commands.js using ES2015 syntax:
17+
import './commands'

frontend/cypress.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from "cypress";
2+
3+
export default defineConfig({
4+
e2e: {
5+
setupNodeEvents(on, config) {
6+
// implement node event listeners here
7+
},
8+
},
9+
});
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
describe('Campaign Details Page', () => {
2+
beforeEach(() => {
3+
cy.setCookie('clastic_cookie', '<cookie-validation-string>');
4+
cy.visit('http://localhost:5173/#/')
5+
cy.get('div.coordinator-campaign-cards').find('.coordinator-campaign-card').first().click()
6+
cy.url().should('match', /\/campaign\/\d+/)
7+
})
8+
9+
it('should display campaign title and rounds section', () => {
10+
cy.get('.campaign-title').should('be.visible')
11+
cy.get('.campaign-rounds').should('be.visible')
12+
})
13+
14+
it('should enter edit mode and show editable fields', () => {
15+
cy.get('.campaign-button-group')
16+
.find('button')
17+
.contains('Edit campaign')
18+
.click()
19+
20+
cy.get('.campaign-name-input').should('be.visible')
21+
cy.get('.date-time-inputs').should('be.visible')
22+
})
23+
24+
it('should cancel edit mode', () => {
25+
cy.get('.campaign-button-group')
26+
.find('button')
27+
.contains('Edit campaign')
28+
.click()
29+
30+
cy.get('.cancel-button').click()
31+
cy.get('.campaign-name-input').should('not.exist')
32+
cy.get('.campaign-title').should('be.visible')
33+
})
34+
35+
it('should show new round form after clicking "Add Round"', () => {
36+
cy.get('.add-round-button').click()
37+
cy.get('.juror-campaign-round-card').should('be.visible')
38+
cy.get('.form-container').should('be.visible')
39+
})
40+
41+
it('should enter campaign edit mode and show editable fields', () => {
42+
cy.get('[datatest="editbutton"]').click()
43+
cy.get('.campaign-name-input').should('be.visible')
44+
cy.get('.date-time-inputs').should('be.visible')
45+
})
46+
47+
it('should save campaign edits', () => {
48+
cy.get('button').contains('Edit').first().click()
49+
cy.get('.campaign-name-input input').clear().type('Updated Campaign Name');
50+
cy.get('button').contains('Save').click()
51+
cy.contains('Updated Campaign Name').should('be.visible')
52+
})
53+
54+
it('should cancel editing campaign details', () => {
55+
cy.get('[datatest="editbutton"]').click()
56+
cy.get('.cancel-button').click()
57+
cy.get('.campaign-name-input').should('not.exist')
58+
cy.get('.campaign-title').should('be.visible')
59+
})
60+
61+
it('should not allow creating a new round when one is already active or paused', () => {
62+
cy.intercept('GET', '/v1/admin/campaign/*', {
63+
fixture: 'campaignWithActiveRound.json'
64+
}).as('getCampaign')
65+
66+
cy.visit('/')
67+
cy.get('div.coordinator-campaign-cards').find('.coordinator-campaign-card').first().click()
68+
cy.wait('@getCampaign')
69+
70+
cy.get('.add-round-button').click()
71+
cy.contains('Only one round can be maintained at a time').should('be.visible')
72+
cy.get('.juror-campaign-round-card').should('not.exist')
73+
})
74+
75+
it('should create a new round successfully', () => {
76+
cy.get('.add-round-button').click()
77+
cy.get('.form-container input[type="text"]').first().clear().type('My Test Round')
78+
cy.get('.form-container').within(() => {
79+
cy.get('input[placeholder="YYYY-MM-DD"]').first().clear().type('2025-08-15')
80+
})
81+
cy.get('input[type="number"]').first().clear().type('3')
82+
cy.get('[data-testid="userlist-search"] input').type('AadarshM07');
83+
cy.get('[data-testid="userlist-search"]')
84+
.find('li')
85+
.first()
86+
.click();
87+
cy.get('.button-group button').contains('Add Round').click().click();
88+
cy.log(' Round created successfully');
89+
})
90+
91+
92+
it('should cancel round creation', () => {
93+
cy.get('.add-round-button').click()
94+
95+
cy.get('.button-group')
96+
.find('button')
97+
.contains('Cancel')
98+
.click()
99+
100+
cy.get('.juror-campaign-round-card').should('not.exist')
101+
cy.get('.add-round-button').should('be.visible')
102+
})
103+
104+
105+
106+
it('should delete a round with confirmation', () => {
107+
cy.get('button').contains('Edit round').first().click()
108+
cy.get('button').contains('Delete').click()
109+
cy.get('.cdx-dialog')
110+
.find('button')
111+
.contains('Delete')
112+
.click()
113+
cy.wait(1000)
114+
})
115+
})

frontend/cypress/e2e/login.cy.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
describe('Home View Conditional Rendering', () => {
2+
3+
beforeEach(() => {
4+
cy.setCookie('clastic_cookie','<cookie-validation-string>');
5+
cy.visit('http://localhost:5173/#/');
6+
})
7+
8+
it('should display main dashboard elements', () => {
9+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
10+
cy.get('.dashboard-header h1').should('be.visible')
11+
})
12+
13+
it('should show "New Campaign" button for organizers', () => {
14+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
15+
cy.get('body').then(($body) => {
16+
if ($body.find('a[href="/campaign/new"]').length > 0) {
17+
cy.get('.dashboard-header-heading').within(() => {
18+
cy.contains('New Campaign').should('exist')
19+
cy.get('a[href="/campaign/new"]').should('exist')
20+
})
21+
}
22+
})
23+
})
24+
25+
it('should create a new campaign via the form submission', () => {
26+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
27+
cy.get('body').then(($body) => {
28+
if ($body.find('a[href="/campaign/new"]').length > 0) {
29+
cy.get('a[href="/campaign/new"]').click()
30+
cy.url().should('include', '/campaign/new')
31+
cy.get('.new-campaign-card').within(() => {
32+
cy.get('input[placeholder="Campaign name"]').type('Test Campaign')
33+
cy.get('input[placeholder="example-slug"]').type('test-campaign')
34+
cy.get('input[placeholder="YYYY-MM-DD"]').eq(0).type('2025-08-01')
35+
cy.get('input[placeholder="HH:mm"]').eq(0).type('10:00')
36+
cy.get('input[placeholder="YYYY-MM-DD"]').eq(1).type('2025-08-10')
37+
cy.get('input[placeholder="HH:mm"]').eq(1).type('18:00')
38+
cy.get('.create-button').click()
39+
})
40+
cy.url().should('match', /\/campaign\/\d+/)
41+
}
42+
})
43+
})
44+
45+
it('should show new campaign card after creation', () => {
46+
cy.visit('http://localhost:5173/#/');
47+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
48+
cy.get('body').then(($body) => {
49+
if ($body.find('.new-campaign-card').length > 0) {
50+
cy.get('.new-campaign-card').should('be.visible')
51+
cy.get('.new-campaign-card h2').should('contain', 'Test Campaign')
52+
}
53+
})
54+
});
55+
56+
57+
it('should show "Add Organizer" button for maintainers and it', () => {
58+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
59+
cy.get('body').then(($body) => {
60+
if ($body.find('button:contains("Add Organizer")').length > 0) {
61+
cy.contains('Add Organizer').should('exist')
62+
63+
}
64+
})
65+
})
66+
67+
it('should open dialog when "Add Organizer" button is clicked', () => {
68+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
69+
cy.get('body').then(($body) => {
70+
if ($body.find('button:contains("Add Organizer")').length > 0) {
71+
cy.contains('Add Organizer').click()
72+
cy.get('[role="dialog"]', { timeout: 5000 }).should('exist')
73+
cy.contains('Add').should('exist')
74+
}
75+
})
76+
})
77+
78+
it('should display juror campaigns if available', () => {
79+
cy.visit('http://localhost:5173/#/');
80+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
81+
cy.get('body').then(($body) => {
82+
if ($body.find('.juror-campaigns').length > 0) {
83+
cy.get('.juror-campaigns h2').should('contain', 'Active voting rounds')
84+
cy.get('.juror-campaigns juror-campaign-card').should('exist')
85+
}
86+
})
87+
})
88+
89+
it('should display coordinator campaign cards if campaigns exist', () => {
90+
cy.visit('http://localhost:5173/#/');
91+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist');
92+
cy.get('body').then(($body) => {
93+
if ($body.find('section.coordinator-campaigns').length > 0) {
94+
cy.get('section.coordinator-campaigns').within(() => {
95+
cy.get('h2').should('contain', 'Coordinator campaigns');
96+
cy.get('.coordinator-campaign-card').should('exist');
97+
});
98+
}
99+
});
100+
});
101+
102+
it('should navigate to view all campaigns page', () => {
103+
cy.get('.dashboard-container', { timeout: 10000 }).should('exist')
104+
cy.get('.dashboard-info a').click()
105+
cy.url().should('include', '/campaign/all')
106+
})
107+
108+
it('should navigate to campaign details page', () => {
109+
cy.get('div.coordinator-campaign-cards')
110+
.find('.coordinator-campaign-card')
111+
.first()
112+
.click()
113+
cy.url().should('match', /\/campaign\/\d+/)
114+
})
115+
116+
117+
});
Lines changed: 5 additions & 0 deletions
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+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// ***********************************************
2+
// This example commands.js shows you how to
3+
// create various custom commands and overwrite
4+
// existing commands.
5+
//
6+
// For more comprehensive examples of custom
7+
// commands please read more here:
8+
// https://on.cypress.io/custom-commands
9+
// ***********************************************
10+
//
11+
//
12+
// -- This is a parent command --
13+
// Cypress.Commands.add('login', (email, password) => { ... })
14+
//
15+
//
16+
// -- This is a child command --
17+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18+
//
19+
//
20+
// -- This is a dual command --
21+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22+
//
23+
//
24+
// -- This will overwrite an existing command --
25+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

frontend/cypress/support/e2e.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// ***********************************************************
2+
// This example support/e2e.js is processed and
3+
// loaded automatically before your test files.
4+
//
5+
// This is a great place to put global configuration and
6+
// behavior that modifies Cypress.
7+
//
8+
// You can change the location of this file or turn off
9+
// automatically serving support files with the
10+
// 'supportFile' configuration option.
11+
//
12+
// You can read more here:
13+
// https://on.cypress.io/configuration
14+
// ***********************************************************
15+
16+
// Import commands.js using ES2015 syntax:
17+
import './commands'

0 commit comments

Comments
 (0)