Files
2026-05-18 11:46:02 +02:00

365 lines
15 KiB
TypeScript

import { test, expect } from '@playwright/test';
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
test.describe('New Blog Features', () => {
test.describe('Related Posts', () => {
test('should display related posts section with heading, list, and 3 post items including title, excerpt, date, and tags', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Wait for page to load
await page.waitForLoadState('networkidle');
// Check for related posts section
const relatedPostsSection = page.locator('.related-posts');
await expect(relatedPostsSection).toBeVisible();
// Check heading
await expect(relatedPostsSection.locator('h3')).toContainText('Related Posts');
// Check that we have related posts displayed
const relatedPostsList = relatedPostsSection.locator('.related-posts-list');
await expect(relatedPostsList).toBeVisible();
// Check that at least one related post item exists
const relatedPostItems = relatedPostsList.locator('.related-post-item');
await expect(relatedPostItems).toHaveCount(3); // Should show 3 related posts
// Verify each related post has required elements
const firstPost = relatedPostItems.first();
await expect(firstPost.locator('h4')).toBeVisible();
await expect(firstPost.locator('.related-post-excerpt')).toBeVisible();
await expect(firstPost.locator('.related-post-meta .post-date')).toBeVisible();
});
test('should navigate to correct blog post URL when clicking on a related post item', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Click on the first related post
await page.locator('.related-post-item').first().click();
// Should navigate to a different blog post
await expect(page).toHaveURL(/\/blog\/.+/);
await expect(page.locator('h1')).toBeVisible();
});
test('should display at least one tag on related posts items', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check that tags are displayed in related posts
const tagsInRelatedPosts = page.locator('.related-post-item .post-tags .tag');
const tagCount = await tagsInRelatedPosts.count();
// At least some posts should have tags
expect(tagCount).toBeGreaterThan(0);
});
});
test.describe('Social Sharing Buttons', () => {
test('should display social sharing section with "Share this post" heading', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
const sharingSection = page.locator('.social-sharing');
await expect(sharingSection).toBeVisible();
// Check heading
await expect(sharingSection.locator('h3')).toContainText('Share this post');
});
test('should display sharing buttons for Twitter, LinkedIn, Facebook, and Email', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
const sharingButtons = page.locator('.share-buttons');
await expect(sharingButtons).toBeVisible();
// Check for each platform's button
await expect(sharingButtons.locator('.share-twitter')).toBeVisible();
await expect(sharingButtons.locator('.share-linkedin')).toBeVisible();
await expect(sharingButtons.locator('.share-facebook')).toBeVisible();
await expect(sharingButtons.locator('.share-email')).toBeVisible();
});
test('should have correct share URLs for Twitter, LinkedIn, Facebook, and Email with proper parameters', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check Twitter share link
const twitterButton = page.locator('.share-twitter');
const twitterHref = await twitterButton.getAttribute('href');
expect(twitterHref).toContain('twitter.com/intent/tweet');
expect(twitterHref).toContain('url=');
// Check LinkedIn share link
const linkedinButton = page.locator('.share-linkedin');
const linkedinHref = await linkedinButton.getAttribute('href');
expect(linkedinHref).toContain('linkedin.com/sharing/share-offsite');
// Check Facebook share link
const facebookButton = page.locator('.share-facebook');
const facebookHref = await facebookButton.getAttribute('href');
expect(facebookHref).toContain('facebook.com/sharer');
// Check Email share link
const emailButton = page.locator('.share-email');
const emailHref = await emailButton.getAttribute('href');
expect(emailHref).toContain('mailto:');
});
test('should have accessible ARIA labels on all sharing buttons for screen readers', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check ARIA labels for accessibility
await expect(page.locator('.share-twitter')).toHaveAttribute('aria-label', /Share on Twitter/i);
await expect(page.locator('.share-linkedin')).toHaveAttribute('aria-label', /Share on LinkedIn/i);
await expect(page.locator('.share-facebook')).toHaveAttribute('aria-label', /Share on Facebook/i);
await expect(page.locator('.share-email')).toHaveAttribute('aria-label', /Share via Email/i);
});
});
test.describe('Table of Contents', () => {
test('should display table of contents section with "Table of Contents" heading when enabled in post frontmatter', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
const tocSection = page.locator('.table-of-contents');
await expect(tocSection).toBeVisible();
// Check heading
await expect(tocSection.locator('h3')).toContainText('Table of Contents');
});
test('should contain navigation links with anchor href attributes pointing to page sections', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
const tocNav = page.locator('.table-of-contents #TableOfContents');
await expect(tocNav).toBeVisible();
// Should have list items with links
const tocLinks = tocNav.locator('a');
const linkCount = await tocLinks.count();
expect(linkCount).toBeGreaterThan(0);
// Check first link
await expect(tocLinks.first()).toBeVisible();
const firstHref = await tocLinks.first().getAttribute('href');
expect(firstHref).toMatch(/^#/); // Should be anchor links
});
test('should scroll to corresponding section and update URL hash when clicking on table of contents links', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Click on a TOC link
const tocLinks = page.locator('.table-of-contents #TableOfContents a');
const firstLink = tocLinks.first();
const linkText = await firstLink.textContent();
await firstLink.click();
// URL should have the hash
await expect(page).toHaveURL(/#.+/);
// Wait a bit for smooth scrolling
await page.waitForTimeout(500);
});
test('should have sticky positioning class when tocSticky is enabled in post frontmatter', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
const tocSection = page.locator('.table-of-contents');
// Check if it has the sticky class (post has tocSticky: true)
await expect(tocSection).toHaveClass(/toc-sticky/);
});
});
test.describe('Enhanced Reading Metadata', () => {
test('should display estimated reading time in minutes', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check for reading time
const readingTime = page.locator('#reading-time');
await expect(readingTime).toBeVisible();
await expect(readingTime).toContainText('min read');
});
test('should display total word count of the post', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check for word count
const wordCount = page.locator('#wordcount');
await expect(wordCount).toBeVisible();
await expect(wordCount).toContainText('Words');
});
test('should display post publish date', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check for publish date
const publishDate = page.locator('#date');
await expect(publishDate).toBeVisible();
});
test('should display last modified date with "Updated" label when lastmod is present in frontmatter', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check for last modified date
const lastModified = page.locator('#last-modified');
// The demo post has lastmod, so it should be visible
await expect(lastModified).toBeVisible();
await expect(lastModified).toContainText('Updated');
});
test('should have properly structured metadata section with semantic HTML', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
const metaSection = page.locator('#meta');
await expect(metaSection).toBeVisible();
// Should be a logical grouping
await expect(metaSection.locator('section')).toBeVisible();
});
});
test.describe('Comments Integration (Structure)', () => {
test('should have main content structure that supports comments section when configured', async ({ page }) => {
// Note: Since comments aren't enabled in demo, this tests the structure
// would exist if configured
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Comments section won't be visible without configuration
// but we can test that the blog single template is properly structured
const mainContent = page.locator('#main-content');
await expect(mainContent).toBeVisible();
});
});
test.describe('Responsive Design', () => {
test('should display related posts, social sharing, and table of contents on mobile viewport (375x667)', async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Related posts should adapt to mobile
const relatedPosts = page.locator('.related-posts');
await expect(relatedPosts).toBeVisible();
// Social sharing should be visible
const socialSharing = page.locator('.social-sharing');
await expect(socialSharing).toBeVisible();
// TOC should be visible but may not be sticky
const toc = page.locator('.table-of-contents');
await expect(toc).toBeVisible();
});
test('should adapt related posts grid layout across desktop (1280px), tablet (768px), and mobile (375px) viewports', async ({ page }) => {
// Test desktop width
await page.setViewportSize({ width: 1280, height: 720 });
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
const relatedPostsList = page.locator('.related-posts-list');
await expect(relatedPostsList).toBeVisible();
// Test tablet width
await page.setViewportSize({ width: 768, height: 1024 });
await expect(relatedPostsList).toBeVisible();
// Test mobile width
await page.setViewportSize({ width: 375, height: 667 });
await expect(relatedPostsList).toBeVisible();
});
});
test.describe('Multilingual Support', () => {
test('should display "Idioma" button and Spanish language option in language switcher dropdown', async ({ page }) => {
test.skip(process.env.TEST_NO_MENUS === 'true', 'Skipping test because TEST_NO_MENUS is true');
// Navigate to Spanish version
await page.goto(`${BASE_URL}/es/`);
// Open language switcher
const languageButton = page.locator('button', { hasText: 'Idioma' }).first();
await languageButton.click();
// Should see language options
await expect(page.locator('#languages-dropdown-header').getByText('Español')).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should use proper h3 heading hierarchy for related posts, social sharing, and table of contents sections', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Check that section headings are h3
await expect(page.locator('.related-posts h3')).toBeVisible();
await expect(page.locator('.social-sharing h3')).toBeVisible();
await expect(page.locator('.table-of-contents h3')).toBeVisible();
});
test('should allow keyboard navigation and focus on social sharing buttons', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Focus first share button
const shareButtons = page.locator('.share-buttons a');
const firstButton = shareButtons.first();
await firstButton.focus();
// Should be focusable
await expect(firstButton).toBeFocused();
});
test('should allow keyboard navigation and focus on table of contents links', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Focus first TOC link
const tocLinks = page.locator('.table-of-contents a');
const firstLink = tocLinks.first();
await firstLink.focus();
// Should be focusable
await expect(firstLink).toBeFocused();
});
test('should use semantic HTML with unordered list (ul/li) elements for related posts', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Related posts should use list elements
const relatedPostsList = page.locator('.related-posts ul');
await expect(relatedPostsList).toBeVisible();
// Each item should be a list item
const listItems = relatedPostsList.locator('li');
await expect(listItems.first()).toBeVisible();
});
});
test.describe('Performance', () => {
test('should load page with all new features in under 5 seconds including network idle state', async ({ page }) => {
const startTime = Date.now();
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
await page.waitForLoadState('networkidle');
const loadTime = Date.now() - startTime;
// Should load in under 5 seconds
expect(loadTime).toBeLessThan(5000);
});
test('should render all feature sections without causing cumulative layout shift after page load', async ({ page }) => {
await page.goto(`${BASE_URL}/blog/new-features-demo/`);
// Wait for page to be fully loaded
await page.waitForLoadState('networkidle');
// All main sections should be visible without scrolling
const relatedPosts = page.locator('.related-posts');
await expect(relatedPosts).toBeAttached();
});
});
});