add playwright tests
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { test as base, expect, Page } from "@playwright/test";
|
||||
import { spawn, ChildProcess } from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
/**
|
||||
* Starts a fresh Sustenance server against a unique, throwaway database on a
|
||||
* unique port for each test, and tears it down afterwards. This gives every
|
||||
* test a clean DB with no shared state between tests.
|
||||
*/
|
||||
export const test = base.extend<{ server: { baseURL: string }; page: Page }>({
|
||||
server: [
|
||||
async ({}, use) => {
|
||||
const dbPath = path.join(
|
||||
os.tmpdir(),
|
||||
`sustenance-e2e-${process.pid}-${Date.now()}-${Math.random()
|
||||
.toString(36)
|
||||
.slice(2)}.db`,
|
||||
);
|
||||
const port = 3200 + Math.floor(Math.random() * 2000);
|
||||
const baseURL = `http://127.0.0.1:${port}`;
|
||||
|
||||
const child = spawn(
|
||||
path.resolve(__dirname, "..", "target", "debug", "sustenance"),
|
||||
[],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
DATABASE_PATH: dbPath,
|
||||
REGISTRATION_MODE: "open",
|
||||
BIND_ADDRESS: `127.0.0.1:${port}`,
|
||||
// Point SEED_CONFIG at a nonexistent file so no default user is created.
|
||||
SEED_CONFIG: path.join(os.tmpdir(), "sustenance-e2e-no-seed.json"),
|
||||
},
|
||||
stdio: "ignore",
|
||||
// Run in its own process group so we can kill the whole tree.
|
||||
detached: true,
|
||||
},
|
||||
);
|
||||
|
||||
await waitForServer(baseURL, child);
|
||||
|
||||
await use({ baseURL });
|
||||
|
||||
await killTree(child);
|
||||
// Clean up the DB files (including -wal / -shm).
|
||||
for (const suffix of ["", "-wal", "-shm"]) {
|
||||
fs.rmSync(dbPath + suffix, { force: true });
|
||||
}
|
||||
},
|
||||
{ scope: "test", auto: true },
|
||||
],
|
||||
|
||||
// Provide a page whose baseURL points at this test's server.
|
||||
page: async ({ browser, server }, use) => {
|
||||
const context = await browser.newContext({ baseURL: server.baseURL });
|
||||
const page = await context.newPage();
|
||||
await use(page);
|
||||
await context.close();
|
||||
},
|
||||
});
|
||||
|
||||
async function waitForServer(baseURL: string, child: ChildProcess) {
|
||||
const deadline = Date.now() + 60_000;
|
||||
while (Date.now() < deadline) {
|
||||
if (child.exitCode !== null) {
|
||||
throw new Error(`server exited early with code ${child.exitCode}`);
|
||||
}
|
||||
try {
|
||||
const res = await fetch(baseURL + "/login");
|
||||
if (res.ok) return;
|
||||
} catch {
|
||||
// not up yet
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
}
|
||||
throw new Error("timed out waiting for server to start");
|
||||
}
|
||||
|
||||
async function killTree(child: ChildProcess) {
|
||||
try {
|
||||
process.kill(-child.pid!, "SIGTERM");
|
||||
} catch {
|
||||
child.kill("SIGTERM");
|
||||
}
|
||||
// Give it a moment to shut down gracefully, then force-kill if needed.
|
||||
const exited = new Promise((resolve) => child.once("exit", resolve));
|
||||
const timeout = new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
await Promise.race([exited, timeout]);
|
||||
try {
|
||||
process.kill(-child.pid!, "SIGKILL");
|
||||
} catch {
|
||||
/* already gone */
|
||||
}
|
||||
}
|
||||
|
||||
export { expect };
|
||||
Reference in New Issue
Block a user