Skip to content

Instantly share code, notes, and snippets.

@sergiodxa
Last active May 15, 2022 22:34
Show Gist options
  • Save sergiodxa/891a3cd1736c1b09c9a0b5fd106d604e to your computer and use it in GitHub Desktop.
Save sergiodxa/891a3cd1736c1b09c9a0b5fd106d604e to your computer and use it in GitHub Desktop.
An E2E helper for Vitest and Puppeteer to test Remix app
import { test, expect, describe, beforeAll, afterAll } from "vitest";
import "pptr-testing-library/extend";
import { type App, start } from "test/helpers/app";
import { loader } from "./articles";
import { logger } from "~/services/logger.server";
import type { PrismaClient } from "@prisma/client";
import { createDatabaseClient } from "test/helpers/db";
describe("E2E", () => {
let app: App;
beforeAll(async () => {
app = await start();
});
afterAll(async () => {
await app.stop();
});
test("Articles page should render list of articles", async () => {
let document = await app.navigate("/articles");
let $h1 = await document.findByRole("heading", {
name: "Articles",
level: 1,
});
expect(await $h1.getNodeText()).toBe("Articles");
});
});
describe("Loader", () => {
let db: PrismaClient;
beforeAll(async () => {
db = await createDatabaseClient();
await db.$connect();
});
afterAll(async () => {
await db.$disconnect();
});
test("The loader should have an articles key", async () => {
let response = await loader({
request: new Request("/articles"),
params: {},
context: { logger, db },
});
let data = await response.json();
expect(data).toHaveProperty("articles");
expect(data.articles).toBeInstanceOf(Array);
});
});
import "pptr-testing-library/extend";
import getPort from "get-port";
import { execa } from "execa";
import puppeteer from "puppeteer";
import { type DATABASE_URL, generateDatabaseUrl, migrateDatabase } from "./db";
import { clear } from "winston";
export type Process = {
stop(): Promise<void>;
port: number;
};
export type App = {
navigate(path: string): Promise<puppeteer.ElementHandle<Element>>;
stop(): Promise<void>;
browser: puppeteer.Browser;
page: puppeteer.Page;
};
function clearBuild() {
return Promise.all([
execa("rm", ["-rf", "server/build"]),
execa("rm", ["-rf", "public/build"]),
]);
}
function buildApp() {
return execa("npm", ["run", "build"]);
}
async function prepareBuild() {
await clearBuild();
await buildApp();
}
async function prepareDatabase() {
let databaseUrl = generateDatabaseUrl();
await migrateDatabase(databaseUrl);
return databaseUrl;
}
async function startProcess({ databaseUrl }: { databaseUrl: DATABASE_URL }) {
let port = await getPort();
let server = execa("npm", ["start"], {
env: {
CI: "true",
NODE_ENV: "test",
PORT: port.toString(),
BASE_URL: `http://localhost:${port}`,
DATABASE_URL: databaseUrl,
},
});
return await new Promise<Process>(async (resolve, reject) => {
server.catch((error) => reject(error));
if (server.stdout === null) return reject("Failed to start server.");
server.stdout.on("data", (stream: Buffer) => {
if (stream.toString().includes(port.toString())) {
return resolve({
async stop() {
if (server.killed) return;
server.cancel();
},
port,
});
}
});
});
}
function openBrowser() {
return puppeteer.launch();
}
function openPage(browser: puppeteer.Browser) {
return browser.newPage();
}
export async function start(): Promise<App> {
let [databaseUrl] = await Promise.all([prepareDatabase(), prepareBuild]);
let { port, stop } = await startProcess({ databaseUrl });
let browser = await openBrowser();
let page = await openPage(browser);
return {
browser,
page,
async navigate(path: string) {
let url = new URL(path, `http://localhost:${port}/`);
await page.goto(url.toString());
return await page.getDocument();
},
async stop() {
await stop();
await browser.close();
await clearBuild();
},
};
}
import { PrismaClient } from "@prisma/client";
import { execa } from "execa";
import { randomUUID } from "node:crypto";
declare const helperDb: unique symbol;
export type DATABASE_URL = string & { [helperDb]: true };
const DATABASE_URL_FORMAT = "file:./test/{{uuid}}.db";
export function generateDatabaseUrl() {
let uuid = randomUUID();
return DATABASE_URL_FORMAT.replace("{{uuid}}", uuid) as DATABASE_URL;
}
export function migrateDatabase(url: DATABASE_URL) {
return execa("npx", ["prisma", "migrate", "dev"], {
env: {
NODE_ENV: "test",
DATABASE_URL: url,
},
});
}
export async function createDatabaseClient() {
let url = generateDatabaseUrl();
await migrateDatabase(url);
return new PrismaClient({
datasources: { db: { url } },
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment