Files
xinmi/scripts/verify_business_browser.cjs
T
yawan f7fc992f57
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
2026-09-22 16:19:06 +08:00

154 lines
9.6 KiB
JavaScript

const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const { chromium } = require('/Users/macclaw/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
async function main() {
const directory = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance'
const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'business-apply-20260916.manifest.json'), 'utf8'))
const credentials = JSON.parse(fs.readFileSync(path.join(directory, 'super-admin-login.json'), 'utf8'))
const login = await fetch('http://127.0.0.1:18080/api/v1/auth/login', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: credentials.email, password: credentials.password}),
})
assert.equal(login.status, 200, 'Administrator login')
const auth = await login.json()
const headers = {'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}`, 'X-Tenant-ID': '10059'}
async function request(url, method = 'GET', data) {
const response = await fetch(`http://127.0.0.1:18080/api/v1${url}`, {method, headers, body: data === undefined ? undefined : JSON.stringify(data)})
assert.ok(response.ok, `Acceptance HTTP ${response.status}`)
return response.json()
}
const checks = [], errors = []
const browser = await chromium.launch({headless: true, channel: 'chrome'})
let fixture, page, stage = 'starting'
const check = (name, passed) => {checks.push({check: name, passed: !!passed}); assert.ok(passed, name)}
try {
const context = await browser.newContext({viewport: {width: 1440, height: 1000}})
await context.addInitScript(a => {
const tenant = {...a.active_tenant, id: '10059'}
const data = {weknora_user: JSON.stringify({...a.user, tenant_id: String(a.user.tenant_id)}),
weknora_tenant: JSON.stringify(tenant), weknora_token: a.token,
weknora_refresh_token: a.refresh_token, weknora_memberships: JSON.stringify(a.memberships)}
for (const [key, value] of Object.entries(data)) localStorage.setItem(key, value)
localStorage.setItem('weknora:new-user-guide-done:v1', '1')
}, auth)
page = await context.newPage()
page.setDefaultTimeout(15000)
page.on('pageerror', () => errors.push('browser_error'))
async function visit(url) {
await page.goto('http://127.0.0.1:15173' + url)
await page.locator('.workbench-resource').first().waitFor({timeout: 30000})
const guide = page.locator('.guide__close')
if (await guide.isVisible()) await guide.click()
}
async function resize(width, height) {
stage = 'resize_viewport'
await page.setViewportSize({width, height})
stage = 'wait_sidebar_collapse'
if (width <= 640) await page.locator('.aside_box--collapsed').waitFor()
stage = 'wait_page_dimensions'
await page.waitForFunction(() => {
const bounds = document.querySelector('.workbench-page')?.getBoundingClientRect()
return bounds && bounds.width >= Math.min(300, innerWidth - 80) && bounds.right <= innerWidth + 2
}, undefined, {polling: 100})
stage = 'wait_layout_transition'
await page.evaluate(async () => {
await Promise.race([
Promise.all(document.getAnimations()
.filter(animation => Number.isFinite(animation.effect?.getComputedTiming().endTime))
.map(animation => animation.finished.catch(() => {}))),
new Promise(resolve => setTimeout(resolve, 1000)),
])
})
}
async function mobileCheck(kind) {
await resize(390, 844)
check(`${kind}_mobile_usable_width`, await page.locator('.workbench-page').evaluate(element => {
const bounds = element.getBoundingClientRect()
const detail = element.querySelector('.workbench-detail')?.getBoundingClientRect()
return bounds.width >= 300 && bounds.right <= innerWidth + 2 && detail && detail.width >= 260 && detail.right <= innerWidth + 2
}))
await page.locator('.workbench-page').evaluate(element => { element.scrollTop = 0 })
await page.screenshot({path: path.join(directory, `business-${kind}-mobile.png`), animations: 'disabled'})
check(`${kind}_mobile_no_overflow`, await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 2))
}
for (const [kind, route, tab] of [
['trainingmap', '/platform/workbench/content', 'maps'],
['campaign', '/platform/workbench/content', 'tasks'],
]) {
const ids = Object.values(manifest.resource_map[kind])
await visit(`${route}?tab=${tab}&resource=${ids.at(-1)}`)
check(`${kind}_list_count`, await page.locator('.workbench-resource').count() === ids.length)
check(`${kind}_history_warning`, await page.locator('.workbench-detail .t-alert').isVisible())
check(`${kind}_delete_hidden`, await page.locator('.workbench-detail').getByRole('button', {name: '删除', exact: true}).count() === 0)
const aiButtons = page.locator('.workbench-detail').getByRole('button', {name: kind === 'campaign' ? '重新拆解' : '重新生成方案', exact: true})
check(`${kind}_execution_disabled`, await aiButtons.isDisabled())
await page.screenshot({path: path.join(directory, `business-${kind}-desktop.png`), animations: 'disabled'})
await page.getByRole('button', {name: '全屏查看', exact: true}).click()
await page.locator('.t-dialog:visible .workbench-preview').waitFor()
check(`${kind}_fullscreen_readable`, await page.locator('.t-dialog:visible .workbench-preview').isVisible())
stage = 'close_desktop_preview'
await page.locator('.t-dialog:visible .t-dialog__close').click()
stage = 'wait_desktop_preview_closed'
await page.locator('.t-dialog:visible').waitFor({state: 'hidden'})
await mobileCheck(kind)
await page.getByRole('button', {name: '全屏查看', exact: true}).click()
await page.locator('.t-dialog:visible .workbench-preview').waitFor()
check(`${kind}_mobile_fullscreen_fits`, await page.locator('.t-dialog:visible').evaluate(element => {
const bounds = element.getBoundingClientRect()
return bounds.left >= 0 && bounds.right <= innerWidth + 2
}))
await page.screenshot({path: path.join(directory, `business-${kind}-mobile-fullscreen.png`), animations: 'disabled'})
await page.locator('.t-dialog:visible .t-dialog__close').click()
await page.locator('.t-dialog:visible').waitFor({state: 'hidden'})
await resize(1440, 1000)
}
await visit('/platform/workbench/flows')
check('three_historical_flows_listed', await page.locator('.workbench-resource').count() === 3)
await page.locator('.workbench-resource').first().click()
check('flow_history_warning', await page.locator('.workbench-detail .t-alert').isVisible())
check('flow_delete_and_save_hidden', await page.locator('.workbench-detail').getByRole('button', {name: /^(保存|解绑并删除)$/}).count() === 0)
await page.screenshot({path: path.join(directory, 'business-flow-desktop.png'), animations: 'disabled'})
await mobileCheck('flow')
await resize(1440, 1000)
const agents = (await request('/agents')).data
const agent = agents.find(a => a.enabled && a.config.model_id && a.created_by === auth.user.id)
assert.ok(agent, 'Runnable owned Agent available for branch editor fixture')
const edges = [{source: 'a', target: 'b'}, {source: 'a', target: 'c'}]
fixture = (await request('/workbench/resources/flows', 'POST', {
name: `Migration branch check ${Date.now()}`,
data: {description: 'Disposable branch preservation check',
nodes: ['a', 'b', 'c'].map(id => ({id, label: `Branch ${id}`, agent_id: agent.id})), edges},
})).data
await visit('/platform/workbench/flows')
await page.locator('.workbench-resource').filter({hasText: fixture.name}).click()
await page.locator('.flow-node').nth(2).getByRole('button').nth(0).click()
const saved = page.waitForResponse(response => response.url().endsWith(fixture.id) && response.request().method() === 'PUT')
await page.getByRole('button', {name: '保存', exact: true}).click()
check('native_branch_save_success', (await saved).status() === 200)
const current = (await request(`/workbench/resources/flows/${fixture.id}`)).data
check('node_display_reordered', current.data.nodes.map(n => n.id).join(',') === 'a,c,b')
check('branch_edges_preserved_after_reorder_and_save', JSON.stringify(current.data.edges) === JSON.stringify(edges))
check('no_browser_javascript_errors', errors.length === 0)
} finally {
if (checks.length < 25 && page && !page.isClosed()) {
console.log(JSON.stringify({stage, layout: await page.evaluate(() => ({
dialogs: document.querySelectorAll('.t-dialog').length,
collapsed: !!document.querySelector('.aside_box--collapsed'),
width: innerWidth,
page: document.querySelector('.workbench-page')?.getBoundingClientRect().toJSON(),
}))}))
await page.screenshot({path: path.join(directory, 'business-browser-debug.png'), animations: 'disabled'})
}
if (fixture) await request(`/workbench/resources/flows/${fixture.id}?detach=true`, 'DELETE')
await browser.close()
fs.writeFileSync(path.join(directory, 'business-browser-20260916.json'), JSON.stringify({
passed: checks.length === 25 && checks.every(c => c.passed), checks, fixture_removed: !!fixture,
}, null, 2) + '\n')
}
console.log(JSON.stringify({checks, passed: checks.every(c => c.passed)}))
}
main().catch(error => { console.error(error instanceof assert.AssertionError ? error.message : `Business browser verification failed (${error.name})`); process.exitCode = 1 })