cac7bd5727
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m52s
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.
87 lines
3.3 KiB
TypeScript
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();
|
|
});
|
|
});
|