Skip to content

chore: enhance CI pipeline with e2e tests and pre-commit hooks#1

Merged
DongDuong2001 merged 8 commits into
mainfrom
ci/enhance-pipeline
Apr 21, 2026
Merged

chore: enhance CI pipeline with e2e tests and pre-commit hooks#1
DongDuong2001 merged 8 commits into
mainfrom
ci/enhance-pipeline

Conversation

@DongDuong2001
Copy link
Copy Markdown
Collaborator

@DongDuong2001 DongDuong2001 commented Apr 21, 2026

Description

This PR introduces several stability and quality-of-life improvements to ensure the project remains maintainable as it grows, enforcing strict checks both locally and in CI.

Changes:

  • Pre-commit Hooks: Added husky and lint-staged to automatically format and lint staged files before they are committed.
    End-to-End Testing: Integrated Playwright and added a foundational E2E test mocking the core user journey (Login -> Builder -> ATS check).
  • Enhanced CI Pipeline: Updated ci.yml to include:
    drizzle-kit check to catch database schema drifts.
    Execution of the new Playwright E2E test suite.

Summary by CodeRabbit

  • New Features

    • Enabled end-to-end testing for the CV Builder user journey.
    • Added automated database schema validation to CI pipeline.
  • Chores

    • Enhanced code quality checks with pre-commit linting and formatting.
    • Updated development dependencies and tooling configuration.

@DongDuong2001 DongDuong2001 requested a review from Copilot April 21, 2026 09:17
@DongDuong2001 DongDuong2001 self-assigned this Apr 21, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lab68cvbuilder Ready Ready Preview, Comment Apr 21, 2026 9:31am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

Warning

Rate limit exceeded

@DongDuong2001 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 46 minutes and 37 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 46 minutes and 37 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7e48324-7a93-451a-be46-68524bf92cb8

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca7898 and 74a8181.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • e2e/builder.spec.ts
📝 Walkthrough

Walkthrough

Introduces end-to-end testing infrastructure using Playwright with a test spec for a CV builder user journey. Adds pre-commit hooks via Husky integrated with lint-staged for automated code quality checks, Prettier for formatting, and extends the CI pipeline with database schema validation and E2E test execution.

Changes

Cohort / File(s) Summary
E2E Testing Framework
playwright.config.ts, e2e/builder.spec.ts
Introduces Playwright configuration for end-to-end tests with baseURL of http://localhost:3000, chromium browser, and parallel execution. Adds a CV builder user journey test that validates navigation, form interactions, resume preview visibility, and ATS analysis feature interactions.
Code Quality & Pre-commit Hooks
.husky/pre-commit
Adds Husky pre-commit hook to execute lint-staged on each commit, enabling automated linting and formatting checks before commits are finalized.
CI/CD Pipeline Extensions
.github/workflows/ci.yml
Extends the quality job by inserting database schema validation (drizzle-kit check) and E2E test execution (playwright install chromium and E2E test run) between existing unit tests and security regression tests.
Dependencies & Scripts
package.json
Adds Playwright test framework, Husky, lint-staged, and Prettier as dev dependencies. Introduces test:e2e and prepare scripts. Adds lint-staged configuration that runs ESLint and Prettier on JS/TS/TSX files and Prettier on JSON/CSS/MD files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop, hop! Our tests now run end-to-end so bright,
With Playwright and Husky keeping code just right,
Pre-commit hooks check quality with care,
And CI pipelines dance through the air! 🎭✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding e2e tests and pre-commit hooks to enhance the CI pipeline, which directly corresponds to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/enhance-pipeline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
playwright.config.ts (1)

34-39: Optional: make the webServer more CI-friendly.

A couple of small hardening suggestions (non-blocking):

  • webServer.command uses npm run dev (= next dev --turbopack). For CI, running against a production build (next build && next start) tends to be faster and more representative, and avoids dev-only behavior (HMR, on-demand compilation, turbopack warmup). You can branch on process.env.CI to swap commands.
  • Consider setting an explicit timeout (default is 60s) so a misconfigured dev server fails fast in CI instead of eating the whole job budget.
  • Hardcoding http://localhost:3000 in both use.baseURL and webServer.url is fine, but extracting to a constant or process.env.PLAYWRIGHT_BASE_URL makes the config easier to point at a preview deployment later.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playwright.config.ts` around lines 34 - 39, Update the Playwright webServer
block to be CI-friendly: when process.env.CI is set use a production start
command (e.g., run next build && next start) instead of npm run dev by branching
the webServer.command based on process.env.CI; add an explicit webServer.timeout
value to fail fast in CI; and extract the base URL into a single constant
(use.baseURL and webServer.url) driven by process.env.PLAYWRIGHT_BASE_URL with a
fallback like http://localhost:3000 so you only change one place if pointing
tests at a preview deployment.
package.json (1)

61-69: Optional: consider running Prettier before ESLint to avoid lint/format churn.

Running eslint --fix then prettier --write can re-format code that ESLint just fixed, and (depending on ESLint rules) the two tools can disagree. A common robust setup is either (a) run prettier --write first and then eslint --fix, or (b) use eslint-config-prettier to turn off conflicting stylistic rules so ESLint and Prettier don't fight. Not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 61 - 69, Update the lint-staged configuration so
Prettier runs before ESLint (or ensure ESLint is configured to defer to
Prettier): for the "*.{js,jsx,ts,tsx}" glob in the "lint-staged" block, swap the
order of the commands to run "prettier --write" first then "eslint --fix", or
alternatively install and enable "eslint-config-prettier" and remove conflicting
stylistic rules so "eslint --fix" and "prettier --write" won't fight; adjust the
package.json "lint-staged" entry accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 40-47: The CI workflow will fail because `drizzle-kit check`
(Database Schema validation step) requires DATABASE_URL at load time and the
Playwright E2E step needs GROQ_API_KEY and CLOUDINARY_URL; update the "Database
Schema validation" step to export DATABASE_URL from GitHub Secrets in an env:
block, and update the "E2E Tests (Playwright)" step to export GROQ_API_KEY and
CLOUDINARY_URL (or set test-mode mocks) in its env: block, switch the Playwright
install command to `npx playwright install --with-deps chromium` and consider
running `next build && next start` instead of `next dev` for E2E to match
production; also remove the trailing whitespace on the workflow line flagged.

In `@e2e/builder.spec.ts`:
- Around line 1-64: The E2E spec (e2e/builder.spec.ts) is flaky/incorrect
because it navigates to a non-existent route, doesn’t establish auth, uses
invalid/incorrect selectors and silently no-ops; fix it by: 1) navigate to a
valid builder URL (create a resume via the dashboard flow or use a seeded id
instead of "/en/builder"); 2) establish an authenticated session before visiting
the builder using Playwright auth storage state or globalSetup so session?.user
check passes; 3) replace invalid selector strings (the atsButton locator and
recommendation locators) with proper Playwright APIs like
page.getByRole/getByText or locator.or and target concrete test ids; 4) update
selectors to match real DOM elements (use data-testid attributes added in
src/components/builder/forms/personal-info-form.tsx and builder-preview.tsx) and
remove the count()-guard pattern so the test fails fast if required elements are
missing; 5) adjust ATS interaction to reflect the real UX (open the Score
section or check ResumeScorePanel in resume-score.tsx) rather than clicking a
non-existent button.

---

Nitpick comments:
In `@package.json`:
- Around line 61-69: Update the lint-staged configuration so Prettier runs
before ESLint (or ensure ESLint is configured to defer to Prettier): for the
"*.{js,jsx,ts,tsx}" glob in the "lint-staged" block, swap the order of the
commands to run "prettier --write" first then "eslint --fix", or alternatively
install and enable "eslint-config-prettier" and remove conflicting stylistic
rules so "eslint --fix" and "prettier --write" won't fight; adjust the
package.json "lint-staged" entry accordingly.

In `@playwright.config.ts`:
- Around line 34-39: Update the Playwright webServer block to be CI-friendly:
when process.env.CI is set use a production start command (e.g., run next build
&& next start) instead of npm run dev by branching the webServer.command based
on process.env.CI; add an explicit webServer.timeout value to fail fast in CI;
and extract the base URL into a single constant (use.baseURL and webServer.url)
driven by process.env.PLAYWRIGHT_BASE_URL with a fallback like
http://localhost:3000 so you only change one place if pointing tests at a
preview deployment.
🪄 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

Run ID: f80c67dd-d2b4-44f8-af80-a60b7df82a59

📥 Commits

Reviewing files that changed from the base of the PR and between 9b22a29 and 4ca7898.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .husky/pre-commit
  • e2e/builder.spec.ts
  • package.json
  • playwright.config.ts

Comment thread .github/workflows/ci.yml
Comment on lines +40 to +47
- name: Database Schema validation
run: npx drizzle-kit check

- name: Security regression tests
run: npm run test:security

- name: E2E Tests (Playwright)
run: npx playwright install chromium && npm run test:e2e
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect drizzle config to see which env vars it reads at load time.
fd -HI 'drizzle.config.*'
fd -HI 'drizzle.config.*' --exec cat {}
# See if any .env.example documents the required runtime env
fd -HI -t f '.env*'

Repository: lab68dev/lab68cvbuilder

Length of output: 868


🏁 Script executed:

# Find and inspect playwright.config.ts
fd -HI 'playwright.config.*' --exec cat {}

# Find and inspect next.config
fd -HI 'next.config.*' --exec cat {}

# Check if there are any test environment files or setup
fd -HI -t f -e 'env.test' -o -e 'vitest.config' -o -e 'jest.config'

# Check for .env files
ls -la | grep -E '\.env'

Repository: lab68dev/lab68cvbuilder

Length of output: 2818


🏁 Script executed:

# Examine the CI file more carefully for env configuration and line 45 whitespace
sed -n '38,50p' .github/workflows/ci.yml | cat -A

Repository: lab68dev/lab68cvbuilder

Length of output: 417


🏁 Script executed:

# Search for DATABASE_URL usage in the codebase to understand test setup
rg "DATABASE_URL" --max-count 20 -B 2 -A 2

Repository: lab68dev/lab68cvbuilder

Length of output: 759


🏁 Script executed:

# Check for actual runtime env dependencies in the app
rg "process\.env\." --max-count 30 -o | sort | uniq -c | sort -rn

# Look for next-auth configuration
rg "nextauth|NextAuthOptions|NextAuth" -t ts -t tsx --max-count 10

# Check for groq and cloudinary usage
rg "groq|cloudinary|GROQ|CLOUDINARY" --max-count 10

# Look for any CI test setup or environment configuration
fd -t f '.github/workflows/ci.yml' --exec cat {}

Repository: lab68dev/lab68cvbuilder

Length of output: 2912


🏁 Script executed:

# Check if there are any test setup files or mocking
fd -HI -t f 'setup|mock|fixture' | head -20

# Look for .env.example or similar documentation
rg "^(DATABASE_URL|AUTH_SECRET|NEXTAUTH|GROQ|CLOUDINARY)" -t txt

Repository: lab68dev/lab68cvbuilder

Length of output: 49


🏁 Script executed:

# Check auth configuration requirements
rg "NEXTAUTH|AUTH_SECRET|NEXT_PUBLIC_URL" --max-count 15

# Look for the actual E2E tests to understand what routes are tested
fd -HI -t f -path '*/e2e/*' | head -20

Repository: lab68dev/lab68cvbuilder

Length of output: 235


🏁 Script executed:

# Check if app will even start without env vars (look for app layout or middleware)
rg "throw new Error|process\.env\." src/app/layout.tsx src/middleware.ts 2>/dev/null | head -20

# Check what routes exist and if they require auth/env
fd -HI -t f -path '*/app/\[locale\]/*' | grep -E 'route\.(ts|tsx)' | head -10

Repository: lab68dev/lab68cvbuilder

Length of output: 235


🏁 Script executed:

# Search for auth configuration in the codebase
rg "NEXTAUTH|AUTH_SECRET|NEXT_PUBLIC" -g "*.ts" -g "*.tsx" --max-count 15

# Look for E2E test files
fd -type f "e2e" | head -20

# Find all test files
rg "describe|test\(" -l | grep -E "(e2e|test)" | head -15

Repository: lab68dev/lab68cvbuilder

Length of output: 1001


🏁 Script executed:

# Check the CI workflow file completely to see if any env is configured
cat .github/workflows/ci.yml

Repository: lab68dev/lab68cvbuilder

Length of output: 980


E2E and schema-check steps will fail in CI without environment variables.

  1. drizzle-kit check (Line 41) will throw an error. The drizzle.config.ts explicitly requires DATABASE_URL at load time and has no fallback—it throws "DATABASE_URL environment variable is not set" if missing. Add DATABASE_URL via GitHub Secrets and expose it in the workflow step's env: block.

  2. E2E Tests (Line 47) will fail. The playwright.config.ts runs npm run dev, and the app depends on GROQ_API_KEY (used in src/actions/tailor.ts, cover-letter.ts, ai.ts) and CLOUDINARY_URL (used in src/app/api/upload/avatar/route.ts). Routes calling these APIs will return 500 errors, causing Playwright tests to fail or hang. Inject these secrets in the E2E step's env: block or mock them in test mode.

Also recommended:

  • Use npx playwright install --with-deps chromium so system libraries are installed on ubuntu-latest.
  • Consider running E2E against next build && next start instead of next dev for closer-to-production behavior.
  • Remove trailing whitespace from Line 45.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 40 - 47, The CI workflow will fail
because `drizzle-kit check` (Database Schema validation step) requires
DATABASE_URL at load time and the Playwright E2E step needs GROQ_API_KEY and
CLOUDINARY_URL; update the "Database Schema validation" step to export
DATABASE_URL from GitHub Secrets in an env: block, and update the "E2E Tests
(Playwright)" step to export GROQ_API_KEY and CLOUDINARY_URL (or set test-mode
mocks) in its env: block, switch the Playwright install command to `npx
playwright install --with-deps chromium` and consider running `next build &&
next start` instead of `next dev` for E2E to match production; also remove the
trailing whitespace on the workflow line flagged.

Comment thread e2e/builder.spec.ts
Comment on lines +1 to +64
import { test, expect } from "@playwright/test";

test.describe("CV Builder User Journey", () => {
test("User can login, open builder, enter details, and see preview/ats suggestions", async ({
page,
}) => {
// 1. Visit Login Page
await page.goto("/en/login");

// Given the project uses Next-Auth, we will assume a known test user
// or public mode if implemented. Let's just navigate to the builder for now.
// If the builder requires auth, adjust this to use the mock session or Playwright's auth-setup.
await page.goto("/en/builder");

// Check if we are redirected to login or staying on the builder.
// If you have a specific "Create Resume" button on the dashboard:
// await page.goto('/en/dashboard');
// await page.click('text="Create Resume"');

// 2. Fill basic details
// Assuming there are input fields in the builder form for the basic resume info
// For example:
const nameInput = page.locator(
'input[name="fullName"], input[placeholder="Full Name"], input[aria-label="Full Name"]',
);
if ((await nameInput.count()) > 0) {
await nameInput.fill("John Doe Test");
}

const jobTitleInput = page.locator(
'input[name="title"], input[placeholder="Job Title"]',
);
if ((await jobTitleInput.count()) > 0) {
await jobTitleInput.fill("Senior Software Engineer");
}

// 3. Verify the PDF/Preview renders with the filled data
// We can check if the preview pane contains the text
await expect(
page.locator(
'.preview-container, .resume-preview, canvas, [data-testid="resume-preview"]',
),
).toBeVisible({ timeout: 10000 });

// Check if the name appears somewhere in the preview area
// Try catching it via a less strict text selector in the DOM if HTML preview, or just rely on canvas visibility.
// await expect(page.locator('text=John Doe Test')).toBeVisible();

// 4. Check ATS Recommendations or Score
// Look for ATS UI elements such as score percentage, "ATS Recommendations" button, etc.
const atsButton = page.locator(
'text="ATS", text="Analyze", button:has-text("Score")',
);
if ((await atsButton.count()) > 0) {
await atsButton.click();
// Assert that the AI suggestions feature or ATS feedback panel pops up
await expect(
page.locator(
'text="Recommendations", text="Suggestions", .ats-feedback',
),
).toBeVisible({ timeout: 10000 });
}
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm builder route shape and that no top-level /builder/page.tsx exists
fd -HI -t f 'page.tsx' src/app | rg -n 'builder'
# Look for any data-testid attributes already wired into the builder UI
rg -nP 'data-testid=' src/components/builder src/app 2>/dev/null | head -50
# Confirm the fullName input attributes
rg -nP -C2 '"fullName"|placeholder="John Doe"' src/components/builder/forms/personal-info-form.tsx

Repository: lab68dev/lab68cvbuilder

Length of output: 755


🏁 Script executed:

#!/bin/bash
# 1. Get full context of the fullName input element
cat -n src/components/builder/forms/personal-info-form.tsx | sed -n '835,855p'

# 2. Search for all data-testid attributes in builder components
echo "=== data-testid attributes in builder ==="
rg 'data-testid=' src/components/builder src/app/\[locale\]/builder 2>/dev/null | head -30

# 3. Find the jobTitle input field
echo "=== Job Title input field ==="
rg -B3 -A3 'jobTitle|job.title' src/components/builder/forms/personal-info-form.tsx | head -50

# 4. Check for preview container references
echo "=== Preview container selectors ==="
rg -n 'preview-container|resume-preview|data-testid.*preview' src/components/builder src/app 2>/dev/null

# 5. Check if builder route requires authentication
echo "=== Builder route auth check ==="
rg -B5 -A5 'middleware|auth|redirect' src/app/\[locale\]/builder/\[id\]/page.tsx | head -30

Repository: lab68dev/lab68cvbuilder

Length of output: 2072


🏁 Script executed:

#!/bin/bash
# Find job title / position input in the form
echo "=== Job Title/Position Input ==="
rg -n -B2 -A4 'position|Job Title' src/components/builder/forms/personal-info-form.tsx | grep -A6 'input'

# Search for job title input attribute definitions
rg -n 'name=.*title|placeholder=.*[Tt]itle' src/components/builder/forms/personal-info-form.tsx

# Search for ATS-related elements in the codebase
echo "=== ATS Button/Panel Search ==="
rg -rn 'ATS|Analyze|ats-feedback|ats-button|Score' src/components/builder src/app 2>/dev/null | grep -i 'button\|text\|locator\|aria' | head -20

# Check for ATS component or feature
echo "=== ATS Component Files ==="
fd -H -t f '.*ats.*|.*recommendation.*' src 2>/dev/null

# Check auth setup in test files
echo "=== Auth Setup in Tests ==="
rg -n 'auth|session|globalSetup|storageState' e2e 2>/dev/null

Repository: lab68dev/lab68cvbuilder

Length of output: 587


🏁 Script executed:

#!/bin/bash
# Get file sizes to understand scope
echo "=== File sizes ==="
wc -l src/components/builder/forms/personal-info-form.tsx src/components/builder/resume-score.tsx src/components/builder/builder-client.tsx 2>/dev/null

# Check all input elements in personal-info-form
echo "=== All input elements in personal-info-form ==="
rg -n '<input' src/components/builder/forms/personal-info-form.tsx

# Check resume-score.tsx for button structure
echo "=== resume-score.tsx button elements ==="
rg -n 'button|onClick|className.*ats' src/components/builder/resume-score.tsx | head -30

# Look for preview container in builder layout
echo "=== Preview container in builder ==="
rg -n 'preview|canvas|data-testid' src/components/builder/builder-client.tsx src/components/builder/builder-layout.tsx 2>/dev/null | head -20

# Check experience form for position/job title
echo "=== Experience form position input ==="
rg -B2 -A4 'position|positionInput' src/components/builder/forms/experience-form.tsx | head -40

# Verify e2e has no globalSetup
echo "=== E2E test setup ==="
ls -la e2e/ | grep -E 'setup|auth|config'
cat e2e/playwright.config.ts 2>/dev/null | head -40

Repository: lab68dev/lab68cvbuilder

Length of output: 1982


🏁 Script executed:

#!/bin/bash
# Read full resume-score.tsx
echo "=== Full resume-score.tsx ==="
cat -n src/components/builder/resume-score.tsx

# Read builder-preview.tsx to check for preview container classes
echo "=== builder-preview.tsx ==="
cat -n src/components/builder/builder-preview.tsx

# Check experience-form for position input structure
echo "=== Position input in experience-form ==="
rg -n -B3 -A8 'value={exp.position}' src/components/builder/forms/experience-form.tsx

Repository: lab68dev/lab68cvbuilder

Length of output: 12658


🏁 Script executed:

#!/bin/bash
# Check builder-client.tsx full content
echo "=== builder-client.tsx ==="
cat -n src/components/builder/builder-client.tsx

# Check for builder layout
echo "=== builder-layout.tsx ==="
cat -n src/components/builder/builder-layout.tsx 2>/dev/null || echo "Not found"

# Search for ResumeScorePanel usage
echo "=== ResumeScorePanel usage ==="
rg -rn 'ResumeScorePanel' src 2>/dev/null

Repository: lab68dev/lab68cvbuilder

Length of output: 8088


This spec will not meaningfully exercise the flow it describes — multiple correctness issues.

Several problems combine to make this E2E test either flaky, silently passing without doing anything, or outright failing:

  1. Wrong builder route. The builder page lives at src/app/[locale]/builder/[id]/page.tsx, so /en/builder (Line 13) is not a valid route — it will 404 or redirect. You need to create a resume first (dashboard → "Create Resume") or navigate to a seeded id, e.g. /en/builder/<seeded-id>.

  2. No authentication. Comments acknowledge next-auth is used, but the test neither logs in nor installs a mock session / storage state. The builder route has if (!session?.user) { redirect("/login"); }, so you'll be redirected before any form is visible. Use Playwright's auth storage state or a globalSetup that seeds a session cookie.

  3. Invalid Playwright selector syntax. page.locator('text="ATS", text="Analyze", button:has-text("Score")') (Line 52) and 'text="Recommendations", text="Suggestions", .ats-feedback' (Line 59) are not valid. Playwright does not parse comma-separated mixtures of the text= engine and CSS inside a single selector string. Use locator.or() or role-based selectors instead:

    const scoreSection = page.getByRole("button", { name: /score/i });
  4. Selectors don't match the real DOM. The full-name input has placeholder "John Doe" but no name or aria-label="Full Name" attribute (see src/components/builder/forms/personal-info-form.tsx Line 844). No top-level "job title" input exists — position is nested in the experience section. The preview pane has no .preview-container, .resume-preview, or [data-testid="resume-preview"] classes — it's just a styled div in builder-preview.tsx. Add data-testid attributes to components and target those instead.

  5. Silent no-op pattern. Guarding fills with if ((await locator.count()) > 0) means the test can pass without ever filling anything or navigating. E2E tests should fail if preconditions aren't met, not silently skip.

  6. ATS feature is not clickable. ResumeScorePanel (in resume-score.tsx) is a static display component that shows score rings and checklist items — not a modal or panel that appears after clicking a button. Users navigate to the "Score" section in the form sidebar to view it. There is no "click to get recommendations" button.

Given the scope of rework, I'd recommend landing this behind a skipped test.describe.skip(...) until auth setup, real selectors (via data-testid), correct route navigation, and accurate ATS flow understanding are in place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/builder.spec.ts` around lines 1 - 64, The E2E spec (e2e/builder.spec.ts)
is flaky/incorrect because it navigates to a non-existent route, doesn’t
establish auth, uses invalid/incorrect selectors and silently no-ops; fix it by:
1) navigate to a valid builder URL (create a resume via the dashboard flow or
use a seeded id instead of "/en/builder"); 2) establish an authenticated session
before visiting the builder using Playwright auth storage state or globalSetup
so session?.user check passes; 3) replace invalid selector strings (the
atsButton locator and recommendation locators) with proper Playwright APIs like
page.getByRole/getByText or locator.or and target concrete test ids; 4) update
selectors to match real DOM elements (use data-testid attributes added in
src/components/builder/forms/personal-info-form.tsx and builder-preview.tsx) and
remove the count()-guard pattern so the test fails fast if required elements are
missing; 5) adjust ATS interaction to reflect the real UX (open the Score
section or check ResumeScorePanel in resume-score.tsx) rather than clicking a
non-existent button.

Copy link
Copy Markdown

Copilot AI left a comment

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 adds local/CI guardrails by introducing Playwright E2E testing, Husky + lint-staged pre-commit hooks, and extra CI validation (including a Drizzle schema check).

Changes:

  • Add Playwright configuration and a starter E2E spec.
  • Add Husky + lint-staged + Prettier for pre-commit formatting/linting.
  • Extend CI workflow to run drizzle-kit check and Playwright E2E tests.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
playwright.config.ts Adds Playwright runner config including webServer startup and Chromium project.
e2e/builder.spec.ts Introduces a “core journey” E2E spec (login → builder → preview/ATS checks).
.github/workflows/ci.yml Runs Drizzle schema validation and Playwright E2E in CI.
.husky/pre-commit Adds a pre-commit hook to execute lint-staged.
package.json Adds Playwright, Husky, lint-staged, Prettier, and test:e2e/prepare scripts.
package-lock.json Locks newly added devDependencies and transitive packages.

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

Comment thread .github/workflows/ci.yml
- name: Unit tests
run: npm run test

- name: Database Schema validation
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

npx drizzle-kit check will fail in CI unless DATABASE_URL is provided. drizzle.config.ts throws when process.env.DATABASE_URL is missing, and this workflow doesn’t set it. Set DATABASE_URL from a GitHub secret (or spin up a Postgres service and point DATABASE_URL at it) for this step.

Suggested change
- name: Database Schema validation
- name: Database Schema validation
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}

Copilot uses AI. Check for mistakes.
Comment thread e2e/builder.spec.ts Outdated
Comment on lines +4 to +19
test("User can login, open builder, enter details, and see preview/ats suggestions", async ({
page,
}) => {
// 1. Visit Login Page
await page.goto("/en/login");

// Given the project uses Next-Auth, we will assume a known test user
// or public mode if implemented. Let's just navigate to the builder for now.
// If the builder requires auth, adjust this to use the mock session or Playwright's auth-setup.
await page.goto("/en/builder");

// Check if we are redirected to login or staying on the builder.
// If you have a specific "Create Resume" button on the dashboard:
// await page.goto('/en/dashboard');
// await page.click('text="Create Resume"');

Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

/en/builder doesn’t appear to be a valid route in this app (builder is under /[locale]/builder/[id]), and the builder page redirects to /login when unauthenticated. This test will likely 404/redirect and then fail the preview assertion. Consider targeting the public /en/try page or creating a resume (or mocking auth) and navigating to /en/builder/<resumeId>.

Suggested change
test("User can login, open builder, enter details, and see preview/ats suggestions", async ({
page,
}) => {
// 1. Visit Login Page
await page.goto("/en/login");
// Given the project uses Next-Auth, we will assume a known test user
// or public mode if implemented. Let's just navigate to the builder for now.
// If the builder requires auth, adjust this to use the mock session or Playwright's auth-setup.
await page.goto("/en/builder");
// Check if we are redirected to login or staying on the builder.
// If you have a specific "Create Resume" button on the dashboard:
// await page.goto('/en/dashboard');
// await page.click('text="Create Resume"');
test("User can open the public builder, enter details, and see preview/ats suggestions", async ({
page,
}) => {
// 1. Visit the public builder entry point.
// `/en/builder` requires a resume id and may redirect unauthenticated users,
// so use the public `/en/try` route for this E2E flow.
await page.goto("/en/try");
// If you later want to cover the authenticated builder flow, create a resume
// (or mock auth) first and then navigate to `/en/builder/<resumeId>`.

Copilot uses AI. Check for mistakes.
Comment thread e2e/builder.spec.ts Outdated
Comment on lines +51 to +61
const atsButton = page.locator(
'text="ATS", text="Analyze", button:has-text("Score")',
);
if ((await atsButton.count()) > 0) {
await atsButton.click();
// Assert that the AI suggestions feature or ATS feedback panel pops up
await expect(
page.locator(
'text="Recommendations", text="Suggestions", .ats-feedback',
),
).toBeVisible({ timeout: 10000 });
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

These Playwright locators use invalid selector syntax: text="ATS", text="Analyze" ... and text="Recommendations", ... are not valid CSS, and Playwright’s text engine selector is text=... (without quotes) or getByText()/getByRole(). As written, atsButton.count() / the follow-up expect() may throw a selector parsing error instead of conditionally passing.

Suggested change
const atsButton = page.locator(
'text="ATS", text="Analyze", button:has-text("Score")',
);
if ((await atsButton.count()) > 0) {
await atsButton.click();
// Assert that the AI suggestions feature or ATS feedback panel pops up
await expect(
page.locator(
'text="Recommendations", text="Suggestions", .ats-feedback',
),
).toBeVisible({ timeout: 10000 });
const atsButton = page
.getByRole("button", { name: /ATS|Analyze|Score/i })
.or(page.locator('[role="button"]', { hasText: /ATS|Analyze|Score/i }));
if ((await atsButton.count()) > 0) {
await atsButton.first().click();
// Assert that the AI suggestions feature or ATS feedback panel pops up
const atsFeedback = page
.getByText(/Recommendations|Suggestions/i)
.or(page.locator(".ats-feedback"));
await expect(atsFeedback.first()).toBeVisible({ timeout: 10000 });

Copilot uses AI. Check for mistakes.
Comment thread e2e/builder.spec.ts Outdated
Comment on lines +21 to +62
// Assuming there are input fields in the builder form for the basic resume info
// For example:
const nameInput = page.locator(
'input[name="fullName"], input[placeholder="Full Name"], input[aria-label="Full Name"]',
);
if ((await nameInput.count()) > 0) {
await nameInput.fill("John Doe Test");
}

const jobTitleInput = page.locator(
'input[name="title"], input[placeholder="Job Title"]',
);
if ((await jobTitleInput.count()) > 0) {
await jobTitleInput.fill("Senior Software Engineer");
}

// 3. Verify the PDF/Preview renders with the filled data
// We can check if the preview pane contains the text
await expect(
page.locator(
'.preview-container, .resume-preview, canvas, [data-testid="resume-preview"]',
),
).toBeVisible({ timeout: 10000 });

// Check if the name appears somewhere in the preview area
// Try catching it via a less strict text selector in the DOM if HTML preview, or just rely on canvas visibility.
// await expect(page.locator('text=John Doe Test')).toBeVisible();

// 4. Check ATS Recommendations or Score
// Look for ATS UI elements such as score percentage, "ATS Recommendations" button, etc.
const atsButton = page.locator(
'text="ATS", text="Analyze", button:has-text("Score")',
);
if ((await atsButton.count()) > 0) {
await atsButton.click();
// Assert that the AI suggestions feature or ATS feedback panel pops up
await expect(
page.locator(
'text="Recommendations", text="Suggestions", .ats-feedback',
),
).toBeVisible({ timeout: 10000 });
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The input locators are likely to match nothing (e.g., the Full Name input uses placeholder John Doe, not Full Name), and the if ((await locator.count()) > 0) guards mean the test can silently skip the “enter details” part and still pass. Prefer stable selectors (e.g., data-testid) and assert the key fields exist/are filled so the test actually validates the journey.

Suggested change
// Assuming there are input fields in the builder form for the basic resume info
// For example:
const nameInput = page.locator(
'input[name="fullName"], input[placeholder="Full Name"], input[aria-label="Full Name"]',
);
if ((await nameInput.count()) > 0) {
await nameInput.fill("John Doe Test");
}
const jobTitleInput = page.locator(
'input[name="title"], input[placeholder="Job Title"]',
);
if ((await jobTitleInput.count()) > 0) {
await jobTitleInput.fill("Senior Software Engineer");
}
// 3. Verify the PDF/Preview renders with the filled data
// We can check if the preview pane contains the text
await expect(
page.locator(
'.preview-container, .resume-preview, canvas, [data-testid="resume-preview"]',
),
).toBeVisible({ timeout: 10000 });
// Check if the name appears somewhere in the preview area
// Try catching it via a less strict text selector in the DOM if HTML preview, or just rely on canvas visibility.
// await expect(page.locator('text=John Doe Test')).toBeVisible();
// 4. Check ATS Recommendations or Score
// Look for ATS UI elements such as score percentage, "ATS Recommendations" button, etc.
const atsButton = page.locator(
'text="ATS", text="Analyze", button:has-text("Score")',
);
if ((await atsButton.count()) > 0) {
await atsButton.click();
// Assert that the AI suggestions feature or ATS feedback panel pops up
await expect(
page.locator(
'text="Recommendations", text="Suggestions", .ats-feedback',
),
).toBeVisible({ timeout: 10000 });
}
// Require the key builder inputs to exist so the journey cannot silently skip data entry.
const nameInput = page.locator(
'[data-testid="full-name-input"], input[name="fullName"], input[aria-label="Full Name"]',
).first();
await expect(nameInput).toBeVisible({ timeout: 10000 });
await nameInput.fill("John Doe Test");
await expect(nameInput).toHaveValue("John Doe Test");
const jobTitleInput = page.locator(
'[data-testid="job-title-input"], input[name="title"], input[aria-label="Job Title"]',
).first();
await expect(jobTitleInput).toBeVisible({ timeout: 10000 });
await jobTitleInput.fill("Senior Software Engineer");
await expect(jobTitleInput).toHaveValue("Senior Software Engineer");
// 3. Verify the PDF/Preview renders with the filled data
// We can check if the preview pane contains the text
await expect(
page.locator(
'[data-testid="resume-preview"], .preview-container, .resume-preview, canvas',
).first(),
).toBeVisible({ timeout: 10000 });
// 4. Check ATS Recommendations or Score
// Require the ATS control and results panel so the journey validates this step.
const atsButton = page.locator(
'[data-testid="ats-button"], button:has-text("ATS"), button:has-text("Analyze"), button:has-text("Score")',
).first();
await expect(atsButton).toBeVisible({ timeout: 10000 });
await atsButton.click();
await expect(
page.locator(
'[data-testid="ats-feedback"], .ats-feedback, text="Recommendations", text="Suggestions"',
).first(),
).toBeVisible({ timeout: 10000 });

Copilot uses AI. Check for mistakes.
Comment thread .husky/pre-commit
@@ -0,0 +1 @@
npx lint-staged
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

This Husky hook is missing a shebang (and the Husky shim if your setup expects it). Without #!/usr/bin/env sh at the top, Git can fail to execute it on Linux/macOS with an “Exec format error”. Add the standard Husky header and ensure the file is executable.

Copilot uses AI. Check for mistakes.
@DongDuong2001 DongDuong2001 added bug Something isn't working enhancement New feature or request labels Apr 21, 2026
@DongDuong2001 DongDuong2001 merged commit 220e149 into main Apr 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants