From 49a9c366e589315843fe1a18886227ac0ae490a7 Mon Sep 17 00:00:00 2001 From: suvajitcodewalnut Date: Mon, 10 Nov 2025 15:23:01 +0530 Subject: [PATCH 1/3] feat: add playwright config --- .gitignore | 6 ++++ bun.lock | 9 +++++ e2e/HomePage.spec.ts | 12 +++++++ package.json | 7 +++- playwright.config.ts | 78 ++++++++++++++++++++++++++++++++++++++++++++ vitest.config.ts | 5 +++ 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 e2e/HomePage.spec.ts create mode 100644 playwright.config.ts diff --git a/.gitignore b/.gitignore index a547bf3..0447179 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ dist-ssr *.njsproj *.sln *.sw? + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/bun.lock b/bun.lock index ff8906e..d411126 100644 --- a/bun.lock +++ b/bun.lock @@ -21,6 +21,7 @@ }, "devDependencies": { "@biomejs/biome": "2.3.2", + "@playwright/test": "^1.56.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", @@ -183,6 +184,8 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@playwright/test": ["@playwright/test@1.56.1", "", { "dependencies": { "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" } }, "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.43", "", {}, "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="], @@ -495,6 +498,10 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "playwright": ["playwright@1.56.1", "", { "dependencies": { "playwright-core": "1.56.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw=="], + + "playwright-core": ["playwright-core@1.56.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], @@ -649,6 +656,8 @@ "make-dir/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "@vitest/coverage-v8/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@4.0.7", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-YY//yxqTmk29+/pK+Wi1UB4DUH3lSVgIm+M10rAJ74pOSMgT7rydMSc+vFuq9LjZLhFvVEXir8EcqMke3SVM6Q=="], diff --git a/e2e/HomePage.spec.ts b/e2e/HomePage.spec.ts new file mode 100644 index 0000000..a6454a4 --- /dev/null +++ b/e2e/HomePage.spec.ts @@ -0,0 +1,12 @@ +// Modules +import { expect, test } from "@playwright/test"; + +test("Homepage", async ({ page }) => { + await page.goto("https://labquick-beta.vercel.app/"); + // Verifying the page url + await expect(page).toHaveURL("https://labquick-beta.vercel.app/"); + // Verifying the page title of the application + await expect(page).toHaveTitle("QUICKLAB"); + + await page.close(); +}); diff --git a/package.json b/package.json index 8903857..588576b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "build": "tsc -b && vite build", "preview": "vite preview", "prepare": "husky", - "application:check": "concurrently \"bun run application:test\" \"bun run application:format\"" + "application:check": "concurrently \"bun run application:test\" \"bun run application:format\"", + "e2e": "playwright test", + "e2e:ui": "playwright test --ui", + "e2e:headed": "playwright test --headed", + "e2e:report": "playwright show-report" }, "dependencies": { "@hookform/resolvers": "^5.2.2", @@ -30,6 +34,7 @@ }, "devDependencies": { "@biomejs/biome": "2.3.2", + "@playwright/test": "^1.56.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..a597498 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,78 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./e2e", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:5173", + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "bun run application:dev", + url: "http://localhost:5173", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts index e414142..6df4d1a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,5 +5,10 @@ export default defineConfig({ globals: true, environment: "jsdom", setupFiles: "./tests/setup.ts", + exclude: [ + "**/node_modules/**", + "**/dist/**", + "**/e2e/**", + ], }, }); From 3da39611a7fae137d1c5b74c704350e69e16dfac Mon Sep 17 00:00:00 2001 From: suvajitcodewalnut Date: Mon, 10 Nov 2025 15:23:32 +0530 Subject: [PATCH 2/3] chore: update vitest config --- vitest.config.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vitest.config.ts b/vitest.config.ts index 6df4d1a..4d2268b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,10 +5,6 @@ export default defineConfig({ globals: true, environment: "jsdom", setupFiles: "./tests/setup.ts", - exclude: [ - "**/node_modules/**", - "**/dist/**", - "**/e2e/**", - ], + exclude: ["**/node_modules/**", "**/dist/**", "**/e2e/**"], }, }); From 4f9482c56b3ca3e373ce5d7fbd078b738853c576 Mon Sep 17 00:00:00 2001 From: suvajitcodewalnut Date: Tue, 11 Nov 2025 10:35:27 +0530 Subject: [PATCH 3/3] test: add AddTicket test automation --- e2e/AddTicket.spec.ts | 157 +++++++++++++++++++++++++++++ playwright.config.ts | 41 -------- src/components/Modal/Form/Form.tsx | 3 +- 3 files changed, 159 insertions(+), 42 deletions(-) create mode 100644 e2e/AddTicket.spec.ts diff --git a/e2e/AddTicket.spec.ts b/e2e/AddTicket.spec.ts new file mode 100644 index 0000000..f7da330 --- /dev/null +++ b/e2e/AddTicket.spec.ts @@ -0,0 +1,157 @@ +// Modules +import { expect, test } from "@playwright/test"; + +test.describe("Add Ticket Functionality", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/"); + await page.waitForLoadState("networkidle"); + }); + + test("should open add ticket modal when clicking Add ticket button", async ({ + page, + }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + + await expect(page.getByText("CREATE NEW TICKET")).toBeVisible(); + + await expect(page.locator("#title")).toBeVisible(); + await expect(page.locator("#description")).toBeVisible(); + await expect(page.locator("#assignee")).toBeVisible(); + await expect(page.locator("#priority")).toBeVisible(); + }); + + test("should successfully add a new ticket with valid data", async ({ + page, + }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + + await page.locator("#title").fill("Bug Fix"); + await page + .locator("#description") + .fill("Fix the login authentication issue"); + await page.locator("#assignee").fill("developer@example.com"); + await page.locator("#priority").fill("HIGH"); + + await page.getByRole("button", { name: /submit/i }).click(); + + await expect(page.getByText("TICKET CREATED SUCCESSFULLY!")).toBeVisible(); + + await expect(page.getByText("Bug Fix")).toBeVisible(); + await expect(page.getByText("developer@example.com")).toBeVisible(); + }); + + test("should show validation error for title less than 3 characters", async ({ + page, + }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + + await page.locator("#title").fill("AB"); + await page + .locator("#description") + .fill("This is a valid description with more than ten characters"); + await page.locator("#assignee").fill("test@example.com"); + await page.locator("#priority").fill("MEDIUM"); + + await page.getByRole("button", { name: /submit/i }).click(); + + await expect( + page.getByText(/Title must be at least 3 characters long!/i), + ).toBeVisible(); + }); + + test("should show validation error for description less than 10 characters", async ({ + page, + }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + + await page.locator("#title").fill("Valid Title"); + await page.locator("#description").fill("Short"); + await page.locator("#assignee").fill("test@example.com"); + await page.locator("#priority").fill("LOW"); + + await page.getByRole("button", { name: /submit/i }).click(); + + await expect( + page.getByText(/Description must be at least 10 characters!/i), + ).toBeVisible(); + }); + + test("should show validation error for invalid email format", async ({ + page, + }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + + await page.locator("#title").fill("Valid Title"); + await page.locator("#description").fill("This is a valid description"); + await page.locator("#assignee").fill("invalid-email"); + await page.locator("#priority").fill("HIGH"); + + await page.getByRole("button", { name: /submit/i }).click(); + + await expect( + page.getByText(/Please provide a valid email address!/i), + ).toBeVisible(); + }); + + test("should show validation error for invalid priority", async ({ + page, + }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + + await page.locator("#title").fill("Valid Title"); + await page.locator("#description").fill("This is a valid description"); + await page.locator("#assignee").fill("test@example.com"); + await page.locator("#priority").fill("URGENT"); + + await page.getByRole("button", { name: /submit/i }).click(); + + await expect( + page.getByText(/Priority must be either HIGH, MEDIUM or LOW/i), + ).toBeVisible(); + }); + + test("should close modal when clicking close button", async ({ page }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + + await expect(page.getByText("CREATE NEW TICKET")).toBeVisible(); + await page.locator("#button-form-close").first().click(); + + await expect(page.getByText("CREATE NEW TICKET")).not.toBeVisible(); + }); + + test("should add multiple tickets successfully", async ({ page }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + await page.locator("#title").fill("First Ticket"); + await page.locator("#description").fill("Description for the first ticket"); + await page.locator("#assignee").fill("user1@example.com"); + await page.locator("#priority").fill("HIGH"); + await page.getByRole("button", { name: /submit/i }).click(); + + await expect(page.getByText("TICKET CREATED SUCCESSFULLY!")).toBeVisible(); + + await page.waitForTimeout(500); + + await page.getByRole("button", { name: /add ticket/i }).click(); + await page.locator("#title").fill("Second Task"); + await page + .locator("#description") + .fill("Description for the second ticket"); + await page.locator("#assignee").fill("user2@example.com"); + await page.locator("#priority").fill("MEDIUM"); + await page.getByRole("button", { name: /submit/i }).click(); + }); + + test("should accept different priority values (case-insensitive)", async ({ + page, + }) => { + await page.getByRole("button", { name: /add ticket/i }).click(); + await page.locator("#title").fill("Low Case"); + await page.locator("#description").fill("Testing lowercase priority"); + await page.locator("#assignee").fill("test@example.com"); + await page.locator("#priority").fill("low"); + await page.getByRole("button", { name: /submit/i }).click(); + + await expect(page.getByText("TICKET CREATED SUCCESSFULLY!")).toBeVisible(); + await expect(page.getByText("Low Case")).toBeVisible(); + }); +}); diff --git a/playwright.config.ts b/playwright.config.ts index a597498..e1f80fa 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,37 +1,17 @@ import { defineConfig, devices } from "@playwright/test"; -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// import dotenv from 'dotenv'; -// import path from 'path'; -// dotenv.config({ path: path.resolve(__dirname, '.env') }); - -/** - * See https://playwright.dev/docs/test-configuration. - */ export default defineConfig({ testDir: "./e2e", - /* Run tests in files in parallel */ fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, - /* Retry on CI only */ retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { - /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: "http://localhost:5173", - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", }, - /* Configure projects for major browsers */ projects: [ { name: "chromium", @@ -47,29 +27,8 @@ export default defineConfig({ name: "webkit", use: { ...devices["Desktop Safari"] }, }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, ], - /* Run your local dev server before starting the tests */ webServer: { command: "bun run application:dev", url: "http://localhost:5173", diff --git a/src/components/Modal/Form/Form.tsx b/src/components/Modal/Form/Form.tsx index 9993235..fc8f9ad 100644 --- a/src/components/Modal/Form/Form.tsx +++ b/src/components/Modal/Form/Form.tsx @@ -51,6 +51,7 @@ const Form = ({ onFormModalClose }: FormPropTypes): JSX.Element => {