feat: add e2e testing setup with Playwright and agent QA loop
- Playwright config with webServer for landing dev server (port 3010) - e2e specs for landing page and database cross-check helper - exclude tests/e2e from vitest to avoid runner conflict - add frontend-design skill (official Anthropic plugin) to project skills - ignore Playwright test artifacts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cd6c7bb726
commit
369a306324
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
|
||||
|
||||
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
|
||||
|
||||
## Design Thinking
|
||||
|
||||
Before coding, understand the context and commit to a BOLD aesthetic direction:
|
||||
- **Purpose**: What problem does this interface solve? Who uses it?
|
||||
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
|
||||
- **Constraints**: Technical requirements (framework, performance, accessibility).
|
||||
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
|
||||
|
||||
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
|
||||
|
||||
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
|
||||
- Production-grade and functional
|
||||
- Visually striking and memorable
|
||||
- Cohesive with a clear aesthetic point-of-view
|
||||
- Meticulously refined in every detail
|
||||
|
||||
## Frontend Aesthetics Guidelines
|
||||
|
||||
Focus on:
|
||||
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
|
||||
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
|
||||
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
|
||||
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
||||
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
|
||||
|
||||
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
|
||||
|
||||
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
|
||||
|
||||
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
||||
|
||||
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
||||
|
|
@ -86,3 +86,7 @@ tmp/
|
|||
# Local Claude config (VPS-specific)
|
||||
CLAUDE.local.md
|
||||
.env.prod
|
||||
|
||||
# Playwright
|
||||
test-results/
|
||||
playwright-report/
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -18,6 +18,9 @@
|
|||
"test:run": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:api": "tsx tests/api/run-all.ts",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"test:e2e:report": "playwright show-report test-results/html-report",
|
||||
"format": "prettier --write \"apps/**/*.{ts,tsx,js,jsx,json,css,md}\" \"packages/**/*.{ts,tsx,js,jsx,json,css,md}\" \"*.{ts,tsx,js,jsx,json,css,md}\" --ignore-unknown",
|
||||
"format:check": "prettier --check \"apps/**/*.{ts,tsx,js,jsx,json,css,md}\" \"packages/**/*.{ts,tsx,js,jsx,json,css,md}\" \"*.{ts,tsx,js,jsx,json,css,md}\" --ignore-unknown",
|
||||
"clean": "pnpm -r clean && rm -rf node_modules",
|
||||
|
|
@ -45,14 +48,15 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@types/node": "^20.11.0",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"eslint-config-prettier": "^9.1.2",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"kill-port": "^2.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4",
|
||||
"tsx": "^4.7.0",
|
||||
"@types/node": "^20.11.0"
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
outputDir: './test-results/artifacts',
|
||||
fullyParallel: true,
|
||||
retries: 0,
|
||||
reporter: [['list'], ['html', { outputFolder: 'test-results/html-report', open: 'never' }]],
|
||||
use: {
|
||||
baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:3010',
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'retain-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'pnpm --filter @banatie/landing dev',
|
||||
url: 'http://localhost:3010',
|
||||
reuseExistingServer: true,
|
||||
timeout: 120_000,
|
||||
},
|
||||
});
|
||||
|
|
@ -12,6 +12,9 @@ importers:
|
|||
|
||||
.:
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.60.0
|
||||
version: 1.60.0
|
||||
'@types/node':
|
||||
specifier: ^20.11.0
|
||||
version: 20.19.17
|
||||
|
|
@ -56,7 +59,7 @@ importers:
|
|||
version: 0.400.0(react@18.3.1)
|
||||
next:
|
||||
specifier: ^14.2.0
|
||||
version: 14.2.33(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 14.2.33(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1
|
||||
|
|
@ -208,7 +211,7 @@ importers:
|
|||
version: 0.400.0(react@19.1.0)
|
||||
next:
|
||||
specifier: 15.5.9
|
||||
version: 15.5.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
version: 15.5.9(@playwright/test@1.60.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0
|
||||
|
|
@ -257,7 +260,7 @@ importers:
|
|||
version: 19.1.5(@types/react@19.1.6)
|
||||
next:
|
||||
specifier: ^14.2.0
|
||||
version: 14.2.33(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 14.2.33(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1
|
||||
|
|
@ -1417,6 +1420,11 @@ packages:
|
|||
resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/test@1.60.0':
|
||||
resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@polka/url@1.0.0-next.29':
|
||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||
|
||||
|
|
@ -3176,6 +3184,11 @@ packages:
|
|||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
|
|
@ -3244,15 +3257,17 @@ packages:
|
|||
glob@10.3.10:
|
||||
resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
globals@13.24.0:
|
||||
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
||||
|
|
@ -4061,6 +4076,7 @@ packages:
|
|||
next@14.2.33:
|
||||
resolution: {integrity: sha512-GiKHLsD00t4ACm1p00VgrI0rUFAC9cRDGReKyERlM57aeEZkOQGcZTpIbsGn0b562FTPJWmYfKwplfO9EaT6ng==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
|
|
@ -4278,6 +4294,16 @@ packages:
|
|||
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
playwright-core@1.60.0:
|
||||
resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.60.0:
|
||||
resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
plimit-lit@1.6.1:
|
||||
resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -4453,6 +4479,7 @@ packages:
|
|||
recharts@2.15.4:
|
||||
resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==}
|
||||
engines: {node: '>=14'}
|
||||
deprecated: 1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide
|
||||
peerDependencies:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
|
@ -6247,6 +6274,10 @@ snapshots:
|
|||
|
||||
'@pkgr/core@0.2.9': {}
|
||||
|
||||
'@playwright/test@1.60.0':
|
||||
dependencies:
|
||||
playwright: 1.60.0
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@react-aria/focus@3.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
|
|
@ -7813,7 +7844,7 @@ snapshots:
|
|||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
||||
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1)
|
||||
|
|
@ -7847,7 +7878,7 @@ snapshots:
|
|||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -7862,7 +7893,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
|
|
@ -8268,6 +8299,9 @@ snapshots:
|
|||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
|
|
@ -9360,7 +9394,7 @@ snapshots:
|
|||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
next@14.2.33(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next@14.2.33(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 14.2.33
|
||||
'@swc/helpers': 0.5.5
|
||||
|
|
@ -9381,11 +9415,12 @@ snapshots:
|
|||
'@next/swc-win32-arm64-msvc': 14.2.33
|
||||
'@next/swc-win32-ia32-msvc': 14.2.33
|
||||
'@next/swc-win32-x64-msvc': 14.2.33
|
||||
'@playwright/test': 1.60.0
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
next@15.5.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
next@15.5.9(@playwright/test@1.60.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@next/env': 15.5.9
|
||||
'@swc/helpers': 0.5.15
|
||||
|
|
@ -9403,6 +9438,7 @@ snapshots:
|
|||
'@next/swc-linux-x64-musl': 15.5.7
|
||||
'@next/swc-win32-arm64-msvc': 15.5.7
|
||||
'@next/swc-win32-x64-msvc': 15.5.7
|
||||
'@playwright/test': 1.60.0
|
||||
sharp: 0.34.4
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
|
|
@ -9580,6 +9616,14 @@ snapshots:
|
|||
dependencies:
|
||||
find-up: 4.1.0
|
||||
|
||||
playwright-core@1.60.0: {}
|
||||
|
||||
playwright@1.60.0:
|
||||
dependencies:
|
||||
playwright-core: 1.60.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
plimit-lit@1.6.1:
|
||||
dependencies:
|
||||
queue-lit: 1.5.2
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
const CONTAINER = process.env.E2E_PG_CONTAINER ?? 'banatie-postgres-dev';
|
||||
const DB_USER = process.env.E2E_PG_USER ?? 'banatie_user';
|
||||
const DB_NAME = process.env.E2E_PG_DB ?? 'banatie_db';
|
||||
|
||||
export const queryDb = <T = Record<string, unknown>>(sql: string): T[] => {
|
||||
const output = execFileSync(
|
||||
'docker',
|
||||
['exec', CONTAINER, 'psql', '-U', DB_USER, '-d', DB_NAME, '--csv', '-c', sql],
|
||||
{ encoding: 'utf-8' },
|
||||
).trim();
|
||||
|
||||
if (!output) return [];
|
||||
|
||||
const [header, ...rows] = output.split('\n');
|
||||
const columns = header.split(',');
|
||||
return rows.map((row) => {
|
||||
const values = row.split(',');
|
||||
return Object.fromEntries(columns.map((col, i) => [col, values[i]])) as T;
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { queryDb } from './helpers/db';
|
||||
|
||||
test.describe('Landing page', () => {
|
||||
test('hero section renders with email capture form', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.getByRole('heading', { level: 1 })).toContainText('AI Image Generation');
|
||||
const heroForm = page.locator('#get-access');
|
||||
await expect(heroForm.getByPlaceholder(/email/i)).toBeVisible();
|
||||
await expect(heroForm.getByRole('button', { name: /early access/i })).toBeVisible();
|
||||
|
||||
await page.screenshot({ path: 'test-results/screenshots/landing-hero.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('page loads without console errors', async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Database cross-check', () => {
|
||||
test('dev postgres is reachable and has expected tables', async () => {
|
||||
const tables = queryDb<{ table_name: string }>(
|
||||
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name",
|
||||
);
|
||||
const names = tables.map((t) => t.table_name);
|
||||
|
||||
expect(names).toContain('api_keys');
|
||||
});
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@ export default defineConfig({
|
|||
globals: true,
|
||||
environment: "node",
|
||||
include: ["**/*.test.ts", "**/*.spec.ts"],
|
||||
exclude: ["**/node_modules/**", "**/dist/**", "**/.next/**"],
|
||||
exclude: ["**/node_modules/**", "**/dist/**", "**/.next/**", "tests/e2e/**"],
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["text", "json", "html"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue