p19-bug-fixes
This commit is contained in:
233
e2e/tests/capabilities/responsive/breakpoints.spec.ts
Normal file
233
e2e/tests/capabilities/responsive/breakpoints.spec.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { SELECTORS } from "../../fixtures/selectors";
|
||||
import { expect, test } from "../../fixtures/test";
|
||||
import {
|
||||
hasHorizontalOverflow,
|
||||
isClipped,
|
||||
VIEWPORT_SIZES,
|
||||
type ViewportSize,
|
||||
} from "../../fixtures/viewports";
|
||||
|
||||
test.describe("Responsive Breakpoint Tests @smoke", () => {
|
||||
for (const viewport of VIEWPORT_SIZES) {
|
||||
test.describe(`${viewport} viewport`, () => {
|
||||
test.beforeEach(async ({ gotoApp, waitForAppReady, setViewport }) => {
|
||||
await setViewport(viewport);
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
});
|
||||
|
||||
test("page has no horizontal overflow", async ({ page }) => {
|
||||
// Check body for overflow
|
||||
const bodyOverflow = await hasHorizontalOverflow(page, "body");
|
||||
expect(bodyOverflow).toBe(false);
|
||||
|
||||
// Check main content
|
||||
const mainOverflow = await hasHorizontalOverflow(page, "main");
|
||||
expect(mainOverflow).toBe(false);
|
||||
|
||||
// Check hero section
|
||||
const heroOverflow = await hasHorizontalOverflow(
|
||||
page,
|
||||
SELECTORS.hero.root,
|
||||
);
|
||||
expect(heroOverflow).toBe(false);
|
||||
|
||||
// Check feed section
|
||||
const feedOverflow = await hasHorizontalOverflow(
|
||||
page,
|
||||
SELECTORS.feed.root,
|
||||
);
|
||||
expect(feedOverflow).toBe(false);
|
||||
});
|
||||
|
||||
test("hero section is not clipped", async ({ page, waitForHero }) => {
|
||||
const hero = await waitForHero();
|
||||
const isHeroClipped = await isClipped(page, SELECTORS.hero.root);
|
||||
expect(isHeroClipped).toBe(false);
|
||||
});
|
||||
|
||||
test("feed articles are not clipped", async ({ page, waitForFeed }) => {
|
||||
const feed = await waitForFeed();
|
||||
const isFeedClipped = await isClipped(page, SELECTORS.feed.root);
|
||||
expect(isFeedClipped).toBe(false);
|
||||
});
|
||||
|
||||
test("modal fits within viewport", async ({
|
||||
page,
|
||||
waitForHero,
|
||||
setViewport,
|
||||
}) => {
|
||||
// Open modal
|
||||
const hero = await waitForHero();
|
||||
await hero.locator(SELECTORS.hero.readButton).click();
|
||||
|
||||
const modal = page.locator(SELECTORS.summaryModal.root);
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
// Get viewport dimensions
|
||||
const viewport = await page.evaluate(() => ({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
}));
|
||||
|
||||
// Get modal dimensions
|
||||
const modalBox = await modal.boundingBox();
|
||||
expect(modalBox).not.toBeNull();
|
||||
|
||||
// Modal should fit within viewport (with some padding)
|
||||
expect(modalBox!.width).toBeLessThanOrEqual(viewport.width);
|
||||
expect(modalBox!.height).toBeLessThanOrEqual(viewport.height * 0.96); // max-h-[96vh]
|
||||
});
|
||||
|
||||
test("interactive controls are reachable", async ({
|
||||
page,
|
||||
waitForFeed,
|
||||
}) => {
|
||||
const feed = await waitForFeed();
|
||||
const firstArticle = feed.locator(SELECTORS.feed.articles).first();
|
||||
|
||||
// Check read button is visible and clickable
|
||||
const readButton = firstArticle.locator(
|
||||
SELECTORS.feed.articleReadButton,
|
||||
);
|
||||
await expect(readButton).toBeVisible();
|
||||
await expect(readButton).toBeEnabled();
|
||||
|
||||
// Check source link is visible if present
|
||||
const sourceLink = firstArticle.locator(SELECTORS.feed.articleSource);
|
||||
const hasSourceLink = (await sourceLink.count()) > 0;
|
||||
if (hasSourceLink) {
|
||||
await expect(sourceLink).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test("header controls remain accessible", async ({ page }) => {
|
||||
// Check logo is visible
|
||||
const logo = page.locator(SELECTORS.header.logo);
|
||||
await expect(logo).toBeVisible();
|
||||
|
||||
// Check theme button is visible
|
||||
const themeButton = page.locator(SELECTORS.header.themeMenuButton);
|
||||
await expect(themeButton).toBeVisible();
|
||||
await expect(themeButton).toBeEnabled();
|
||||
|
||||
// Check language select is visible (may be hidden on very small screens)
|
||||
const languageSelect = page.locator(SELECTORS.header.languageSelect);
|
||||
const isVisible = await languageSelect.isVisible().catch(() => false);
|
||||
|
||||
if (isVisible) {
|
||||
await expect(languageSelect).toBeEnabled();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.describe("Responsive Layout Adaptations", () => {
|
||||
test("mobile shows single column feed", async ({
|
||||
gotoApp,
|
||||
waitForAppReady,
|
||||
setViewport,
|
||||
waitForFeed,
|
||||
}) => {
|
||||
await setViewport("mobile");
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
const feed = await waitForFeed();
|
||||
const articles = feed.locator(SELECTORS.feed.articles);
|
||||
|
||||
// Articles should be in single column (full width)
|
||||
const firstArticle = articles.first();
|
||||
const articleBox = await firstArticle.boundingBox();
|
||||
|
||||
// Get feed container width
|
||||
const feedBox = await feed.boundingBox();
|
||||
|
||||
// Article should take most of the width (single column)
|
||||
expect(articleBox!.width).toBeGreaterThan(feedBox!.width * 0.8);
|
||||
});
|
||||
|
||||
test("tablet shows appropriate layout", async ({
|
||||
gotoApp,
|
||||
waitForAppReady,
|
||||
setViewport,
|
||||
waitForFeed,
|
||||
}) => {
|
||||
await setViewport("tablet");
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
const feed = await waitForFeed();
|
||||
const articles = feed.locator(SELECTORS.feed.articles);
|
||||
|
||||
// Should have multiple articles visible
|
||||
const count = await articles.count();
|
||||
expect(count).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// Articles should be side by side (multi-column)
|
||||
const firstArticle = articles.first();
|
||||
const secondArticle = articles.nth(1);
|
||||
|
||||
const firstBox = await firstArticle.boundingBox();
|
||||
const secondBox = await secondArticle.boundingBox();
|
||||
|
||||
// Second article should be to the right of first (or below in some layouts)
|
||||
expect(secondBox!.x).not.toBe(firstBox!.x);
|
||||
});
|
||||
|
||||
test("desktop shows multi-column feed", async ({
|
||||
gotoApp,
|
||||
waitForAppReady,
|
||||
setViewport,
|
||||
waitForFeed,
|
||||
}) => {
|
||||
await setViewport("desktop");
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
const feed = await waitForFeed();
|
||||
const articles = feed.locator(SELECTORS.feed.articles);
|
||||
|
||||
// Should have multiple articles in a row
|
||||
const count = await articles.count();
|
||||
expect(count).toBeGreaterThanOrEqual(3);
|
||||
|
||||
// First three articles should be in a row
|
||||
const articleBoxes = await articles
|
||||
.slice(0, 3)
|
||||
.evaluateAll((els) => els.map((el) => el.getBoundingClientRect()));
|
||||
|
||||
// Articles should be at different x positions (side by side)
|
||||
const xPositions = articleBoxes.map((box) => box.x);
|
||||
const uniqueXPositions = [...new Set(xPositions)];
|
||||
expect(uniqueXPositions.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
test("hero image maintains aspect ratio", async ({
|
||||
gotoApp,
|
||||
waitForAppReady,
|
||||
setViewport,
|
||||
waitForHero,
|
||||
}) => {
|
||||
for (const viewport of ["mobile", "tablet", "desktop"] as ViewportSize[]) {
|
||||
await setViewport(viewport);
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
const hero = await waitForHero();
|
||||
const image = hero.locator(SELECTORS.hero.image);
|
||||
|
||||
const box = await image.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
|
||||
// Image should have reasonable dimensions
|
||||
expect(box!.width).toBeGreaterThan(0);
|
||||
expect(box!.height).toBeGreaterThan(0);
|
||||
|
||||
// Aspect ratio should be roughly maintained (wider than tall)
|
||||
expect(box!.width / box!.height).toBeGreaterThan(1);
|
||||
expect(box!.width / box!.height).toBeLessThan(5);
|
||||
}
|
||||
});
|
||||
});
|
||||
250
e2e/tests/capabilities/responsive/sticky.spec.ts
Normal file
250
e2e/tests/capabilities/responsive/sticky.spec.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { SELECTORS } from "../../fixtures/selectors";
|
||||
import { expect, test } from "../../fixtures/test";
|
||||
import { getStickyPosition, ViewportSize } from "../../fixtures/viewports";
|
||||
|
||||
test.describe("Sticky Header Behavior @smoke", () => {
|
||||
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
});
|
||||
|
||||
test("header is sticky on scroll", async ({ page }) => {
|
||||
const header = page.locator(SELECTORS.header.root);
|
||||
|
||||
// Check initial position
|
||||
const initialPosition = await getStickyPosition(
|
||||
page,
|
||||
SELECTORS.header.root,
|
||||
);
|
||||
expect(initialPosition.isSticky).toBe(true);
|
||||
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 500));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Header should still be at top
|
||||
const scrolledPosition = await getStickyPosition(
|
||||
page,
|
||||
SELECTORS.header.root,
|
||||
);
|
||||
expect(scrolledPosition.top).toBeLessThanOrEqual(10); // Allow small offset
|
||||
expect(scrolledPosition.isSticky).toBe(true);
|
||||
});
|
||||
|
||||
test("header shrinks on scroll", async ({ page }) => {
|
||||
const header = page.locator(SELECTORS.header.root);
|
||||
const headerContainer = header.locator("> div");
|
||||
|
||||
// Get initial height
|
||||
const initialHeight = await headerContainer.evaluate(
|
||||
(el) => el.offsetHeight,
|
||||
);
|
||||
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 300));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Get scrolled height
|
||||
const scrolledHeight = await headerContainer.evaluate(
|
||||
(el) => el.offsetHeight,
|
||||
);
|
||||
|
||||
// Header should shrink (or stay same, but not grow)
|
||||
expect(scrolledHeight).toBeLessThanOrEqual(initialHeight);
|
||||
});
|
||||
|
||||
test("header maintains glass effect on scroll", async ({ page }) => {
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 500));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check header has backdrop blur
|
||||
const hasBlur = await page.evaluate(() => {
|
||||
const header = document.querySelector("header");
|
||||
if (!header) return false;
|
||||
const style = window.getComputedStyle(header);
|
||||
return style.backdropFilter.includes("blur");
|
||||
});
|
||||
|
||||
expect(hasBlur).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Sticky Footer Behavior", () => {
|
||||
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
});
|
||||
|
||||
test("footer is sticky at bottom", async ({ page }) => {
|
||||
const footer = page.locator(SELECTORS.footer.root);
|
||||
|
||||
// Check footer is visible
|
||||
await expect(footer).toBeVisible();
|
||||
|
||||
// Check footer position
|
||||
const footerBox = await footer.boundingBox();
|
||||
const viewportHeight = await page.evaluate(() => window.innerHeight);
|
||||
|
||||
// Footer should be at bottom of viewport
|
||||
expect(footerBox!.y + footerBox!.height).toBeGreaterThanOrEqual(
|
||||
viewportHeight - 10,
|
||||
);
|
||||
});
|
||||
|
||||
test("footer does not overlap main content", async ({ page }) => {
|
||||
const footer = page.locator(SELECTORS.footer.root);
|
||||
const mainContent = page.locator("main");
|
||||
|
||||
// Get bounding boxes
|
||||
const footerBox = await footer.boundingBox();
|
||||
const mainBox = await mainContent.boundingBox();
|
||||
|
||||
// Main content should have padding at bottom to account for footer
|
||||
const bodyPadding = await page.evaluate(() => {
|
||||
const body = document.body;
|
||||
const style = window.getComputedStyle(body);
|
||||
return parseInt(style.paddingBottom || "0");
|
||||
});
|
||||
|
||||
expect(bodyPadding).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Back to Top Behavior @smoke", () => {
|
||||
test.beforeEach(async ({ gotoApp, waitForAppReady }) => {
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
});
|
||||
|
||||
test("back to top is hidden initially", async ({ page }) => {
|
||||
const backToTop = page.locator(SELECTORS.backToTop.root);
|
||||
|
||||
// Should not be visible at top of page
|
||||
const isVisible = await backToTop.isVisible().catch(() => false);
|
||||
expect(isVisible).toBe(false);
|
||||
});
|
||||
|
||||
test("back to top appears on scroll", async ({ page }) => {
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 800));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const backToTop = page.locator(SELECTORS.backToTop.root);
|
||||
|
||||
// Should be visible after scroll
|
||||
await expect(backToTop).toBeVisible();
|
||||
});
|
||||
|
||||
test("back to top scrolls to top when clicked", async ({ page }) => {
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 1000));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click back to top
|
||||
const backToTop = page.locator(SELECTORS.backToTop.root);
|
||||
await backToTop.click();
|
||||
|
||||
// Wait for scroll animation
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Should be at top
|
||||
const scrollPosition = await page.evaluate(() => window.scrollY);
|
||||
expect(scrollPosition).toBeLessThanOrEqual(50);
|
||||
});
|
||||
|
||||
test("back to top is accessible", async ({ page }) => {
|
||||
// Scroll down to make visible
|
||||
await page.evaluate(() => window.scrollTo(0, 800));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const backToTop = page.locator(SELECTORS.backToTop.root);
|
||||
|
||||
// Should have aria-label
|
||||
await expect(backToTop).toHaveAttribute("aria-label");
|
||||
|
||||
// Should be keyboard focusable
|
||||
await backToTop.focus();
|
||||
await expect(backToTop).toBeFocused();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Sticky Behavior Across Breakpoints", () => {
|
||||
test("header and footer work on mobile", async ({
|
||||
gotoApp,
|
||||
waitForAppReady,
|
||||
setViewport,
|
||||
page,
|
||||
}) => {
|
||||
await setViewport("mobile");
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
// Check header is sticky
|
||||
const header = page.locator(SELECTORS.header.root);
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 500));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Header should still be visible
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// Footer should be visible
|
||||
const footer = page.locator(SELECTORS.footer.root);
|
||||
await expect(footer).toBeVisible();
|
||||
});
|
||||
|
||||
test("header and footer work on tablet", async ({
|
||||
gotoApp,
|
||||
waitForAppReady,
|
||||
setViewport,
|
||||
page,
|
||||
}) => {
|
||||
await setViewport("tablet");
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
// Check header is sticky
|
||||
const header = page.locator(SELECTORS.header.root);
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 500));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Header should still be visible
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// Footer should be visible
|
||||
const footer = page.locator(SELECTORS.footer.root);
|
||||
await expect(footer).toBeVisible();
|
||||
});
|
||||
|
||||
test("header and footer work on desktop", async ({
|
||||
gotoApp,
|
||||
waitForAppReady,
|
||||
setViewport,
|
||||
page,
|
||||
}) => {
|
||||
await setViewport("desktop");
|
||||
await gotoApp();
|
||||
await waitForAppReady();
|
||||
|
||||
// Check header is sticky
|
||||
const header = page.locator(SELECTORS.header.root);
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// Scroll down
|
||||
await page.evaluate(() => window.scrollTo(0, 500));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Header should still be visible
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// Footer should be visible
|
||||
const footer = page.locator(SELECTORS.footer.root);
|
||||
await expect(footer).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user