Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development Data Seeds via Cypress #1938

Open
wants to merge 5 commits into
base: edge
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 31 additions & 16 deletions e2e/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ Cypress.Commands.add('anonymousParticipant', ({ convoId }) => {
)
})

Cypress.Commands.add('createConvo', (topic, description) => {
cy.ensureUser('moderator')
Cypress.Commands.add('createConvo', (topic, description, user) => {
// If user provided, login and set up session
if (user) {
apiLogin(user)
}

cy.request('POST', '/api/v3/conversations', {
is_active: true,
is_draft: true,
Expand Down Expand Up @@ -135,7 +139,6 @@ Cypress.Commands.add('ensureConversation', (userLabel) => {
Cypress.Commands.add('seedComment', (convoId, commentText) => {
const text = commentText || faker.lorem.sentences()

cy.ensureUser('moderator')
cy.request('POST', '/api/v3/comments', {
conversation_id: convoId,
is_seed: true,
Expand Down Expand Up @@ -213,24 +216,36 @@ Cypress.Commands.add('vote', () => {
})
})

Cypress.Commands.add('initAndVote', (userLabel, convoId) => {
// Core voting logic that can be used after establishing a session
Cypress.Commands.add('voteOnConversation', (convoId, xid) => {
cy.intercept('GET', '/api/v3/participationInit*').as('participationInit')

cy.ensureUser(userLabel)
cy.visit('/' + convoId)
let url = '/' + convoId;
if (xid) {
url += '?xid=' + xid;
}
cy.visit(url)
cy.wait('@participationInit')

recursiveVote()
cy.get('[data-view-name="vote-view"]', { timeout: 10000 }).then(function voteLoop($voteView) {
if ($voteView.find('button#agreeButton').length && !$voteView.find('.Notification.Notification--warning').length) {
cy.vote()
cy.get('[data-view-name="vote-view"]').then(voteLoop)
}
})
})

// Legacy support for visualization tests
Cypress.Commands.add('initAndVote', (userLabel, convoId) => {
cy.ensureUser(userLabel)
cy.voteOnConversation(convoId)
})

function apiLogin(user) {
cy.request('POST', '/api/v3/auth/login', { email: user.email, password: user.password })
}

function recursiveVote() {
cy.get('[data-view-name="vote-view"]').then(($voteView) => {
if ($voteView.find('button#agreeButton').length) {
cy.vote().then(() => recursiveVote())
}
cy.request('POST', '/api/v3/auth/login', {
email: user.email,
password: user.password
}).then((response) => {
cy.setCookie('token2', response.body.token)
cy.setCookie('uid2', String(response.body.uid))
})
}
30 changes: 0 additions & 30 deletions e2e/docker-compose.yml

This file was deleted.

5 changes: 3 additions & 2 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"lint:fix": "eslint . --fix",
"prettier": "prettier --write .",
"build:embed": "node build-embed.js",
"build:integrated": "node build-integrated.js"
"build:integrated": "node build-integrated.js",
"seed": "node scripts/seed.js"
},
"author": "",
"license": "ISC",
Expand All @@ -32,4 +33,4 @@
"eslint-plugin-mocha": "^10.2.0",
"prettier": "^3.0.3"
}
}
}
96 changes: 96 additions & 0 deletions e2e/scripts/seed.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
describe('Database Seeding', function () {
let moderator
let convoIds = []
let completedVoters = 0

before(function () {
moderator = {
name: 'Test Moderator',
email: '[email protected]',
password: 'Te$tP@ssw0rd*'
}
})

it('creates moderator and conversations', function () {
cy.register(moderator)

const numConversations = Cypress.env('numConversations') || 2
const commentsPerConvo = Cypress.env('commentsPerConvo') || 3

// Create conversations and store their IDs
for (let i = 0; i < numConversations; i++) {
const topic = `Test Conversation ${i + 1}`
const description = `This is a test conversation ${i + 1} created by the seeding script`

cy.createConvo(topic, description).then(function () {
const convoId = this.convoId
convoIds.push(convoId)

// Add seed comments
for (let j = 0; j < commentsPerConvo; j++) {
cy.seedComment(convoId)
}
})
}

cy.clearCookie('token2')
cy.clearCookie('uid2')
})

it('adds votes from participants', function () {
const numVoters = Cypress.env('numVoters') || 5
const batchSize = 20 // Process participants in batches to manage memory
const totalBatches = Math.ceil(numVoters / batchSize)

// Process participants in batches
for (let batchStart = 0; batchStart < numVoters; batchStart += batchSize) {
const batchEnd = Math.min(batchStart + batchSize, numVoters)
const currentBatch = Math.floor(batchStart / batchSize) + 1

// For each participant in this batch
for (let i = batchStart; i < batchEnd; i++) {
const participantId = `participant_${i}`

// Initialize participant and vote on conversations
cy.session(participantId, () => {
cy.request('/api/v3/participationInit?conversation_id=' + convoIds[0] + '&pid=mypid&lang=acceptLang')

const xid = `seed-${i + 1}`

// Vote on all conversations in this session
convoIds.forEach((convoId) => {
cy.voteOnConversation(convoId, xid)
})
}, {
validate: () => {
cy.getCookie('pc').should('exist')
},
cacheAcrossSpecs: false
})

// Track progress
completedVoters++

// Clean up after each participant
cy.clearAllCookies()
cy.clearAllSessionStorage()
cy.clearAllLocalStorage()
}

// After each batch
Cypress.session.clearAllSavedSessions()

// Small delay between batches to allow for GC
if (currentBatch < totalBatches) {
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000)
}
}

// Final progress report
cy.log('Seeding Complete:')
cy.log(`- Total voters processed: ${completedVoters}`)
cy.log(`- Total conversations: ${convoIds.length}`)
cy.log(`- Comments per conversation: ${Cypress.env('commentsPerConvo') || 3}`)
})
})
102 changes: 102 additions & 0 deletions e2e/scripts/seed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const cypress = require('cypress')

/**
* Seed Script Usage:
* -----------------
* Basic usage:
* npm run seed
*
* With custom parameters:
* npm run seed -- --numVoters=10 --numConversations=3 --commentsPerConvo=5
*
* Environment Variables:
* CYPRESS_BASE_URL - Set the API server URL (default: http://localhost)
* Example: CYPRESS_BASE_URL=http://localhost:5001 npm run seed
*
* Available Arguments:
* --numVoters Number of anonymous participants to create (default: 5)
* --numConversations Number of conversations to create (default: 2)
* --commentsPerConvo Number of seed comments per conversation (default: 3)
*
* Notes:
* - A moderator user will always be created ([email protected])
* - Each conversation will have the specified number of seed comments
* - For each conversation, the specified number of anonymous participants will be created
* - Each participant will make the specified number of votes on their conversation
* - The script is idempotent - running it multiple times will not create duplicate data
*/

async function seed({
numVoters = 5,
numConversations = 2,
commentsPerConvo = 3
} = {}) {
console.log('\n🌱 Starting database seeding...\n')
console.log('Configuration:')
console.log(`- Anonymous participants per conversation: ${numVoters}`)
console.log(`- Conversations to create: ${numConversations}`)
console.log(`- Comments per conversation: ${commentsPerConvo}`)
console.log('\n')

const config = {
config: {
baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost',
experimentalMemoryManagement: true,
numTestsKeptInMemory: 0,
video: false,
screenshotOnRunFailure: false,
e2e: {
specPattern: 'scripts/seed.cy.js'
}
},
env: {
numVoters,
numConversations,
commentsPerConvo
}
}

try {
const results = await cypress.run(config)

if (results.totalFailed === 0) {
const totalVotes = numVoters * commentsPerConvo * numConversations
console.log(`\n✅ Database seeded successfully!
- Created 1 moderator
- Created ${numConversations} conversations
- Added ${numConversations * commentsPerConvo} total comments
- Created ${numVoters} participants
- Added ${totalVotes} total votes\n`)
process.exit(0)
} else {
console.log('\n❌ Failed to seed database')
console.log(`Failed tests: ${results.totalFailed}`)
console.log('Check the output above for detailed error messages.\n')
console.log('Common issues:')
console.log(`1. Make sure the API server is running at ${config.config.baseUrl}`)
console.log('2. Make sure the database is accessible')
console.log('3. Check if the moderator account already exists\n')
process.exit(1)
}
} catch (err) {
console.error('\n❌ Error running tests:', err.message)
process.exit(1)
}
}

// If script is run directly (not imported)
if (require.main === module) {
const args = process.argv.slice(2)
const options = {}

args.forEach(arg => {
const [key, value] = arg.replace('--', '').split('=')
if (value) {
options[key] = parseInt(value)
}
})

seed(options)
}

module.exports = { seed }
Loading