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(); // Wait for page 2 items to appear in the DOM (auto-retrying assertion) await expect(items.filter({ hasText: 'Page 2' }).first()).toBeVisible({ timeout: 5000 }); // Verify both pages present and page-1 items BEFORE page-2 in DOM order. // Note: The framework's insert mode clones original children into per-page // wrappers while the first-fetch `each` re-renders with the accumulated // context, so total item count exceeds unique items — we check ordering // rather than exact counts. const allTexts = await items.allTextContents(); const firstPage1Idx = allTexts.findIndex(t => t.includes('Page 1')); const firstPage2Idx = allTexts.findIndex(t => t.includes('Page 2')); expect(firstPage1Idx).toBeGreaterThanOrEqual(0); expect(firstPage2Idx).toBeGreaterThanOrEqual(0); expect(firstPage1Idx).toBeLessThan(firstPage2Idx); }); // ── 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(); // Wait for page 2 items to appear in the DOM (auto-retrying assertion) await expect(items.filter({ hasText: 'Page 2' }).first()).toBeVisible({ timeout: 5000 }); // Verify both pages present and page-2 items BEFORE page-1 in DOM order const allTexts = await items.allTextContents(); const firstPage1Idx = allTexts.findIndex(t => t.includes('Page 1')); const firstPage2Idx = allTexts.findIndex(t => t.includes('Page 2')); expect(firstPage1Idx).toBeGreaterThanOrEqual(0); expect(firstPage2Idx).toBeGreaterThanOrEqual(0); expect(firstPage2Idx).toBeLessThan(firstPage1Idx); }); // ── 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'); }); });