import { test, expect } from '@playwright/test'; test.describe('Insert Modes', () => { // Return a JSON array of items for a given page number. // Each item has a `name` field rendered by the fixture templates via `bind="item.name"`. function jsonPage(pageNum: number) { return [ { name: `Page ${pageNum} Item A` }, { name: `Page ${pageNum} Item B` }, ]; } // ── get-insert="append" ────────────────────────────────────────────── test('1 — get-insert="append": new content appended after existing', async ({ page }) => { await page.route('**/api/items*', (route) => { const url = new URL(route.request().url(), 'http://localhost'); const p = parseInt(url.searchParams.get('page') || '1'); if (p > 2) { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) }); } else { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(jsonPage(p)) }); } }); await page.goto('/e2e/examples/pagination-button.html'); // Page 1 loads — template renders
  • elements const items = page.getByTestId('btn-item'); await expect(items.first()).toBeVisible({ timeout: 5000 }); await expect(items).toHaveCount(2); await expect(items.nth(0)).toHaveText('Page 1 Item A'); await expect(items.nth(1)).toHaveText('Page 1 Item B'); // Load page 2 const loadMoreBtn = page.getByTestId('button-container').locator('[data-nojs-load-more]'); await expect(loadMoreBtn).toBeVisible({ timeout: 5000 }); await loadMoreBtn.click(); // After dedup fix: exactly 4 unique items — 2 from page 1, 2 from page 2 await expect(items).toHaveCount(4, { timeout: 5000 }); await expect(items.nth(0)).toHaveText('Page 1 Item A'); await expect(items.nth(1)).toHaveText('Page 1 Item B'); await expect(items.nth(2)).toHaveText('Page 2 Item A'); await expect(items.nth(3)).toHaveText('Page 2 Item B'); }); // ── get-insert="prepend" ───────────────────────────────────────────── test('2 — get-insert="prepend": new content prepended before existing', async ({ page }) => { await page.route('**/api/feed*', (route) => { const url = new URL(route.request().url(), 'http://localhost'); const p = parseInt(url.searchParams.get('page') || '1'); if (p > 2) { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) }); } else { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(jsonPage(p)) }); } }); await page.goto('/e2e/examples/insert-prepend-data.html'); // Page 1 loads — template renders
  • elements const items = page.getByTestId('prepend-item'); await expect(items.first()).toBeVisible({ timeout: 5000 }); await expect(items).toHaveCount(2); await expect(items.nth(0)).toHaveText('Page 1 Item A'); await expect(items.nth(1)).toHaveText('Page 1 Item B'); // Load page 2 const loadMoreBtn = page.getByTestId('prepend-container').locator('[data-nojs-load-more]'); await expect(loadMoreBtn).toBeVisible({ timeout: 5000 }); await loadMoreBtn.click(); // After dedup fix: exactly 4 unique items — page 2 prepended before page 1 await expect(items).toHaveCount(4, { timeout: 5000 }); await expect(items.nth(0)).toHaveText('Page 2 Item A'); await expect(items.nth(1)).toHaveText('Page 2 Item B'); await expect(items.nth(2)).toHaveText('Page 1 Item A'); await expect(items.nth(3)).toHaveText('Page 1 Item B'); }); // ── Default (no get-insert): replace ───────────────────────────────── test('3 — default (no get-insert): content replaced on each fetch', async ({ page }) => { await page.route('**/api/users', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { name: 'Alice' }, { name: 'Bob' }, ]), }); }); await page.route('**/api/slow', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ message: 'Done!' }) }); }); await page.route('**/api/error', (route) => { route.fulfill({ status: 500, contentType: 'application/json', body: '{}' }); }); await page.route('**/api/empty', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) }); }); await page.route('**/api/users/1', (route) => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ name: 'User One' }) }); }); // Use the existing fetch.html which has no get-insert (replace mode) await page.goto('/e2e/examples/fetch.html'); // First fetch renders const items = page.getByTestId('user-item'); await expect(items.first()).toBeVisible({ timeout: 5000 }); expect(await items.count()).toBe(2); // Content matches the response (replace, not accumulate) await expect(items.first()).toHaveText('Alice'); await expect(items.nth(1)).toHaveText('Bob'); }); // ── Sentinel element ───────────────────────────────────────────────── test('4 — sentinel element exists in append mode', async ({ page }) => { await page.route('**/api/items*', (route) => { const url = new URL(route.request().url(), 'http://localhost'); const p = parseInt(url.searchParams.get('page') || '1'); if (p > 1) { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([]) }); } else { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(jsonPage(1)) }); } }); await page.goto('/e2e/examples/pagination-button.html'); // Wait for page 1 to load await expect(page.getByTestId('btn-item').first()).toBeVisible({ timeout: 5000 }); // Check that the sentinel element exists within the container const sentinel = page.getByTestId('button-container').locator('[data-nojs-sentinel]'); await expect(sentinel).toBeAttached(); // Sentinel should be zero-height and hidden from accessibility await expect(sentinel).toHaveAttribute('aria-hidden', 'true'); const height = await sentinel.evaluate((el) => getComputedStyle(el).height); expect(height).toBe('0px'); }); });