// @ts-check // Tests: S1–S7 — story mode rendering and navigation // Requires demo data: run `make demo-load` before this suite. const { test, expect } = require('@playwright/test'); const STORIES_URL = '/trips/italy-2026-demo/stories'; const STORY_GALLERY = '/trips/italy-2026-demo/stories/val-dorcia-at-dawn'; // gallery-led: snap-gallery × 2, chapter-break, text-only pull-quote const STORY_SCROLLY = '/trips/italy-2026-demo/stories/sorano-rock-and-time'; // scrolly-led: scrolly-section × 2, chapter-break, pull-quote with image const DEMO_STORY = '/trips/italy-2026-demo/stories/val-dorcia-at-dawn'; // used for cross-trip hero sanity check // ── S1: Stories listing shows cards ────────────────────────────────────────── test('S1: stories listing renders at least 3 story cards', async ({ page }) => { await page.goto(STORIES_URL); const cards = page.locator('.story-card'); await expect(cards.first()).toBeVisible({ timeout: 5000 }); const count = await cards.count(); expect(count, 'At least 3 story cards').toBeGreaterThanOrEqual(3); }); // ── S2: Gallery-led story — hero image + snap-gallery + chapter-break + text-only pull-quote ── test('S2: gallery-led story renders hero, snap-gallery, chapter-break, text-only pull-quote', async ({ page }) => { await page.goto(STORY_GALLERY); // Hero: real image rendered, no placeholder await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); await expect(page.locator('.story-hero__img-placeholder')).toHaveCount(0); // Snap-gallery (there are two in this story) const galleries = page.locator('.pgallery'); await expect(galleries.first()).toBeVisible(); expect(await galleries.count(), 'Two snap-galleries').toBe(2); // Chapter-break await expect(page.locator('.chapter-break')).toBeVisible(); // Text-only pull-quote (no background image variant) await expect(page.locator('.pull-quote__inner--no-image')).toBeVisible(); }); // ── S3: Scrolly-led story — two scrolly-sections + pull-quote with image ───── test('S3: scrolly-led story renders two scrolly-sections and pull-quote with background image', async ({ page }) => { await page.goto(STORY_SCROLLY); await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); // Two scrolly-sections const scrollySections = page.locator('.scrolly'); await expect(scrollySections.first()).toBeVisible(); expect(await scrollySections.count(), 'Two scrolly-sections').toBe(2); // Pull-quote with background image await expect(page.locator('.pull-quote__bg')).toBeVisible(); }); // ── S4: Scrolly story loads without JS errors (Scrollama CDN) ──────────────── test('S4: scrolly story page loads without JS errors', async ({ page }) => { const errors = []; page.on('pageerror', e => errors.push(e.message)); await page.goto(STORY_SCROLLY); await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); await page.waitForTimeout(1000); expect(errors, 'No JS errors on story page').toHaveLength(0); }); // ── S5: Back button returns to stories listing ──────────────────────────────── test('S5: back button navigates back to stories listing', async ({ page }) => { // Establish history: listing → story → back await page.goto(STORIES_URL); await page.locator('.story-card').first().click(); await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); await page.locator('.story-escape').click(); // After history.back(), URL should be the stories listing await expect(page).toHaveURL(/italy-2026-demo\/stories$/); await expect(page.locator('.story-card').first()).toBeVisible(); }); // ── S6: Demo story — hero image sanity check ───────────────────────────────── test('S6: demo story renders hero image without placeholder', async ({ page }) => { await page.goto(DEMO_STORY); await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); await expect(page.locator('.story-hero__img-placeholder')).toHaveCount(0); }); // ── S7: Story body back link is styled as a back-pill ──────────────────────── test('S7: story body back link has back-pill class', async ({ page }) => { await page.goto('/trips/italy-2026-demo/stories/val-dorcia-at-dawn'); await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); // Scroll past the hero to reveal the story body await page.evaluate(() => window.scrollBy(0, window.innerHeight * 1.5)); await page.waitForTimeout(300); const bodyBack = page.locator('.story-footer .back-pill'); await expect(bodyBack).toBeAttached(); await expect(bodyBack).toHaveText(/← Back/); });