WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
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
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
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const { chromium } = require('/Users/macclaw/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
|
||||
const origin = process.env.VERIFY_FRONTEND_ORIGIN || 'http://127.0.0.1:15173'
|
||||
const apiOrigin = process.env.VERIFY_API_ORIGIN || 'http://127.0.0.1:18080'
|
||||
const reportName = process.env.VERIFY_REPORT_NAME || 'agent-creation-ui'
|
||||
assert.match(reportName, /^[a-zA-Z0-9_-]+$/)
|
||||
const startedAt = new Date().toISOString()
|
||||
|
||||
// Draft AI output is a fixture; other reads use the running local API. No resources are saved.
|
||||
async function main() {
|
||||
const directory = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance'
|
||||
const credentials = JSON.parse(fs.readFileSync(`${directory}/super-admin-login.json`, 'utf8'))
|
||||
const response = await fetch(`${apiOrigin}/api/v1/auth/login`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: credentials.email, password: credentials.password }),
|
||||
})
|
||||
assert.equal(response.status, 200)
|
||||
const auth = await response.json()
|
||||
const browser = await chromium.launch({ headless: true, channel: 'chrome' })
|
||||
const checks = [], errors = [], failures = [], requests = [], failedRequests = [], slowRequests = []
|
||||
const pendingRequests = new Map()
|
||||
let finalPath = ''
|
||||
const check = (name, passed) => { checks.push({ name, passed: !!passed }); assert.ok(passed, name) }
|
||||
try {
|
||||
const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } })
|
||||
await context.addInitScript(a => {
|
||||
const entries = {
|
||||
weknora_user: JSON.stringify({ ...a.user, tenant_id: String(a.user.tenant_id) }),
|
||||
weknora_tenant: JSON.stringify({ ...a.active_tenant, id: '10059' }),
|
||||
weknora_token: a.token, weknora_refresh_token: a.refresh_token,
|
||||
weknora_memberships: JSON.stringify(a.memberships),
|
||||
'weknora:new-user-guide-done:v1': '1',
|
||||
}
|
||||
for (const [key, value] of Object.entries(entries)) localStorage.setItem(key, value)
|
||||
}, auth)
|
||||
let releaseDraft, draftRequest
|
||||
await context.route('**/api/v1/agents/draft', route => new Promise(resolve => {
|
||||
draftRequest = route.request().postDataJSON()
|
||||
releaseDraft = async () => {
|
||||
await route.fulfill({ status: 200, json: { success: true, data: {
|
||||
name: '课程框架规划助手', description: '按用户目标整理课程结构,确保前后衔接。', avatar: '',
|
||||
config: { role: 'training_map', agent_type: 'custom', agent_mode: 'quick-answer', model_id: draftRequest.model_id, system_prompt: '规划课程框架并说明每一天的前置条件。', kb_selection_mode: 'none', knowledge_bases: [], mcp_selection_mode: 'none', skills_selection_mode: 'none', selected_skills: [], instruction_skill_ids: [] },
|
||||
} } })
|
||||
resolve()
|
||||
}
|
||||
}))
|
||||
const page = await context.newPage()
|
||||
page.on('request', request => {
|
||||
requests.push({ path: new URL(request.url()).pathname, method: request.method() })
|
||||
pendingRequests.set(request, Date.now())
|
||||
})
|
||||
page.on('requestfinished', request => {
|
||||
const started = pendingRequests.get(request)
|
||||
pendingRequests.delete(request)
|
||||
const duration = started === undefined ? 0 : Date.now() - started
|
||||
if (duration >= 3000) slowRequests.push({ path: new URL(request.url()).pathname, duration_ms: duration })
|
||||
})
|
||||
page.on('requestfailed', request => {
|
||||
pendingRequests.delete(request)
|
||||
const failure = { path: new URL(request.url()).pathname, error: request.failure()?.errorText || '' }
|
||||
failedRequests.push(failure)
|
||||
if (failure.error !== 'net::ERR_ABORTED') errors.push(failure)
|
||||
})
|
||||
page.on('pageerror', error => errors.push({ stage: checks.at(-1)?.name, error: error.stack || error.message }))
|
||||
await page.goto(`${origin}/platform/agents`)
|
||||
const ai = page.getByRole('button', { name: 'AI 创建', exact: true })
|
||||
await ai.waitFor()
|
||||
check('manual_and_ai_creation_visible', await page.getByRole('button', { name: '手动创建', exact: true }).isVisible())
|
||||
await page.locator('.agent-installations').first().waitFor()
|
||||
check('installation_status_visible_on_agent_cards', await page.locator('.agent-installations').count() > 0)
|
||||
await page.waitForLoadState('networkidle', { timeout: 15000 })
|
||||
check('hidden_agent_editor_not_requested', !requests.some(item => /\/AgentEditorModal-[^/]+\.js$/.test(item.path)))
|
||||
check('hidden_draft_dialog_not_requested', !requests.some(item => /\/AgentDraftDialog-[^/]+\.js$/.test(item.path)))
|
||||
check('retired_resource_versions_menu_absent', await page.locator('.sidebar').getByText('资源版本', { exact: true }).count() === 0)
|
||||
await page.screenshot({ path: `${directory}/agent-creation-list-desktop.png`, animations: 'disabled' })
|
||||
await ai.click()
|
||||
const dialog = page.locator('.t-dialog:visible')
|
||||
await dialog.getByPlaceholder('例如:面向课程学员,负责将每日课程框架整理为有前后衔接的讲稿,重点解释概念和案例。').fill('请创建负责课程规划与每日框架衔接的导图助手。')
|
||||
await dialog.getByRole('button', { name: '生成配置', exact: true }).click()
|
||||
await dialog.locator('progress').waitFor()
|
||||
check('ai_generation_progress_visible', await dialog.locator('progress').isVisible())
|
||||
check('ai_double_submit_prevented', await dialog.getByRole('button', { name: /生成配置/ }).isDisabled())
|
||||
await page.screenshot({ path: `${directory}/agent-draft-progress-desktop.png`, animations: 'disabled' })
|
||||
await page.setViewportSize({ width: 390, height: 844 })
|
||||
check('draft_dialog_fits_mobile', await dialog.evaluate(element => { const rect = element.getBoundingClientRect(); return rect.left >= 0 && rect.right <= innerWidth + 2 }))
|
||||
await page.screenshot({ path: `${directory}/agent-draft-progress-mobile.png`, animations: 'disabled' })
|
||||
await releaseDraft()
|
||||
const name = page.locator('[data-guide="agent-create-name"] input')
|
||||
await name.waitFor()
|
||||
await page.waitForFunction(() => document.querySelector('[data-guide="agent-create-name"] input')?.value === '课程框架规划助手')
|
||||
check('ai_draft_loaded_into_editable_editor', await name.inputValue() === '课程框架规划助手')
|
||||
check('draft_request_preserves_requirement', draftRequest.requirement === '请创建负责课程规划与每日框架衔接的导图助手。')
|
||||
await page.screenshot({ path: `${directory}/agent-draft-editor-mobile.png`, animations: 'disabled' })
|
||||
// Restore normal browser caching after the one mocked AI request.
|
||||
await context.unroute('**/api/v1/agents/draft')
|
||||
await page.goto(`${origin}/platform/workbench/resource-versions`)
|
||||
await ai.waitFor()
|
||||
check('retired_route_redirects_to_agents', new URL(page.url()).pathname === '/platform/agents')
|
||||
await page.getByRole('button', { name: '手动创建', exact: true }).click()
|
||||
await name.waitFor()
|
||||
check('manual_creation_does_not_reuse_ai_draft', await name.inputValue() !== '课程框架规划助手')
|
||||
check('no_business_resources_saved', !requests.some(item => ['POST', 'PUT', 'PATCH', 'DELETE'].includes(item.method) && item.path.startsWith('/api/') && !item.path.startsWith('/api/v1/auth/') && item.path !== '/api/v1/agents/draft'))
|
||||
check('no_vue_runtime_errors', errors.length === 0)
|
||||
} catch (error) {
|
||||
failures.push(error.message)
|
||||
const pages = browser.contexts().flatMap(context => context.pages())
|
||||
await pages[0]?.screenshot({ path: `${directory}/agent-creation-ui-failure.png` })
|
||||
} finally {
|
||||
finalPath = browser.contexts().flatMap(context => context.pages()).map(page => new URL(page.url()).pathname).join(',')
|
||||
const diagnostics = { path: finalPath, slow_requests: slowRequests, failed_requests: failedRequests,
|
||||
pending_requests: [...pendingRequests].map(([request, started]) => ({ path: new URL(request.url()).pathname, duration_ms: Date.now() - started })) }
|
||||
await browser.close()
|
||||
const report = { started_at: startedAt, checked_at: new Date().toISOString(), origin, checks, errors, failures, diagnostics, ai_output_mocked: true, passed: failures.length === 0 && errors.length === 0 && checks.every(item => item.passed) }
|
||||
fs.writeFileSync(`${directory}/${reportName}.json`, JSON.stringify(report, null, 2))
|
||||
console.log(JSON.stringify(report))
|
||||
if (!report.passed) process.exitCode = 1
|
||||
}
|
||||
}
|
||||
main().catch(error => { console.error(error.message); process.exitCode = 1 })
|
||||
Reference in New Issue
Block a user