diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml new file mode 100644 index 0000000..e6f0576 --- /dev/null +++ b/.gitea/workflows/test.yaml @@ -0,0 +1,191 @@ +name: nightly-tests + +on: + schedule: + - cron: "30 2 * * *" # 2:30 AM daily + workflow_dispatch: # Manual trigger + +env: + DOCKER_HOST: unix:///var/run/docker.sock + +jobs: + # ── Unit Tests ──────────────────────────────────────────────────────────────── + unit-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test + env: + CI: "true" + + - name: Upload Vitest coverage + uses: actions/upload-artifact@v4 + if: always() + with: + name: vitest-coverage + path: test-results/vitest-coverage/ + + # ── E2E Tests ───────────────────────────────────────────────────────────────── + e2e-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install chromium --with-deps + + - name: Start dev server + run: npm run dev & + env: + PORT: 3000 + shell: bash + + - name: Wait for server + run: npx wait-on http://localhost:3000 --timeout 60000 + + - name: Run E2E tests + run: npx playwright test --config=playwright.config.ts + env: + CI: "true" + + - name: Upload Playwright report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: test-results/ + + - name: Upload test videos + uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-videos + path: test-videos/ + + # ── Accessibility Tests ──────────────────────────────────────────────────────── + a11y-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install chromium --with-deps + + - name: Start dev server + run: npm run dev & + env: + PORT: 3000 + shell: bash + + - name: Wait for server + run: npx wait-on http://localhost:3000 --timeout 60000 + + - name: Run accessibility tests + run: npx playwright test --config=playwright.a11y.config.ts + env: + CI: "true" + + - name: Upload a11y report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-a11y-report + path: test-results/ + + # ── Visual Tests ────────────────────────────────────────────────────────────── + visual-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install chromium --with-deps + + - name: Start dev server + run: npm run dev & + env: + PORT: 3000 + shell: bash + + - name: Wait for server + run: npx wait-on http://localhost:3000 --timeout 60000 + + - name: Run visual tests + run: npx playwright test --config=playwright.visual.config.ts + env: + CI: "true" + + - name: Upload visual diffs + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-visual-diffs + path: test-results/visual/ + + # ── Test Summary ────────────────────────────────────────────────────────────── + test-summary: + runs-on: ubuntu-latest + needs: [unit-tests, e2e-tests, a11y-tests] + if: always() + steps: + - name: Test results summary + run: | + echo "## Nightly Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY + echo "|------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Unit Tests | ${{ needs.unit-tests.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| E2E Tests | ${{ needs.e2e-tests.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Accessibility Tests | ${{ needs.a11y-tests.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Visual Tests | ${{ needs.visual-tests.result }} |" >> $GITHUB_STEP_SUMMARY + + - name: Notify on failure + if: needs.unit-tests.result == 'failure' || needs.e2e-tests.result == 'failure' || needs.a11y-tests.result == 'failure' + run: | + echo "⚠️ Some tests failed. Check the artifacts for details." \ No newline at end of file diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..654f054 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,134 @@ +# Testing Guide — Nxtgauge Frontend + +## Test Stack + +| Type | Tool | Config | +|------|------|--------| +| Unit | Vitest | `vitest.config.ts` | +| E2E | Playwright | `playwright.config.ts` | +| Accessibility | Playwright + axe-core | `playwright.a11y.config.ts` | +| Visual | Playwright screenshot diff | `playwright.visual.config.ts` | + +## Running Tests + +### Local (before push) + +```bash +# Unit tests +npm run test + +# Unit tests (watch mode) +npm run test:watch + +# Unit tests with coverage +npm run test:coverage + +# All E2E tests +npm run test:e2e + +# Accessibility tests only +npm run test:accessibility + +# Visual tests only +npm run test:visual +``` + +### Dev server must be running for E2E/a11y/visual tests + +```bash +npm run dev & +npx wait-on http://localhost:3000 +# Then run tests in another terminal +``` + +## Test Directories + +``` +tests/ +├── e2e/ +│ ├── ai-chat-widget.spec.ts # AI Chat Widget E2E +│ ├── company-jobs.spec.ts # Company Jobs Page E2E +│ ├── company-verification-flow.spec.ts +│ ├── signup-verification-submission.spec.ts +│ ├── accessibility.spec.ts +│ └── visual/ +│ └── *.png # Visual baseline screenshots +├── vitest/ +│ └── components/ +│ ├── AiChatWidget.test.tsx +│ └── PublicFooter.test.tsx +``` + +## Writing E2E Tests + +```typescript +import { test, expect } from "@playwright/test"; + +test("description of test", async ({ page }) => { + await page.goto("/route"); + await page.waitForLoadState("networkidle"); + + // Assertions + await expect(page.locator("text=Expected")).toBeVisible(); + + // Interactions + await page.click("button[type='submit']"); + await page.fill("input[name='email']", "test@example.com"); +}); +``` + +## Writing Vitest Unit Tests + +```typescript +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@solidjs/testing-library"; +import { MyComponent } from "../src/components/MyComponent"; + +global.fetch = vi.fn(); + +describe("MyComponent", () => { + it("renders correctly", () => { + render(() => ); + expect(screen.getByText("Hello")).toBeTruthy(); + }); +}); +``` + +## Visual Tests + +Visual tests compare screenshots against baselines in `tests/e2e/visual/`. +- Update baselines: `npx playwright test --config=playwright.visual.config.ts --update-snapshots` +- Review diffs in `test-results/visual/` + +## CI / Nightly Runs + +GitHub Actions runs tests nightly via `.gitea/workflows/test.yaml`: +- **2:30 AM daily** — all test suites +- **On-demand** — use `workflow_dispatch` trigger in Gitea + +Artifacts are uploaded: +- `vitest-coverage/` — coverage reports +- `playwright-report/` — HTML test report +- `playwright-videos/` — recordings of failed tests +- `playwright-a11y-report/` — accessibility results +- `playwright-visual-diffs/` — screenshot diffs + +## Coverage Requirements + +- Minimum coverage target: **70%** +- Run `npm run test:coverage` to generate coverage report +- Coverage report location: `test-results/vitest-coverage/` + +## Adding New Tests + +1. **E2E tests**: Add `.spec.ts` to `tests/e2e/` +2. **Unit tests**: Add `.test.tsx` to `tests/vitest/components/` +3. **Visual tests**: Add page screenshots to `tests/e2e/visual/` as baselines + +## Troubleshooting + +**Playwright timeout**: Increase `timeout` in config or use `test.setTimeout()` + +**Flaky tests**: Use `await page.waitForLoadState("networkidle")` instead of arbitrary waits + +**MSW not intercepting**: Ensure `setup.ts` is imported in `vitest.config.ts` via `setupFiles` \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..294a465 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests/e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 4 : undefined, + reporter: [["list"], ["html", { outputFolder: "./playwright-reports/html" }]], + use: { + baseURL: "http://localhost:3000", + trace: "on-first-retry", + screenshot: "only-on-failure", + video: "retain-on-failure", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "chromium-mobile", + use: { ...devices["Pixel 5"] }, + }, + ], + webServer: process.env.CI + ? undefined + : { + command: "npm run dev", + url: "http://localhost:3000", + reuseExistingServer: true, + timeout: 120 * 1000, + }, +}); \ No newline at end of file diff --git a/src/app.css b/src/app.css index 11c93c6..23a5db1 100644 --- a/src/app.css +++ b/src/app.css @@ -6709,3 +6709,8 @@ body { font-size: 13px; padding: 20px 0; } + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/src/components/AiChatWidget.tsx b/src/components/AiChatWidget.tsx index cba4122..4715b83 100644 --- a/src/components/AiChatWidget.tsx +++ b/src/components/AiChatWidget.tsx @@ -116,6 +116,9 @@ export function AiChatWidget() { {/* Chat window */}
{/* Header */} @@ -154,6 +157,7 @@ export function AiChatWidget() {
+ setField("title", e.currentTarget.value)} @@ -324,7 +430,29 @@ export default function CompanyJobsPage() { />
- +
+ + +
setField("category", e.currentTarget.value)} @@ -354,7 +482,29 @@ export default function CompanyJobsPage() { />
- +
+ + +