import { test, expect } from '@playwright/test'; test.describe('Blog Listing Tests', () => { test.beforeEach(async ({ page }) => { await page.goto('/blog'); }); test('blog page displays posts', async ({ page }) => { // Look for blog post cards/articles const posts = page.locator('article, [class*="post"], [class*="card"], [class*="blog"]').filter({ hasText: /./ }); const count = await posts.count(); expect(count).toBeGreaterThan(0); }); test('blog posts have required elements', async ({ page }) => { const firstPost = page.locator('article, [class*="post"], [class*="card"]').first(); if (await firstPost.isVisible()) { // Check for title const title = firstPost.locator('h1, h2, h3, [class*="title"]'); await expect(title).toBeVisible(); // Check for date or metadata const hasMeta = await firstPost.locator('time, [class*="date"], [class*="meta"]').count() > 0; expect(hasMeta).toBeTruthy(); } }); test('blog posts link to detail pages', async ({ page }) => { const postLink = page.locator('article a, [class*="post"] a, [class*="card"] a').first(); if (await postLink.isVisible()) { const href = await postLink.getAttribute('href'); expect(href).toContain('/blog/'); } }); test('clicking blog post navigates to detail', async ({ page }) => { const postLink = page.locator('article a, [class*="post"] a, [class*="card"] a').first(); if (await postLink.isVisible()) { await postLink.click(); await expect(page).toHaveURL(/\/blog\//); } }); }); test.describe('Blog Post Detail Tests', () => { test('blog post page renders markdown content', async ({ page }) => { // Navigate directly to a known blog post await page.goto('/blog/getting-started-with-astro'); // Check for article content const article = page.locator('article, main, [class*="content"], [class*="prose"]').first(); await expect(article).toBeVisible(); // Check for heading const heading = page.locator('h1'); await expect(heading).toBeVisible(); // Check for paragraphs (markdown content) const paragraphs = page.locator('p'); const pCount = await paragraphs.count(); expect(pCount).toBeGreaterThan(0); }); test('blog post has proper typography', async ({ page }) => { // Navigate directly to a known blog post await page.goto('/blog/getting-started-with-astro'); // Check for prose or typography classes const proseContent = page.locator('[class*="prose"], [class*="typography"], article').first(); await expect(proseContent).toBeVisible(); // Check text is readable (not too small) const paragraph = page.locator('p').first(); if (await paragraph.isVisible()) { const fontSize = await paragraph.evaluate((el) => { return parseFloat(window.getComputedStyle(el).fontSize); }); expect(fontSize).toBeGreaterThanOrEqual(14); // Minimum readable size } }); test('code blocks render correctly', async ({ page }) => { // Navigate directly to a known blog post that should have code blocks await page.goto('/blog/getting-started-with-astro'); // Check for code blocks (if present in the post) const codeBlocks = page.locator('pre, code, [class*="shiki"], [class*="highlight"]'); const codeCount = await codeBlocks.count(); if (codeCount > 0) { // Code blocks should have monospace font const fontFamily = await codeBlocks.first().evaluate((el) => { return window.getComputedStyle(el).fontFamily; }); expect(fontFamily.toLowerCase()).toMatch(/mono|consolas|courier|ui-monospace/); } }); test('blog post images load', async ({ page }) => { // Navigate directly to a known blog post await page.goto('/blog/getting-started-with-astro'); // Wait for page to fully load await page.waitForLoadState('networkidle'); // Check for any visible images on the blog post page const images = page.locator('img'); const imgCount = await images.count(); // Check at least some images exist expect(imgCount).toBeGreaterThan(0); // Verify first visible image loads correctly for (let i = 0; i < Math.min(imgCount, 3); i++) { const img = images.nth(i); const isVisible = await img.isVisible(); if (isVisible) { // Wait for image to complete loading await img.evaluate((el: HTMLImageElement) => { return new Promise((resolve) => { if (el.complete) { resolve(true); } else { el.onload = () => resolve(true); el.onerror = () => resolve(false); } }); }); break; // Found at least one visible image } } }); }); test.describe('Blog Category Tests', () => { test('posts show category badges', async ({ page }) => { await page.goto('/blog'); const categoryBadges = page.locator('[class*="category"], [class*="tag"], [class*="badge"]'); const badgeCount = await categoryBadges.count(); // Categories should be displayed expect(badgeCount).toBeGreaterThanOrEqual(0); }); }); test.describe('Blog Responsive Tests', () => { test('blog listing adapts to mobile', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto('/blog'); // Posts should stack on mobile const posts = page.locator('article, [class*="post"], [class*="card"]'); await expect(posts.first()).toBeVisible(); // No horizontal scroll const bodyWidth = await page.evaluate(() => document.body.scrollWidth); expect(bodyWidth).toBeLessThanOrEqual(395); }); test('blog post detail is readable on mobile', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); // Navigate directly to a known blog post await page.goto('/blog/getting-started-with-astro'); // Content should not overflow (small tolerance for scrollbar) const bodyWidth = await page.evaluate(() => document.body.scrollWidth); expect(bodyWidth).toBeLessThanOrEqual(400); // Text should be readable const content = page.locator('article, [class*="prose"], [class*="content"]').first(); await expect(content).toBeVisible(); }); });