import { test as base } from "@playwright/test"; import { spawn, spawnSync } from "child_process"; import { v4 as uuid } from "uuid"; import getPort from "get-port"; import * as path from "path"; import * as dgram from "dgram"; import * as http from "http"; import * as fs from "fs/promises"; import { MongoClient } from "mongodb"; import { MONGO_PORT, MONGO_HOST } from "../src/back/config.js"; type TestFixtures = { backend: { url: string; env: Record }; setMarkdownValue: (field_name: string, value: string) => Promise; }; const c8 = "./node_modules/c8/bin/c8.js"; const app_host = process.env.TESTS_RUN_IN_DOCKER ? "test" // the docker name : "172.17.0.1"; // ip to access localhost from within docker (playwright always runs in docker) export async function waitForHttpPort({ port, host = app_host, timeout = 5000 * 20, interval = 200, }: { port: number; host?: string; timeout?: number; interval?: number; }): Promise { const start = Date.now(); return new Promise((resolve, reject) => { const check = () => { console.log("Making a request to", { host, port }); const req = http.get({ hostname: host, port, timeout: 1000 }, (res) => { res.destroy(); resolve(); // Port is ready }); req.on("error", () => { if (Date.now() - start > timeout) { reject(new Error(`Timeout waiting for HTTP port ${port}`)); } else { setTimeout(check, interval); } }); }; check(); }); } function triggerKillswitch(port: number) { const client = dgram.createSocket("udp4"); const message = Buffer.from("Hello, UDP!"); const HOST = "127.0.0.1"; client.send(message, port, HOST, () => { client.close(); }); } async function dropMongoDB(mongo_db_name: string) { const client = new MongoClient(`mongodb://${MONGO_HOST}:${MONGO_PORT}`); try { await client.connect(); const db = client.db(mongo_db_name); const result = await db.dropDatabase(); console.log(`Database "${mongo_db_name}" dropped:`, result); } catch (err) { console.error("Error dropping database:", err); } finally { await client.close(); } } export const test = base.extend({ backend: async ({}, use, { file, titlePath }) => { const port = await getPort(); const mongo_db_name = `test-app-${uuid()}`; const killswitch_port = await getPort(); const coverage_temp_dir = path.join( "coverage", "tmp_split", Date.now().toString() ); await fs.mkdir(coverage_temp_dir, { recursive: true, }); const env = { ...process.env, SEALIOUS_PORT: `${port}`, SEALIOUS_MONGO_DB_NAME: mongo_db_name, ...(process.env.TESTS_RUN_IN_DOCKER ? { MEILISEARCH_HOST: `http://meilisearch:7700` } : {}), SEALIOUS_KILLSWITCH_PORT: `${killswitch_port}`, }; const backendProcess = spawn( c8, [ "--src", "src", "--reporter", "none", "--temp-directory", coverage_temp_dir, "--all", "node", "dist/back/index.js", ], { env, stdio: "inherit", } ); const onEndPromise = new Promise((resolve) => { backendProcess.on("exit", async () => { await dropMongoDB(mongo_db_name); spawnSync( c8, [ "report", "--temp-directory", coverage_temp_dir, "--reporter", "lcovonly", "--report-dir", path.join("coverage", Date.now().toString()), ], { stdio: "inherit" } ); resolve(); }); }); await waitForHttpPort({ port, timeout: 20000 }); await use({ url: `http://${app_host}:${port}`, env }); triggerKillswitch(killswitch_port); await onEndPromise; }, setMarkdownValue: ({ page }, use) => { use(async (field_name: string, value: string) => { for (let i = 1; i <= 5; i++) { try { await page.getByRole("button", { name: "Preview" }).click(); await page .locator(`textarea[name="${field_name}"] ~ .CodeMirror`) .first() .click({ timeout: 1000 }); await page.getByRole("textbox").press("ControlOrMeta+a"); await page.getByRole("textbox").first().type("Hamster, a dentist"); break; } catch (e) { console.error(e); continue; } } }); }, }); export { expect } from "@playwright/test";