p19-bug-fixes
This commit is contained in:
368
e2e/tests/capabilities/core-journeys/hero-feed.spec.ts
Normal file
368
e2e/tests/capabilities/core-journeys/hero-feed.spec.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user