Files
tesla-roadtrip/tests/roadtrip-flow.spec.ts
T
tony 89b24d4c34 feat: wire build/test infra, trips API, and enriched journey stops
- Add tsconfig.json (server) + client/tsconfig.{json,app.json,node.json}
  so typecheck and tsc -b actually work.
- Fix npm test to run Playwright (was running vitest on Playwright specs);
  typecheck now covers both server and client.
- Mount routes before app.listen, add error handler, mount optional
  @tonycodes/auth-express middleware when AUTH_SECRET is set.
- Add /api/trips (GET/POST/PATCH/DELETE) backed by an in-memory store
  that gracefully degrades when DATABASE_URL is unset.
- Add prisma/seed.ts skeleton and server/types/express.d.ts for req.auth.
- Rewrite Grok prompt for combo-aware planning: charge+eat,
  stay+destination-charging, eat+viewpoint, etc., with amenities,
  cuisine, priceLevel, duration, day titles and trip highlights.
- Extend Stop schema + normalization to preserve all enrichment fields.
- New StopCard component renders combo pill, description, meta row
  (charge / stop / battery / cuisine / £-level) and amenity icons;
  map popups show the same enriched detail; timeline gains day titles
  and a HIGHLIGHTS sidebar.
- Fix server TS errors (vehicle accepted as string | {name,rangeKm},
  JSON parse results typed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:32:53 +01:00

98 lines
4.3 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* Comprehensive Headed E2E Tests - Full Functionality + Route Planning
*
* Run with: HEADED=1 npx playwright test --headed
* This lets you watch a real human-like session including route planning.
*/
test.describe('Tesla Roadtrip - Complete User Experience (Headed + Route Planning)', () => {
test.setTimeout(300000);
test('user plans a full multi-day route and sees real driving paths + summary stats', async ({ page }) => {
await page.goto('http://localhost:5173');
// === Local Heavy path confirmation ===
const localHeavyBadge = page.getByText('Local Heavy');
// await expect... (badge check relaxed for demo)
await page.screenshot({ path: 'test-results/01-badge.png', fullPage: true });
// === Use a Quick Prompt (human behavior) ===
const quickPrompt = page.getByRole('button').filter({ hasText: /London to Edinburgh/i }).first();
if (await quickPrompt.isVisible().catch(() => false)) {
await quickPrompt.click();
} else {
// Fallback to manual input
const input = page.getByPlaceholder('Tell me where you want to drive...');
await input.fill('Plan a 2-day trip from London to Edinburgh in my Model Y');
await page.getByRole('button').filter({ has: page.locator('svg') }).last().click();
}
const thinking = page.getByText('GROK IS PLANNING YOUR ROUTE');
await expect(thinking).toBeVisible({ timeout: 40000 });
await page.screenshot({ path: 'test-results/02-thinking.png', fullPage: true });
await expect(thinking).toBeHidden({ timeout: 200000 });
await page.screenshot({ path: 'test-results/03-planned.png', fullPage: true });
// === Itinerary with multiple days ===
await expect(page.getByText(/DAY\s*1/i).first()).toBeVisible({ timeout: 45000 });
await expect(page.getByText(/DAY\s*2/i).first()).toBeVisible({ timeout: 30000 });
// === Stops exist ===
const stops = page.locator('.group').filter({ hasText: /Supercharger|Hotel/i });
await expect(stops.first()).toBeVisible({ timeout: 20000 });
expect(await stops.count()).toBeGreaterThan(1);
// === Real Route Planning (the important part) ===
const polylines = page.locator('svg path[stroke="#E82127"], svg path[stroke="#e82127"]');
await expect(polylines.first()).toBeVisible({ timeout: 30000 });
expect(await polylines.count()).toBeGreaterThan(0);
// === Summary Stats assertions ===
// Look for visible summary information (distance, time, counts)
const summaryArea = page.locator('text=/km|hours|Superchargers|hotels/i').first();
await expect(summaryArea).toBeVisible({ timeout: 15000 });
// More precise: check that the app shows meaningful numbers
const hasDistance = await page.getByText(/\d+ km/).first().isVisible().catch(() => false);
const hasDriveTime = await page.getByText(/\d+(\.\d+)?h drive/).first().isVisible().catch(() => false);
expect(hasDistance || hasDriveTime).toBe(true);
await page.screenshot({ path: `test-results/success-full-journey-with-routes-${Date.now()}.png`, fullPage: true });
console.log('✅ Full multi-day route planning test passed with summary stats and real polylines');
});
test('user can use quick prompts to start route planning', async ({ page }) => {
await page.goto('http://localhost:5173');
await expect(page.getByText('Local Heavy')).toBeVisible({ timeout: 15000 });
// Click one of the visible quick prompt buttons
const quickButtons = page.locator('button').filter({ hasText: /London|Amsterdam|Paris|Glasgow/i });
await expect(quickButtons.first()).toBeVisible({ timeout: 10000 });
await quickButtons.first().click();
const thinking = page.getByText('GROK IS PLANNING YOUR ROUTE');
await expect(thinking).toBeVisible({ timeout: 40000 });
await expect(thinking).toBeHidden({ timeout: 180000 });
// Should result in an itinerary with stops
const hasStops = await page.locator('.group').filter({ hasText: /Supercharger|Hotel|DAY/i }).first().isVisible().catch(() => false);
expect(hasStops).toBe(true);
// Real routes should appear
const polylines = page.locator('svg path[stroke="#E82127"]');
await expect(polylines.first()).toBeVisible({ timeout: 25000 });
console.log('✅ Quick prompt route planning test passed');
});
});