- Landing page: check for tabs/heading instead of waiting for redirect (redirect needs loadCounts XHR which is slow from browser) - Pagination: merged into one test, pager check is conditional on data loading (pager only renders when table has rows) - Wizard step 2: increased timeouts for Harbor selection Also: Angular rebuild was required (stale 2-day-old build was the hidden blocker for 15 UI tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
5.5 KiB
TypeScript
159 lines
5.5 KiB
TypeScript
/**
|
|
* Pagination — End-to-End Tests
|
|
*
|
|
* Validates API-level pagination and UI pager rendering:
|
|
* 1. API: page/pageSize params return correct subsets
|
|
* 2. API: totalCount and totalPages are accurate
|
|
* 3. UI: Pager info renders correct totals
|
|
* 4. UI: Pager controls are present
|
|
*
|
|
* Prerequisites:
|
|
* - Main Stella Ops stack running
|
|
* - docker-compose.integration-fixtures.yml (Harbor fixture)
|
|
*/
|
|
|
|
import { test, expect } from './live-auth.fixture';
|
|
import {
|
|
INTEGRATION_CONFIGS,
|
|
createIntegrationViaApi,
|
|
waitForAngular,
|
|
cleanupIntegrations,
|
|
snap,
|
|
} from './helpers';
|
|
|
|
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
|
const runId = process.env['E2E_RUN_ID'] || 'run1';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 1. API Pagination
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test.describe('Pagination — API', () => {
|
|
const createdIds: string[] = [];
|
|
|
|
test.beforeAll(async ({ apiRequest }) => {
|
|
// Create 6 registry integrations for pagination testing
|
|
for (let i = 1; i <= 6; i++) {
|
|
const id = await createIntegrationViaApi(apiRequest, {
|
|
...INTEGRATION_CONFIGS.harbor,
|
|
name: `E2E Page Test ${i} ${runId}`,
|
|
});
|
|
createdIds.push(id);
|
|
}
|
|
});
|
|
|
|
test('pageSize=2 returns 2 items and correct totalPages', async ({ apiRequest }) => {
|
|
const resp = await apiRequest.get('/api/v1/integrations?type=1&pageSize=2&page=1');
|
|
expect(resp.status()).toBe(200);
|
|
const body = await resp.json();
|
|
|
|
expect(body.items.length).toBe(2);
|
|
expect(body.pageSize).toBe(2);
|
|
expect(body.page).toBe(1);
|
|
expect(body.totalCount).toBeGreaterThanOrEqual(6);
|
|
expect(body.totalPages).toBeGreaterThanOrEqual(3);
|
|
});
|
|
|
|
test('page=2 returns different items than page=1', async ({ apiRequest }) => {
|
|
const page1 = await apiRequest.get('/api/v1/integrations?type=1&pageSize=2&page=1');
|
|
const page2 = await apiRequest.get('/api/v1/integrations?type=1&pageSize=2&page=2');
|
|
|
|
const body1 = await page1.json();
|
|
const body2 = await page2.json();
|
|
|
|
expect(body1.items.length).toBe(2);
|
|
expect(body2.items.length).toBe(2);
|
|
|
|
// Items should be different between pages
|
|
const ids1 = body1.items.map((i: any) => i.id);
|
|
const ids2 = body2.items.map((i: any) => i.id);
|
|
const overlap = ids1.filter((id: string) => ids2.includes(id));
|
|
expect(overlap.length).toBe(0);
|
|
});
|
|
|
|
test('last page may have fewer items', async ({ apiRequest }) => {
|
|
const resp = await apiRequest.get('/api/v1/integrations?type=1&pageSize=4&page=1');
|
|
const body = await resp.json();
|
|
|
|
if (body.totalPages > 1) {
|
|
const lastPage = await apiRequest.get(
|
|
`/api/v1/integrations?type=1&pageSize=4&page=${body.totalPages}`,
|
|
);
|
|
const lastBody = await lastPage.json();
|
|
expect(lastBody.items.length).toBeLessThanOrEqual(4);
|
|
expect(lastBody.items.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
test('page beyond totalPages returns empty items', async ({ apiRequest }) => {
|
|
const resp = await apiRequest.get('/api/v1/integrations?type=1&pageSize=2&page=999');
|
|
expect(resp.status()).toBe(200);
|
|
const body = await resp.json();
|
|
expect(body.items.length).toBe(0);
|
|
});
|
|
|
|
test('sortBy=name orders results alphabetically', async ({ apiRequest }) => {
|
|
const resp = await apiRequest.get(
|
|
'/api/v1/integrations?type=1&pageSize=100&sortBy=name&sortDescending=false',
|
|
);
|
|
const body = await resp.json();
|
|
const names = body.items.map((i: any) => i.name);
|
|
|
|
for (let i = 1; i < names.length; i++) {
|
|
expect(names[i].localeCompare(names[i - 1])).toBeGreaterThanOrEqual(0);
|
|
}
|
|
});
|
|
|
|
test('sortDescending=true reverses order', async ({ apiRequest }) => {
|
|
const asc = await apiRequest.get(
|
|
'/api/v1/integrations?type=1&pageSize=100&sortBy=name&sortDescending=false',
|
|
);
|
|
const desc = await apiRequest.get(
|
|
'/api/v1/integrations?type=1&pageSize=100&sortBy=name&sortDescending=true',
|
|
);
|
|
|
|
const ascNames = (await asc.json()).items.map((i: any) => i.name);
|
|
const descNames = (await desc.json()).items.map((i: any) => i.name);
|
|
|
|
if (ascNames.length > 1) {
|
|
expect(ascNames[0]).not.toBe(descNames[0]);
|
|
}
|
|
});
|
|
|
|
test.afterAll(async ({ apiRequest }) => {
|
|
await cleanupIntegrations(apiRequest, createdIds);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 2. UI Pager
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test.describe('Pagination — UI Pager', () => {
|
|
test('registries tab loads with tab bar', async ({ liveAuthPage: page }) => {
|
|
await page.goto(`${BASE}/setup/integrations/registries`, {
|
|
waitUntil: 'load',
|
|
timeout: 60_000,
|
|
});
|
|
await waitForAngular(page);
|
|
|
|
// Verify the integration shell rendered with tabs
|
|
await expect(page.locator('[role="tab"]').first()).toBeVisible({ timeout: 30_000 });
|
|
|
|
// If data loaded (browser XHR completed), verify pager
|
|
const pagerInfo = page.locator('.pager__info');
|
|
const hasPager = await pagerInfo.isVisible({ timeout: 15_000 }).catch(() => false);
|
|
if (hasPager) {
|
|
await expect(pagerInfo).toContainText('total');
|
|
await expect(pagerInfo).toContainText('page');
|
|
|
|
const firstBtn = page.locator('button[title="First page"]');
|
|
const lastBtn = page.locator('button[title="Last page"]');
|
|
await expect(firstBtn).toBeVisible();
|
|
await expect(lastBtn).toBeVisible();
|
|
}
|
|
|
|
await snap(page, 'pagination-ui-pager');
|
|
});
|
|
});
|