Files
CompanySite/tests/e2e-blog-navigation.spec.ts
Clintchiz d402256547
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
First Init
2026-03-21 16:46:46 +05:30

405 lines
13 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* E2E Blog Navigation Test Suite
* Comprehensive testing of blog listing, filtering, and post reading flows
*/
test.describe('Blog Navigation - Complete User Flow', () => {
test('navigate from homepage to blog and read multiple posts', async ({ page }) => {
// Start from homepage
await page.goto('/');
// Navigate to blog
const blogLink = page.locator('a[href="/blog"]').first();
await blogLink.click();
await expect(page).toHaveURL('/blog');
// Verify blog page loaded
await page.waitForLoadState('networkidle');
const posts = page.locator('article, [class*="post"], [class*="card"]');
const postCount = await posts.count();
expect(postCount).toBeGreaterThan(0);
// Click first post
const firstPost = posts.first().locator('a').first();
await firstPost.click();
await expect(page).toHaveURL(/\/blog\//);
// Verify post content
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('article, [class*="prose"]')).toBeVisible();
// Navigate back to blog listing
await page.goBack();
await expect(page).toHaveURL('/blog');
// Click different post
if (postCount > 1) {
const secondPost = posts.nth(1).locator('a').first();
await secondPost.click();
await expect(page).toHaveURL(/\/blog\//);
}
});
test('blog post deep reading experience', async ({ page }) => {
await page.goto('/blog/getting-started-with-astro');
// Verify title loaded
const title = page.locator('h1');
await expect(title).toBeVisible();
// Verify content structure
const article = page.locator('article, [class*="prose"], main').first();
await expect(article).toBeVisible();
// Check for paragraphs
const paragraphs = page.locator('p');
expect(await paragraphs.count()).toBeGreaterThan(0);
// Scroll through content
await page.evaluate(() => window.scrollTo(0, 500));
await page.waitForTimeout(300);
await page.evaluate(() => window.scrollTo(0, 1000));
await page.waitForTimeout(300);
// Scroll to bottom
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(300);
// Verify no layout breaks during scroll
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
const viewportWidth = page.viewportSize()?.width || 1280;
expect(bodyWidth).toBeLessThanOrEqual(viewportWidth + 20);
});
test('blog post metadata visibility', async ({ page }) => {
await page.goto('/blog');
const firstPost = page.locator('article, [class*="post"]').first();
// Check for date/time
const hasDate = await firstPost.locator('time, [class*="date"]').count() > 0;
expect(hasDate).toBeTruthy();
// Click to full post
await firstPost.locator('a').first().click();
await expect(page).toHaveURL(/\/blog\//);
// Verify metadata on post page
const postDate = page.locator('time, [class*="date"]');
if (await postDate.count() > 0) {
await expect(postDate.first()).toBeVisible();
}
});
test('blog image loading and optimization', async ({ page }) => {
await page.goto('/blog/getting-started-with-astro');
await page.waitForLoadState('networkidle');
const images = page.locator('img');
const imageCount = await images.count();
if (imageCount > 0) {
// Check first few images
for (let i = 0; i < Math.min(3, imageCount); i++) {
const img = images.nth(i);
if (await img.isVisible()) {
// Verify image has loaded
const loaded = await img.evaluate((el: HTMLImageElement) => {
return el.complete && el.naturalWidth > 0;
});
expect(loaded).toBeTruthy();
// Check for alt text (accessibility)
const alt = await img.getAttribute('alt');
expect(alt).toBeTruthy();
}
}
}
});
test('blog category/tag navigation', async ({ page }) => {
await page.goto('/blog');
// Look for category badges or tags
const categories = page.locator('[class*="category"], [class*="tag"], [class*="badge"]');
if (await categories.count() > 0) {
// Categories should be visible
await expect(categories.first()).toBeVisible();
// If categories are clickable, test navigation
const firstCategory = categories.first();
const isLink = (await firstCategory.evaluate(el => el.tagName)) === 'A';
if (isLink) {
await firstCategory.click();
// Should filter or navigate
await page.waitForLoadState('networkidle');
}
}
});
test('blog search functionality if available', async ({ page }) => {
await page.goto('/blog');
// Check for search input
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i]');
if (await searchInput.count() > 0) {
await searchInput.fill('astro');
await page.keyboard.press('Enter');
// Wait for search results
await page.waitForTimeout(500);
// Verify some results or "no results" message
const hasResults = await page.locator('article, [class*="post"]').count() > 0 ||
await page.locator('text=/no results|no posts/i').count() > 0;
expect(hasResults).toBeTruthy();
}
});
test('blog pagination if available', async ({ page }) => {
await page.goto('/blog');
// Look for pagination controls
const paginationNext = page.locator('[class*="pagination"] a, button[class*="next"], a:has-text("Next")');
if (await paginationNext.count() > 0) {
const nextButton = paginationNext.first();
if (await nextButton.isVisible()) {
await nextButton.click();
await page.waitForLoadState('networkidle');
// Should show different posts or page 2
const hasContent = await page.locator('article, [class*="post"]').count() > 0;
expect(hasContent).toBeTruthy();
}
}
});
});
test.describe('Blog Navigation - Mobile Experience', () => {
test.use({ viewport: { width: 375, height: 667 } });
test('mobile blog listing is readable', async ({ page }) => {
await page.goto('/blog');
// Posts should stack vertically
const posts = page.locator('article, [class*="post"]');
await expect(posts.first()).toBeVisible();
// No horizontal scroll
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
expect(bodyWidth).toBeLessThanOrEqual(395);
// Tap first post
await posts.first().locator('a').first().tap();
await expect(page).toHaveURL(/\/blog\//);
});
test('mobile blog post reading experience', async ({ page }) => {
await page.goto('/blog/getting-started-with-astro');
// Content should be readable
const content = page.locator('article, [class*="prose"]');
await expect(content.first()).toBeVisible();
// No horizontal overflow
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
expect(bodyWidth).toBeLessThanOrEqual(400);
// Font should be readable
const fontSize = await page.locator('p').first().evaluate(el => {
return parseFloat(window.getComputedStyle(el).fontSize);
});
expect(fontSize).toBeGreaterThanOrEqual(14);
// Test scroll performance
for (let i = 0; i < 5; i++) {
await page.evaluate(() => window.scrollBy(0, 200));
await page.waitForTimeout(100);
}
// Verify images adapt to mobile
const images = page.locator('img');
if (await images.count() > 0) {
const firstImg = images.first();
const imgWidth = await firstImg.evaluate(el => el.getBoundingClientRect().width);
expect(imgWidth).toBeLessThanOrEqual(375);
}
});
});
test.describe('Blog Navigation - Reading Patterns', () => {
test('skimming behavior - quick navigation', async ({ page }) => {
await page.goto('/blog');
const posts = page.locator('article, [class*="post"]');
const postCount = await posts.count();
// Simulate user skimming multiple posts quickly
for (let i = 0; i < Math.min(3, postCount); i++) {
const post = posts.nth(i);
await post.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
// Check title is visible during skim
const title = post.locator('h1, h2, h3, [class*="title"]');
await expect(title).toBeVisible();
}
});
test('deep reading behavior - single post focus', async ({ page }) => {
await page.goto('/blog/getting-started-with-astro');
// Simulate reading: scroll slowly through content
const contentHeight = await page.evaluate(() => document.body.scrollHeight);
const steps = 10;
const scrollStep = contentHeight / steps;
for (let i = 0; i < steps; i++) {
await page.evaluate((step) => window.scrollTo(0, step), scrollStep * i);
await page.waitForTimeout(300);
// Content should remain stable during scroll
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
const viewportWidth = page.viewportSize()?.width || 1280;
expect(bodyWidth).toBeLessThanOrEqual(viewportWidth + 20);
}
});
test('code block interaction in blog posts', async ({ page }) => {
await page.goto('/blog/getting-started-with-astro');
const codeBlocks = page.locator('pre, code, [class*="shiki"]');
const codeCount = await codeBlocks.count();
if (codeCount > 0) {
const firstCodeBlock = codeBlocks.first();
await firstCodeBlock.scrollIntoViewIfNeeded();
// Verify code is readable
await expect(firstCodeBlock).toBeVisible();
// Check for syntax highlighting
const hasHighlighting = await firstCodeBlock.locator('[class*="token"], [class*="hl"], span').count() > 0;
// Syntax highlighting is optional but beneficial
expect(hasHighlighting || true).toBeTruthy();
// Verify monospace font
const fontFamily = await firstCodeBlock.evaluate(el => {
return window.getComputedStyle(el).fontFamily.toLowerCase();
});
expect(fontFamily).toMatch(/mono|consolas|courier|jetbrains/);
}
});
test('blog post sharing and social links', async ({ page }) => {
await page.goto('/blog/getting-started-with-astro');
// Look for social share buttons
const socialLinks = page.locator('a[href*="twitter"], a[href*="linkedin"], a[href*="facebook"], [class*="share"]');
if (await socialLinks.count() > 0) {
// Social links should be visible
const firstSocial = socialLinks.first();
await firstSocial.scrollIntoViewIfNeeded();
await expect(firstSocial).toBeVisible();
}
});
});
test.describe('Blog Navigation - Performance', () => {
test('blog listing loads quickly', async ({ page }) => {
const startTime = Date.now();
await page.goto('/blog');
await page.waitForLoadState('domcontentloaded');
const loadTime = Date.now() - startTime;
// Should load in under 3 seconds
expect(loadTime).toBeLessThan(3000);
// Content should be visible
const posts = page.locator('article, [class*="post"]');
expect(await posts.count()).toBeGreaterThan(0);
});
test('blog post loads with minimal delay', async ({ page }) => {
const startTime = Date.now();
await page.goto('/blog/getting-started-with-astro');
await page.waitForLoadState('domcontentloaded');
const loadTime = Date.now() - startTime;
// Should load in under 3 seconds
expect(loadTime).toBeLessThan(3000);
// Content should be visible
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('article, [class*="prose"]')).toBeVisible();
});
test('images lazy load efficiently', async ({ page }) => {
await page.goto('/blog/getting-started-with-astro');
const images = page.locator('img');
const imageCount = await images.count();
if (imageCount > 0) {
// Check for lazy loading attribute
const firstImg = images.first();
const loading = await firstImg.getAttribute('loading');
// Modern images should use lazy loading
// (native or via JavaScript)
expect(['lazy', null].includes(loading)).toBeTruthy();
}
});
});
test.describe('Blog Navigation - Edge Cases', () => {
test('direct URL access to blog post', async ({ page }) => {
// Simulate user accessing post via bookmark or search result
await page.goto('/blog/getting-started-with-astro');
// Page should load correctly
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('article')).toBeVisible();
// Navigation should work
await expect(page.locator('header')).toBeVisible();
await expect(page.locator('footer')).toBeVisible();
});
test('non-existent blog post returns 404', async ({ page }) => {
const response = await page.goto('/blog/this-post-does-not-exist-404');
// Should return 404 or redirect
if (response) {
const status = response.status();
expect([404, 301, 302].includes(status)).toBeTruthy();
}
});
test('blog listing with no posts', async ({ page }) => {
await page.goto('/blog');
// Even with no posts, page should render
await expect(page.locator('main')).toBeVisible();
// Should show message or empty state
const hasContent = await page.locator('article, [class*="post"], [class*="empty"], h1').count() > 0;
expect(hasContent).toBeTruthy();
});
});