Skip to content

Commit d4bd181

Browse files
committed
fix vitest
1 parent db28428 commit d4bd181

File tree

7 files changed

+137
-79
lines changed

7 files changed

+137
-79
lines changed

template/frontend/nuxt.config.ts.jinja

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import { defineNuxtConfig } from "nuxt/config";
33
export default defineNuxtConfig({
44
compatibilityDate: "2024-11-01",
5+
future: {
6+
compatibilityVersion: 4,
7+
},
58
devtools: { enabled: true },
69
// the conditional modules added in by the template make it complicated to format consistently...at least with only 3 'always included' modules
710
// prettier-ignore

template/frontend/package.json.jinja

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@
1212
"generate": "pnpm run type-check && nuxt generate",
1313
"preview": "nuxt preview",
1414
"postinstall": "nuxt prepare && pnpm exec playwright-core install --only-shell chromium-headless-shell",
15-
"test-unit": "vitest --dir tests/unit --run --coverage",
16-
"test-unit:watch": "vitest --dir tests/unit",
17-
"test-compiled": "vitest --test-timeout=15000 --dir tests/compiled --run",
18-
"test-e2e": "{% endraw %}{% if not deploy_as_executable %}{% raw %}docker compose --file=../docker-compose.yaml build && dotenv -v USE_DOCKER_COMPOSE_FOR_VITEST_E2E=1{% endraw %}{% else %}{% raw %}dotenv -v USE_BUILT_BACKEND_FOR_VITEST_E2E=1{% endraw %}{% endif %}{% raw %} -- vitest --test-timeout=15000 --dir tests/e2e --run",
19-
"test-compiled:watch": "vitest --dir tests/compiled"{% endraw %}{% if frontend_uses_graphql %}{% raw %},
15+
"test-unit": "vitest --exclude=\"tests/e2e/**\" --exclude=\"tests/compiled/**\" --run --coverage",
16+
"test-unit:watch": "vitest --exclude=\"tests/e2e/**\" --exclude=\"tests/compiled/**\"",
17+
"test-compiled": "vitest --exclude=\"tests/e2e/**\" --exclude=\"tests/unit/**\" --test-timeout=15000 --run",
18+
"test-e2e": "docker compose --file=../docker-compose.yaml build && dotenv -v USE_DOCKER_COMPOSE_FOR_VITEST_E2E=1 -- vitest --exclude=\"tests/unit/**\" --exclude=\"tests/compiled/**\" --test-timeout=15000 --run",
19+
"test-compiled:watch": "vitest --exclude=\"tests/e2e/**\" --exclude=\"tests/unit/**\" --test-timeout=15000",{% endraw %}{% if frontend_uses_graphql %}{% raw %},
2020
"codegen": "graphql-codegen --config codegen.ts"{% endraw %}{% endif %}{% raw %}
2121
},
2222
"dependencies": {
2323
"@nuxt/ui": "{% endraw %}{{ nuxt_ui_version }}{% raw %}",
2424
"nuxt": "{% endraw %}{{ nuxt_version }}{% raw %}",
25-
"typescript": "{% endraw %}{{ typescript_version }}{% raw %}",
2625
"vue": "{% endraw %}{{ vue_version }}{% raw %}",
2726
"vue-router": "{% endraw %}{{ vue_router_version }}{% raw %}"
2827
},
@@ -39,6 +38,7 @@
3938
"@nuxt/test-utils": "^3.17.2",{% endraw %}{% if frontend_uses_graphql %}{% raw %}
4039
"@nuxtjs/apollo": "5.0.0-alpha.14",{% endraw %}{% endif %}{% raw %}
4140
"@nuxtjs/eslint-config-typescript": "^12.1.0",
41+
"@playwright/test": "^1.52.0",
4242
"@vitest/coverage-istanbul": "^3.1.3",{% endraw %}{% if frontend_uses_graphql %}{% raw %}
4343
"@vue/apollo-composable": "^4.2.2",{% endraw %}{% endif %}{% raw %}
4444
"@vue/devtools-api": "^7.7.2",
@@ -57,6 +57,7 @@
5757
"playwright-core": "^1.52.0",
5858
"postcss": "^8.5.3",
5959
"tailwindcss": "^4.0.14",
60+
"typescript": "{% endraw %}{{ typescript_version }}{% raw %}",
6061
"vitest": "^3.1.3",
6162
"vue-eslint-parser": "^10.1.1",
6263
"vue-tsc": "^2.2.8"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { inject } from "vitest";
2+
3+
export function url(path: string): string {
4+
if (!path.startsWith("/")) {
5+
path = `/${path}`;
6+
}
7+
return `${inject("baseUrl")}${path}`;
8+
}

template/frontend/tests/e2e/index.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, test } from "vitest";
2-
import { getPage, url } from "~/tests/setup/app";
2+
import { url } from "~/tests/e2e/helpers/playwright";
3+
import { getPage } from "~/tests/setup/app";
34

45
describe("Index page", async () => {
56
test("Page displays Hello World", async () => {

template/frontend/tests/setup/app.ts

Lines changed: 6 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { execSync, spawn } from "child_process";
21
import fs from "fs";
32
import path from "path";
43
import type { Browser, Page } from "playwright";
54
import { chromium } from "playwright";
6-
import { afterAll, beforeAll } from "vitest";
5+
import { afterAll, beforeAll, beforeEach } from "vitest";
76

87
import { APP_NAME, DEPLOYED_BACKEND_PORT_NUMBER, DEPLOYED_FRONTEND_PORT_NUMBER } from "~/tests/setup/constants";
98

109
const isE2E = process.env.USE_DOCKER_COMPOSE_FOR_VITEST_E2E || process.env.USE_BUILT_BACKEND_FOR_VITEST_E2E;
11-
const isDockerE2E = process.env.USE_DOCKER_COMPOSE_FOR_VITEST_E2E;
10+
1211
const isBuiltBackendE2E = process.env.USE_BUILT_BACKEND_FOR_VITEST_E2E;
1312
let browser: Browser;
1413
let page: Page;
@@ -23,81 +22,16 @@ if (isBuiltBackendE2E) {
2322
export const BASE_URL = `http://127.0.0.1:${
2423
isBuiltBackendE2E ? DEPLOYED_BACKEND_PORT_NUMBER : DEPLOYED_FRONTEND_PORT_NUMBER
2524
}`;
26-
export function url(path: string): string {
27-
if (!path.startsWith("/")) {
28-
path = `/${path}`;
29-
}
30-
return `${BASE_URL}${path}`;
31-
}
32-
const healthCheckUrl = `http://127.0.0.1:${
33-
isBuiltBackendE2E ? DEPLOYED_BACKEND_PORT_NUMBER.toString() + "/api/healthcheck" : DEPLOYED_FRONTEND_PORT_NUMBER
34-
}`; // TODO: if there is a backend, check that too, even if it's a docker-compose situation
25+
3526
if (isE2E) {
3627
beforeAll(async () => {
37-
if (isBuiltBackendE2E) {
38-
console.log(`Starting app at ${executablePath} ...`);
39-
const child = spawn(executablePath, ["--host", "0.0.0.0"], {
40-
// TODO: figure out why Github CI pipelines fail without setting all allowed hosts
41-
stdio: "inherit",
42-
});
43-
child.on("close", (code) => {
44-
console.log(`Process exited with code ${code}`);
45-
});
46-
}
47-
if (isDockerE2E) {
48-
console.log("Starting docker-compose...");
49-
execSync("docker compose --file=../docker-compose.yaml up --detach --force-recreate --renew-anon-volumes", {
50-
stdio: "inherit",
51-
});
52-
}
5328
browser = await chromium.launch(); // headless by default
54-
page = await browser.newPage();
55-
// Wait for /api/healthcheck to become available
56-
const maxAttempts = 10;
57-
let attempts = 0;
58-
while (attempts < maxAttempts) {
59-
try {
60-
const res = await fetch(healthCheckUrl);
61-
if (res.ok) {
62-
break;
63-
}
64-
} catch {
65-
// ignore errors // TODO: make this more specific (e.g., only ignore network errors)
66-
}
67-
attempts++;
68-
console.log(`Waiting for ${healthCheckUrl} to become available... Attempt ${attempts}`);
69-
await new Promise((resolve) => setTimeout(resolve, 1000));
70-
}
71-
if (attempts === maxAttempts) {
72-
throw new Error(`Timeout waiting for ${healthCheckUrl}`);
73-
}
7429
}, 40 * 1000); // increase timeout to allow application to start
75-
30+
beforeEach(async () => {
31+
page = await browser.newPage();
32+
});
7633
afterAll(async () => {
7734
await browser.close();
78-
if (isBuiltBackendE2E) {
79-
console.log("Stopping application...");
80-
try {
81-
const res = await fetch(`${BASE_URL}/api/shutdown`);
82-
if (!res.ok) {
83-
throw new Error(`Failed to stop the application: ${res.statusText}`);
84-
}
85-
} catch (error) {
86-
const logFilePath = path.resolve(repoRoot, `./frontend/logs/${APP_NAME}-backend.log`);
87-
// sometimes it takes a second for the log file to be fully written to disk
88-
await new Promise((resolve) => setTimeout(resolve, 1000));
89-
if (!fs.existsSync(logFilePath) || !fs.statSync(logFilePath).isFile()) {
90-
throw new Error(`Log file not found: ${logFilePath}`, { cause: error });
91-
}
92-
const logData = fs.readFileSync(logFilePath, "utf-8");
93-
console.log("Application logs:\n", logData);
94-
throw error;
95-
}
96-
}
97-
if (isDockerE2E) {
98-
console.log("Stopping docker-compose...");
99-
execSync("docker compose --file=../docker-compose.yaml down", { stdio: "inherit" });
100-
}
10135
}, 40 * 1000); // increase timeout to allow application to stop
10236
}
10337

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { execSync, spawn } from "child_process";
2+
import fs from "fs";
3+
import path from "path";
4+
import type { Browser } from "playwright";
5+
import { chromium } from "playwright";
6+
import type { TestProject } from "vitest/node";
7+
import { APP_NAME, DEPLOYED_BACKEND_PORT_NUMBER, DEPLOYED_FRONTEND_PORT_NUMBER } from "~/tests/setup/constants";
8+
9+
const isE2E = process.env.USE_DOCKER_COMPOSE_FOR_VITEST_E2E || process.env.USE_BUILT_BACKEND_FOR_VITEST_E2E;
10+
const isDockerE2E = process.env.USE_DOCKER_COMPOSE_FOR_VITEST_E2E;
11+
const isBuiltBackendE2E = process.env.USE_BUILT_BACKEND_FOR_VITEST_E2E;
12+
let browser: Browser;
13+
14+
const executableExtension = process.platform === "win32" ? ".exe" : "";
15+
const repoRoot = path.resolve(__dirname, "../../../");
16+
const executablePath = path.resolve(repoRoot, `./backend/dist/${APP_NAME}/${APP_NAME}${executableExtension}`);
17+
if (isBuiltBackendE2E) {
18+
if (!fs.existsSync(executablePath) || !fs.statSync(executablePath).isFile()) {
19+
throw new Error(`File not found: ${executablePath}`);
20+
}
21+
}
22+
export const BASE_URL = `http://127.0.0.1:${
23+
isBuiltBackendE2E ? DEPLOYED_BACKEND_PORT_NUMBER : DEPLOYED_FRONTEND_PORT_NUMBER
24+
}`;
25+
export function url(path: string): string {
26+
if (!path.startsWith("/")) {
27+
path = `/${path}`;
28+
}
29+
return `${BASE_URL}${path}`;
30+
}
31+
const healthCheckUrl = `http://127.0.0.1:${
32+
isBuiltBackendE2E ? DEPLOYED_BACKEND_PORT_NUMBER.toString() + "/api/healthcheck" : DEPLOYED_FRONTEND_PORT_NUMBER
33+
}`; // TODO: if there is a backend, check that too, even if it's a docker-compose situation
34+
35+
export async function setup(project: TestProject) {
36+
if (isE2E) {
37+
if (isBuiltBackendE2E) {
38+
console.log(`Starting app at ${executablePath} ...`);
39+
const child = spawn(executablePath, ["--host", "0.0.0.0"], {
40+
// TODO: figure out why Github CI pipelines fail without setting all allowed hosts
41+
stdio: "inherit",
42+
});
43+
child.on("close", (code) => {
44+
console.log(`Process exited with code ${code}`);
45+
});
46+
}
47+
if (isDockerE2E) {
48+
console.log("Starting docker-compose...");
49+
execSync("docker compose --file=../docker-compose.yaml up --detach --force-recreate --renew-anon-volumes", {
50+
stdio: "inherit",
51+
});
52+
}
53+
browser = await chromium.launch(); // headless by default
54+
project.provide("baseUrl", BASE_URL);
55+
// Wait for /api/healthcheck to become available
56+
const maxAttempts = 10;
57+
let attempts = 0;
58+
while (attempts < maxAttempts) {
59+
try {
60+
const res = await fetch(healthCheckUrl);
61+
if (res.ok) {
62+
break;
63+
}
64+
} catch {
65+
// ignore errors // TODO: make this more specific (e.g., only ignore network errors)
66+
}
67+
attempts++;
68+
console.log(`Waiting for ${healthCheckUrl} to become available... Attempt ${attempts}`);
69+
await new Promise((resolve) => setTimeout(resolve, 1000));
70+
}
71+
if (attempts === maxAttempts) {
72+
throw new Error(`Timeout waiting for ${healthCheckUrl}`);
73+
}
74+
}
75+
}
76+
export async function teardown() {
77+
if (isE2E) {
78+
await browser.close();
79+
if (isBuiltBackendE2E) {
80+
console.log("Stopping application...");
81+
try {
82+
const res = await fetch(`${BASE_URL}/api/shutdown`);
83+
if (!res.ok) {
84+
throw new Error(`Failed to stop the application: ${res.statusText}`);
85+
}
86+
} catch (error) {
87+
const logFilePath = path.resolve(repoRoot, `./frontend/logs/${APP_NAME}-backend.log`);
88+
// sometimes it takes a second for the log file to be fully written to disk
89+
await new Promise((resolve) => setTimeout(resolve, 1000));
90+
if (!fs.existsSync(logFilePath) || !fs.statSync(logFilePath).isFile()) {
91+
throw new Error(`Log file not found: ${logFilePath}`, { cause: error });
92+
}
93+
const logData = fs.readFileSync(logFilePath, "utf-8");
94+
console.log("Application logs:\n", logData);
95+
throw error;
96+
}
97+
}
98+
if (isDockerE2E) {
99+
console.log("Stopping docker-compose...");
100+
execSync("docker compose --file=../docker-compose.yaml down", { stdio: "inherit" });
101+
}
102+
}
103+
}
104+
105+
declare module "vitest" {
106+
export interface ProvidedContext {
107+
baseUrl: string;
108+
}
109+
}

template/frontend/vitest.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default defineVitestConfig({
1111
sequence: {
1212
shuffle: true,
1313
},
14+
include: ["tests/**/*.spec.ts"],
1415
coverage: {
1516
provider: "istanbul",
1617
reporter: ["text", "json", "html"],
@@ -21,5 +22,6 @@ export default defineVitestConfig({
2122
exclude: ["**/generated/graphql.ts", "**/codegen.ts", "**/nuxt.config.ts", ...coverageConfigDefaults.exclude],
2223
},
2324
setupFiles: ["./tests/setup/faker.ts", "./tests/setup/app.ts"],
25+
globalSetup: "./tests/setup/globalSetup.ts",
2426
},
2527
});

0 commit comments

Comments
 (0)