p19-bug-fixes
This commit is contained in:
191
e2e/tests/capabilities/modal-experience/deep-link.spec.ts
Normal file
191
e2e/tests/capabilities/modal-experience/deep-link.spec.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { SELECTORS } from "../../fixtures/selectors";
|
||||
import { expect, test } from "../../fixtures/test";
|
||||
|
||||
test.describe("Deep Link Permalink Tests @smoke", () => {
|
||||
test.beforeEach(async ({ waitForAppReady }) => {
|
||||
await waitForAppReady();
|
||||
});
|
||||
|
||||
test("valid article permalink opens modal automatically", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
isSummaryModalOpen,
|
||||
}) => {
|
||||
// First get an article ID from the feed
|
||||
await gotoApp();
|
||||
await page.waitForSelector(SELECTORS.feed.articles, { timeout: 10000 });
|
||||
|
||||
const firstArticle = page.locator(SELECTORS.feed.articles).first();
|
||||
const articleId = await firstArticle
|
||||
.getAttribute("id")
|
||||
.then((id) => (id ? parseInt(id.replace("news-", "")) : null));
|
||||
|
||||
expect(articleId).not.toBeNull();
|
||||
|
||||
// Navigate to article permalink
|
||||
await gotoApp({ articleId: articleId! });
|
||||
await page.waitForTimeout(2000); // Wait for modal to open
|
||||
|
||||
// Modal should be open
|
||||
const isOpen = await isSummaryModalOpen();
|
||||
expect(isOpen).toBe(true);
|
||||
|
||||
// Modal should show correct article
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const modalHeadline = await modal
|
||||
.locator(SELECTORS.summaryModal.headline)
|
||||
.textContent();
|
||||
const articleHeadline = await firstArticle.locator("h3").textContent();
|
||||
expect(modalHeadline).toBe(articleHeadline);
|
||||
});
|
||||
|
||||
test("invalid article permalink shows error state", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
}) => {
|
||||
// Navigate to invalid article ID
|
||||
await gotoApp({ articleId: 999999 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Should not show summary modal
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
await expect(modal).not.toBeVisible();
|
||||
|
||||
// Should still show the page (not crash)
|
||||
const hero = page.locator(SELECTORS.hero.root);
|
||||
const feed = page.locator(SELECTORS.feed.root);
|
||||
|
||||
const heroVisible = await hero.isVisible().catch(() => false);
|
||||
const feedVisible = await feed.isVisible().catch(() => false);
|
||||
|
||||
expect(heroVisible || feedVisible).toBe(true);
|
||||
});
|
||||
|
||||
test("hero-origin modal flow via permalink", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
isSummaryModalOpen,
|
||||
}) => {
|
||||
// Get hero article ID
|
||||
await gotoApp();
|
||||
await page.waitForSelector(SELECTORS.hero.root, { timeout: 10000 });
|
||||
|
||||
const hero = page.locator(SELECTORS.hero.root);
|
||||
const heroId = await hero
|
||||
.getAttribute("id")
|
||||
.then((id) => (id ? parseInt(id.replace("news-", "")) : null));
|
||||
|
||||
expect(heroId).not.toBeNull();
|
||||
|
||||
// Navigate directly to hero article
|
||||
await gotoApp({ articleId: heroId! });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Modal should open
|
||||
const isOpen = await isSummaryModalOpen();
|
||||
expect(isOpen).toBe(true);
|
||||
|
||||
// Modal should show hero article content
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const modalHeadline = await modal
|
||||
.locator(SELECTORS.summaryModal.headline)
|
||||
.textContent();
|
||||
const heroHeadline = await hero.locator("h1").textContent();
|
||||
expect(modalHeadline).toBe(heroHeadline);
|
||||
});
|
||||
|
||||
test("closing permalink modal updates URL", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
isSummaryModalOpen,
|
||||
}) => {
|
||||
// Open via permalink
|
||||
await gotoApp({ articleId: 1 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// URL should have article parameter
|
||||
await expect(page).toHaveURL(/\?article=\d+/);
|
||||
|
||||
// Close modal
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// URL should be cleaned up (parameter removed)
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
await expect(page).not.toHaveURL(/\?article=/);
|
||||
});
|
||||
|
||||
test("modal state persists on page refresh", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
isSummaryModalOpen,
|
||||
}) => {
|
||||
// Open via permalink
|
||||
await gotoApp({ articleId: 1 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Verify modal is open
|
||||
expect(await isSummaryModalOpen()).toBe(true);
|
||||
|
||||
// Refresh page
|
||||
await page.reload();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Modal should still be open
|
||||
expect(await isSummaryModalOpen()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Policy Modal Deep Links", () => {
|
||||
test("terms policy modal opens via URL parameter", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
}) => {
|
||||
await gotoApp({ policy: "terms" });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Policy modal should be visible
|
||||
const modal = page.locator(SELECTORS.policyModal.root);
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
// Should show terms title
|
||||
const title = modal.locator(SELECTORS.policyModal.termsTitle);
|
||||
await expect(title).toBeVisible();
|
||||
});
|
||||
|
||||
test("attribution policy modal opens via URL parameter", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
}) => {
|
||||
await gotoApp({ policy: "attribution" });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Policy modal should be visible
|
||||
const modal = page.locator(SELECTORS.policyModal.root);
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
// Should show attribution title
|
||||
const title = modal.locator(SELECTORS.policyModal.attributionTitle);
|
||||
await expect(title).toBeVisible();
|
||||
});
|
||||
|
||||
test("closing policy modal clears URL parameter", async ({
|
||||
page,
|
||||
gotoApp,
|
||||
}) => {
|
||||
await gotoApp({ policy: "terms" });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// URL should have policy parameter
|
||||
await expect(page).toHaveURL(/\?policy=terms/);
|
||||
|
||||
// Close modal
|
||||
const modal = page.locator(SELECTORS.policyModal.root);
|
||||
await modal.locator(SELECTORS.policyModal.closeButton).click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// URL should be cleaned up
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
await expect(page).not.toHaveURL(/\?policy=/);
|
||||
});
|
||||
});
|
||||
193
e2e/tests/capabilities/modal-experience/share.spec.ts
Normal file
193
e2e/tests/capabilities/modal-experience/share.spec.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { SELECTORS } from "../../fixtures/selectors";
|
||||
import { expect, test } from "../../fixtures/test";
|
||||
|
||||
test.describe("Source CTA and Share Interactions", () => {
|
||||
test.beforeEach(async ({ gotoApp, waitForAppReady, waitForHero }) => {
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
// Open modal from hero
|
||||
const hero = await waitForHero();
|
||||
await hero.locator(SELECTORS.hero.readButton).click();
|
||||
await expect(page.locator(SELECTORS.summaryModal.root)).toBeVisible();
|
||||
});
|
||||
|
||||
let page: any;
|
||||
|
||||
test("source link opens in new tab @smoke", async ({ page: p, context }) => {
|
||||
page = p;
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const sourceLink = modal.locator(SELECTORS.summaryModal.sourceLink);
|
||||
|
||||
// Check link attributes
|
||||
await expect(sourceLink).toHaveAttribute("target", "_blank");
|
||||
await expect(sourceLink).toHaveAttribute("rel", "noopener");
|
||||
await expect(sourceLink).toHaveAttribute("href");
|
||||
|
||||
// Click should open new tab
|
||||
const [newPage] = await Promise.all([
|
||||
context.waitForEvent("page"),
|
||||
sourceLink.click(),
|
||||
]);
|
||||
|
||||
// New page should have loaded
|
||||
expect(newPage).toBeDefined();
|
||||
await newPage.close();
|
||||
|
||||
// Modal should remain open
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
|
||||
test("share on X opens correct URL", async ({ page: p, context }) => {
|
||||
page = p;
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const shareX = modal.locator(SELECTORS.summaryModal.shareX);
|
||||
|
||||
// Get article headline for URL verification
|
||||
const headline = await modal
|
||||
.locator(SELECTORS.summaryModal.headline)
|
||||
.textContent();
|
||||
|
||||
// Click should open X share in new tab
|
||||
const [newPage] = await Promise.all([
|
||||
context.waitForEvent("page"),
|
||||
shareX.click(),
|
||||
]);
|
||||
|
||||
// Verify URL contains X intent
|
||||
const url = newPage.url();
|
||||
expect(url).toContain("x.com/intent/tweet");
|
||||
expect(url).toContain(encodeURIComponent(headline || ""));
|
||||
|
||||
await newPage.close();
|
||||
});
|
||||
|
||||
test("share on WhatsApp opens correct URL", async ({ page: p, context }) => {
|
||||
page = p;
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const shareWhatsApp = modal.locator(SELECTORS.summaryModal.shareWhatsApp);
|
||||
|
||||
// Get article headline for URL verification
|
||||
const headline = await modal
|
||||
.locator(SELECTORS.summaryModal.headline)
|
||||
.textContent();
|
||||
|
||||
// Click should open WhatsApp share in new tab
|
||||
const [newPage] = await Promise.all([
|
||||
context.waitForEvent("page"),
|
||||
shareWhatsApp.click(),
|
||||
]);
|
||||
|
||||
// Verify URL contains WhatsApp share
|
||||
const url = newPage.url();
|
||||
expect(url).toContain("wa.me");
|
||||
expect(url).toContain(encodeURIComponent(headline || ""));
|
||||
|
||||
await newPage.close();
|
||||
});
|
||||
|
||||
test("share on LinkedIn opens correct URL", async ({ page: p, context }) => {
|
||||
page = p;
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const shareLinkedIn = modal.locator(SELECTORS.summaryModal.shareLinkedIn);
|
||||
|
||||
// Click should open LinkedIn share in new tab
|
||||
const [newPage] = await Promise.all([
|
||||
context.waitForEvent("page"),
|
||||
shareLinkedIn.click(),
|
||||
]);
|
||||
|
||||
// Verify URL contains LinkedIn share
|
||||
const url = newPage.url();
|
||||
expect(url).toContain("linkedin.com/sharing");
|
||||
|
||||
await newPage.close();
|
||||
});
|
||||
|
||||
test("copy link button copies permalink to clipboard @smoke", async ({
|
||||
page: p,
|
||||
context,
|
||||
}) => {
|
||||
page = p;
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const copyButton = modal.locator(SELECTORS.summaryModal.shareCopy);
|
||||
|
||||
// Grant clipboard permissions
|
||||
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
|
||||
// Click copy button
|
||||
await copyButton.click();
|
||||
|
||||
// Wait for success message
|
||||
const successMessage = modal.locator(SELECTORS.summaryModal.copySuccess);
|
||||
await expect(successMessage).toBeVisible();
|
||||
await expect(successMessage).toContainText("Permalink copied");
|
||||
|
||||
// Verify clipboard content
|
||||
const clipboardContent = await page.evaluate(() =>
|
||||
navigator.clipboard.readText(),
|
||||
);
|
||||
expect(clipboardContent).toContain("/?article=");
|
||||
expect(clipboardContent).toMatch(/http/);
|
||||
});
|
||||
|
||||
test("copy link does not navigate away", async ({ page: p }) => {
|
||||
page = p;
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
const copyButton = modal.locator(SELECTORS.summaryModal.shareCopy);
|
||||
|
||||
// Get current URL
|
||||
const currentUrl = page.url();
|
||||
|
||||
// Click copy button
|
||||
await copyButton.click();
|
||||
|
||||
// Wait a moment
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// URL should not have changed
|
||||
await expect(page).toHaveURL(currentUrl);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
|
||||
test("navigation is preserved after share interactions", async ({
|
||||
page: p,
|
||||
context,
|
||||
}) => {
|
||||
page = p;
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
|
||||
// Interact with multiple share buttons
|
||||
const shareButtons = [
|
||||
modal.locator(SELECTORS.summaryModal.shareX),
|
||||
modal.locator(SELECTORS.summaryModal.shareWhatsApp),
|
||||
modal.locator(SELECTORS.summaryModal.shareLinkedIn),
|
||||
];
|
||||
|
||||
for (const button of shareButtons) {
|
||||
if (await button.isVisible()) {
|
||||
const [newPage] = await Promise.all([
|
||||
context.waitForEvent("page"),
|
||||
button.click(),
|
||||
]);
|
||||
await newPage.close();
|
||||
|
||||
// Modal should remain open after each interaction
|
||||
await expect(modal).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
// Close modal
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Should be back on main page without navigation
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
|
||||
// Feed should be visible
|
||||
const feed = page.locator(SELECTORS.feed.root);
|
||||
await expect(feed).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user