Skip to content
Merged
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
28 changes: 19 additions & 9 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import { defineConfig, devices } from "@playwright/test";

/**
* Playwright E2E configuration for Vanguard VDP.
* Tests run against the Next.js dev server on localhost:3000.
*
* By default runs against the local Next.js dev server (localhost:3000).
* Set BASE_URL to run against a remote environment, e.g.:
* BASE_URL=https://vanguard.laet4x.com npx playwright test
*
* @see https://playwright.dev/docs/test-configuration
*/
const BASE_URL = process.env.BASE_URL ?? "http://localhost:3000";
const isRemote = BASE_URL.startsWith("https://") || BASE_URL.startsWith("http://") && !BASE_URL.includes("localhost");

export default defineConfig({
testDir: "./tests/e2e",
fullyParallel: true,
Expand All @@ -15,7 +21,7 @@ export default defineConfig({
reporter: "html",

use: {
baseURL: "http://localhost:3000",
baseURL: BASE_URL,
trace: "on-first-retry",
screenshot: "only-on-failure",
},
Expand All @@ -27,11 +33,15 @@ export default defineConfig({
},
],

/* Start the Next.js dev server before running tests */
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
/* Only spin up the local dev server when not targeting a remote URL */
...(isRemote
? {}
: {
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
}),
});
61 changes: 61 additions & 0 deletions tests/e2e/deployment-smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect, test } from "@playwright/test";

const expectedHeaders = [
"x-frame-options",
"x-content-type-options",
"referrer-policy",
"permissions-policy",
"content-security-policy",
];

test.describe("Deployment smoke test", () => {
test("public pages respond without server errors", async ({ request }) => {
const paths = ["/", "/policy", "/hall-of-fame", "/submit", "/sign-in"];

for (const path of paths) {
const response = await request.get(path, { maxRedirects: 0 });

expect(
response.status(),
`${path} should not return a server error`,
).toBeLessThan(500);
}
});

test("homepage renders the core public shell", async ({ page }) => {
await page.goto("/");

const header = page.locator("header");

await expect(page).toHaveTitle(/Vanguard/i);
await expect(header).toBeVisible();
await expect(header.getByRole("link", { name: /Hall of Fame/i })).toBeVisible();
await expect(header.getByRole("link", { name: /Submit/i })).toBeVisible();
await expect(page.locator("footer")).toBeVisible();
});

test("security headers are present on page responses", async ({ request }) => {
const response = await request.get("/");

expect(response.ok()).toBeTruthy();

for (const header of expectedHeaders) {
expect(response.headers()[header], `${header} should be present`).toBeTruthy();
}

expect(response.headers()["x-frame-options"]).toBe("DENY");
expect(response.headers()["x-content-type-options"]).toBe("nosniff");
});

test("robots.txt disallows protected application routes", async ({ request }) => {
const response = await request.get("/robots.txt");
const body = await response.text();

expect(response.ok()).toBeTruthy();
expect(body).toContain("User-agent: *");

for (const path of ["/admin", "/triage", "/dashboard", "/api", "/sign-in", "/sign-up"]) {
expect(body).toContain(`Disallow: ${path}`);
}
});
});
Loading