|
| 1 | +import { test, expect, Page } from '@playwright/test'; |
| 2 | + |
| 3 | +/** |
| 4 | + * Wait for the DataTable to finish loading. |
| 5 | + * The tables show a #table-loading indicator while fetching data, |
| 6 | + * then render rows into #mainContentList. |
| 7 | + */ |
| 8 | +async function waitForTableLoaded(page: Page) { |
| 9 | + // Wait for loading indicator to appear then disappear |
| 10 | + await expect(page.locator('#table-loading')).toBeHidden({ timeout: 30000 }); |
| 11 | + // Table should have at least one data row |
| 12 | + await expect(page.locator('#mainContentList tbody tr').first()).toBeVisible({ timeout: 15000 }); |
| 13 | +} |
| 14 | + |
| 15 | +// Content type pages and their expected column headers |
| 16 | +const contentPages = [ |
| 17 | + { path: '/content/canon/', name: 'Canonical Works', minRows: 50 }, |
| 18 | + { path: '/content/monographs/', name: 'Monographs', minRows: 50 }, |
| 19 | + { path: '/content/articles/', name: 'Articles', minRows: 50, extraColumn: 'Journal' }, |
| 20 | + { path: '/content/booklets/', name: 'Booklets', minRows: 10 }, |
| 21 | + { path: '/content/essays/', name: 'Essays', minRows: 20 }, |
| 22 | + { path: '/content/papers/', name: 'Papers', minRows: 10 }, |
| 23 | + { path: '/content/excerpts/', name: 'Excerpts', minRows: 10 }, |
| 24 | + { path: '/content/av/', name: 'Audio/Video', minRows: 20 }, |
| 25 | + { path: '/content/reference/', name: 'Reference Shelf', minRows: 5 }, |
| 26 | +]; |
| 27 | + |
| 28 | +test('content index page lists all types', async ({ page }) => { |
| 29 | + await page.goto('/content/'); |
| 30 | + await expect(page).toHaveTitle(/Content/i); |
| 31 | + |
| 32 | + // Verify all content type links exist |
| 33 | + for (const { name } of contentPages) { |
| 34 | + await expect(page.getByRole('link', { name })).toBeVisible(); |
| 35 | + } |
| 36 | +}); |
| 37 | + |
| 38 | +for (const { path, name, minRows, extraColumn } of contentPages) { |
| 39 | + test(`${name} table loads with data`, async ({ page }) => { |
| 40 | + await page.goto(path); |
| 41 | + await waitForTableLoaded(page); |
| 42 | + |
| 43 | + // Standard columns should be present |
| 44 | + const headers = page.locator('#mainContentList th'); |
| 45 | + await expect(headers.filter({ hasText: 'Name' })).toBeVisible(); |
| 46 | + await expect(headers.filter({ hasText: 'Year' })).toBeVisible(); |
| 47 | + await expect(headers.filter({ hasText: 'Author' })).toBeVisible(); |
| 48 | + |
| 49 | + // Check type-specific extra column |
| 50 | + if (extraColumn) { |
| 51 | + await expect(headers.filter({ hasText: extraColumn })).toBeVisible(); |
| 52 | + } |
| 53 | + |
| 54 | + // Verify minimum row count |
| 55 | + const rows = page.locator('#mainContentList tbody tr'); |
| 56 | + const count = await rows.count(); |
| 57 | + expect(count).toBeGreaterThanOrEqual(minRows); |
| 58 | + }); |
| 59 | +} |
| 60 | + |
| 61 | +test('canon table has clickable entry links', async ({ page }) => { |
| 62 | + await page.goto('/content/canon/'); |
| 63 | + await waitForTableLoaded(page); |
| 64 | + |
| 65 | + // First row should have a link in the Name column |
| 66 | + const firstLink = page.locator('#mainContentList tbody tr').first().locator('a').first(); |
| 67 | + await expect(firstLink).toBeVisible(); |
| 68 | + const href = await firstLink.getAttribute('href'); |
| 69 | + expect(href).toBeTruthy(); |
| 70 | +}); |
| 71 | + |
| 72 | +test('articles table sorting by year', async ({ page }) => { |
| 73 | + await page.goto('/content/articles/'); |
| 74 | + await waitForTableLoaded(page); |
| 75 | + |
| 76 | + // Click the Year header to sort |
| 77 | + const yearHeader = page.locator('#mainContentList th', { hasText: 'Year' }); |
| 78 | + await yearHeader.click(); |
| 79 | + |
| 80 | + // After sorting, first visible year should be a number |
| 81 | + const firstYearCell = page.locator('#mainContentList tbody tr').first().locator('td').nth(2); |
| 82 | + const yearText = await firstYearCell.textContent(); |
| 83 | + expect(yearText?.trim()).toMatch(/^\d{4}$/); |
| 84 | +}); |
| 85 | + |
| 86 | +test('datatable search/filter works', async ({ page }) => { |
| 87 | + await page.goto('/content/monographs/'); |
| 88 | + await waitForTableLoaded(page); |
| 89 | + |
| 90 | + const initialCount = await page.locator('#mainContentList tbody tr').count(); |
| 91 | + |
| 92 | + // DataTables adds a search input — find and use it |
| 93 | + const dtSearch = page.locator('input[type="search"]'); |
| 94 | + if (await dtSearch.isVisible()) { |
| 95 | + await dtSearch.fill('meditation'); |
| 96 | + // Wait for filtering to apply |
| 97 | + await page.waitForTimeout(500); |
| 98 | + const filteredCount = await page.locator('#mainContentList tbody tr').count(); |
| 99 | + // Filtered results should be fewer (or equal if all match) |
| 100 | + expect(filteredCount).toBeLessThanOrEqual(initialCount); |
| 101 | + // At least one result should mention meditation |
| 102 | + if (filteredCount > 0) { |
| 103 | + const firstRow = page.locator('#mainContentList tbody tr').first(); |
| 104 | + await expect(firstRow).toContainText(/[Mm]editation/); |
| 105 | + } |
| 106 | + } |
| 107 | +}); |
| 108 | + |
| 109 | +test('all content page loads total count', async ({ page }) => { |
| 110 | + await page.goto('/content/all'); |
| 111 | + await waitForTableLoaded(page); |
| 112 | + |
| 113 | + // Should have a large number of entries |
| 114 | + const rows = page.locator('#mainContentList tbody tr'); |
| 115 | + const count = await rows.count(); |
| 116 | + expect(count).toBeGreaterThanOrEqual(200); |
| 117 | +}); |
0 commit comments