Skip to content

test: add Playwright E2E frontend test suite for 7 core user flows#456

Open
bh462007 wants to merge 3 commits into
Kuldeeep18:mainfrom
bh462007:test/playwright-e2e-coverage
Open

test: add Playwright E2E frontend test suite for 7 core user flows#456
bh462007 wants to merge 3 commits into
Kuldeeep18:mainfrom
bh462007:test/playwright-e2e-coverage

Conversation

@bh462007

@bh462007 bh462007 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Related Issue

Closes #293


Summary

This PR adds a complete Playwright end-to-end (E2E) test suite for the LeadOrbit frontend.

The test suite covers the seven required core user flows using mocked API responses, so the tests can run without a backend. It also adds GitHub Actions integration to run the tests automatically and updates the README with instructions for running them locally.

No production code was modified.


Type of Change

  • Testing
  • Documentation

Files Added

File Purpose
frontend/package.json Project configuration and test scripts
frontend/playwright.config.js Playwright configuration
frontend/static-server.js Lightweight static server for serving the frontend during tests
frontend/run-tests.js Cross-platform test runner that starts and stops the server automatically
frontend/.gitignore Ignores generated test artifacts
frontend/tests/fixtures.js Shared fixtures and API mocking helpers
frontend/tests/login.spec.js Login flow tests
frontend/tests/register.spec.js Registration flow tests
frontend/tests/dashboard.spec.js Dashboard tests
frontend/tests/campaigns.spec.js Campaigns page tests
frontend/tests/leads.spec.js Leads page tests
frontend/tests/settings.spec.js Settings page tests
.github/workflows/e2e.yml GitHub Actions workflow for running E2E tests
README.md Added instructions for running the test suite locally

Test Coverage

Flow Tests Status
Login 2 Passed
Registration 3 Passed
Dashboard 3 Passed
Campaigns 4 Passed
Leads 5 Passed
Settings 5 Passed
Total 22 Passed

Acceptance Criteria

  • Covers all 7 required user flows
  • Tests can be run locally with npm run test:e2e
  • Integrated into GitHub Actions
  • Tests run consistently without flakiness
  • README includes setup instructions
  • No backend required; all API responses are mocked

Running the Tests

cd frontend
npm install
npx playwright install --with-deps chromium
npm run test:e2e

Test Results

Running 22 tests using 1 worker

✓ Login
✓ Registration
✓ Dashboard
✓ Campaigns
✓ Leads
✓ Settings

22 passed (1.1m)

Notes

  • All API requests are intercepted using Playwright's page.route() and return deterministic mock responses. The tests never contact a real backend or any external service.
  • A custom run-tests.js launcher is used instead of Playwright's built-in webServer option to avoid process cleanup issues on Windows.
  • Tests rely on stable selectors such as id attributes and accessible roles rather than fragile CSS selectors, making them less prone to breaking after UI changes.
  • Each test starts with a clean state by clearing localStorage, ensuring tests remain isolated from one another.

Summary by CodeRabbit

  • New Features

    • Added automated frontend end-to-end coverage for login, registration, dashboard, campaigns, leads, and settings flows.
    • Added CI runs for these browser tests on pushes and pull requests targeting the main branch.
  • Documentation

    • Expanded the testing guide with setup, run commands, mocked API behavior, and coverage details for the new browser test suite.
  • Bug Fixes

    • Improved handling of test artifacts and local test files so generated reports and caches are not tracked.

Copilot AI review requested due to automatic review settings June 26, 2026 06:56
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Playwright-based frontend E2E test setup with CI, local server tooling, shared fixtures, and page coverage for login, registration, dashboard, campaigns, leads, and settings. The README now documents how to run the suite locally and in CI.

Changes

Frontend Playwright E2E Suite

Layer / File(s) Summary
Workflow and project setup
.github/workflows/e2e.yml, frontend/package.json, frontend/playwright.config.js, frontend/.gitignore, README.md
Adds the Playwright workflow, npm scripts/config, ignore rules, and README usage notes for running the frontend E2E suite.
Static server and launcher
frontend/static-server.js, frontend/run-tests.js
Adds the local static server and wrapper script that waits for readiness, runs Playwright, and shuts down on completion or error.
Shared test fixtures
frontend/tests/fixtures.js
Adds auth token helpers and Playwright fixtures for API mocking and authenticated pages.
Login and registration flows
frontend/tests/login.spec.js, frontend/tests/register.spec.js
Adds login and registration E2E coverage for success paths, failure paths, redirects, dialogs, and form fields.
Dashboard and settings
frontend/tests/dashboard.spec.js, frontend/tests/settings.spec.js
Adds dashboard and settings coverage for KPI rendering, activity, redirects, profile fields, and theme/personalization controls.
Campaigns and leads
frontend/tests/campaigns.spec.js, frontend/tests/leads.spec.js
Adds campaigns and leads coverage for table rendering, search, counts, filters, empty states, and import modal visibility.

Sequence Diagram(s)

sequenceDiagram
  participant RunTests as run-tests.js
  participant StaticServer as static-server.js
  participant PlaywrightCLI as Playwright CLI
  RunTests->>StaticServer: start on 127.0.0.1:8081
  RunTests->>StaticServer: poll until ready
  RunTests->>PlaywrightCLI: launch frontend E2E tests
  PlaywrightCLI->>StaticServer: load pages and assets
  PlaywrightCLI-->>RunTests: exit code on completion
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through login, quick and spry,
Then dashboard stars lit up the sky.
With Playwright ears and carrot cheer,
I watched the tests all run right here.
No bugs could sneak past my wee nose—
The E2E garden now just glows!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Most setup and six core flows are covered, but campaign builder coverage and the seven-flow requirement from [#293] are not met in the provided changes. Add a Playwright test for the campaign builder flow and ensure the suite covers at least seven core flows, including the campaigns status filter if required.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a Playwright frontend E2E test suite.
Out of Scope Changes check ✅ Passed The changes stay focused on Playwright frontend E2E setup, tests, CI, and documentation.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a Playwright-based E2E test suite for the LeadOrbit frontend, intended to run fully offline via mocked API responses and to be enforced in CI on PRs to main.

Changes:

  • Added Playwright test specs + shared fixtures for key frontend flows.
  • Added a Node-based static server + runner script to start/stop the server around Playwright runs.
  • Added CI workflow and README instructions for running E2E tests locally.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
README.md Documents the new Playwright E2E suite and how to run it locally/CI.
frontend/package.json Adds E2E scripts and Playwright devDependencies.
frontend/package-lock.json Locks Playwright (and other) dependencies for CI reproducibility.
frontend/playwright.config.js Configures Playwright (baseURL, artifacts, retries, single worker).
frontend/static-server.js Adds a lightweight static file server used during E2E runs.
frontend/run-tests.js Adds a cross-platform test runner to start the server and invoke Playwright.
frontend/.gitignore Ignores Playwright artifacts and node_modules under frontend/.
frontend/tests/fixtures.js Adds shared Playwright fixtures/helpers for API mocking and auth seeding.
frontend/tests/login.spec.js Login flow coverage using mocked auth + dashboard API.
frontend/tests/register.spec.js Registration flow coverage with success/failure scenarios.
frontend/tests/dashboard.spec.js Dashboard rendering + unauth redirect behavior coverage.
frontend/tests/campaigns.spec.js Campaigns list page coverage (grid, search, stats, empty state).
frontend/tests/leads.spec.js Leads page coverage (table, KPIs, search, filters, CSV import modal).
frontend/tests/settings.spec.js Settings page coverage (profile data, buttons, toggles).
.github/workflows/e2e.yml Runs the frontend E2E suite on pushes/PRs targeting main.
Files not reviewed (1)
  • frontend/package-lock.json: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/static-server.js
Comment on lines +25 to +47
let urlPath = decodeURIComponent(req.url.split('?')[0]);
if (urlPath === '/') urlPath = '/index.html';

const filePath = path.join(ROOT, urlPath);

// Prevent path traversal outside the served root
if (!filePath.startsWith(ROOT)) {
res.writeHead(403);
res.end('Forbidden');
return;
}

fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
return;
}
const ext = path.extname(filePath);
res.writeHead(200, {
'Content-Type': MIME_TYPES[ext] || 'application/octet-stream',
'Cache-Control': 'no-store',
});
Comment thread frontend/package.json
"serve": "node static-server.js",
"test:e2e": "node run-tests.js",
"test:e2e:headed": "node run-tests.js --headed",
"test:e2e:debug": "node_modules/.bin/playwright test --debug",
Comment thread frontend/package.json
Comment on lines +15 to +18
"devDependencies": {
"@playwright/test": "^1.61.0",
"http-server": "^14.1.1"
}
Comment on lines +31 to +33
test.describe('Campaigns list', () => {
test.beforeEach(async ({ page }) => {
await seedAuthTokens(page);
Comment thread README.md
- Tests live in frontend/tests/
- API responses are mocked — no real backend needed
- A lightweight static file server serves the frontend on port 8081
- Tests cover 7 core user flows: Login, Registration, Dashboard, Campaigns, Leads, Settings
Comment thread README.md
Comment on lines +505 to +527
### Frontend E2E Tests (Playwright)

**Prerequisites:** Node.js 18+

**Install dependencies:**
```bash
cd frontend
npm install
npx playwright install --with-deps chromium
```

**Run all E2E tests (single command):**
```bash
cd frontend
npm run test:e2e
```

**Other commands:**
```bash
npm run test:e2e:headed # Run with visible browser window
npm run test:e2e:debug # Run in debug mode (step through tests)
npm run test:e2e:report # View the last HTML test report
```
Comment thread README.md
Comment on lines +529 to +535
**How it works:**
- Tests live in `frontend/tests/`
- API responses are mocked via Playwright's `page.route()` — no real backend needed
- A lightweight static file server (`static-server.js`) serves the frontend on port 8081
- Tests cover 7 core user flows: Login, Registration, Dashboard, Campaigns, Leads, Settings, and Campaign Builder

**CI:** E2E tests run automatically on every PR targeting `main` via GitHub Actions (`.github/workflows/e2e.yml`).
Comment thread frontend/run-tests.js
Comment on lines +11 to +12
http.get('http://127.0.0.1:8081/', (res) => {
const args = ['test', ...process.argv.slice(2)];

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/e2e.yml:
- Around line 15-23: The workflow is using floating action tags and keeping
checkout credentials enabled unnecessarily. Update the GitHub Actions references
in the checkout/setup steps and the later job steps under the same workflow to
fixed version pins instead of mutable tags, and disable persisted credentials on
the repository checkout since this job does not push changes back. Use the
existing “Checkout repository” and “Set up Node.js” steps, plus the other
affected actions in the later section, as the places to harden.

In `@frontend/package.json`:
- Around line 7-10: The `test:e2e:debug` script bypasses the shared
`run-tests.js` harness, so it does not start `frontend/static-server.js` like
the other E2E commands. Update the `test:e2e:debug` entry in `package.json` to
route through `run-tests.js` with the debug flag, matching `test:e2e` and
`test:e2e:headed`, so the standalone debug flow uses the same server startup
path.

In `@frontend/run-tests.js`:
- Around line 5-36: The readiness check in waitForServer can falsely pass if
static-server.js exits early and some other process answers on 8081. Add
explicit monitoring for the server child’s exit/close event and fail immediately
if it dies before readiness, then only proceed to spawn Playwright when the
expected server process is still alive and the HTTP probe succeeds. Use the
existing server spawn and waitForServer flow to wire this in, and keep the
server shutdown/exit handling centralized so the test runner exits with a clear
error when the static server cannot start.

In `@frontend/static-server.js`:
- Around line 25-33: The path traversal guard in static-server.js is vulnerable
because filePath.startsWith(ROOT) can be bypassed by sibling paths that share a
prefix. In the request handling flow around urlPath, filePath, and the root
containment check, resolve the requested path to a canonical absolute path and
verify it is truly inside ROOT using a safe directory-boundary check before
serving any file. Keep the existing logic in the static file handler, but
replace the string-prefix containment test with a normalized path comparison
that rejects escapes outside the served root.

In `@frontend/tests/campaigns.spec.js`:
- Around line 69-76: The empty-state test in campaigns.spec.js only verifies
zeroed stats and can pass without actually rendering the empty-state branch.
Update the test around the campaigns page assertions to also check the
empty-state UI copy/CTA that is shown when the campaigns list is empty, and
assert that no campaign cards/items are rendered. Use the existing test case for
empty campaigns and the page locators tied to the campaigns grid or empty-state
container so the test proves the request/render path executed.

In `@frontend/tests/dashboard.spec.js`:
- Around line 24-25: The auth setup in dashboard.spec.js is mixing token seeding
and localStorage clearing in the same describe, which makes the unauthenticated
redirect test order-dependent and flaky. Update the test structure around
test.beforeEach and seedAuthTokens(page) so the unauthenticated case runs in a
separate describe or fixture that does not seed auth, or make auth seeding
opt-in per test instead of global. Keep the redirect test’s page.addInitScript
localStorage.clear behavior isolated from the seeded-auth tests.

In `@frontend/tests/fixtures.js`:
- Around line 16-19: The clearAuthTokens helper is over-clearing localStorage
instead of matching the app’s token cleanup behavior. Update clearAuthTokens in
fixtures.js to remove only the auth-related keys used by the token helpers in
api.js, namely access_token and refresh_token, rather than calling
localStorage.clear(). Keep the helper’s name and usage the same, but make its
behavior mirror the real sign-out/token removal flow so unrelated state stays
intact.

In `@README.md`:
- Around line 217-245: The README has duplicated Playwright E2E setup/docs in
the main E2E section and the later frontend-testing section, and they have
already drifted on the covered flows. Consolidate the Playwright documentation
into a single source of truth by removing one copy and updating the remaining
section’s coverage list in the E2E test instructions so it matches the actual
flows referenced by the frontend E2E docs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 500fff12-5753-4fe5-a8a0-fda4448aed75

📥 Commits

Reviewing files that changed from the base of the PR and between 4a33158 and b6af498.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .github/workflows/e2e.yml
  • README.md
  • frontend/.gitignore
  • frontend/package.json
  • frontend/playwright.config.js
  • frontend/run-tests.js
  • frontend/static-server.js
  • frontend/tests/campaigns.spec.js
  • frontend/tests/dashboard.spec.js
  • frontend/tests/fixtures.js
  • frontend/tests/leads.spec.js
  • frontend/tests/login.spec.js
  • frontend/tests/register.spec.js
  • frontend/tests/settings.spec.js

Comment thread .github/workflows/e2e.yml
Comment on lines +15 to +23
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pin the GitHub Actions and stop persisting checkout credentials.

Lines 16, 19, and 39 use floating tags, so upstream action changes can alter this workflow without a PR here. Also, this job never pushes back to the repo, so leaving the checkout token configured is unnecessary exposure.

Suggested hardening
-      - name: Checkout repository
-        uses: actions/checkout@v4
+      - name: Checkout repository
+        uses: actions/checkout@<full-commit-sha>
+        with:
+          persist-credentials: false

-      - name: Set up Node.js
-        uses: actions/setup-node@v4
+      - name: Set up Node.js
+        uses: actions/setup-node@<full-commit-sha>

-      - name: Upload Playwright report
+      - name: Upload Playwright report
         if: failure()
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@<full-commit-sha>

Also applies to: 37-43

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 15-16: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 19-19: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e.yml around lines 15 - 23, The workflow is using
floating action tags and keeping checkout credentials enabled unnecessarily.
Update the GitHub Actions references in the checkout/setup steps and the later
job steps under the same workflow to fixed version pins instead of mutable tags,
and disable persisted credentials on the repository checkout since this job does
not push changes back. Use the existing “Checkout repository” and “Set up
Node.js” steps, plus the other affected actions in the later section, as the
places to harden.

Source: Linters/SAST tools

Comment thread frontend/package.json
Comment on lines +7 to +10
"test:e2e": "node run-tests.js",
"test:e2e:headed": "node run-tests.js --headed",
"test:e2e:debug": "node_modules/.bin/playwright test --debug",
"test:e2e:report": "node_modules/.bin/playwright show-report"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Route the debug command through the same harness.

test:e2e:debug is the only documented E2E command that bypasses run-tests.js, so it never starts frontend/static-server.js. Because frontend/playwright.config.js also has no webServer, npm run test:e2e:debug needs a separate manual server even though the README presents it as a standalone command.

Suggested fix
-    "test:e2e:debug": "node_modules/.bin/playwright test --debug",
+    "test:e2e:debug": "node run-tests.js --debug",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"test:e2e": "node run-tests.js",
"test:e2e:headed": "node run-tests.js --headed",
"test:e2e:debug": "node_modules/.bin/playwright test --debug",
"test:e2e:report": "node_modules/.bin/playwright show-report"
"test:e2e": "node run-tests.js",
"test:e2e:headed": "node run-tests.js --headed",
"test:e2e:debug": "node run-tests.js --debug",
"test:e2e:report": "node_modules/.bin/playwright show-report"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/package.json` around lines 7 - 10, The `test:e2e:debug` script
bypasses the shared `run-tests.js` harness, so it does not start
`frontend/static-server.js` like the other E2E commands. Update the
`test:e2e:debug` entry in `package.json` to route through `run-tests.js` with
the debug flag, matching `test:e2e` and `test:e2e:headed`, so the standalone
debug flow uses the same server startup path.

Comment thread frontend/run-tests.js
Comment on lines +5 to +36
const server = spawn(process.execPath, ['static-server.js'], { stdio: 'pipe' });

// Use the actual Playwright CLI entry point, not the shell wrapper
const playwrightCli = path.join(__dirname, 'node_modules', '@playwright', 'test', 'cli.js');

function waitForServer(retries = 20) {
http.get('http://127.0.0.1:8081/', (res) => {
const args = ['test', ...process.argv.slice(2)];
const playwright = spawn(process.execPath, [playwrightCli, ...args], {
stdio: 'inherit',
});
playwright.on('exit', (code) => {
server.kill();
process.exit(code ?? 0);
});
}).on('error', () => {
if (retries > 0) {
setTimeout(() => waitForServer(retries - 1), 300);
} else {
console.error('Server did not start in time');
server.kill();
process.exit(1);
}
});
}

server.on('error', (err) => {
console.error('Failed to start server:', err);
process.exit(1);
});

setTimeout(() => waitForServer(), 500);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fail fast when the static server exits before readiness.

This script never watches the child exit event, and the readiness probe accepts any HTTP response. If static-server.js dies immediately (for example, EADDRINUSE on 8081), the probe can still succeed against some other local process and run the suite against the wrong server.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: require('child_process')
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/run-tests.js` around lines 5 - 36, The readiness check in
waitForServer can falsely pass if static-server.js exits early and some other
process answers on 8081. Add explicit monitoring for the server child’s
exit/close event and fail immediately if it dies before readiness, then only
proceed to spawn Playwright when the expected server process is still alive and
the HTTP probe succeeds. Use the existing server spawn and waitForServer flow to
wire this in, and keep the server shutdown/exit handling centralized so the test
runner exits with a clear error when the static server cannot start.

Comment thread frontend/static-server.js
Comment on lines +25 to +33
let urlPath = decodeURIComponent(req.url.split('?')[0]);
if (urlPath === '/') urlPath = '/index.html';

const filePath = path.join(ROOT, urlPath);

// Prevent path traversal outside the served root
if (!filePath.startsWith(ROOT)) {
res.writeHead(403);
res.end('Forbidden');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden the traversal check against sibling-path escapes.

path.join(ROOT, urlPath) plus filePath.startsWith(ROOT) is not a safe containment check. A request like /../frontend-backup/secret.txt resolves outside the served root but still shares the same string prefix, so it passes this guard.

Suggested fix
-  let urlPath = decodeURIComponent(req.url.split('?')[0]);
+  let urlPath = decodeURIComponent(req.url.split('?')[0]);
   if (urlPath === '/') urlPath = '/index.html';

-  const filePath = path.join(ROOT, urlPath);
+  const filePath = path.resolve(ROOT, `.${urlPath}`);

   // Prevent path traversal outside the served root
-  if (!filePath.startsWith(ROOT)) {
+  if (filePath !== ROOT && !filePath.startsWith(`${ROOT}${path.sep}`)) {
     res.writeHead(403);
     res.end('Forbidden');
     return;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let urlPath = decodeURIComponent(req.url.split('?')[0]);
if (urlPath === '/') urlPath = '/index.html';
const filePath = path.join(ROOT, urlPath);
// Prevent path traversal outside the served root
if (!filePath.startsWith(ROOT)) {
res.writeHead(403);
res.end('Forbidden');
let urlPath = decodeURIComponent(req.url.split('?')[0]);
if (urlPath === '/') urlPath = '/index.html';
const filePath = path.resolve(ROOT, `.${urlPath}`);
// Prevent path traversal outside the served root
if (filePath !== ROOT && !filePath.startsWith(`${ROOT}${path.sep}`)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/static-server.js` around lines 25 - 33, The path traversal guard in
static-server.js is vulnerable because filePath.startsWith(ROOT) can be bypassed
by sibling paths that share a prefix. In the request handling flow around
urlPath, filePath, and the root containment check, resolve the requested path to
a canonical absolute path and verify it is truly inside ROOT using a safe
directory-boundary check before serving any file. Keep the existing logic in the
static file handler, but replace the string-prefix containment test with a
normalized path comparison that rejects escapes outside the served root.

Source: Linters/SAST tools

Comment on lines +69 to +76
test('empty state shows when no campaigns exist', async ({ page, mockApi }) => {
await mockApi('**/api/v1/campaigns/**', []);

await page.goto('/campaigns.html');

await expect(page.locator('#campaigns-grid')).toBeVisible({ timeout: 10000 });
await expect(page.locator('#stat-total')).toHaveText('0');
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the empty-state test prove the empty-state branch rendered.

Right now this can pass on default zeroed UI without exercising the request/render path. The test name says "empty state shows", but it never checks the empty-state copy/CTA or that no campaign cards are rendered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/tests/campaigns.spec.js` around lines 69 - 76, The empty-state test
in campaigns.spec.js only verifies zeroed stats and can pass without actually
rendering the empty-state branch. Update the test around the campaigns page
assertions to also check the empty-state UI copy/CTA that is shown when the
campaigns list is empty, and assert that no campaign cards/items are rendered.
Use the existing test case for empty campaigns and the page locators tied to the
campaigns grid or empty-state container so the test proves the request/render
path executed.

Comment on lines +24 to +25
test.beforeEach(async ({ page }) => {
await seedAuthTokens(page);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Playwright 1.61.0 guarantee the execution order of multiple page.addInitScript() calls on the same page? Please cite the official Playwright docs.

💡 Result:

No, Playwright does not guarantee the execution order of multiple page.addInitScript calls. According to the official Playwright documentation, the order of evaluation for multiple scripts installed via page.addInitScript (as well as browserContext.addInitScript) is explicitly defined as not defined [1][2][3].

Citations:


🏁 Script executed:

sed -n '1,120p' frontend/tests/dashboard.spec.js && printf '\n---\n' && sed -n '1,120p' frontend/tests/fixtures.js

Repository: Kuldeeep18/LeadOrbit

Length of output: 3527


Avoid mixing auth seeding and auth clearing via page.addInitScript() in the same describe.
seedAuthTokens(page) in test.beforeEach and the unauthenticated test's page.addInitScript(() => window.localStorage.clear()) rely on an undefined init-script order, so the clear can run before the tokens are seeded and make this redirect test flaky. Move the unauthenticated case to a separate describe/fixture that skips auth seeding, or make seeding opt-in per test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/tests/dashboard.spec.js` around lines 24 - 25, The auth setup in
dashboard.spec.js is mixing token seeding and localStorage clearing in the same
describe, which makes the unauthenticated redirect test order-dependent and
flaky. Update the test structure around test.beforeEach and seedAuthTokens(page)
so the unauthenticated case runs in a separate describe or fixture that does not
seed auth, or make auth seeding opt-in per test instead of global. Keep the
redirect test’s page.addInitScript localStorage.clear behavior isolated from the
seeded-auth tests.

Comment on lines +16 to +19
async function clearAuthTokens(page) {
await page.addInitScript(() => {
window.localStorage.clear();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

clearAuthTokens should only remove the auth keys.

The app-side token helpers in frontend/api.js:8-18 delete only access_token and refresh_token, but this helper clears all local storage. That can erase unrelated state like api_base_url or UI preferences and make test behavior diverge from the real auth flow.

Suggested fix
 async function clearAuthTokens(page) {
   await page.addInitScript(() => {
-    window.localStorage.clear();
+    window.localStorage.removeItem('access_token');
+    window.localStorage.removeItem('refresh_token');
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function clearAuthTokens(page) {
await page.addInitScript(() => {
window.localStorage.clear();
});
async function clearAuthTokens(page) {
await page.addInitScript(() => {
window.localStorage.removeItem('access_token');
window.localStorage.removeItem('refresh_token');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/tests/fixtures.js` around lines 16 - 19, The clearAuthTokens helper
is over-clearing localStorage instead of matching the app’s token cleanup
behavior. Update clearAuthTokens in fixtures.js to remove only the auth-related
keys used by the token helpers in api.js, namely access_token and refresh_token,
rather than calling localStorage.clear(). Keep the helper’s name and usage the
same, but make its behavior mirror the real sign-out/token removal flow so
unrelated state stays intact.

Comment thread README.md
Comment on lines +217 to +245
### Frontend E2E Tests (Playwright)

**Prerequisites:** Node.js 18+

**Install dependencies:**

cd frontend
npm install
npx playwright install --with-deps chromium

**Run all E2E tests (single command):**

cd frontend
npm run test:e2e

**Other commands:**

npm run test:e2e:headed # Run with visible browser window
npm run test:e2e:debug # Run in debug mode
npm run test:e2e:report # View the last HTML test report

**How it works:**
- Tests live in frontend/tests/
- API responses are mocked — no real backend needed
- A lightweight static file server serves the frontend on port 8081
- Tests cover 7 core user flows: Login, Registration, Dashboard, Campaigns, Leads, Settings

**CI:** E2E tests run automatically on every PR targeting main via GitHub Actions (.github/workflows/e2e.yml).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Consolidate the duplicated Playwright README section.

The README now has two separate E2E sections, and they already disagree: Line 242 says there are 7 flows but only lists 6, while Line 533 adds Campaign Builder explicitly. Keeping both copies will keep the setup instructions and coverage claims drifting.

Also applies to: 504-535

🧰 Tools
🪛 LanguageTool

[uncategorized] ~244-~244: The official name of this software platform is spelled with a capital “H”.
Context: ... PR targeting main via GitHub Actions (.github/workflows/e2e.yml). ### Backend Tests ...

(GITHUB)

🪛 markdownlint-cli2 (0.22.1)

[warning] 223-223: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


[warning] 229-229: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)


[warning] 234-234: Code block style
Expected: fenced; Actual: indented

(MD046, code-block-style)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 217 - 245, The README has duplicated Playwright E2E
setup/docs in the main E2E section and the later frontend-testing section, and
they have already drifted on the covered flows. Consolidate the Playwright
documentation into a single source of truth by removing one copy and updating
the remaining section’s coverage list in the E2E test instructions so it matches
the actual flows referenced by the frontend E2E docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LO-064 [Advanced - Testing]: Add Frontend E2E Tests with Playwright

2 participants