import fs from 'node:fs' import { fileURLToPath } from 'node:url' import path from 'node:path' import assert from 'node:assert/strict' import { createRequire } from 'node:module' const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..') const require = createRequire(path.join(root, 'ai_person/ape2e/package.json')) const { chromium, expect: baseExpect } = require('@playwright/test') const expect = baseExpect.configure({ timeout: 25000 }) const report = path.join(root, 'unreal_tran/ute2e/reports/open-platform-style-20260909') fs.mkdirSync(report, { recursive: true }) const values = new Map() for (const line of fs.readFileSync(path.join(root, 'ai_person/账号.MD'), 'utf8').split(/\r?\n/)) { const match = line.match(/^\s*([^::]+)\s*[::]\s*(.*?)\s*$/) if (match?.[1] && match[2]) values.set(match[1].trim(), match[2].trim()) } const base = 'http://127.0.0.1:8003' const response = await fetch(base + '/api/auth/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: values.get('账号') || values.get('用户名'), password: values.get('密码'), roleCode: 'admin' }) }) const payload = await response.json() assert.equal(payload.code, 0, 'login must succeed') const token = payload.data.accessToken || payload.data.tokens?.accessToken const nav = await fetch(base + '/api/auth/v1/menus/navigation', { headers: { Authorization: 'Bearer ' + token } }).then(r => r.json()) assert.equal(nav.code, 0) assert(!JSON.stringify(nav.data).includes('settings-open-api-docs'), 'docs must not be in server menu') assert(JSON.stringify(nav.data).includes('settings-open-applications')) const schema = await fetch(base + '/open-api/v1/openapi.json').then(r => r.json()) const operationCount = Object.values(schema.paths).reduce((n, methods) => n + Object.keys(methods).length, 0) const checks = ['服务端菜单移除文档,保留开放应用'] const browser = await chromium.launch({ channel: 'msedge', headless: true }) const errors = [] try { const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } }) page.on('pageerror', error => errors.push(error.message)) await page.goto(base + '/login') await page.evaluate(token => sessionStorage.setItem('ai-person:web:access-token:v1', token), token) await page.goto(base + '/settings/open-applications') await page.getByText('数字车间开发联调', { exact: true }).waitFor() await expect(page.locator('#main-sidebar')).not.toContainText('开放 API 文档') const search = page.getByPlaceholder('搜索应用名称或 APPID') const searchResponse = page.waitForResponse(response => response.url().includes('open-applications?keyword=%') && response.request().method() === 'GET') await search.fill('数字车间开发联调') await page.getByRole('button', { name: '搜索', exact: true }).click() assert.equal((await (await searchResponse).json()).data.total, 1) await expect(page.locator('.applications-table .el-table__row')).toHaveCount(1) await page.getByRole('button', { name: '重置', exact: true }).click() await expect(search).toHaveValue('') await expect(page.locator('.applications-table .el-table__row')).toHaveCount(2) checks.push('应用搜索、重置、分页区域正常') await page.screenshot({ path: path.join(report, '01-applications-light.png') }) await page.getByRole('button', { name: '开放 API 文档', exact: true }).click() await expect(page).toHaveURL(/settings\/open-api-docs/) await expect(page.locator('.endpoint-section')).toHaveCount(operationCount) assert.equal(operationCount, 13) assert.equal(await page.locator('.el-collapse').count(), 0) for (const heading of ['1. 接口描述', '2. 输入参数', '3. 输出参数', '4. 错误码']) { await expect(page.locator('.endpoint-section').getByRole('heading', { name: heading, exact: true })).toHaveCount(13) } await expect(page.locator('#main-sidebar')).not.toContainText('开放 API 文档') await expect(page.locator('#main-sidebar .el-menu-item.is-active')).toContainText('开放应用') checks.push('文档作为二级页,开放应用菜单正确高亮', '13 个接口与服务端契约一致,均直接展示四个章节和返回示例') await page.screenshot({ path: path.join(report, '02-docs-overview-light.png') }) await page.locator('.docs-nav a[href="#endpoint-9"]').click() await expect(page.locator('#endpoint-9')).toBeInViewport() await page.screenshot({ path: path.join(report, '09-generation-input.png') }) await page.locator('#endpoint-9').screenshot({ path: path.join(report, '03-generation-document.png') }) const idem = page.locator('#endpoint-9 .parameter-table').first().getByRole('row').filter({ hasText: 'Idempotency-Key' }) await expect(idem).toContainText('是') await expect(page.locator('#endpoint-9')).toContainText('默认值:1280') checks.push('目录定位、生成幂等请求头必填和默认尺寸参数正确') await page.getByRole('button', { name: '切换到深色主题' }).click() await page.getByRole('link', { name: '接入说明', exact: true }).click() await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') await page.screenshot({ path: path.join(report, '04-docs-dark.png') }) await page.getByRole('button', { name: '返回开放应用', exact: true }).click() await page.getByText('数字车间开发联调', { exact: true }).waitFor() await page.screenshot({ path: path.join(report, '05-applications-dark.png') }) await page.locator('tr').filter({ hasText: '数字车间开发联调' }).getByRole('button', { name: '配置', exact: true }).click() await expect(page.getByRole('dialog')).toBeVisible() await expect(page.getByRole('dialog').getByText('授权资源', { exact: true })).toBeVisible() await page.screenshot({ path: path.join(report, '06-application-dialog-dark.png') }) await page.getByRole('dialog').getByRole('button', { name: '取消', exact: true }).click() checks.push('浅色与深色页面及配置弹窗正常,返回开放应用正常') const publicPage = await browser.newPage({ viewport: { width: 390, height: 844 } }) publicPage.on('pageerror', error => errors.push(error.message)) await publicPage.goto(base + '/developer') await expect(publicPage.locator('.endpoint-section')).toHaveCount(13) await expect(publicPage.getByRole('button', { name: '返回开放应用' })).toHaveCount(0) const dimensions = await publicPage.evaluate(() => ({ width: document.documentElement.clientWidth, scroll: document.documentElement.scrollWidth })) assert(dimensions.scroll <= dimensions.width + 1, `mobile overflow: ${JSON.stringify(dimensions)}`) await publicPage.screenshot({ path: path.join(report, '07-docs-mobile.png') }) await publicPage.locator('#endpoint-1').scrollIntoViewIfNeeded() await publicPage.screenshot({ path: path.join(report, '08-docs-mobile-parameters.png') }) checks.push('公共文档无需登录,390px 窄屏无页面横向溢出') assert.deepEqual(errors, []) checks.push('浏览器无未捕获脚本错误') fs.writeFileSync(path.join(report, 'results.json'), JSON.stringify({ passed: true, operationCount, checks, errors, timestamp: new Date().toISOString() }, null, 2)) console.log(JSON.stringify({ passed: true, operationCount, checks: checks.length, report })) } finally { await browser.close() }