|
|
|
@@ -0,0 +1,98 @@ |
|
|
|
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() } |