103 lines
8.0 KiB
JavaScript
103 lines
8.0 KiB
JavaScript
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 directory = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance'
|
||
|
|
|
||
|
|
// Visual-state verification only. Native copy/approval behavior is tested in Go.
|
||
|
|
async function main() {
|
||
|
|
const credentials = JSON.parse(fs.readFileSync(`${directory}/super-admin-login.json`, 'utf8'))
|
||
|
|
const response = 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(response.status, 200, 'acceptance login')
|
||
|
|
const auth = await response.json()
|
||
|
|
const browser = await chromium.launch({ headless: true, channel: 'chrome' })
|
||
|
|
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)
|
||
|
|
const base = { owner_id: auth.user.id, kind: 'knowledge', source_id: 'source', personal_id: 'personal', no_export: true, hidden: false, created_at: new Date().toISOString() }
|
||
|
|
const forks = [
|
||
|
|
{ ...base, id: 'copy', name: '教学资料与实操案例完整知识库个人版本', status: 'copying' },
|
||
|
|
{ ...base, id: 'failed', name: '需要重新复制的资料', status: 'failed' },
|
||
|
|
]
|
||
|
|
const jobs = [
|
||
|
|
{ id: 'job-copy', fork_id: 'copy', purpose: 'fork', status: 'running', processed: 35, total: 100 },
|
||
|
|
{ id: 'job-failed', fork_id: 'failed', purpose: 'fork', status: 'failed', error: '资料复制失败,请重试。' },
|
||
|
|
{ id: 'job-review', submission_id: 'review', purpose: 'submission', status: 'running', processed: 14, total: 20 },
|
||
|
|
]
|
||
|
|
const submissions = [{ ...base, id: 'review', fork_id: 'copy', owner_name: auth.user.username, name: '教学资料知识库', status: 'snapshotting', note: '更新分类与教学案例', base_snapshot: { description: '原说明' }, proposed_snapshot: { description: '新的说明', configuration: { indexing: 'vector' } } }]
|
||
|
|
let releaseFirstDocument, failSecondDocument = false
|
||
|
|
const docA = { id: 'doc-a', title: '第一份完整教学资料与实践案例说明文档', type: 'file' }
|
||
|
|
const docB = { id: 'doc-b', title: '第二份文档:本次提交时的只读正文和分类信息', type: 'file' }
|
||
|
|
await context.route('**/api/v1/resource-versions**', async route => {
|
||
|
|
const url = new URL(route.request().url())
|
||
|
|
if (route.request().method() !== 'GET') return route.fulfill({ status: 405, json: { message: 'Visual test does not mutate resources' } })
|
||
|
|
if (url.pathname.endsWith('/knowledge')) {
|
||
|
|
const id = url.searchParams.get('document_id')
|
||
|
|
if (!id) return route.fulfill({ status: 200, json: { success: true, data: { page: 1, total: 2, documents: [docA, docB] } } })
|
||
|
|
if (id === docB.id && failSecondDocument) return route.fulfill({ status: 503, json: { message: '暂时无法读取提交正文,请重试' } })
|
||
|
|
const body = { success: true, data: { page: 1, total: 1, chunks: [{ id: id + '-chunk', chunk_index: 0, is_enabled: true, content: id === docA.id ? '第一份迟到的旧请求正文' : '第二份文档的完整提交正文。人工修改在提交时保存为独立快照,后续个人编辑不会改变这一段。\n\n文档中的段落与换行均完整保留。' }] } }
|
||
|
|
if (id === docA.id && releaseFirstDocument === undefined) return new Promise(resolve => {
|
||
|
|
releaseFirstDocument = async () => { await route.fulfill({ status: 200, json: body }); resolve() }
|
||
|
|
})
|
||
|
|
return route.fulfill({ status: 200, json: body })
|
||
|
|
}
|
||
|
|
const data = url.pathname.endsWith('/catalog') ? [{ kind: 'knowledge', id: 'source', name: '教学资料知识库', owner_name: '管理员', can_fork: true, hidden: false }] : { forks, submissions, jobs, history: [] }
|
||
|
|
return route.fulfill({ status: 200, json: { success: true, data } })
|
||
|
|
})
|
||
|
|
const page = await context.newPage(), errors = []
|
||
|
|
page.on('pageerror', error => errors.push(error.message))
|
||
|
|
await page.goto('http://127.0.0.1:15173/platform/workbench/resource-versions')
|
||
|
|
await page.getByRole('heading', { name: '我的资源版本' }).waitFor()
|
||
|
|
await page.getByText('个人版本', { exact: true }).click()
|
||
|
|
await page.getByText('已复制 35 / 100 份资料', { exact: true }).waitFor()
|
||
|
|
assert.ok(await page.getByRole('button', { name: '重新复制', exact: true }).isVisible())
|
||
|
|
assert.ok(await page.getByRole('button', { name: '编辑知识库', exact: true }).first().isDisabled())
|
||
|
|
await page.screenshot({ path: `${directory}/resource-versions-desktop.png`, animations: 'disabled' })
|
||
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
||
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 2), 'mobile overflow')
|
||
|
|
await page.screenshot({ path: `${directory}/resource-versions-mobile.png`, animations: 'disabled' })
|
||
|
|
await page.getByText('提交记录', { exact: true }).click()
|
||
|
|
await page.getByText('已复制 14 / 20 份资料', { exact: true }).waitFor()
|
||
|
|
await page.getByRole('button', { name: '查看对比', exact: true }).click()
|
||
|
|
await page.getByText('版本对比', { exact: true }).waitFor()
|
||
|
|
assert.equal(await page.getByRole('button', { name: '批准更新全局', exact: true }).count(), 0, 'unfinished snapshot cannot approve')
|
||
|
|
await page.screenshot({ path: `${directory}/resource-version-review-mobile.png`, animations: 'disabled' })
|
||
|
|
submissions[0].status = 'pending'
|
||
|
|
submissions[0].snapshot_id = 'stage'
|
||
|
|
await page.reload()
|
||
|
|
await page.getByRole('heading', { name: '我的资源版本' }).waitFor()
|
||
|
|
await page.getByText('提交记录', { exact: true }).click()
|
||
|
|
await page.getByRole('button', { name: '查看对比', exact: true }).click()
|
||
|
|
await page.getByRole('heading', { name: '提交知识全文' }).waitFor()
|
||
|
|
await page.getByRole('button', { name: docB.title, exact: true }).click()
|
||
|
|
await page.getByText('第二份文档的完整提交正文。', { exact: false }).waitFor()
|
||
|
|
assert.equal(typeof releaseFirstDocument, 'function')
|
||
|
|
await releaseFirstDocument()
|
||
|
|
assert.equal(await page.getByText('第一份迟到的旧请求正文', { exact: true }).count(), 0, 'stale document request overwrote active selection')
|
||
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 2), 'snapshot mobile overflow')
|
||
|
|
await page.screenshot({ path: `${directory}/resource-version-fulltext-mobile.png`, animations: 'disabled' })
|
||
|
|
await page.setViewportSize({ width: 1440, height: 1000 })
|
||
|
|
await page.screenshot({ path: `${directory}/resource-version-fulltext-desktop.png`, animations: 'disabled' })
|
||
|
|
failSecondDocument = true
|
||
|
|
await page.getByRole('button', { name: docB.title, exact: true }).click()
|
||
|
|
await page.getByText('暂时无法读取提交正文,请重试', { exact: true }).waitFor()
|
||
|
|
failSecondDocument = false
|
||
|
|
await page.getByRole('button', { name: '重试', exact: true }).click()
|
||
|
|
await page.getByText('第一份迟到的旧请求正文', { exact: true }).waitFor()
|
||
|
|
assert.deepEqual(errors, [], 'Vue runtime errors')
|
||
|
|
console.log('Resource version visual states passed: desktop/mobile progress and full text, disabled editing, retry, immutable snapshot preparation, stale request isolation, error recovery, review drawer.')
|
||
|
|
} finally { await browser.close() }
|
||
|
|
}
|
||
|
|
main().catch(error => { console.error(error.message); process.exitCode = 1 })
|