feat: add Playwright for end-to-end testing and create initial test suite

This commit is contained in:
2026-01-20 01:18:26 +00:00
parent 5b01e6bfee
commit 565bc1f2ba
4 changed files with 141 additions and 1 deletions

63
package-lock.json generated
View File

@@ -64,6 +64,7 @@
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@playwright/test": "^1.57.0",
"@types/node": "^25.0.9",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.0.4",
@@ -1921,6 +1922,22 @@
"react-dom": ">= 16.8"
}
},
"node_modules/@playwright/test": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.57.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@radix-ui/colors": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz",
@@ -6199,6 +6216,20 @@
}
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -8046,6 +8077,38 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/playwright": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.57.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",

View File

@@ -9,7 +9,8 @@
"start": "next start",
"lint": "eslint .",
"stylelint": "stylelint \"src/**/*.{scss,css}\"",
"kill": "fuser -k 5000/tcp"
"kill": "fuser -k 5000/tcp",
"test:e2e": "playwright test"
},
"dependencies": {
"@babel/standalone": "^7.28.6",
@@ -68,6 +69,7 @@
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@playwright/test": "^1.57.0",
"@types/node": "^25.0.9",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.0.4",

38
playwright.config.ts Normal file
View File

@@ -0,0 +1,38 @@
import { defineConfig, devices } from "@playwright/test"
export default defineConfig({
testDir: "./tests/e2e",
timeout: 60_000,
expect: {
timeout: 10_000,
},
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: "http://127.0.0.1:3002",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{
name: "chromium-desktop",
use: {
...devices["Desktop Chrome"],
viewport: { width: 1400, height: 900 },
},
},
{
name: "chromium-mobile",
use: {
...devices["Pixel 5"],
},
},
],
webServer: {
command: "PORT=3002 npm run dev -- --hostname 0.0.0.0",
port: 3002,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
})

37
tests/e2e/home.spec.ts Normal file
View File

@@ -0,0 +1,37 @@
import { expect, test } from "@playwright/test"
test.describe("home page", () => {
test("renders key sections without console errors", async ({ page }) => {
const consoleErrors: string[] = []
page.on("console", (message) => {
if (message.type() === "error") {
consoleErrors.push(message.text())
}
})
await page.goto("/")
await expect(page.getByRole("heading", { name: "My Snippets" })).toBeVisible()
await expect(
page.getByText("Save, organize, and share your code snippets", { exact: true })
).toBeVisible()
await expect(page.locator("header")).toBeVisible()
await expect(page.locator("main")).toBeVisible()
await expect(page.locator("footer")).toBeVisible()
expect(consoleErrors).toEqual([])
})
test("stays within viewport on mobile (no horizontal overflow)", async ({ page }, testInfo) => {
test.skip(!testInfo.project.name.includes("mobile"), "mobile-only check")
await page.goto("/")
const overflow = await page.evaluate(() =>
Math.max(document.documentElement.scrollWidth - window.innerWidth, 0)
)
expect(overflow).toBeLessThanOrEqual(1)
await expect(page.getByRole("heading", { name: "My Snippets" })).toBeVisible()
})
})