Files
clawfort/e2e/tests/capabilities/core-journeys/hero-feed.spec.ts
2026-02-13 16:57:45 -05:00

369 lines
11 KiB
TypeScript

import { SELECTORS } from "../../fixtures/selectors";
import { expect, test } from "../../fixtures/test";
test.describe("Hero and Feed Browsing @smoke", () => {
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
await gotoApp();
await waitForAppReady();
});
test("hero section loads with article content", async ({
page,
waitForHero,
}) => {
const hero = await waitForHero();
// Hero should be visible
await expect(hero).toBeVisible();
// Hero should have headline
const headline = hero.locator(SELECTORS.hero.headline);
await expect(headline).toBeVisible();
await expect(headline).not.toBeEmpty();
// Hero should have summary
const summary = hero.locator(SELECTORS.hero.summary);
await expect(summary).toBeVisible();
await expect(summary).not.toBeEmpty();
// Hero should have "Read TL;DR" button
const readButton = hero.locator(SELECTORS.hero.readButton);
await expect(readButton).toBeVisible();
await expect(readButton).toBeEnabled();
// Hero should have image
const image = hero.locator(SELECTORS.hero.image);
await expect(image).toBeVisible();
});
test("news feed loads with multiple articles", async ({
page,
waitForFeed,
}) => {
const feed = await waitForFeed();
// Feed section should be visible
await expect(feed).toBeVisible();
// Should have "Recent News" heading
const heading = feed.locator("h2");
await expect(heading).toContainText("Recent News");
// Should have multiple article cards (at least 1)
const articles = feed.locator(SELECTORS.feed.articles);
const count = await articles.count();
expect(count).toBeGreaterThanOrEqual(1);
// Each article should have required elements
const firstArticle = articles.first();
await expect(firstArticle.locator("h3")).toBeVisible();
await expect(
firstArticle.locator(SELECTORS.feed.articleSummary),
).toBeVisible();
await expect(
firstArticle.locator(SELECTORS.feed.articleReadButton),
).toBeVisible();
});
test("feed article cards have correct structure", async ({
page,
waitForFeed,
}) => {
const feed = await waitForFeed();
const articles = feed.locator(SELECTORS.feed.articles);
// Check structure of first article
const firstArticle = articles.first();
// Should have image container
const imageContainer = firstArticle.locator(".relative.h-48");
await expect(imageContainer).toBeVisible();
// Should have content area
const contentArea = firstArticle.locator(".p-5");
await expect(contentArea).toBeVisible();
// Should have headline
const headline = firstArticle.locator("h3");
await expect(headline).toBeVisible();
await expect(headline).toHaveClass(/news-card-title/);
// Should have summary
const summary = firstArticle.locator(SELECTORS.feed.articleSummary);
await expect(summary).toBeVisible();
await expect(summary).toHaveClass(/news-card-summary/);
});
test("hero article displays correct metadata", async ({
page,
waitForHero,
}) => {
const hero = await waitForHero();
// Should have "LATEST" pill
const latestPill = hero.locator(SELECTORS.hero.latestPill);
await expect(latestPill).toBeVisible();
await expect(latestPill).toContainText("LATEST");
// Should have time ago
const timePill = hero.locator(SELECTORS.hero.timePill);
await expect(timePill).toBeVisible();
// Time should contain "ago" or "just now"
const timeText = await timePill.textContent();
expect(timeText).toMatch(/ago|just now/);
});
test("source link is present and clickable in hero", async ({
page,
waitForHero,
}) => {
const hero = await waitForHero();
const sourceLink = hero.locator(SELECTORS.hero.sourceLink);
// Source link may not be present if no source URL
const count = await sourceLink.count();
if (count > 0) {
await expect(sourceLink).toBeVisible();
await expect(sourceLink).toHaveAttribute("href");
await expect(sourceLink).toHaveAttribute("target", "_blank");
await expect(sourceLink).toHaveAttribute("rel", "noopener");
}
});
});
test.describe("Summary Modal Flows", () => {
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
await gotoApp();
await waitForAppReady();
});
test("opens summary modal from hero @smoke", async ({
page,
waitForHero,
isSummaryModalOpen,
}) => {
const hero = await waitForHero();
// Click "Read TL;DR" button in hero
const readButton = hero.locator(SELECTORS.hero.readButton);
await readButton.click();
// Modal should open
const isOpen = await isSummaryModalOpen();
expect(isOpen).toBe(true);
// Modal should have correct structure
const modal = page.locator(SELECTORS.summaryModal.root);
await expect(modal.locator(SELECTORS.summaryModal.headline)).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.tldrSection),
).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.summarySection),
).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.sourceSection),
).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.shareSection),
).toBeVisible();
});
test("opens summary modal from feed article @smoke", async ({
page,
waitForFeed,
isSummaryModalOpen,
}) => {
const feed = await waitForFeed();
// Get first feed article
const articles = feed.locator(SELECTORS.feed.articles);
const firstArticle = articles.first();
// Click "Read TL;DR" button
const readButton = firstArticle.locator(SELECTORS.feed.articleReadButton);
await readButton.click();
// Modal should open
const isOpen = await isSummaryModalOpen();
expect(isOpen).toBe(true);
// Modal headline should match article headline
const modal = page.locator(SELECTORS.summaryModal.root);
const articleHeadline = await firstArticle.locator("h3").textContent();
const modalHeadline = await modal
.locator(SELECTORS.summaryModal.headline)
.textContent();
expect(modalHeadline).toBe(articleHeadline);
});
test("closes summary modal via close button @smoke", async ({
page,
waitForHero,
isSummaryModalOpen,
closeSummaryModal,
}) => {
// Open modal from hero
const hero = await waitForHero();
await hero.locator(SELECTORS.hero.readButton).click();
// Verify modal is open
expect(await isSummaryModalOpen()).toBe(true);
// Close modal
await closeSummaryModal();
// Verify modal is closed
expect(await isSummaryModalOpen()).toBe(false);
});
test("closes summary modal via backdrop click", async ({
page,
waitForHero,
isSummaryModalOpen,
}) => {
// Open modal from hero
const hero = await waitForHero();
await hero.locator(SELECTORS.hero.readButton).click();
// Verify modal is open
expect(await isSummaryModalOpen()).toBe(true);
// Click backdrop (outside modal content)
const backdrop = page.locator(".fixed.inset-0.bg-black\\/70").first();
await backdrop.click();
// Verify modal is closed
await page.waitForTimeout(500);
expect(await isSummaryModalOpen()).toBe(false);
});
test("closes summary modal via escape key @smoke", async ({
page,
waitForHero,
isSummaryModalOpen,
}) => {
// Open modal from hero
const hero = await waitForHero();
await hero.locator(SELECTORS.hero.readButton).click();
// Verify modal is open
expect(await isSummaryModalOpen()).toBe(true);
// Press escape
await page.keyboard.press("Escape");
// Verify modal is closed
await page.waitForTimeout(500);
expect(await isSummaryModalOpen()).toBe(false);
});
test("modal displays correct content sections", async ({
page,
waitForHero,
}) => {
// Open modal from hero
const hero = await waitForHero();
await hero.locator(SELECTORS.hero.readButton).click();
const modal = page.locator(SELECTORS.summaryModal.root);
// Check all required sections are present
await expect(modal.locator(SELECTORS.summaryModal.headline)).toBeVisible();
await expect(modal.locator(SELECTORS.summaryModal.image)).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.tldrSection),
).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.summarySection),
).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.sourceSection),
).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.shareSection),
).toBeVisible();
await expect(modal.locator(SELECTORS.summaryModal.poweredBy)).toBeVisible();
// Check TL;DR list has items
const tldrList = modal.locator(SELECTORS.summaryModal.tldrList);
const tldrItems = await tldrList.locator("li").count();
expect(tldrItems).toBeGreaterThanOrEqual(1);
// Check summary body has content
const summaryBody = modal.locator(SELECTORS.summaryModal.summaryBody);
await expect(summaryBody).not.toBeEmpty();
// Check source link is present
const sourceLink = modal.locator(SELECTORS.summaryModal.sourceLink);
await expect(sourceLink).toBeVisible();
await expect(sourceLink).toHaveAttribute("target", "_blank");
});
test("modal share controls are present and accessible", async ({
page,
waitForHero,
}) => {
// Open modal from hero
const hero = await waitForHero();
await hero.locator(SELECTORS.hero.readButton).click();
const modal = page.locator(SELECTORS.summaryModal.root);
// Check all share buttons are present
await expect(modal.locator(SELECTORS.summaryModal.shareX)).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.shareWhatsApp),
).toBeVisible();
await expect(
modal.locator(SELECTORS.summaryModal.shareLinkedIn),
).toBeVisible();
await expect(modal.locator(SELECTORS.summaryModal.shareCopy)).toBeVisible();
// Check accessible labels
await expect(modal.locator(SELECTORS.summaryModal.shareX)).toHaveAttribute(
"aria-label",
"Share on X",
);
await expect(
modal.locator(SELECTORS.summaryModal.shareWhatsApp),
).toHaveAttribute("aria-label", "Share on WhatsApp");
await expect(
modal.locator(SELECTORS.summaryModal.shareLinkedIn),
).toHaveAttribute("aria-label", "Share on LinkedIn");
await expect(
modal.locator(SELECTORS.summaryModal.shareCopy),
).toHaveAttribute("aria-label", "Copy article link");
});
test("modal returns to feed context after closing", async ({
page,
waitForHero,
waitForFeed,
isSummaryModalOpen,
}) => {
// Wait for feed to be visible
await waitForFeed();
// Open modal from hero
const hero = await waitForHero();
await hero.locator(SELECTORS.hero.readButton).click();
// Close modal
await page.keyboard.press("Escape");
await page.waitForTimeout(500);
// Verify modal is closed
expect(await isSummaryModalOpen()).toBe(false);
// Verify we're still on the same page (no navigation occurred)
await expect(page).toHaveURL(/\/$/);
// Verify feed is still visible
const feed = page.locator(SELECTORS.feed.root);
await expect(feed).toBeVisible();
});
});