Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "muiogo-e2e",
"private": true,
"scripts": {
"test": "npx playwright test",
"test:headed": "npx playwright test --headed",
"test:report": "npx playwright show-report"
},
"devDependencies": {
"@playwright/test": "^1.50.0"
}
}
21 changes: 21 additions & 0 deletions e2e/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @ts-check
const { defineConfig } = require("@playwright/test");

module.exports = defineConfig({
testDir: "./tests",
timeout: 30000,
retries: 1,
use: {
baseURL: "http://127.0.0.1:5002",
headless: true,
screenshot: "only-on-failure",
trace: "retain-on-failure",
},
webServer: {
command: "python ../API/app.py",
url: "http://127.0.0.1:5002",
timeout: 30000,
reuseExistingServer: true,
},
reporter: [["html", { open: "never" }], ["list"]],
});
25 changes: 25 additions & 0 deletions e2e/tests/app-load.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// @ts-check
const { test, expect } = require("@playwright/test");

test.describe("Application Load", () => {
test("homepage loads successfully", async ({ page }) => {
const response = await page.goto("/");
expect(response.status()).toBe(200);
});

test("page title is correct", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/MUIO/);
});

test("main UI elements are visible", async ({ page }) => {
await page.goto("/");
// Wait for the page to fully load
await page.waitForLoadState("networkidle");
// The app should render without JavaScript errors
const errors = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.waitForTimeout(2000);
expect(errors).toHaveLength(0);
});
});
34 changes: 34 additions & 0 deletions e2e/tests/case-management.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @ts-check
const { test, expect } = require("@playwright/test");

test.describe("Case Management", () => {
test("GET /getCases returns valid JSON", async ({ request }) => {
const response = await request.get("/getCases");
expect(response.status()).toBe(200);
const data = await response.json();
expect(Array.isArray(data) || typeof data === "object").toBeTruthy();
});

test("GET /getSession returns session info", async ({ request }) => {
const response = await request.get("/getSession");
expect(response.status()).toBe(200);
});

test("POST /setSession sets session data", async ({ request }) => {
const response = await request.post("/setSession", {
data: { key: "testKey", value: "testValue" },
headers: { "Content-Type": "application/json" },
});
expect([200, 204]).toContain(response.status());
});

test("GET /getCases with nonexistent case returns appropriate status", async ({
request,
}) => {
const response = await request.get("/getParamFile", {
params: { case: "nonexistent_case_12345", file: "test.csv" },
});
// Should not crash — either 404 or empty result
expect([200, 404, 400]).toContain(response.status());
});
});
30 changes: 30 additions & 0 deletions e2e/tests/diagnostics.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @ts-check
const { test, expect } = require("@playwright/test");

test.describe("Diagnostics & Security", () => {
test("path traversal is blocked", async ({ request }) => {
const response = await request.get("/getParamFile", {
params: { case: "../../../etc/passwd", file: "test.csv" },
});
// Should be rejected, not serve the file
expect([400, 403, 404, 500]).toContain(response.status());
});

test("null bytes in parameters are rejected", async ({ request }) => {
const response = await request.get("/getParamFile", {
params: { case: "test\x00case", file: "data.csv" },
});
expect([400, 403, 404, 500]).toContain(response.status());
});

test("POST to GET-only routes returns 405", async ({ request }) => {
const response = await request.post("/getCases");
expect(response.status()).toBe(405);
});

test("CORS headers are present", async ({ request }) => {
const response = await request.get("/");
// Flask-CORS should add headers
expect(response.status()).toBe(200);
});
});
27 changes: 27 additions & 0 deletions e2e/tests/navigation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// @ts-check
const { test, expect } = require("@playwright/test");

test.describe("Navigation", () => {
test("root path serves the frontend", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("domcontentloaded");
// The page should have HTML content (not a JSON API response)
const contentType = await page.evaluate(() => document.contentType);
expect(contentType).toBe("text/html");
});

test("unknown API routes return 404", async ({ request }) => {
const response = await request.get("/api/nonexistent");
expect([404, 405]).toContain(response.status());
});

test("static assets are served", async ({ page }) => {
await page.goto("/");
await page.waitForLoadState("networkidle");
// Check that CSS loaded (bootstrap is used)
const hasStyles = await page.evaluate(() => {
return document.styleSheets.length > 0;
});
expect(hasStyles).toBeTruthy();
});
});
Loading