personal-website-sveltekit/tests/home.test.ts

226 lines
9.1 KiB
TypeScript
Raw Normal View History

import { expect, test } from '@playwright/test';
test.describe('Home page', () => {
test('has expected main heading', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toHaveText("Hello! I'm Bradley Shellnut.");
});
test('header/footer links hover: color becomes shellYellow', async ({ page }) => {
await page.goto('/');
const shellYellow = await page.evaluate(() => {
const probe = document.createElement('div');
probe.style.color = 'var(--shellYellow)';
document.body.appendChild(probe);
const color = getComputedStyle(probe).color;
probe.remove();
return color;
});
const areas = [
'header[aria-label="header navigation"]',
'footer nav[aria-label="footer navigation"]',
];
for (const area of areas) {
const nav = page.locator(area);
await expect(nav).toBeVisible();
const link = nav.getByRole('link', { name: 'Portfolio', exact: true });
await expect(link).toBeVisible();
const before = await link.evaluate((el) => {
const cs = getComputedStyle(el as Element) as CSSStyleDeclaration;
return { color: cs.color };
});
await link.hover();
const after = await link.evaluate((el) => {
const cs = getComputedStyle(el as Element) as CSSStyleDeclaration;
return { color: cs.color };
});
expect(after.color).toBe(shellYellow);
expect(after.color).not.toBe(before.color);
}
});
test('current page (Home) link is active in header and footer', async ({ page }) => {
await page.goto('/');
const areas = [
'header[aria-label="header navigation"]',
'footer nav[aria-label="footer navigation"]',
];
for (const area of areas) {
const nav = page.locator(area);
const link = nav.getByRole('link', { name: 'Home', exact: true });
await expect(link).toBeVisible();
const isActive = await link.evaluate((el) => (el as Element).classList.contains('active'));
expect(isActive).toBeTruthy();
}
});
test('header navigation links go to correct routes', async ({ page }) => {
await page.goto('/');
const headerNav = page.locator('header[aria-label="header navigation"]');
// About
await headerNav.getByRole('link', { name: 'About', exact: true }).click();
await expect(page).toHaveURL(/\/about\/?$/);
// Portfolio
await headerNav.getByRole('link', { name: 'Portfolio', exact: true }).click();
await expect(page).toHaveURL(/\/portfolio\/?$/);
// Uses
await headerNav.getByRole('link', { name: 'Uses', exact: true }).click();
await expect(page).toHaveURL(/\/uses\/?$/);
// Home
await headerNav.getByRole('link', { name: 'Home', exact: true }).click();
await expect(page).toHaveURL(/\/?$/);
});
test('header navigation shows expected links', async ({ page }) => {
await page.goto('/');
const headerNavContainer = page.locator('header[aria-label="header navigation"]');
await expect(headerNavContainer).toBeVisible();
await expect(headerNavContainer.getByRole('link', { name: 'Home', exact: true })).toBeVisible();
await expect(headerNavContainer.getByRole('link', { name: 'About', exact: true })).toBeVisible();
await expect(headerNavContainer.getByRole('link', { name: 'Portfolio', exact: true })).toBeVisible();
await expect(headerNavContainer.getByRole('link', { name: 'Uses', exact: true })).toBeVisible();
});
test('shows key sections', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { level: 2, name: 'Currently listening to:' })).toBeVisible();
await expect(page.getByRole('heading', { level: 2, name: 'Favorite Articles' })).toBeVisible();
});
test('renders Bandcamp albums (max 6)', async ({ page }) => {
await page.goto('/');
const albumImages = page.locator('.albumsStyles .album-artwork');
const count = await albumImages.count();
expect(count).toBeGreaterThan(0);
expect(count).toBeLessThanOrEqual(6);
});
test('renders at least one favorite article card', async ({ page }) => {
await page.goto('/');
const cards = page.locator('section.articles article.card');
await expect(cards.first()).toBeVisible();
});
test('"more articles" link points to /articles and navigates', async ({ page }) => {
await page.goto('/');
const more = page.locator('a.moreArticles');
await expect(more).toHaveAttribute('href', '/articles');
await expect(more).toContainText('more articles');
await more.click();
await expect(page).toHaveURL(/\/articles(\/\d+)?\/?$/);
});
test('has social/contact links', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('link', { name: 'Contact through LinkedIn', exact: true })).toBeVisible();
await expect(page.getByRole('link', { name: 'Contact through Github', exact: true })).toBeVisible();
});
test('footer shows expected links', async ({ page }) => {
await page.goto('/');
const footerNav = page.getByRole('navigation', { name: 'footer navigation' });
await expect(footerNav).toBeVisible();
await expect(footerNav.getByRole('link', { name: 'Home', exact: true })).toBeVisible();
await expect(footerNav.getByRole('link', { name: 'About', exact: true })).toBeVisible();
await expect(footerNav.getByRole('link', { name: 'Portfolio', exact: true })).toBeVisible();
await expect(footerNav.getByRole('link', { name: 'Uses', exact: true })).toBeVisible();
await expect(footerNav.getByRole('link', { name: 'Privacy', exact: true })).toBeVisible();
await expect(footerNav.getByRole('link', { name: 'Favorite Articles', exact: true })).toBeVisible();
});
test('small viewport: Bandcamp grid 2x3 above Articles', async ({ page }) => {
await page.setViewportSize({ width: 800, height: 1000 }); // <1000px and >575px
await page.goto('/');
const albumsGrid = page.locator('.albumsStyles');
const articlesSection = page.locator('section.articles');
await expect(albumsGrid).toBeVisible();
await expect(articlesSection).toBeVisible();
// Order: Bandcamp above Articles
const [albumsTop, articlesTop] = await Promise.all([
albumsGrid.boundingBox().then((b) => b?.y ?? Number.POSITIVE_INFINITY),
articlesSection.boundingBox().then((b) => b?.y ?? Number.NEGATIVE_INFINITY),
]);
expect(albumsTop).toBeLessThan(articlesTop);
// Layout: assert first two items share the same row, third wraps to next row
const albumItems = page.locator('.albumsStyles .album-artwork');
const n = await albumItems.count();
expect(n).toBeGreaterThanOrEqual(3);
const [b0, b1, b2] = await Promise.all([
albumItems.nth(0).boundingBox(),
albumItems.nth(1).boundingBox(),
albumItems.nth(2).boundingBox(),
]);
expect(b0 && b1 && b2).toBeTruthy();
if (b0 && b1 && b2) {
expect(Math.abs(b0.y - b1.y)).toBeLessThan(6); // same row
expect(b2.y).toBeGreaterThan(b0.y + 10); // next row
}
});
test('mobile viewport: Bandcamp vertical scroll, Articles stacked', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 800 }); // <=575px rules apply
await page.goto('/');
const albumsGrid = page.locator('.albumsStyles');
const articlesSection = page.locator('section.articles');
await expect(albumsGrid).toBeVisible();
await expect(articlesSection).toBeVisible();
// Order: Bandcamp above Articles
const [albumsTop, articlesTop] = await Promise.all([
albumsGrid.boundingBox().then((b) => b?.y ?? Number.POSITIVE_INFINITY),
articlesSection.boundingBox().then((b) => b?.y ?? Number.NEGATIVE_INFINITY),
]);
expect(albumsTop).toBeLessThan(articlesTop);
// Layout: single column and scrollable vertically
const scrollInfo = await albumsGrid.evaluate((el) => ({
overflowY: getComputedStyle(el as HTMLElement).overflowY,
scrollHeight: (el as HTMLElement).scrollHeight,
clientHeight: (el as HTMLElement).clientHeight,
}));
expect(scrollInfo.clientHeight).toBeLessThan(scrollInfo.scrollHeight);
expect(['auto', 'scroll']).toContain(scrollInfo.overflowY);
// Albums are a vertical list (y increasing); first two must be on different rows
const albumItems = page.locator('.albumsStyles .album-artwork');
const m = await albumItems.count();
expect(m).toBeGreaterThanOrEqual(2);
const [a0, a1] = await Promise.all([
albumItems.nth(0).boundingBox(),
albumItems.nth(1).boundingBox(),
]);
expect(a0 && a1).toBeTruthy();
if (a0 && a1) {
expect(a1.y).toBeGreaterThan(a0.y + 10);
expect(Math.abs(a1.x - a0.x)).toBeLessThan(6);
}
// Articles are a vertical list (same x, increasing y)
const boxes = await page.locator('section.articles article.card').evaluateAll((els) =>
(els as HTMLElement[]).slice(0, Math.min(4, els.length)).map((el) => el.getBoundingClientRect())
);
expect(boxes.length).toBeGreaterThan(0);
const x0 = boxes[0].left;
for (let i = 1; i < boxes.length; i++) {
expect(Math.abs(boxes[i].left - x0)).toBeLessThan(6);
expect(boxes[i].top).toBeGreaterThan(boxes[i - 1].top);
}
});
});