Initial public release
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${1:-${GE_INTEL_BASE_URL:-http://localhost:1202}}"
|
||||
OUTPUT_ROOT="${2:-${SCREENSHOT_OUTPUT_DIR:-artifacts/screenshots}}"
|
||||
TIMESTAMP="$(date -u +"%Y%m%dT%H%M%SZ")"
|
||||
OUTPUT_DIR="${OUTPUT_ROOT%/}/workbench-${TIMESTAMP}"
|
||||
CAPTURE_MOBILE="${CAPTURE_MOBILE:-1}"
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "${ROOT}"
|
||||
|
||||
NODE_BIN=""
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
NODE_BIN="$(command -v node)"
|
||||
elif command -v node.exe >/dev/null 2>&1; then
|
||||
NODE_BIN="$(command -v node.exe)"
|
||||
fi
|
||||
|
||||
if [ -z "${NODE_BIN}" ]; then
|
||||
echo "Node.js is required for screenshot capture." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! "${NODE_BIN}" --input-type=module -e "await import('playwright')" >/dev/null 2>&1; then
|
||||
cat >&2 <<'EOF'
|
||||
Playwright is required for screenshot capture but is not available to Node.
|
||||
|
||||
Install or expose Playwright in the runner environment, then retry:
|
||||
npm install --no-save playwright
|
||||
npx playwright install chromium
|
||||
|
||||
GeoIntel does not add Playwright as a frontend dependency by default; this
|
||||
script is an optional visual regression handoff tool.
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
tmp_js="$(mktemp "${ROOT}/.capture_workbench_screenshots.XXXXXX.mjs")"
|
||||
trap 'rm -f "${tmp_js}"' EXIT
|
||||
|
||||
cat >"${tmp_js}" <<'JS'
|
||||
import { chromium, request } from 'playwright'
|
||||
import { mkdir, writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
const [baseUrl, outputDir, captureMobileRaw] = process.argv.slice(2)
|
||||
const captureMobile = captureMobileRaw === '1'
|
||||
const normalizedBaseUrl = baseUrl.replace(/\/$/, '')
|
||||
|
||||
const workspaces = [
|
||||
['overview', 'Overview'],
|
||||
['data', 'Data'],
|
||||
['map', 'Map'],
|
||||
['analysis', 'QA/QC'],
|
||||
['ai', 'AI Labs'],
|
||||
['exports', 'Exports'],
|
||||
['system', 'System'],
|
||||
]
|
||||
|
||||
const viewports = [
|
||||
{ name: 'desktop', width: 1366, height: 900 },
|
||||
]
|
||||
|
||||
if (captureMobile) {
|
||||
viewports.push({ name: 'mobile', width: 390, height: 844 })
|
||||
}
|
||||
|
||||
function sanitize(value) {
|
||||
return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
|
||||
}
|
||||
|
||||
async function ensureDemoWorkflow() {
|
||||
const api = await request.newContext({ baseURL: normalizedBaseUrl })
|
||||
const response = await api.post('/api/v1/demo/workflow')
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Demo workflow seed failed with HTTP ${response.status()}`)
|
||||
}
|
||||
const payload = await response.json()
|
||||
if (!payload?.data?.project_id) {
|
||||
throw new Error('Demo workflow response did not include data.project_id')
|
||||
}
|
||||
await api.dispose()
|
||||
return payload.data
|
||||
}
|
||||
|
||||
async function capture() {
|
||||
await mkdir(outputDir, { recursive: true })
|
||||
const demo = await ensureDemoWorkflow()
|
||||
const browser = await chromium.launch()
|
||||
const screenshots = []
|
||||
const consoleMessages = []
|
||||
|
||||
try {
|
||||
for (const viewport of viewports) {
|
||||
const page = await browser.newPage({ viewport })
|
||||
page.on('console', (message) => {
|
||||
if (['error', 'warning'].includes(message.type())) {
|
||||
consoleMessages.push({
|
||||
viewport: viewport.name,
|
||||
type: message.type(),
|
||||
text: message.text(),
|
||||
})
|
||||
}
|
||||
})
|
||||
page.on('pageerror', (error) => {
|
||||
consoleMessages.push({
|
||||
viewport: viewport.name,
|
||||
type: 'pageerror',
|
||||
text: error.message,
|
||||
})
|
||||
})
|
||||
|
||||
await page.goto(normalizedBaseUrl, { waitUntil: 'networkidle' })
|
||||
await page.locator('#root').waitFor({ state: 'visible', timeout: 15000 })
|
||||
await page.getByText('GeoIntel', { exact: false }).first().waitFor({ timeout: 15000 })
|
||||
|
||||
const bodyText = await page.locator('body').innerText({ timeout: 15000 })
|
||||
if (/vite|webpack|runtime error|failed to compile/i.test(bodyText)) {
|
||||
throw new Error(`Framework error overlay detected in ${viewport.name} viewport`)
|
||||
}
|
||||
|
||||
for (const [workspaceKey, label] of workspaces) {
|
||||
await page.locator(`[data-testid="workspace-nav-${workspaceKey}"]`).click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.locator('.workspace-heading').waitFor({ state: 'visible', timeout: 10000 })
|
||||
const screenshotName = `${viewport.name}-${sanitize(workspaceKey)}.png`
|
||||
const screenshotPath = path.join(outputDir, screenshotName)
|
||||
await page.screenshot({ path: screenshotPath, fullPage: false })
|
||||
screenshots.push({
|
||||
viewport: viewport.name,
|
||||
workspace: workspaceKey,
|
||||
label,
|
||||
path: screenshotPath,
|
||||
})
|
||||
}
|
||||
|
||||
await page.close()
|
||||
}
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
const relevantConsoleMessages = consoleMessages.filter((message) => message.type() !== 'warning')
|
||||
const manifest = {
|
||||
captured_at: new Date().toISOString(),
|
||||
base_url: normalizedBaseUrl,
|
||||
demo_project_id: demo.project_id,
|
||||
output_dir: outputDir,
|
||||
capture_mobile: captureMobile,
|
||||
screenshots,
|
||||
console_messages: consoleMessages,
|
||||
}
|
||||
await writeFile(path.join(outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8')
|
||||
|
||||
if (relevantConsoleMessages.length > 0) {
|
||||
throw new Error(`Console/page errors detected. See ${path.join(outputDir, 'manifest.json')}`)
|
||||
}
|
||||
|
||||
console.log(`Workbench screenshots captured: ${screenshots.length}`)
|
||||
console.log(`Output: ${outputDir}`)
|
||||
}
|
||||
|
||||
capture().catch((error) => {
|
||||
console.error(error.message)
|
||||
process.exit(1)
|
||||
})
|
||||
JS
|
||||
|
||||
"${NODE_BIN}" "${tmp_js}" "${BASE_URL}" "${OUTPUT_DIR}" "${CAPTURE_MOBILE}"
|
||||
Reference in New Issue
Block a user