From 6c66781b3c036b2555a8c2d9ab82349355e56c77 Mon Sep 17 00:00:00 2001 From: Kari Lintulaakso Date: Fri, 7 Nov 2025 11:26:11 +0200 Subject: [PATCH] fix: stabilize cypress database connectivity --- cypress.config.js | 87 +++++++++++++++++++++++++-- cypress/e2e/updateLogNavigation.cy.ts | 4 ++ cypress/support/index.d.ts | 10 +++ docker-compose.anon.yml | 3 + 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/cypress.config.js b/cypress.config.js index 009b094f..a9fd5973 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,18 +1,97 @@ const { defineConfig } = require("cypress"); +const net = require("net"); + +const DEFAULT_DB_HOST = + process.env.CYPRESS_DB_HOST ?? process.env.MARIADB_HOST ?? "127.0.0.1"; +const DEFAULT_DB_PORT = Number( + process.env.CYPRESS_DB_PORT ?? process.env.MARIADB_PORT ?? 3306, +); +const DEFAULT_DB_WAIT_TIMEOUT_MS = Number( + process.env.CYPRESS_DB_WAIT_TIMEOUT ?? 30_000, +); + +const RETRY_DELAY_MS = 1_000; +const SOCKET_TIMEOUT_MS = 2_000; + +/** + * @param {{ host?: string; port?: number; timeoutMs?: number }} [options] + * @param {{ dbHost?: string; dbPort?: number; dbWaitTimeoutMs?: number }} [env] + */ +async function waitForDatabase(options = {}, env = {}) { + const host = options.host ?? env.dbHost ?? DEFAULT_DB_HOST; + const port = Number(options.port ?? env.dbPort ?? DEFAULT_DB_PORT); + const timeoutMs = Number( + options.timeoutMs ?? env.dbWaitTimeoutMs ?? DEFAULT_DB_WAIT_TIMEOUT_MS, + ); + + if (!Number.isFinite(port)) { + throw new Error(`Invalid database port provided for waitForDbHealthy: ${port}`); + } + + const deadline = Date.now() + timeoutMs; + + return new Promise((resolve, reject) => { + const attemptConnection = () => { + const socket = net.createConnection({ host, port }); + + const cleanup = () => { + socket.removeAllListeners(); + }; + + const scheduleRetry = (error) => { + cleanup(); + socket.destroy(); + + if (Date.now() >= deadline) { + reject( + new Error( + `Timed out waiting for MariaDB at ${host}:${port} (${error.message})`, + ), + ); + return; + } + + setTimeout(attemptConnection, RETRY_DELAY_MS); + }; + + socket.once("connect", () => { + cleanup(); + socket.end(); + resolve(null); + }); + + socket.once("error", scheduleRetry); + socket.setTimeout(SOCKET_TIMEOUT_MS, () => { + scheduleRetry(new Error("Connection attempt timed out")); + }); + }; + + attemptConnection(); + }); +} module.exports = defineConfig({ e2e: { - setupNodeEvents(on, _config) { - require('@cypress/code-coverage/task')(on, _config) + setupNodeEvents(on, config) { + require('@cypress/code-coverage/task')(on, config) + + on('task', { + waitForDbHealthy(options) { + return waitForDatabase(options, config.env) + }, + }) // It's IMPORTANT to return the config object // with any changed environment variables - return _config + return config }, baseUrl: 'http://localhost:5173/', }, env: { - databaseResetUrl: "http://localhost:4000/test/reset-test-database" + databaseResetUrl: "http://localhost:4000/test/reset-test-database", + dbHost: DEFAULT_DB_HOST, + dbPort: DEFAULT_DB_PORT, + dbWaitTimeoutMs: DEFAULT_DB_WAIT_TIMEOUT_MS, }, viewportHeight: 900, viewportWidth: 1200, diff --git a/cypress/e2e/updateLogNavigation.cy.ts b/cypress/e2e/updateLogNavigation.cy.ts index 111dab67..5570cd8c 100644 --- a/cypress/e2e/updateLogNavigation.cy.ts +++ b/cypress/e2e/updateLogNavigation.cy.ts @@ -1,6 +1,10 @@ /// describe('Update log navigation', () => { + before('Wait for database to become ready', () => { + cy.task('waitForDbHealthy') + }) + before('Reset database', () => { cy.request(Cypress.env('databaseResetUrl')) }) diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index 0bd5f9fd..6e611763 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -8,4 +8,14 @@ declare namespace Cypress { addReferenceAndSave(): Chainable clearNewSpeciesForm(): Chainable } + + interface Tasks { + waitForDbHealthy(options?: WaitForDbHealthyOptions): null + } +} + +interface WaitForDbHealthyOptions { + host?: string + port?: number + timeoutMs?: number } diff --git a/docker-compose.anon.yml b/docker-compose.anon.yml index 30b1fe09..22588c37 100644 --- a/docker-compose.anon.yml +++ b/docker-compose.anon.yml @@ -38,6 +38,9 @@ services: container_name: nowdb-db-anon image: mariadb:11.4.3 env_file: .anon.env + environment: + - MYSQL_WAIT_TIMEOUT=28800 + - MYSQL_INTERACTIVE_TIMEOUT=28800 volumes: - ./test_data/:/docker-entrypoint-initdb.d - nowdb-db-anon:/var/lib/mysql/data