Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped
181 lines
6.2 KiB
TypeScript
181 lines
6.2 KiB
TypeScript
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();
|
|
});
|
|
});
|