Files
will.anderson cac7bd5727
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m52s
test: full Playwright + API test suite for stage
159 tests across three Playwright projects (api, chromium, mobile):
- tests/api/security.test.ts: security headers, CORS on /api/supabase-config
  (origin allowlist enforced), auth gate on /api/demo, Stripe webhook
  signature enforcement, source file leakage, path traversal, input
  validation (8000-char message cap)
- tests/api/endpoints.test.ts: /api/health, /api/founding-count shape
  invariants, /api/supabase-config JWT shape, sitemap.xml, robots.txt,
  /llms.txt, /api/soul-health internal gate, 404 for unknown routes
- tests/e2e/landing.spec.ts: title, h1 count, meta description, OG tags,
  canonical (no stage leak), JSON-LD schema, demo widget DOM presence,
  JS error filtering (known GTM/CSP noise excluded)
- tests/e2e/seo.spec.ts: per-page title patterns, noindex on checkout,
  canonical URLs, sitemap production-URL enforcement
- tests/e2e/checkout.spec.ts: all three plan variants, auth section, payment
  element, canonical
- tests/e2e/chat.spec.ts: widget DOM structure, auth gate (send button
  disabled without session), API-level auth rejection
- tests/e2e/navigation.spec.ts: all public routes return 200, 404s for
  removed/old paths (/terms, /enterprise-terms, /gallery), static files

All 159 pass against stage. CI step added to stage.yaml after smoke test.
2026-05-11 00:28:33 -05:00

87 lines
3.3 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('Landing page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle(/Neuron/);
});
test('Has exactly one h1', async ({ page }) => {
const h1s = page.locator('h1');
await expect(h1s).toHaveCount(1);
});
test('Has meta description with sufficient length', async ({ page }) => {
const meta = page.locator('meta[name="description"]');
await expect(meta).toHaveCount(1);
const content = await meta.getAttribute('content');
expect(content?.length).toBeGreaterThan(50);
});
test('Has og:title and og:description', async ({ page }) => {
await expect(page.locator('meta[property="og:title"]')).toHaveCount(1);
await expect(page.locator('meta[property="og:description"]')).toHaveCount(1);
});
test('Has canonical URL pointing to production domain', async ({ page }) => {
const canonical = page.locator('link[rel="canonical"]');
await expect(canonical).toHaveCount(1);
const href = await canonical.getAttribute('href');
expect(href).toContain('neurontechnologies.ai');
expect(href).not.toContain('stage');
expect(href).not.toContain('run.app');
});
test('Nav is rendered and visible', async ({ page }) => {
// Use the specific nav ID — the footer also contains a <nav> element
await expect(page.locator('#nav')).toBeVisible();
});
test('Hero section is visible', async ({ page }) => {
await expect(page.locator('section').first()).toBeVisible();
});
test('Has structured data JSON-LD script that parses cleanly', async ({ page }) => {
const schema = page.locator('script[type="application/ld+json"]');
await expect(schema).toHaveCount(1);
const content = await schema.textContent();
expect(() => JSON.parse(content!)).not.toThrow();
});
test('Page loads without first-party JavaScript 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');
// Filter known third-party noise:
// - GTM / Google Analytics fire CSP-blocked connect-src violations
// because their scripts attempt analytics.google.com, www.google.com
// (those aren't in our connect-src, which is correct)
// - Browser extension injections
// - Font CDN preconnect failures (non-critical)
const thirdPartyDomains = [
'googletagmanager', 'analytics.google', 'google.com', 'gstatic',
'cloudflare', 'cdn.jsdelivr', 'fonts.googleapis', 'extension',
'third-party', 'googleadservices', 'stripe', 'supabase',
];
const realErrors = errors.filter(
e => !thirdPartyDomains.some(domain => e.includes(domain)),
);
expect(realErrors).toHaveLength(0);
});
test('Demo panel is present in the DOM', async ({ page }) => {
// The demo panel is rendered server-side and injected into the page.
await expect(page.locator('#neuron-demo-panel')).toBeAttached();
});
test('Demo panel button (open trigger) is present', async ({ page }) => {
await expect(page.locator('#neuron-demo-btn')).toBeAttached();
});
});