Answer
Scenario: Testing Infinite Scroll Pagination
Infinite scroll loads more content as the user scrolls down, rather than using traditional pagination buttons.
Strategy 1 — Scroll to Bottom, Verify More Items Load
TypeScript
import { test, expect } from '@playwright/test';
test('infinite scroll loads more products', async ({ page }) => {
await page.goto('/products');
// Get initial count
const initialCount = await page.locator('.product-card').count();
expect(initialCount).toBe(20); // First page of 20
// Scroll to the bottom of the page
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
// Wait for new items to load
await page.locator('.product-card').nth(20).waitFor({ state: 'visible' });
// Verify more items loaded
const afterScrollCount = await page.locator('.product-card').count();
expect(afterScrollCount).toBeGreaterThan(initialCount);
expect(afterScrollCount).toBe(40); // Second batch of 20 loaded
});
Strategy 2 — Scroll Element Container (Not Full Page)
TypeScript
test('scroll within a product list container', async ({ page }) => {
await page.goto('/feed');
const feed = page.locator('.infinite-feed');
const initialItems = await page.locator('.feed-item').count();
// Scroll to bottom of the container
await feed.evaluate(el => el.scrollTo(0, el.scrollHeight));
// Wait for new items
await page.locator('.feed-item').nth(initialItems).waitFor();
const newCount = await page.locator('.feed-item').count();
expect(newCount).toBeGreaterThan(initialItems);
});
Strategy 3 — Scroll Multiple Times Until Target
TypeScript
test('load 100 products via infinite scroll', async ({ page }) => {
await page.goto('/products');
let previousCount = 0;
let currentCount = await page.locator('.product-card').count();
// Keep scrolling until we have 100 products or no more load
while (currentCount < 100) {
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
// Wait for network activity to settle
await page.waitForLoadState('networkidle');
previousCount = currentCount;
currentCount = await page.locator('.product-card').count();
// Break if no new items loaded (end of list)
if (currentCount === previousCount) break;
}
expect(currentCount).toBeGreaterThanOrEqual(100);
});
Strategy 4 — Keyboard-Based Scrolling
TypeScript
test('scroll using keyboard', async ({ page }) => {
await page.goto('/news-feed');
// Click on the page first to give it focus
await page.locator('body').click();
const before = await page.locator('.news-card').count();
// Press End key to jump to page bottom
await page.keyboard.press('End');
// Wait for more content
await page.locator('.news-card').nth(before).waitFor({ timeout: 5000 });
const after = await page.locator('.news-card').count();
expect(after).toBeGreaterThan(before);
});
Strategy 5 — Intercept API to Verify Pagination Parameters
TypeScript
test('infinite scroll sends correct pagination params', async ({ page }) => {
const apiCalls: string[] = [];
await page.route('/api/products*', async route => {
apiCalls.push(route.request().url());
await route.continue();
});
await page.goto('/products');
// Scroll once
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForLoadState('networkidle');
// Verify second API call has page=2 or cursor parameter
expect(apiCalls.length).toBeGreaterThan(1);
expect(apiCalls[1]).toContain('page=2');
// OR check cursor: expect(apiCalls[1]).toMatch(/cursor=\w+/);
});
Verify End of List
TypeScript
test('shows end-of-list message after loading all products', async ({ page }) => {
await page.goto('/products');
// Keep scrolling until end message appears
let attempts = 0;
while (attempts < 20) {
if (await page.locator('.end-of-list').isVisible()) break;
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(500); // Allow brief load time
attempts++;
}
await expect(page.locator('.end-of-list')).toBeVisible();
await expect(page.locator('.end-of-list')).toContainText('You have reached the end');
});
