Upgrade async GPU analysis and workbench UX
This commit is contained in:
+141
-5
@@ -63,9 +63,34 @@ async function auditInteractiveNames(page, label) {
|
||||
return unnamed.length
|
||||
}
|
||||
|
||||
async function prepareAuditSession(page, baseUrl) {
|
||||
const sessionResponse = await page.request.get(`${baseUrl}/api/v1/auth/session`)
|
||||
assert(sessionResponse.ok(), `Session preflight failed with HTTP ${sessionResponse.status()}`)
|
||||
const sessionEnvelope = await sessionResponse.json()
|
||||
const session = sessionEnvelope?.data
|
||||
|
||||
if (!session?.authentication_required || session.authenticated) return session
|
||||
assert.equal(
|
||||
session.guest_access_enabled,
|
||||
true,
|
||||
'UX audit needs an authenticated session or enabled guest access',
|
||||
)
|
||||
|
||||
const guestResponse = await page.request.post(`${baseUrl}/api/v1/auth/guest`)
|
||||
assert(guestResponse.ok(), `Guest audit session failed with HTTP ${guestResponse.status()}`)
|
||||
const guestEnvelope = await guestResponse.json()
|
||||
return guestEnvelope?.data
|
||||
}
|
||||
|
||||
async function layoutEvidence(page) {
|
||||
return page.evaluate(() => {
|
||||
const root = document.documentElement
|
||||
const rect = (selector) => {
|
||||
const bounds = document.querySelector(selector)?.getBoundingClientRect()
|
||||
return bounds
|
||||
? { top: bounds.top, bottom: bounds.bottom, left: bounds.left, right: bounds.right, width: bounds.width, height: bounds.height }
|
||||
: null
|
||||
}
|
||||
const main = document.querySelector('.workbench-main')?.getBoundingClientRect()
|
||||
const map = document.querySelector('.geo-map-stage')?.getBoundingClientRect()
|
||||
const theme = document.querySelector('.geo-theme-panel')?.getBoundingClientRect()
|
||||
@@ -75,6 +100,11 @@ async function layoutEvidence(page) {
|
||||
document_width: root.scrollWidth,
|
||||
body_width: document.body.scrollWidth,
|
||||
horizontal_overflow_px: Math.max(0, root.scrollWidth - root.clientWidth),
|
||||
shell_navigation: rect('.workbench-sidebar'),
|
||||
topbar: rect('.workbench-topbar'),
|
||||
guest_banner: rect('.guest-mode-banner'),
|
||||
explorer_header: rect('.geo-explorer-header'),
|
||||
live_analysis_journey: rect('.live-analysis-journey'),
|
||||
main: main ? { left: main.left, right: main.right, width: main.width } : null,
|
||||
map: map ? { left: map.left, right: map.right, width: map.width, height: map.height } : null,
|
||||
theme: theme ? { left: theme.left, right: theme.right, width: theme.width } : null,
|
||||
@@ -82,6 +112,57 @@ async function layoutEvidence(page) {
|
||||
})
|
||||
}
|
||||
|
||||
async function runLandingViewport(browser, baseUrl, outputDir, viewport) {
|
||||
const page = await browser.newPage({ viewport })
|
||||
const consoleErrors = []
|
||||
const failedRequests = []
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') consoleErrors.push(message.text())
|
||||
})
|
||||
page.on('pageerror', (error) => consoleErrors.push(error.message))
|
||||
page.on('requestfailed', (request) => {
|
||||
if (request.url().startsWith(baseUrl)) {
|
||||
failedRequests.push(`${request.method()} ${request.url()}: ${request.failure()?.errorText}`)
|
||||
}
|
||||
})
|
||||
try {
|
||||
await page.goto(baseUrl, { waitUntil: 'networkidle', timeout: 60_000 })
|
||||
await page.locator('.landing-page').waitFor({ state: 'visible', timeout: 15_000 })
|
||||
await auditInteractiveNames(page, `${viewport.width}px landing`)
|
||||
const horizontalOverflow = await page.evaluate(() => (
|
||||
Math.max(0, document.documentElement.scrollWidth - document.documentElement.clientWidth)
|
||||
))
|
||||
assert.equal(horizontalOverflow, 0, `${viewport.width}px landing overflows horizontally`)
|
||||
assert.equal(
|
||||
await page.getByRole('heading', { level: 1 }).count(),
|
||||
1,
|
||||
`${viewport.width}px landing needs one clear primary heading`,
|
||||
)
|
||||
|
||||
if (viewport.width <= 760) {
|
||||
const menu = page.locator('.landing-menu-toggle')
|
||||
assert.equal(await menu.getAttribute('aria-label'), 'Navigatie openen')
|
||||
await menu.click()
|
||||
assert.equal(await menu.getAttribute('aria-expanded'), 'true')
|
||||
await page.getByRole('navigation', { name: 'Landingspagina' }).waitFor({ state: 'visible' })
|
||||
await page.getByRole('button', { name: 'Navigatie sluiten' }).click()
|
||||
}
|
||||
|
||||
await page.screenshot({
|
||||
path: path.join(outputDir, `landing-${viewport.width}x${viewport.height}.png`),
|
||||
fullPage: true,
|
||||
})
|
||||
return {
|
||||
viewport,
|
||||
horizontal_overflow_px: horizontalOverflow,
|
||||
console_errors: consoleErrors,
|
||||
failed_requests: failedRequests,
|
||||
}
|
||||
} finally {
|
||||
await page.close()
|
||||
}
|
||||
}
|
||||
|
||||
async function runViewport(browser, baseUrl, outputDir, viewport) {
|
||||
const page = await browser.newPage({ viewport })
|
||||
const consoleErrors = []
|
||||
@@ -96,14 +177,41 @@ async function runViewport(browser, baseUrl, outputDir, viewport) {
|
||||
}
|
||||
})
|
||||
try {
|
||||
await prepareAuditSession(page, baseUrl)
|
||||
const startedAt = Date.now()
|
||||
await page.goto(baseUrl, { waitUntil: 'networkidle', timeout: 60_000 })
|
||||
await page.getByTestId('map-workspace').waitFor({ state: 'visible', timeout: 30_000 })
|
||||
const readyMs = Date.now() - startedAt
|
||||
await auditInteractiveNames(page, `${viewport.width}px map explorer`)
|
||||
const layout = await layoutEvidence(page)
|
||||
const clippedNavigationLabels = await page.locator('.nav-item span').evaluateAll((labels) => labels
|
||||
.filter((label) => label.getClientRects().length > 0 && label.scrollWidth > label.clientWidth + 1)
|
||||
.map((label) => label.textContent?.trim() || ''))
|
||||
assert.equal(layout.horizontal_overflow_px, 0, `${viewport.width}px layout overflows horizontally`)
|
||||
assert.deepEqual(clippedNavigationLabels, [], `${viewport.width}px navigation clips visible labels`)
|
||||
assert(layout.map && layout.map.width >= Math.min(320, viewport.width - 32), `${viewport.width}px map is too narrow`)
|
||||
if (layout.topbar && layout.guest_banner) {
|
||||
assert(
|
||||
layout.topbar.bottom <= layout.guest_banner.top + 1,
|
||||
`${viewport.width}px topbar overlaps the guest access banner`,
|
||||
)
|
||||
}
|
||||
if (layout.guest_banner && layout.explorer_header) {
|
||||
assert(
|
||||
layout.guest_banner.bottom <= layout.explorer_header.top + 1,
|
||||
`${viewport.width}px guest access banner overlaps the explorer heading`,
|
||||
)
|
||||
}
|
||||
if (layout.shell_navigation && layout.live_analysis_journey) {
|
||||
const verticalOverlap = Math.min(layout.shell_navigation.bottom, layout.live_analysis_journey.bottom)
|
||||
- Math.max(layout.shell_navigation.top, layout.live_analysis_journey.top)
|
||||
const horizontalOverlap = Math.min(layout.shell_navigation.right, layout.live_analysis_journey.right)
|
||||
- Math.max(layout.shell_navigation.left, layout.live_analysis_journey.left)
|
||||
assert(
|
||||
verticalOverlap <= 1 || horizontalOverlap <= 1,
|
||||
`${viewport.width}px navigation overlaps the live analysis journey`,
|
||||
)
|
||||
}
|
||||
|
||||
const currentTab = page.getByRole('tab', { name: 'Laatste toestand' })
|
||||
const evolutionTab = page.getByRole('tab', { name: 'Evolutie' })
|
||||
@@ -127,6 +235,7 @@ async function runViewport(browser, baseUrl, outputDir, viewport) {
|
||||
viewport,
|
||||
ready_ms: readyMs,
|
||||
layout,
|
||||
clipped_navigation_labels: clippedNavigationLabels,
|
||||
console_errors: consoleErrors,
|
||||
failed_requests: failedRequests,
|
||||
}
|
||||
@@ -144,6 +253,7 @@ async function runLoadingAndAdvancedAudit(browser, baseUrl, outputDir) {
|
||||
await route.continue()
|
||||
})
|
||||
try {
|
||||
const auditSession = await prepareAuditSession(page, baseUrl)
|
||||
await page.goto(baseUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 })
|
||||
const loadingStatus = page.getByRole('status', { name: '' }).filter({
|
||||
hasText: 'Databronnen worden gecontroleerd',
|
||||
@@ -175,10 +285,30 @@ async function runLoadingAndAdvancedAudit(browser, baseUrl, outputDir) {
|
||||
await page.screenshot({ path: path.join(outputDir, 'advanced-coverage-budget.png') })
|
||||
|
||||
const auditedWorkspaces = []
|
||||
for (const workspace of ['data', 'assistant', 'analysis', 'ai', 'exports', 'overview', 'system']) {
|
||||
const workspaceKeys = ['data', 'assistant', 'analysis', 'ai', 'exports', 'overview']
|
||||
if (auditSession?.role === 'guest') {
|
||||
assert.equal(
|
||||
await page.getByTestId('workspace-nav-system').count(),
|
||||
0,
|
||||
'Guest session exposes operator-only system settings',
|
||||
)
|
||||
} else {
|
||||
workspaceKeys.push('system')
|
||||
}
|
||||
for (const workspace of workspaceKeys) {
|
||||
await page.getByTestId(`workspace-nav-${workspace}`).click()
|
||||
await page.waitForTimeout(100)
|
||||
await auditInteractiveNames(page, `${workspace} workspace`)
|
||||
if (workspace === 'ai') {
|
||||
await page.screenshot({ path: path.join(outputDir, 'ai-workspace.png'), fullPage: true })
|
||||
const segmentationDisclosure = page.locator('.segmentation-disclosure')
|
||||
await segmentationDisclosure.scrollIntoViewIfNeeded()
|
||||
await segmentationDisclosure.locator('summary').first().click()
|
||||
await page.waitForTimeout(150)
|
||||
await auditInteractiveNames(page, 'open segmentation lab')
|
||||
await segmentationDisclosure.locator('.ai-lab-run-surface').scrollIntoViewIfNeeded()
|
||||
await page.screenshot({ path: path.join(outputDir, 'ai-segmentation.png') })
|
||||
}
|
||||
auditedWorkspaces.push(workspace)
|
||||
}
|
||||
|
||||
@@ -203,21 +333,27 @@ async function main() {
|
||||
schema_version: 1,
|
||||
base_url: args.baseUrl,
|
||||
started_at: new Date().toISOString(),
|
||||
landing_viewports: [],
|
||||
viewports: [],
|
||||
bootstrap: null,
|
||||
status: 'running',
|
||||
}
|
||||
try {
|
||||
for (const viewport of [
|
||||
const viewports = [
|
||||
{ width: 390, height: 844 },
|
||||
{ width: 1366, height: 768 },
|
||||
{ width: 2560, height: 1080 },
|
||||
]) {
|
||||
]
|
||||
for (const viewport of viewports) {
|
||||
evidence.landing_viewports.push(await runLandingViewport(browser, args.baseUrl, outputDir, viewport))
|
||||
}
|
||||
for (const viewport of viewports) {
|
||||
evidence.viewports.push(await runViewport(browser, args.baseUrl, outputDir, viewport))
|
||||
}
|
||||
evidence.bootstrap = await runLoadingAndAdvancedAudit(browser, args.baseUrl, outputDir)
|
||||
const unexpectedConsoleErrors = evidence.viewports.flatMap((item) => item.console_errors)
|
||||
const unexpectedFailedRequests = evidence.viewports.flatMap((item) => item.failed_requests)
|
||||
const auditedPages = [...evidence.landing_viewports, ...evidence.viewports]
|
||||
const unexpectedConsoleErrors = auditedPages.flatMap((item) => item.console_errors)
|
||||
const unexpectedFailedRequests = auditedPages.flatMap((item) => item.failed_requests)
|
||||
assert.deepEqual(unexpectedConsoleErrors, [], 'UX audit captured console errors')
|
||||
assert.deepEqual(unexpectedFailedRequests, [], 'UX audit captured failed API requests')
|
||||
evidence.status = 'passed'
|
||||
|
||||
Reference in New Issue
Block a user