96 lines
4.3 KiB
TypeScript
96 lines
4.3 KiB
TypeScript
/**
|
|
* Navigation Discovery — scans the cPad sidebar and collects all /cp links.
|
|
*
|
|
* This test acts as both:
|
|
* 1. A standalone spec that validates the nav scan returns ≥ 1 link.
|
|
* 2. A data producer: it writes the discovered URLs to
|
|
* tests/.discovered/cpad-links.json so that navigation.spec.ts can
|
|
* consume them dynamically.
|
|
*
|
|
* Ignored links:
|
|
* • /cp/logout
|
|
* • javascript:void / # anchors
|
|
* • External URLs (not starting with /cp)
|
|
*
|
|
* Run:
|
|
* npx playwright test tests/cpad/navigation-discovery.spec.ts --project=cpad
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { DASHBOARD_PATH, CP_PATH } from '../helpers/auth';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const DISCOVERED_DIR = path.join('tests', '.discovered');
|
|
const DISCOVERED_FILE = path.join(DISCOVERED_DIR, 'cpad-links.json');
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Helpers
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/** Returns true if the href should be included in navigation tests */
|
|
function isNavigableLink(href: string | null): boolean {
|
|
if (!href) return false;
|
|
if (!href.startsWith(CP_PATH)) return false;
|
|
if (href.includes('/logout')) return false;
|
|
if (href.startsWith('javascript:')) return false;
|
|
if (href === CP_PATH || href === CP_PATH + '/') return false; // exclude root (same as dashboard)
|
|
return true;
|
|
}
|
|
|
|
/** Remove query strings and anchors for clean deduplication */
|
|
function normalise(href: string): string {
|
|
try {
|
|
const u = new URL(href, 'http://placeholder');
|
|
return u.pathname;
|
|
} catch {
|
|
return href.split('?')[0].split('#')[0];
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Discovery test
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
test.describe('cPad Navigation Discovery', () => {
|
|
test('scan sidebar and collect all /cp navigation links', async ({ page }) => {
|
|
await page.goto(DASHBOARD_PATH);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Ensure we're not on the login page
|
|
expect(page.url()).not.toContain('/login');
|
|
|
|
// ── Widen the scan to cover lazy-loaded submenu items ──────────────────
|
|
// Hover over sidebar nav items to expand hidden submenus
|
|
const menuItems = page.locator('.nav-item, .sidebar-item, .menu-item, li.nav-item');
|
|
const menuCount = await menuItems.count();
|
|
for (let i = 0; i < Math.min(menuCount, 30); i++) {
|
|
await menuItems.nth(i).hover().catch(() => null);
|
|
}
|
|
|
|
// ── Collect all anchor hrefs ───────────────────────────────────────────
|
|
const rawHrefs: string[] = await page.$$eval('a[href]', (anchors) =>
|
|
anchors.map((a) => (a as HTMLAnchorElement).getAttribute('href') ?? ''),
|
|
);
|
|
|
|
const discovered = [...new Set(
|
|
rawHrefs
|
|
.map((h) => normalise(h))
|
|
.filter(isNavigableLink),
|
|
)].sort();
|
|
|
|
console.log(`[discovery] Found ${discovered.length} navigable /cp links`);
|
|
discovered.forEach((l) => console.log(' •', l));
|
|
|
|
// Must find at least a few links — if not, something is wrong with auth
|
|
expect(discovered.length, 'Expected to find /cp navigation links').toBeGreaterThanOrEqual(1);
|
|
|
|
// ── Persist for navigation.spec.ts ────────────────────────────────────
|
|
if (!fs.existsSync(DISCOVERED_DIR)) {
|
|
fs.mkdirSync(DISCOVERED_DIR, { recursive: true });
|
|
}
|
|
fs.writeFileSync(DISCOVERED_FILE, JSON.stringify(discovered, null, 2), 'utf8');
|
|
console.log(`[discovery] Links saved to ${DISCOVERED_FILE}`);
|
|
});
|
|
});
|