Init Commit
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL: string = process.env.TEST_BASE_URL ?? 'http://localhost:1313';
|
||||
|
||||
if (!BASE_URL.startsWith('http')) {
|
||||
throw new Error('TEST_BASE_URL must be a valid URL starting with http:// or https://');
|
||||
}
|
||||
console.log(`Running tests against ${BASE_URL}`);
|
||||
|
||||
test.describe('Theme basic functionality', () => {
|
||||
test.beforeAll(async () => {
|
||||
// Health check
|
||||
try {
|
||||
await fetch(BASE_URL);
|
||||
} catch (error) {
|
||||
console.error(`Failed to connect to ${BASE_URL}. Is the Hugo server running?`);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('homepage loads correctly', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await expect(page).toHaveTitle(/Adritian/);
|
||||
});
|
||||
|
||||
test('theme switcher works', async ({ page }) => {
|
||||
test.skip(process.env.TEST_NO_MENUS === 'true', 'Skipping test because TEST_NO_MENUS is true');
|
||||
|
||||
// set the browser theme preference to "light"
|
||||
// Emulate dark color scheme
|
||||
await page.emulateMedia({ colorScheme: 'dark' });
|
||||
|
||||
|
||||
await page.goto(BASE_URL);
|
||||
// attribute exists
|
||||
await expect(page.locator('html')).toHaveAttribute('data-bs-theme');
|
||||
// click on theme switcher
|
||||
await page.click('div#footer-color-selector button.bd-theme-selector');
|
||||
/* click on the html element:
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
|
||||
☀️ Light
|
||||
</button>
|
||||
*/
|
||||
await page.getByRole('button', { name: '☀️ Light' }).last().click();
|
||||
await expect(page.locator('html')).toHaveAttribute('data-bs-theme', 'light');
|
||||
// click on theme switcher
|
||||
await page.click('div#footer-color-selector button.bd-theme-selector');
|
||||
/* click on the html element:
|
||||
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="dark" aria-pressed="true">
|
||||
🌑 Dark
|
||||
<span class="theme-icon dark d-none" aria-hidden="true"></span>
|
||||
</button>
|
||||
*/
|
||||
await expect(page.getByText('🌑 Dark').last()).toBeVisible();
|
||||
await expect(page.locator('html')).toHaveAttribute('data-bs-theme', 'light');
|
||||
});
|
||||
|
||||
test('navigation is visible', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await expect(page.locator('nav.navbar')).toBeVisible();
|
||||
});
|
||||
|
||||
test('footer links work', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await expect(page.locator('.footer_links')).toBeVisible();
|
||||
});
|
||||
|
||||
test('skip to content link works', async ({ page }) => {
|
||||
// Navigate to a content page
|
||||
await page.goto(`${BASE_URL}/blog`);
|
||||
|
||||
// The skip link should be initially hidden but in the DOM
|
||||
const skipLink = page.locator('.skip-to-content-link');
|
||||
await expect(skipLink).toBeAttached();
|
||||
|
||||
// Focus the skip link (simulates tabbing to it)
|
||||
await skipLink.focus();
|
||||
|
||||
// Now it should be visible
|
||||
await expect(skipLink).toBeVisible();
|
||||
|
||||
// Press Enter key on the skip link
|
||||
await skipLink.press('Enter');
|
||||
|
||||
// Verify we jumped to the main content
|
||||
// Check URL hash
|
||||
await expect(page).toHaveURL(/#main-content$/);
|
||||
|
||||
// Verify focus is moved to main content
|
||||
const mainContent = page.locator('#main-content');
|
||||
await expect(mainContent).toBeFocused();
|
||||
});
|
||||
});
|
||||
|
||||
test('footer_right should contain exactly 2 dropdown element for language and theme', async ({ page }) => {
|
||||
test.skip(process.env.TEST_NO_MENUS === 'true', 'Skipping test because TEST_NO_MENUS is true');
|
||||
|
||||
await page.goto(`${BASE_URL}/disable-menu/`);
|
||||
|
||||
// Verify footer and footer_right exist
|
||||
await expect(page.locator('footer')).toBeAttached();
|
||||
await expect(page.locator('footer .footer_right')).toBeAttached();
|
||||
|
||||
// Verify exactly 2 dropdown elements exist within footer_right
|
||||
await expect(page.locator('footer .footer_right .dropdown')).toHaveCount(2);
|
||||
});
|
||||
|
||||
|
||||
test('should load all homepage images correctly', async ({ page }) => {
|
||||
await page.goto('http://localhost:1313');
|
||||
|
||||
// Wait for network to be idle (most images loaded)
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Helper function to check images - handles both regular and lazy-loaded images
|
||||
const checkImageElements = async (selector: string) => {
|
||||
const elements = page.locator(selector);
|
||||
const count = await elements.count();
|
||||
|
||||
console.log(`Checking images with selector "${selector}" - found ${count} elements`);
|
||||
await expect(count).toBeGreaterThan(0);
|
||||
if (count === 0) throw new Error(`Expected to find at least one element matching "${selector}"`);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const element = elements.nth(i);
|
||||
await expect(element).toBeVisible();
|
||||
|
||||
const isLazyLoaded = await element.evaluate(el =>
|
||||
el.classList.contains('lozad') || el.hasAttribute('data-src'));
|
||||
|
||||
if (isLazyLoaded) {
|
||||
// For lazy-loaded images, check that data attributes exist
|
||||
const dataSrc = await element.getAttribute('data-src');
|
||||
expect(dataSrc).toBeTruthy();
|
||||
} else {
|
||||
// For regular images, check naturalWidth
|
||||
const naturalWidth = await element.evaluate(el => (el as HTMLImageElement).naturalWidth);
|
||||
await expect(naturalWidth).toBeGreaterThan(0);
|
||||
if (naturalWidth === 0) throw new Error('Image should have loaded (naturalWidth > 0)');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check all required image elements
|
||||
await checkImageElements('.profile-image img');
|
||||
|
||||
// .image-left-overflow might be an element with background image or contain an img
|
||||
const leftOverflowElement = page.locator('.image-left-overflow').first();
|
||||
await expect(leftOverflowElement).toBeVisible();
|
||||
|
||||
// Check if it contains any img elements
|
||||
const hasImg = await page.locator('.image-left-overflow img').count() > 0;
|
||||
if (hasImg) {
|
||||
await checkImageElements('.image-left-overflow img');
|
||||
}
|
||||
|
||||
await checkImageElements('.client-works-container picture img');
|
||||
await checkImageElements('.testimonial__author .picture img');
|
||||
});
|
||||
@@ -0,0 +1,364 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
test.describe('Blog page content', () => {
|
||||
test('should show "Demo Blog" heading and "Welcome to the demo blog" text', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/blog`);
|
||||
|
||||
// Verify heading is displayed
|
||||
await expect(page.getByRole('heading', { name: /demo blog/i })).toBeVisible();
|
||||
|
||||
// Verify body text is displayed
|
||||
await expect(page.getByText('Welcome to the demo blog')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should navigate to the sample blog article by clicking on the post link', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/blog`);
|
||||
await page.click('text=Sample content: formatting styles'); // Adjust text if needed
|
||||
await expect(page).toHaveURL(`${BASE_URL}/blog/sample/`);
|
||||
await expect(page.getByRole('heading', { name: /sample content/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL: string = process.env.TEST_BASE_URL ?? 'http://localhost:1313';
|
||||
|
||||
if (!BASE_URL.startsWith('http')) {
|
||||
throw new Error('TEST_BASE_URL must be a valid URL starting with http:// or https://');
|
||||
}
|
||||
console.log(`Running tests against ${BASE_URL}`);
|
||||
|
||||
test.describe('Contact Form Functionality', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Navigate to the page with the contact form (usually footer)
|
||||
await page.goto(BASE_URL);
|
||||
// Scroll to the bottom of the page where the contact form is typically located
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
});
|
||||
|
||||
test('contact form placeholders are visible', async ({ page }) => {
|
||||
// Check that all form fields have visible placeholders
|
||||
const nameInput = page.locator('form input[name="full_name"]');
|
||||
const emailInput = page.locator('form input[name="email"]');
|
||||
const phoneInput = page.locator('form input[name="phone"]');
|
||||
const messageTextarea = page.locator('form textarea[name="message"]');
|
||||
|
||||
// Verify the inputs exist
|
||||
await expect(nameInput).toBeVisible();
|
||||
await expect(emailInput).toBeVisible();
|
||||
await expect(phoneInput).toBeVisible();
|
||||
await expect(messageTextarea).toBeVisible();
|
||||
|
||||
// Verify that placeholders are set
|
||||
await expect(nameInput).toHaveAttribute('placeholder');
|
||||
await expect(emailInput).toHaveAttribute('placeholder');
|
||||
await expect(phoneInput).toHaveAttribute('placeholder');
|
||||
await expect(messageTextarea).toHaveAttribute('placeholder');
|
||||
|
||||
// Verify the placeholders aren't empty
|
||||
const namePlaceholder = await nameInput.getAttribute('placeholder');
|
||||
const emailPlaceholder = await emailInput.getAttribute('placeholder');
|
||||
const phonePlaceholder = await phoneInput.getAttribute('placeholder');
|
||||
const messagePlaceholder = await messageTextarea.getAttribute('placeholder');
|
||||
|
||||
expect(namePlaceholder).toBeTruthy();
|
||||
expect(emailPlaceholder).toBeTruthy();
|
||||
expect(phonePlaceholder).toBeTruthy();
|
||||
expect(messagePlaceholder).toBeTruthy();
|
||||
});
|
||||
|
||||
test('contact form placeholders match values from shortcode', async ({ page }) => {
|
||||
// Get the form field elements
|
||||
const nameInput = page.locator('form input[name="full_name"]');
|
||||
const emailInput = page.locator('form input[name="email"]');
|
||||
const phoneInput = page.locator('form input[name="phone"]');
|
||||
const messageTextarea = page.locator('form textarea[name="message"]');
|
||||
|
||||
// Get the placeholder values
|
||||
const namePlaceholder = await nameInput.getAttribute('placeholder');
|
||||
const emailPlaceholder = await emailInput.getAttribute('placeholder');
|
||||
const phonePlaceholder = await phoneInput.getAttribute('placeholder');
|
||||
const messagePlaceholder = await messageTextarea.getAttribute('placeholder');
|
||||
|
||||
// Log the placeholder values for debugging
|
||||
console.log('Placeholder values:', {
|
||||
name: namePlaceholder,
|
||||
email: emailPlaceholder,
|
||||
phone: phonePlaceholder,
|
||||
message: messagePlaceholder
|
||||
});
|
||||
|
||||
// Verify placeholders have meaningful content
|
||||
// The specific values will depend on what's set in your shortcode or defaults
|
||||
// We're checking they're not empty and not just generic defaults
|
||||
expect(namePlaceholder).toBeTruthy();
|
||||
expect(namePlaceholder?.length).toBeGreaterThan(2);
|
||||
|
||||
expect(emailPlaceholder).toBeTruthy();
|
||||
expect(emailPlaceholder?.length).toBeGreaterThan(2);
|
||||
|
||||
expect(phonePlaceholder).toBeTruthy();
|
||||
expect(phonePlaceholder?.length).toBeGreaterThan(2);
|
||||
|
||||
expect(messagePlaceholder).toBeTruthy();
|
||||
expect(messagePlaceholder?.length).toBeGreaterThan(2);
|
||||
|
||||
// Check that placeholders are not just "placeholder" or generic text
|
||||
expect(namePlaceholder?.toLowerCase()).not.toBe('placeholder');
|
||||
expect(emailPlaceholder?.toLowerCase()).not.toBe('placeholder');
|
||||
expect(phonePlaceholder?.toLowerCase()).not.toBe('placeholder');
|
||||
expect(messagePlaceholder?.toLowerCase()).not.toBe('placeholder');
|
||||
});
|
||||
|
||||
test('contact form has proper CSS styling', async ({ page }) => {
|
||||
// Verify that the form elements have the expected CSS classes
|
||||
await expect(page.locator('section.section--contact')).toBeVisible();
|
||||
await expect(page.locator('form.contact__form')).toHaveClass(/contact__form/);
|
||||
|
||||
// Check specific form field styling
|
||||
await expect(page.locator('input[name="full_name"]')).toHaveClass(/form-control/);
|
||||
await expect(page.locator('input[name="email"]')).toHaveClass(/form-control/);
|
||||
await expect(page.locator('input[name="phone"]')).toHaveClass(/form-control/);
|
||||
await expect(page.locator('textarea[name="message"]')).toHaveClass(/form-control/);
|
||||
|
||||
// Check submit button styling
|
||||
await expect(page.locator('form.contact__form button[type="submit"]')).toHaveClass(/btn btn-primary/);
|
||||
});
|
||||
|
||||
test('message textarea has the correct number of rows', async ({ page }) => {
|
||||
// Verify that the textarea has the correct number of rows (default is 2)
|
||||
const messageTextarea = page.locator('form textarea[name="message"]');
|
||||
const rowsValue = await messageTextarea.getAttribute('rows');
|
||||
|
||||
// The actual value will depend on your implementation, but should be at least "2"
|
||||
expect(Number(rowsValue)).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
test('contact section displays correct information from shortcode', async ({ page }) => {
|
||||
// Assuming the contact form has a title
|
||||
const contactTitle = page.locator('section.section--contact h2');
|
||||
await expect(contactTitle).toBeVisible();
|
||||
|
||||
// Check contact information is displayed correctly
|
||||
// For example, the phone number, email, and address sections
|
||||
const contactInfo = page.locator('.contact__info');
|
||||
await expect(contactInfo).toBeVisible();
|
||||
|
||||
// Check headings in contact info section
|
||||
const infoHeadings = contactInfo.locator('h3');
|
||||
// Replace with a meaningful assertion if the expected count is known
|
||||
await expect(infoHeadings).toHaveCount(3); // Example: Replace 3 with the actual expected count
|
||||
// If your site has email info displayed, check it
|
||||
const emailSection = contactInfo.locator('h3', { hasText: /mail|email/i }).first();
|
||||
if (await emailSection.count() > 0) {
|
||||
await expect(emailSection).toBeVisible();
|
||||
// Check if there's an email value below the heading
|
||||
const emailValue = emailSection.locator('+ span');
|
||||
await expect(emailValue).toBeVisible();
|
||||
}
|
||||
|
||||
// If your site has phone info displayed, check it
|
||||
const phoneSection = contactInfo.locator('h3', { hasText: /phone/i }).first();
|
||||
if (await phoneSection.count() > 0) {
|
||||
await expect(phoneSection).toBeVisible();
|
||||
// Check if there's a phone value below the heading
|
||||
const phoneValue = phoneSection.locator('+ span');
|
||||
await expect(phoneValue).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('submit button displays text from the shortcode', async ({ page }) => {
|
||||
// Check that the submit button has text content
|
||||
const submitButton = page.locator('form.contact__form button[type="submit"]');
|
||||
await expect(submitButton).toBeVisible();
|
||||
const buttonText = await submitButton.textContent();
|
||||
expect(buttonText?.trim()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('form action is set correctly', async ({ page }) => {
|
||||
// Verify that the form has an action attribute
|
||||
const form = page.locator('form').first();
|
||||
const action = await form.getAttribute('action');
|
||||
expect(action).toBeTruthy();
|
||||
|
||||
// Also check the method attribute
|
||||
const method = await form.getAttribute('method');
|
||||
expect(method).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
test.describe('Experience items functionality', () => {
|
||||
test('navigates to experience items in English', async ({ page }) => {
|
||||
// Start from homepage
|
||||
await page.goto(BASE_URL);
|
||||
|
||||
// Click on first experience item
|
||||
await page.getByText('Chief Intern').first().click();
|
||||
|
||||
// Verify URL and content
|
||||
await expect(page).toHaveURL(/\/experience\/job-2\/?$/);
|
||||
await expect(page.getByText('Internet Affairs Inc.').first()).toBeVisible();
|
||||
await expect(page.getByText('Stavanger, Norway').first()).toBeVisible();
|
||||
await expect(page.getByText('2023-2024').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('navigates to experience items in Spanish', async ({ page }) => {
|
||||
// Go to Spanish version
|
||||
await page.goto(`${BASE_URL}/es/`);
|
||||
|
||||
// Click on first experience item
|
||||
await page.getByText('Becario Jefe').first().click();
|
||||
|
||||
// Verify URL and Spanish content
|
||||
await expect(page).toHaveURL(/\/es\/experience\/job-2\/?$/);
|
||||
await expect(page.getByText('Internet Affairs Inc.').first()).toBeVisible();
|
||||
await expect(page.getByText('Stavanger, Noruega').first()).toBeVisible();
|
||||
await expect(page.getByText('Stavanger, Noruega').first()).toBeVisible();
|
||||
await expect(page.getByText('2023-2024').first()).toBeVisible();
|
||||
await expect(page.getByText('Arreglando el mundo, un byte a la vez').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('navigates to experience items in French', async ({ page }) => {
|
||||
// Go to French version
|
||||
await page.goto(`${BASE_URL}/fr/`);
|
||||
|
||||
// Click on first experience item
|
||||
await page.getByText('Stagiaire en Chef').first().click();
|
||||
|
||||
await expect(page).toHaveURL(/\/fr\/experience\/job-2\/?$/);
|
||||
// Remove specific count checks as they might be fragile
|
||||
await expect(page.getByText('Internet Affairs Inc.').first()).toBeVisible();
|
||||
await expect(page.getByText('Stavanger').first()).toBeVisible();
|
||||
await expect(page.getByText('2023-2024').first()).toBeVisible();
|
||||
await expect(page.getByText('Réparer le monde, un octet à la fois').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('verifies experience list page shows all items', async ({ page }) => {
|
||||
// Go to experience list page
|
||||
await page.goto(`${BASE_URL}/experience`);
|
||||
|
||||
// Verify multiple experience items are visible
|
||||
const experienceItems = page.locator('.experience');
|
||||
await expect(experienceItems).toHaveCount(4);
|
||||
|
||||
// Verify specific job titles are present
|
||||
await expect(page.locator('.experience__title', { hasText: 'Chief Intern' })).toBeVisible();
|
||||
await expect(page.locator('.experience__title', { hasText: 'Junior Intern' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('verifies job-card content and structure', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/experience/job-2`);
|
||||
|
||||
// Verify the job-card container exists
|
||||
const jobCard = page.locator('.job-card');
|
||||
await expect(jobCard).toBeVisible();
|
||||
|
||||
// Verify header content
|
||||
await expect(page.getByRole('heading', { name: 'Chief Intern' }).first()).toBeVisible();
|
||||
await expect(page.locator('.text-muted', { hasText: 'Internet Affairs Inc.' })).toBeVisible();
|
||||
|
||||
// Verify metadata
|
||||
await expect(page.locator('.job-card__dates')).toHaveText('2023-2024');
|
||||
await expect(page.locator('.job-card__location')).toContainText('Stavanger, Norway');
|
||||
});
|
||||
|
||||
test('verifies company logo is visible on job page', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/experience/job-1`);
|
||||
|
||||
// Verify the company logo is visible
|
||||
const companyLogo = page.locator('.company-logo');
|
||||
await expect(companyLogo).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
test.describe('HTML validation', () => {
|
||||
test('homepage has valid HTML structure', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
|
||||
// Check basic HTML structure
|
||||
await expect(page.locator('html')).toHaveAttribute('lang');
|
||||
await expect(page.locator('head')).toBeAttached();
|
||||
await expect(page.locator('body')).toBeAttached();
|
||||
|
||||
// Verify meta tags
|
||||
await expect(page.locator('meta[charset]')).toBeAttached();
|
||||
await expect(page.locator('meta[name="viewport"]')).toBeAttached();
|
||||
await expect(page.locator('meta[name="description"]')).toBeAttached();
|
||||
|
||||
// Check main structural elements
|
||||
await expect(page.locator('header')).toBeAttached();
|
||||
await expect(page.locator('footer')).toBeAttached();
|
||||
|
||||
});
|
||||
|
||||
test('page has valid theme attributes', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
|
||||
const html = page.locator('html');
|
||||
|
||||
// Check theme attributes are present
|
||||
await expect(html).toHaveAttribute('data-bs-theme');
|
||||
|
||||
// If theme is forced, these attributes should be present
|
||||
const isForcedTheme = await html.getAttribute('theme-forced');
|
||||
if (isForcedTheme === 'true') {
|
||||
await expect(html).toHaveAttribute('theme-auto', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = 'http://localhost:1313';
|
||||
|
||||
test.describe('Language switching functionality', () => {
|
||||
test('switches between languages and verifies lang attribute', async ({ page }) => {
|
||||
test.skip(process.env.TEST_NO_MENUS === 'true', 'Skipping test because TEST_NO_MENUS is true');
|
||||
|
||||
// Go to homepage
|
||||
await page.goto(BASE_URL);
|
||||
|
||||
// Verify initial English state
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'en');
|
||||
await expect(page.getByText('Language').last()).toBeVisible();
|
||||
await expect(page.getByText('Experience').first()).toBeVisible();
|
||||
|
||||
// Switch to Spanish
|
||||
await page.locator('div#footer-language-selector button').click();
|
||||
await page.getByText('Español').last().click();
|
||||
|
||||
// Verify Spanish
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'es');
|
||||
await expect(page.getByText('Idioma').last()).toBeVisible();
|
||||
await expect(page.getByText('Experiencia').first()).toBeVisible();
|
||||
|
||||
// Switch to French
|
||||
await page.locator('div#footer-language-selector button').click();
|
||||
await page.getByText('Français').last().click();
|
||||
|
||||
// Verify French
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
|
||||
await expect(page.getByText('Langue').last()).toBeVisible();
|
||||
await expect(page.getByText('Expérience').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('maintains language when navigating to experience pages', async ({ page }) => {
|
||||
// Start in Spanish
|
||||
await page.goto(`${BASE_URL}/es/`);
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'es');
|
||||
|
||||
// Click on an experience link
|
||||
await page.getByText('Ver todo').click();
|
||||
|
||||
// Verify URL and language maintained
|
||||
await expect(page).toHaveURL(`${BASE_URL}/es/experience/`);
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'es');
|
||||
await expect(page.getByText('Experiencia').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('preserves translations across page types', async ({ page }) => {
|
||||
test.skip(process.env.TEST_NO_MENUS === 'true', 'Skipping test because TEST_NO_MENUS is true');
|
||||
|
||||
// Go to French experience page
|
||||
await page.goto(`${BASE_URL}/fr/experience`);
|
||||
|
||||
// Verify French state
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
|
||||
await expect(page.getByText('Expérience').first()).toBeVisible();
|
||||
|
||||
// Navigate to home
|
||||
await page.getByText('ACCUEIL').first().click();
|
||||
|
||||
// Verify language maintained
|
||||
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
|
||||
await expect(page.getByText('Langue').last()).toBeVisible();
|
||||
});
|
||||
|
||||
test('section IDs are translated in Spanish and French', async ({ page }) => {
|
||||
// Spanish
|
||||
await page.goto(`${BASE_URL}/es/`);
|
||||
await expect(page.locator('#sobre-mi')).toBeVisible();
|
||||
await expect(page.locator('#social')).toBeVisible();
|
||||
await expect(page.locator('#sobre-mi')).toBeVisible();
|
||||
await expect(page.locator('#formacion-academica')).toBeVisible();
|
||||
await expect(page.locator('#experiencia-laboral')).toBeVisible();
|
||||
await expect(page.locator('#trabajo')).toBeVisible();
|
||||
await expect(page.locator('#testimonios')).toBeVisible();
|
||||
|
||||
// French
|
||||
await page.goto(`${BASE_URL}/fr/`);
|
||||
await expect(page.locator('#section-vedette')).toBeVisible();
|
||||
await expect(page.locator('#liens-plateforme')).toBeVisible();
|
||||
await expect(page.locator('#a-propos')).toBeVisible();
|
||||
await expect(page.locator('#formation-academique')).toBeVisible();
|
||||
await expect(page.locator('#experience-professionnelle')).toBeVisible();
|
||||
await expect(page.locator('#travail')).toBeVisible();
|
||||
await expect(page.locator('#temoignages')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
test.describe('List style functionality', () => {
|
||||
test('summary style shows full post previews', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/tags/sample`);
|
||||
|
||||
// Check for summary style elements
|
||||
const articleSummary = page.locator('article.post.summary');
|
||||
await expect(articleSummary).toBeVisible();
|
||||
|
||||
// Verify summary components are present
|
||||
await expect(page.locator('article.post.summary h2').first()).toBeVisible();
|
||||
await expect(page.locator('article.post.summary .post-meta').first()).toBeVisible();
|
||||
await expect(page.locator('article.post.summary .post-summary').first()).toBeVisible();
|
||||
await expect(page.locator('article.post.summary .btn-outline-secondary').first()).toBeVisible();
|
||||
|
||||
// Verify tags are displayed
|
||||
await expect(page.locator('ul.tags li')).toHaveCount(2);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,364 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
test.describe('Site without menus', () => {
|
||||
// Read environment variable TEST_NO_MENUS
|
||||
test.skip(process.env.TEST_NO_MENUS !== 'true', 'Skipping test because TEST_NO_MENUS is true');
|
||||
|
||||
test('page should not have navigation menus when disabled', async ({ page }) => {
|
||||
// Navigate to the site with disabled menus
|
||||
await page.goto(`${BASE_URL}/disable-menu/`);
|
||||
|
||||
// Verify header still exists but doesn't contain navigation
|
||||
await expect(page.locator('header')).toBeAttached();
|
||||
|
||||
// Verify language selector is not present
|
||||
await expect(page.locator('#selector-language')).not.toBeAttached();
|
||||
|
||||
// Verify main menu elements are not present
|
||||
await expect(page.locator('#main-menu')).not.toBeAttached();
|
||||
await expect(page.locator('#main-menu-mobile')).not.toBeAttached();
|
||||
|
||||
// Verify the page still has basic required elements
|
||||
await expect(page.locator('main')).toBeAttached();
|
||||
await expect(page.locator('footer')).toBeAttached();
|
||||
});
|
||||
|
||||
|
||||
test('content should still be properly displayed', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}`);
|
||||
|
||||
// Verify main content area exists and is visible
|
||||
const mainContent = page.locator('#main-content');
|
||||
await expect(mainContent).toBeVisible();
|
||||
await expect(mainContent.locator('.display-1')).toBeVisible();
|
||||
|
||||
// Verify page title is still present
|
||||
await expect(page.locator('h1').first()).toBeVisible();
|
||||
});
|
||||
|
||||
//
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL: string = process.env.TEST_BASE_URL ?? 'http://localhost:1313';
|
||||
|
||||
if (!BASE_URL.startsWith('http')) {
|
||||
throw new Error('TEST_BASE_URL must be a valid URL starting with http:// or https://');
|
||||
}
|
||||
|
||||
test.describe('Search functionality', () => {
|
||||
test.beforeAll(async () => {
|
||||
// Health check
|
||||
try {
|
||||
await fetch(BASE_URL);
|
||||
} catch (error) {
|
||||
console.error(`Failed to connect to ${BASE_URL}. Is the Hugo server running?`);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
test('search page loads correctly', async ({ page }) => {
|
||||
const response = await page.goto(`${BASE_URL}/search`);
|
||||
await expect(page).toHaveTitle(/Search/);
|
||||
await expect(page.locator('h2.mb-4.text-center')).toHaveText('Search the Site');
|
||||
await expect(page.locator('#search-query')).toBeVisible();
|
||||
|
||||
// Check that the search page returns HTTP status 200
|
||||
});
|
||||
|
||||
test('searching for "theme" shows exactly one result', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/search`);
|
||||
|
||||
// Wait for the page to be fully loaded
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Type "theme" in the search box
|
||||
await page.locator('#search-query').fill('theme');
|
||||
|
||||
// Wait for search results to appear (the debounce is 300ms)
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check URL is updated with search parameter
|
||||
await expect(page).toHaveURL(/s=theme/);
|
||||
|
||||
// Verify exactly one result is shown
|
||||
await expect(page.locator('#search-results div[id^="summary-"]')).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('clearing search box removes results', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/search`);
|
||||
|
||||
// Type something to get results first
|
||||
await page.locator('#search-query').fill('theme');
|
||||
|
||||
// Wait for search results to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify we have at least one result
|
||||
await expect(page.locator('#search-results div[id^="summary-"]').first()).toBeVisible();
|
||||
|
||||
// Now clear the search box
|
||||
await page.locator('#search-query').fill('');
|
||||
|
||||
// Wait for the UI to update
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check that results are cleared and we have the prompt message
|
||||
await expect(page.locator('#search-results div[id^="summary-"]')).toHaveCount(0);
|
||||
await expect(page.locator('#search-results .alert')).toBeVisible();
|
||||
await expect(page.locator('#search-results .alert')).toHaveText('Please enter at least 2 characters to search');
|
||||
});
|
||||
|
||||
test('searching for "adritian" shows multiple results', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/search`);
|
||||
|
||||
// Type "adritian" in the search box
|
||||
await page.locator('#search-query').fill('adritian');
|
||||
|
||||
// Wait for search results to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check URL is updated with search parameter
|
||||
await expect(page).toHaveURL(/s=adritian/);
|
||||
|
||||
// Verify we have multiple results
|
||||
const resultsCount = await page.locator('#search-results div[id^="summary-"]').count();
|
||||
expect(resultsCount).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
test('search handles special characters correctly', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/search`);
|
||||
|
||||
// Test with special characters that might break URL encoding
|
||||
const specialCharQuery = 'test & special';
|
||||
await page.locator('#search-query').fill(specialCharQuery);
|
||||
|
||||
// Wait for search results to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check URL is properly encoded
|
||||
await expect(page).toHaveURL(`${BASE_URL}/search/?s=test+%26+special`.replace('&', '%26'));
|
||||
|
||||
// Verify the search box still contains the original query
|
||||
await expect(page.locator('#search-query')).toHaveValue(specialCharQuery);
|
||||
});
|
||||
|
||||
test('search updates results in real-time as user types', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/search`);
|
||||
|
||||
// Type one character at a time and verify the behavior
|
||||
await page.locator('#search-query').fill('a');
|
||||
|
||||
// With just one character, we should see the minimum character message
|
||||
await page.waitForTimeout(500);
|
||||
await expect(page.locator('#search-results .alert')).toContainText('at least 2 characters');
|
||||
|
||||
// Type a second character to reach minimum
|
||||
await page.locator('#search-query').fill('ad');
|
||||
|
||||
// Wait for search results to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Should now show results
|
||||
await expect(page.locator('#search-results div[id^="summary-"]').first()).toBeVisible();
|
||||
|
||||
// Continue typing to refine search
|
||||
await page.locator('#search-query').fill('adri');
|
||||
|
||||
// Wait for updated results
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Should still show results, potentially different count
|
||||
await expect(page.locator('#search-results div[id^="summary-"]').first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
test.describe('Hugo sections functionality', () => {
|
||||
test('articles section loads correctly', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/articles/`);
|
||||
|
||||
// Check that section page loads
|
||||
await expect(page.locator('h1')).toContainText('Articles');
|
||||
|
||||
// Verify section content is displayed
|
||||
await expect(page.getByText('Welcome to the articles section')).toBeVisible();
|
||||
|
||||
// Check that article posts are listed
|
||||
const articleCount = await page.locator('article.post').count();
|
||||
expect(articleCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('individual article pages load correctly', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/articles/first-article/`);
|
||||
|
||||
// Check that individual article loads
|
||||
await expect(page.locator('h1')).toContainText('First Article');
|
||||
await expect(page.getByText('This is the first article content')).toBeVisible();
|
||||
});
|
||||
|
||||
test('news section loads correctly', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/news/`);
|
||||
|
||||
// Check that section page loads
|
||||
await expect(page.locator('h1')).toContainText('News');
|
||||
// Verify section content is displayed
|
||||
await expect(page.getByText('Latest news updates').first()).toBeVisible();
|
||||
|
||||
// Check that news posts are listed
|
||||
const newsCount = await page.locator('article.post').count();
|
||||
expect(newsCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('individual news pages load correctly', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/news/breaking-news/`);
|
||||
|
||||
// Check that individual news item loads
|
||||
await expect(page.locator('h1')).toContainText('Breaking News');
|
||||
await expect(page.getByText('This is breaking news content')).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
test('section pagination works if applicable', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/articles/`);
|
||||
|
||||
// Check if pagination exists and works
|
||||
const paginationExists = await page.locator('.pagination').count() > 0;
|
||||
|
||||
if (paginationExists) {
|
||||
// Test pagination if it exists
|
||||
await expect(page.locator('.pagination')).toBeVisible();
|
||||
|
||||
// Check if there's a next page link and it works
|
||||
const nextLink = page.locator('.pagination a[rel="next"]');
|
||||
if (await nextLink.count() > 0) {
|
||||
await nextLink.click();
|
||||
await expect(page.locator('h1')).toContainText('Articles');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('section RSS feeds are accessible', async ({ page }) => {
|
||||
// Test articles RSS feed
|
||||
const articlesRssResponse = await page.request.get(`${BASE_URL}/articles/index.xml`);
|
||||
expect(articlesRssResponse.status()).toBe(200);
|
||||
|
||||
// Test news RSS feed
|
||||
const newsRssResponse = await page.request.get(`${BASE_URL}/news/index.xml`);
|
||||
expect(newsRssResponse.status()).toBe(200);
|
||||
});
|
||||
|
||||
test('existing blog section still works', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/blog/`);
|
||||
|
||||
// Verify existing blog section still works
|
||||
await expect(page.locator('h1').first()).toContainText('Demo Blog');
|
||||
await expect(page.getByText('Welcome to the demo blog')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
test.describe('Tag functionality', () => {
|
||||
test('visiting tags list', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/tags`);
|
||||
// Verify we reached the tags page
|
||||
await expect(page.getByRole('heading', { name: 'Tags', level: 1 })).toBeVisible();
|
||||
});
|
||||
|
||||
test('verify there are 2 tags, each with 1 article', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/tags`);
|
||||
await expect(page.locator('ul.list-taxonomy li')).toHaveCount(11);
|
||||
|
||||
// Each item shows how many articles
|
||||
// e.g. "Lorem-Ipsum (1)" or "Sample (1)"
|
||||
await expect(page.getByText('Sample (1)')).toBeVisible();
|
||||
});
|
||||
|
||||
test('click on a tag -> renders the tag page', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/tags`);
|
||||
await page.getByRole('link', { name: /Sample/ }).click();
|
||||
// Verify tag page
|
||||
await expect(page).toHaveURL(/\/tags\/sample/);
|
||||
await expect(page.getByRole('heading', { name: 'Sample content: formatting styles' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('tag page content links', async ({ page }) => {
|
||||
// Go directly to the Sample tag page
|
||||
await page.goto(`${BASE_URL}/tags/sample`);
|
||||
// Verify any article link is visible (1 article)
|
||||
const articleLink = page.locator('a[href*="/blog/sample/"]');
|
||||
await expect(articleLink.first()).toBeVisible();
|
||||
await articleLink.first().click();
|
||||
// Article should list the tags
|
||||
await expect(page.locator('ul.tags li')).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:1313';
|
||||
|
||||
// Extract repeated selectors into constants
|
||||
const FOOTER_THEME_CONTAINER = 'div#footer-color-selector';
|
||||
const FOOTER_THEME_SELECTOR = `${FOOTER_THEME_CONTAINER} button.bd-theme-selector`;
|
||||
const FOOTER_THEME_LIGHT = `${FOOTER_THEME_CONTAINER} .dropdown-item[data-bs-theme-value="light"]`;
|
||||
const FOOTER_THEME_DARK = `${FOOTER_THEME_CONTAINER} .dropdown-item[data-bs-theme-value="dark"]`;
|
||||
const FOOTER_THEME_AUTO = `${FOOTER_THEME_CONTAINER} .dropdown-item[data-bs-theme-value="auto"]`;
|
||||
|
||||
test.describe('Theme switching functionality', () => {
|
||||
test('verifies bold styling of selected theme and updates on switch', async ({ page }) => {
|
||||
test.skip(process.env.TEST_NO_MENUS === 'true', 'Skipping test because TEST_NO_MENUS is true');
|
||||
|
||||
// Go to homepage
|
||||
await page.goto(BASE_URL);
|
||||
|
||||
// Open theme dropdown
|
||||
await page.locator(FOOTER_THEME_SELECTOR).click();
|
||||
|
||||
// Verify initial Light theme is bold (font-weight: 700)
|
||||
const lightButton = page.locator(FOOTER_THEME_LIGHT);
|
||||
await expect(lightButton).toHaveCSS('font-weight', '700');
|
||||
|
||||
// Switch to Dark theme
|
||||
await page.locator(FOOTER_THEME_DARK).click();
|
||||
|
||||
// Verify Dark theme is now bold and Light is not
|
||||
const darkButton = page.locator(FOOTER_THEME_DARK);
|
||||
await expect(darkButton).toHaveCSS('font-weight', '700');
|
||||
await expect(lightButton).toHaveCSS('font-weight', '400');
|
||||
|
||||
// Switch to Auto theme
|
||||
await page.locator(FOOTER_THEME_SELECTOR).click();
|
||||
await page.locator(FOOTER_THEME_AUTO).click();
|
||||
|
||||
// Verify Auto theme is bold and others are not
|
||||
const autoButton = page.locator(FOOTER_THEME_AUTO);
|
||||
await expect(autoButton).toHaveCSS('font-weight', '700');
|
||||
await expect(darkButton).toHaveCSS('font-weight', '400');
|
||||
await expect(lightButton).toHaveCSS('font-weight', '400');
|
||||
});
|
||||
|
||||
test('theme selection persists after page reload', async ({ page }) => {
|
||||
test.skip(process.env.TEST_NO_MENUS === 'true', 'Skipping test because TEST_NO_MENUS is true');
|
||||
|
||||
// Go to homepage
|
||||
await page.goto(BASE_URL);
|
||||
|
||||
// Switch to Dark theme
|
||||
await page.locator(FOOTER_THEME_SELECTOR).click();
|
||||
await page.locator(FOOTER_THEME_DARK).click();
|
||||
|
||||
// Reload page
|
||||
await page.reload();
|
||||
|
||||
// Open dropdown and verify Dark theme is still bold
|
||||
await page.locator(FOOTER_THEME_SELECTOR).click();
|
||||
const darkButton = page.locator(FOOTER_THEME_DARK);
|
||||
await expect(darkButton).toHaveCSS('font-weight', '700');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user