diff --git a/.gitignore b/.gitignore
index 0d1c31ca..dfb17e9d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,7 @@ venv
.pytest_cache
__snapshots__
coverage
+.nuxtrc
*.db
package-lock.json
/build
diff --git a/nuxt.config.js b/nuxt.config.js
index b88cce40..0a2b03ba 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -5,6 +5,14 @@ import path from "node:path";
import package_json from "./package.json";
const __dirname = import.meta.dirname;
+console.log(`Loading Nuxt config: ${import.meta.url}`);
+
+const sharedAlias = {
+ "@ogw_front": path.resolve(__dirname, "app"),
+ "@ogw_internal": path.resolve(__dirname, "internal"),
+ "@ogw_server": path.resolve(__dirname, "server"),
+ "@ogw_tests": path.resolve(__dirname, "tests"),
+};
const oneMinute = 60_000;
export default defineNuxtConfig({
@@ -12,23 +20,25 @@ export default defineNuxtConfig({
public: {
COMMAND_BACK: "opengeodeweb-back",
COMMAND_VIEWER: "opengeodeweb-viewer",
+ DATA_FOLDER_PATH: path.join("tests", "integration", "data", "uploads"),
NUXT_ROOT_PATH: __dirname,
MODE: process.env.MODE || "CLOUD",
PROJECT: package_json.name,
},
},
- modules: [["@pinia/nuxt", { autoImports: ["defineStore", "storeToRefs"] }], "@vueuse/nuxt"],
+ modules: [
+ ["@pinia/nuxt", { autoImports: ["defineStore", "storeToRefs"] }],
+ "@vueuse/nuxt",
+ "vuetify-nuxt-module",
+ ],
imports: {
scan: false,
},
- alias: {
- "@ogw_front": path.resolve(__dirname, "app"),
- "@ogw_internal": path.resolve(__dirname, "internal"),
- "@ogw_server": path.resolve(__dirname, "server"),
- "@ogw_tests": path.resolve(__dirname, "tests"),
- },
+ ssr: false,
+
+ alias: sharedAlias,
// ** Global CSS
css: ["vuetify/lib/styles/main.sass"],
@@ -61,16 +71,36 @@ export default defineNuxtConfig({
},
vite: {
+ alias: sharedAlias,
+ build: {
+ sourcemap: process.env.NODE_ENV === "test",
+ },
optimizeDeps: {
include: [
"ajv",
+ "dexie",
"fast-deep-equal",
"globalthis",
"h3",
+ "get-port-please",
"js-file-download",
"lodash",
+ "lodash/merge",
+ "realistic-structured-clone",
"seedrandom",
+ "spark-md5",
+ "util",
+ "ws",
+ "xmlbuilder2",
],
},
+ server: {
+ fs: {
+ allow: [
+ path.resolve(__dirname, "../../node_modules/@fontsource"),
+ path.resolve(__dirname, "../../node_modules/@mdi/font"),
+ ],
+ },
+ },
},
});
diff --git a/package.json b/package.json
index e7ed430c..60c24c16 100644
--- a/package.json
+++ b/package.json
@@ -28,8 +28,10 @@
"build:microservices": "concurrently \"npm run build:back\" \"npm run build:viewer\"",
"test": "npm run test:unit",
"tests": "vitest --config ./tests/vitest.config.js",
- "test:unit": "npm run tests --project unit",
+ "test:browser": "npm run tests --project browser",
"test:integration": "npm run tests --project integration",
+ "test:e2e": "npm run tests --project e2e",
+ "test:unit": "npm run tests --project unit",
"geode_objects": "node scripts/generate_geode_objects.js && prettier ./assets/geode_objects.js --write",
"build": ""
},
@@ -69,20 +71,25 @@
"wslink": "1.12.4"
},
"devDependencies": {
- "@nuxt/test-utils": "3.21.0",
+ "@nuxt/test-utils": "4.0.2",
"@pinia/testing": "1.0.3",
"@vitejs/plugin-vue": "6.0.4",
+ "@vitest/browser": "4.1.4",
+ "@vitest/browser-playwright": "4.1.4",
"@vue/test-utils": "2.4.6",
- "happy-dom": "20.0.11",
+ "cross-env": "10.0.0",
+ "happy-dom": "20.8.9",
"msw": "2.11.1",
- "playwright-core": "1.52.0",
+ "playwright": "1.59.1",
+ "playwright-core": "1.59.1",
"resize-observer-polyfill": "1.5.1",
"unplugin-auto-import": "20.0.0",
"vite": "7.3.1",
- "vite-plugin-vuetify": "2.1.1",
- "vitest": "4.0.15",
- "vitest-environment-nuxt": "1.0.1",
- "vitest-indexeddb": "0.0.1"
+ "vite-plugin-node-polyfills": "0.26.0",
+ "vite-plugin-vuetify": "2.1.3",
+ "vitest": "4.1.4",
+ "vitest-browser-vue": "2.1.0",
+ "vitest-indexeddb": "0.0.2"
},
"overrides": {
"vue": "latest"
diff --git a/tests/e2e/app/pages/index.vue b/tests/e2e/app/pages/index.vue
new file mode 100644
index 00000000..eb61e01f
--- /dev/null
+++ b/tests/e2e/app/pages/index.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
diff --git a/tests/e2e/app/plugins/microservices.client.js b/tests/e2e/app/plugins/microservices.client.js
new file mode 100644
index 00000000..018e2d63
--- /dev/null
+++ b/tests/e2e/app/plugins/microservices.client.js
@@ -0,0 +1,12 @@
+import { useGeodeStore } from "@ogw_front/stores/geode";
+import { useInfraStore } from "@ogw_front/stores/infra";
+import { useViewerStore } from "@ogw_front/stores/viewer";
+
+export default defineNuxtPlugin(() => {
+ const geodeStore = useGeodeStore();
+ const infraStore = useInfraStore();
+ const viewerStore = useViewerStore();
+
+ infraStore.register_microservice(geodeStore);
+ infraStore.register_microservice(viewerStore);
+});
diff --git a/tests/e2e/cells.nuxt.test.js b/tests/e2e/cells.nuxt.test.js
new file mode 100644
index 00000000..bad04271
--- /dev/null
+++ b/tests/e2e/cells.nuxt.test.js
@@ -0,0 +1,46 @@
+// Node imports
+import path from "node:path";
+
+// Third party imports
+import { beforeAll, describe, expect, test } from "vitest";
+import { createPage, setup, url } from "@nuxt/test-utils/e2e";
+
+import { setupIntegrationTests } from "@ogw_tests/integration/setup";
+
+// Local imports
+
+const timeout = 180_000; // increased
+const INTERVAL_TIMEOUT = 180_000; // for beforeAll
+
+await setup({
+ rootDir: path.resolve(import.meta.dirname),
+ server: true,
+ browser: true,
+ setupTimeout: 80_000,
+});
+
+const file_name = "test.og_rgd2d";
+const geode_object = "RegularGrid2D";
+let id = "",
+ projectFolderPath = "";
+
+beforeAll(async () => {
+ id = "";
+ projectFolderPath = "";
+ await setupIntegrationTests(file_name, geode_object);
+}, INTERVAL_TIMEOUT);
+
+describe("Dashboard E2E – Real Nitro Backend + Playwright Browser", () => {
+ test(
+ "HybridRenderingView component renders",
+ async () => {
+ const page = await createPage(url("/"));
+ console.log("→ Page opened. Waiting for full load and rendering...");
+ await page.waitForLoadState("networkidle", { timeout: 90_000 });
+ page.waitForTimeout(5000);
+ console.log("→ Taking screenshot...");
+ await expect(page).toMatchScreenshot("hybrid-rendering-view");
+ },
+ timeout,
+ );
+});
diff --git a/tests/e2e/global_setup.js b/tests/e2e/global_setup.js
new file mode 100644
index 00000000..6271513e
--- /dev/null
+++ b/tests/e2e/global_setup.js
@@ -0,0 +1,23 @@
+import { buildNuxt, loadNuxt } from "@nuxt/kit";
+import path from "node:path";
+import { setupActivePinia } from "@ogw_tests/utils";
+
+let nuxt = undefined;
+
+export default async function setup() {
+ console.log("FROM SETUP.JS - Current Directory:", import.meta.dirname);
+ await setupActivePinia();
+ nuxt = await loadNuxt({
+ rootDir: path.resolve(import.meta.dirname),
+ server: true,
+ dev: true,
+ dotenv: { env: { MODE: "BROWSER" } },
+ });
+ console.log("FROM SETUP.JS - buildNuxt", import.meta.dirname);
+ await buildNuxt(nuxt);
+
+ await nuxt.server?.listen();
+
+ process.env.NUXT_TEST_URL = `http://localhost:${nuxt.options.devServer.port}`;
+ console.log("✓ Nuxt ready at", process.env.NUXT_TEST_URL);
+}
diff --git a/tests/e2e/nuxt.config.js b/tests/e2e/nuxt.config.js
new file mode 100644
index 00000000..df6588ce
--- /dev/null
+++ b/tests/e2e/nuxt.config.js
@@ -0,0 +1,24 @@
+import path from "node:path";
+
+const __dirname = import.meta.dirname;
+console.log(`Loading Nuxt config: ${import.meta.url}`);
+const rootNuxtConfigPath = path.resolve(__dirname, "../..", "nuxt.config.js");
+const workspaceNodeModules = path.resolve(__dirname, "../../../../node_modules");
+
+export default defineNuxtConfig({
+ devtools: false,
+ extends: [rootNuxtConfigPath],
+ compatibilityDate: "2026-04-15",
+
+ modules: [["@pinia/nuxt", { autoImports: ["defineStore", "storeToRefs"] }], "@vueuse/nuxt"],
+ vite: {
+ define: {
+ name: "true",
+ },
+ server: {
+ fs: {
+ allow: [".", workspaceNodeModules],
+ },
+ },
+ },
+});
diff --git a/tests/e2e/package.json b/tests/e2e/package.json
new file mode 100644
index 00000000..c670be6e
--- /dev/null
+++ b/tests/e2e/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@geode/opengeodeweb-front/tests/e2e",
+ "version": "0.0.0",
+ "scripts": {
+ "build:browser": "cross-env MODE=BROWSER nuxi build",
+ "dev": "cross-env MODE=BROWSER nuxt dev"
+ }
+}
diff --git a/tests/e2e/test.test.js b/tests/e2e/test.test.js
new file mode 100644
index 00000000..e4206f81
--- /dev/null
+++ b/tests/e2e/test.test.js
@@ -0,0 +1,8 @@
+import { createPage, setup } from "@nuxt/test-utils/e2e";
+import { test } from "vitest";
+
+await setup();
+
+test("should run without error", async () => {
+ await createPage("/");
+}, 5000);
diff --git a/tests/integration/microservices/back/requirements.txt b/tests/integration/microservices/back/requirements.txt
deleted file mode 100644
index bd3a3ef5..00000000
--- a/tests/integration/microservices/back/requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-# pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in
-#
-
diff --git a/tests/integration/microservices/viewer/requirements.txt b/tests/integration/microservices/viewer/requirements.txt
deleted file mode 100644
index 4d097394..00000000
--- a/tests/integration/microservices/viewer/requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-# pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in
-#
-
diff --git a/tests/integration/stores/viewer.nuxt.test.js b/tests/integration/stores/viewer.nuxt.test.js
index 4eb1fc0d..04e20705 100644
--- a/tests/integration/stores/viewer.nuxt.test.js
+++ b/tests/integration/stores/viewer.nuxt.test.js
@@ -7,7 +7,7 @@ import opengeodeweb_viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb
// Local imports
import { Status } from "@ogw_front/utils/status";
import { cleanupBackend } from "@ogw_front/utils/local/cleanup";
-import { runMicroservices } from "@ogw_tests/integration/setup";
+// import { runMicroservices } from "@ogw_tests/integration/setup";
import { setupActivePinia } from "@ogw_tests/utils";
import { useViewerStore } from "@ogw_front/stores/viewer";
@@ -17,7 +17,7 @@ let projectFolderPath = "";
beforeAll(async () => {
setupActivePinia();
- ({ projectFolderPath } = await runMicroservices());
+ // ({ projectFolderPath } = await runMicroservices());
});
afterAll(async () => {
diff --git a/tests/vitest.config.js b/tests/vitest.config.js
index 911fe8ed..3556e3d1 100644
--- a/tests/vitest.config.js
+++ b/tests/vitest.config.js
@@ -1,6 +1,8 @@
+import path from "node:path";
+
import { defineConfig } from "vitest/config";
import { defineVitestProject } from "@nuxt/test-utils/config";
-import path from "node:path";
+import { playwright } from "@vitest/browser-playwright";
const __dirname = import.meta.dirname;
@@ -9,51 +11,96 @@ const DEFAULT_RETRY = 0;
const TIMEOUTS = {
unit: 5000,
integration: 15_000,
+ e2e: 120_000,
};
const globalRetry = process.env.CI ? RETRIES : DEFAULT_RETRY;
+const setupIndexedDB = path.resolve(__dirname, "./setup_indexeddb.js");
+
+const repoRoot = path.resolve(__dirname, "..");
+const commonTestConfig = {
+ setupFiles: [setupIndexedDB],
+ retry: globalRetry,
+ server: {
+ deps: {
+ inline: ["vuetify"],
+ },
+ },
+};
+
+const sharedAlias = {
+ "@ogw_tests": __dirname,
+ "@ogw_front": path.resolve(repoRoot, "app"),
+ "@ogw_internal": path.resolve(repoRoot, "internal"),
+ "@ogw_server": path.resolve(repoRoot, "server"),
+};
+
+const e2eAppPath = path.resolve(__dirname, "e2e");
+const e2eGlobalSetupPath = path.resolve(e2eAppPath, "global_setup.js");
+console.log("E2E Test App Path:", e2eAppPath);
// oxlint-disable-next-line import/no-default-export
export default defineConfig({
resolve: {
- alias: {
- "@ogw_tests": path.resolve(__dirname, "."),
- },
+ alias: sharedAlias,
},
test: {
- setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")],
+ ...commonTestConfig,
projects: [
await defineVitestProject({
+ plugins: [
+ {
+ name: "ignore-bun-test",
+ enforce: "pre",
+ resolveId(id) {
+ if (id === "bun:test") {
+ return { id: "bun:test", external: true };
+ }
+ },
+ },
+ ],
+ environments: {
+ client: {
+ noExternal: false,
+ external: ["@nuxt/test-utils", "bun:test"],
+ },
+ },
+ resolve: {
+ alias: sharedAlias,
+ },
test: {
- name: "unit",
- include: ["tests/unit/**/*.test.js"],
- globals: true,
+ name: "e2e",
+ extends: true,
+ include: ["tests/e2e/test.test.js"],
+ testTimeout: TIMEOUTS.e2e,
environment: "nuxt",
- testTimeout: TIMEOUTS.unit,
- setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")],
server: {
deps: {
- inline: ["vuetify"],
+ external: ["bun:test", "@nuxt/test-utils"],
},
},
- retry: globalRetry,
+ setupFiles: [setupIndexedDB],
+ },
+ }),
+ await defineVitestProject({
+ test: {
+ name: "unit",
+ extends: true,
+ include: ["tests/unit/**/*.test.js"],
+ environment: "nuxt",
+ testTimeout: TIMEOUTS.unit,
+ setupFiles: [setupIndexedDB],
},
}),
await defineVitestProject({
test: {
name: "integration",
- include: ["tests/integration/**/*.test.js"],
- globals: true,
+ extends: true,
+ include: ["tests/integration/stores/data_style/mesh/cells.nuxt.test.js"],
environment: "nuxt",
fileParallelism: false,
testTimeout: TIMEOUTS.integration,
- setupFiles: [path.resolve(__dirname, "./setup_indexeddb.js")],
- server: {
- deps: {
- inline: ["vuetify"],
- },
- },
- retry: globalRetry,
+ setupFiles: [setupIndexedDB],
},
}),
],