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