From 565bc1f2bae5f45aa53b96543a9fcd8ddd43ba39 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Tue, 20 Jan 2026 01:18:26 +0000 Subject: [PATCH] feat: add Playwright for end-to-end testing and create initial test suite --- package-lock.json | 63 ++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++- playwright.config.ts | 38 +++++++++++++++++++++++++ tests/e2e/home.spec.ts | 37 +++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 playwright.config.ts create mode 100644 tests/e2e/home.spec.ts diff --git a/package-lock.json b/package-lock.json index b9fda32..8ad3d21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index cf96018..c277f16 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..27452ad --- /dev/null +++ b/playwright.config.ts @@ -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, + }, +}) diff --git a/tests/e2e/home.spec.ts b/tests/e2e/home.spec.ts new file mode 100644 index 0000000..d2947d9 --- /dev/null +++ b/tests/e2e/home.spec.ts @@ -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() + }) +})