From b9bab44274311a0abcfc336c0be26ab71ce423af Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 16 Dec 2025 21:12:08 +0100 Subject: [PATCH] fix(other): improve e2e test reliability with API mocking and race condition fixes (#621) --- .gitignore | 1 + backend/prepare-seed.js | 99 +++++++++++++++++++++++++++ backend/seed.sh | 3 + cypress/e2e/search/search-flows.cy.ts | 2 +- cypress/support/commands.ts | 6 ++ cypress/support/e2e.ts | 65 ++++++++++++++++++ 6 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 backend/prepare-seed.js diff --git a/.gitignore b/.gitignore index 040520f3..10379bdf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ cypress/node_modules/ cypress/results/ cypress/runner-results/ cypress/screenshots/ + diff --git a/backend/prepare-seed.js b/backend/prepare-seed.js new file mode 100644 index 00000000..94f025df --- /dev/null +++ b/backend/prepare-seed.js @@ -0,0 +1,99 @@ +#!/usr/bin/env node + +/** + * Prepares seed data by updating event dates relative to the current date. + * + * This script modifies items.json in-place, updating all items with + * layer "layer-events" to have realistic start/end dates that ensure + * events are visible in the app (end dates in the future). + * + * Date strategy: + * - Event 1 (item-event-1): Long-running, started 30 days ago, ends in 365 days + * - Event 2 (item-event-2): Upcoming single-day event in 7 days + * - Event 3 (item-event-3): Ongoing event, started yesterday, ends tomorrow + * - Event 4 (item-event-4): Upcoming multi-day conference in 30 days + */ + +const fs = require('fs') +const path = require('path') + +const seedPath = path.join(__dirname, 'directus-config/development/seed/items.json') + +function addDays(date, days) { + const result = new Date(date) + result.setDate(result.getDate() + days) + return result +} + +function addHours(date, hours) { + const result = new Date(date) + result.setHours(result.getHours() + hours) + return result +} + +function formatDateTime(date) { + return date.toISOString().slice(0, 19) +} + +/** + * Calculate dynamic dates for each event based on current time + */ +function getEventDates(syncId, now) { + const dateConfigs = { + // "Some Event" - long-running, started in past, ends far in future + 'item-event-1': { + start: addDays(now, -30), + end: addDays(now, 365) + }, + // "Tech Meetup Munich" - upcoming single-day event (the one used in search tests) + 'item-event-2': { + start: addHours(addDays(now, 7), 18), + end: addHours(addDays(now, 7), 21) + }, + // "Sustainability Workshop NYC" - ongoing event (started yesterday, ends tomorrow) + 'item-event-3': { + start: addHours(addDays(now, -1), 14), + end: addHours(addDays(now, 1), 17) + }, + // "Open Source Conference" - upcoming multi-day conference + 'item-event-4': { + start: addHours(addDays(now, 30), 9), + end: addHours(addDays(now, 32), 18) + } + } + + return dateConfigs[syncId] || null +} + +function prepareSeedData() { + // Read the current items.json + const content = fs.readFileSync(seedPath, 'utf8') + const seedData = JSON.parse(content) + + const now = new Date() + let updatedCount = 0 + + // Update event items with dynamic dates + for (const item of seedData.data) { + if (item.layer === 'layer-events' && item._sync_id) { + const dates = getEventDates(item._sync_id, now) + if (dates) { + item.start = formatDateTime(dates.start) + item.end = formatDateTime(dates.end) + console.log(` ${item._sync_id} (${item.name}):`) + console.log(` start: ${item.start}`) + console.log(` end: ${item.end}`) + updatedCount++ + } + } + } + + // Write back to items.json + fs.writeFileSync(seedPath, JSON.stringify(seedData, null, 4)) + + console.log(`\nUpdated ${updatedCount} event(s) with dynamic dates.`) +} + +console.log('Preparing seed data with dynamic dates...\n') +prepareSeedData() + diff --git a/backend/seed.sh b/backend/seed.sh index b62384f6..52811ed7 100755 --- a/backend/seed.sh +++ b/backend/seed.sh @@ -15,6 +15,9 @@ PGDATABASE="${PGDATABASE:-'directus'}" PROJECT_NAME="${PROJECT:-development}" PROJECT_FOLDER=$SCRIPT_DIR/directus-config/$PROJECT_NAME +echo "Preparing seed data with dynamic dates" +node $SCRIPT_DIR/prepare-seed.js || exit 1 + echo "Seed data" npx directus-sync@3.4.0 seed push \ --seed-path $PROJECT_FOLDER/seed \ diff --git a/cypress/e2e/search/search-flows.cy.ts b/cypress/e2e/search/search-flows.cy.ts index 9b24b84e..fc16a276 100644 --- a/cypress/e2e/search/search-flows.cy.ts +++ b/cypress/e2e/search/search-flows.cy.ts @@ -74,7 +74,7 @@ describe('Utopia Map Search', () => { cy.contains('Wat Arun').first().click() }) - cy.get('.leaflet-popup').should('be.visible') + cy.get('.leaflet-popup', { timeout: 15000 }).should('exist') cy.get('.leaflet-popup-content').should('contain', 'Wat Arun') }) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 72832e81..10d7bd52 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -76,6 +76,12 @@ Cypress.Commands.add('searchFor', (query: string) => { Cypress.Commands.add('waitForMapReady', () => { cy.get('[data-cy="search-input"]', { timeout: 10000 }).should('be.visible') cy.get('.leaflet-container', { timeout: 10000 }).should('be.visible') + cy.wait('@getLayers', { timeout: 15000 }).then((interception) => { + const layerCount = interception.response?.body?.data?.length || 3 + for (let i = 0; i < layerCount; i++) { + cy.wait('@getLayerItems', { timeout: 15000 }) + } + }) cy.get('.leaflet-marker-icon', { timeout: 15000 }).should('have.length.at.least', 1) }) diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index fc990283..cafcadf7 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -5,6 +5,71 @@ import './commands' // for screenshot embedding import addContext from 'mochawesome/addContext' +const photonMockData: Record = { + berlin: { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + osm_type: 'R', + osm_id: 62422, + osm_key: 'place', + osm_value: 'city', + type: 'city', + countrycode: 'DE', + name: 'Berlin', + country: 'Germany', + state: 'Berlin', + extent: [13.088345, 52.6755087, 13.7611609, 52.3382448], + }, + geometry: { type: 'Point', coordinates: [13.3951309, 52.5173885] }, + }, + ], + }, + 'wat arun': { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + osm_type: 'W', + osm_id: 25867629, + osm_key: 'tourism', + osm_value: 'attraction', + type: 'attraction', + countrycode: 'TH', + name: 'Wat Arun', + country: 'Thailand', + city: 'Bangkok', + extent: [100.4882, 13.7437, 100.4912, 13.7407], + }, + geometry: { type: 'Point', coordinates: [100.4897, 13.7437] }, + }, + ], + }, +} + +beforeEach(() => { + cy.intercept('GET', 'https://photon.komoot.io/api/*', (req) => { + const url = new URL(req.url) + const query = (url.searchParams.get('q') || '').toLowerCase() + + const mockKey = Object.keys(photonMockData).find((key) => + query.includes(key.toLowerCase()), + ) + + if (mockKey) { + req.reply(photonMockData[mockKey]) + } else { + req.reply({ type: 'FeatureCollection', features: [] }) + } + }).as('photonApi') + + cy.intercept('GET', '**/items/layers*').as('getLayers') + cy.intercept('GET', '**/items/items*').as('getLayerItems') +}) + // Global exception handler Cypress.on('uncaught:exception', (err) => { // eslint-disable-next-line no-console