fb6a4be797
按对齐计划 Phase1:Commands 页增加推送日志筛选视图;补全 servers_import_template.csv;扩写功能指南 §12.6/12.8/12.10 与 acceptance_catalog。
150 lines
5.5 KiB
JavaScript
150 lines
5.5 KiB
JavaScript
/**
|
|
* Full-site UI acceptance — every SPA route + primary controls (no destructive push).
|
|
* Run from frontend/: NEXUS_E2E_USER=admin NEXUS_E2E_PASSWORD=*** npx playwright test e2e/full-acceptance.spec.mjs
|
|
*/
|
|
import { test, expect } from '@playwright/test'
|
|
|
|
const BASE = process.env.NEXUS_E2E_BASE || 'https://api.synaglobal.vip'
|
|
const USER = process.env.NEXUS_E2E_USER || 'admin'
|
|
const PASS = process.env.NEXUS_E2E_PASSWORD || ''
|
|
const APP = `${BASE.replace(/\/$/, '')}/app/`
|
|
|
|
test.describe.configure({ mode: 'serial' })
|
|
|
|
async function login(page) {
|
|
if (!PASS) throw new Error('Set NEXUS_E2E_PASSWORD')
|
|
await page.goto(`${APP}#/login`)
|
|
await page.getByLabel('用户名').fill(USER)
|
|
await page.getByLabel('密码', { exact: true }).fill(PASS)
|
|
const totp = page.getByLabel('TOTP 验证码')
|
|
if (await totp.isVisible().catch(() => false)) {
|
|
throw new Error('TOTP required — disable TOTP on test admin or provide code')
|
|
}
|
|
await page.getByRole('button', { name: '登录' }).click()
|
|
await expect(page).not.toHaveURL(/#\/login/, { timeout: 20000 })
|
|
}
|
|
|
|
async function nav(page, hash) {
|
|
await page.goto(`${APP}#${hash}`)
|
|
await page.waitForLoadState('domcontentloaded')
|
|
}
|
|
|
|
test('01 login + dashboard stats', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/')
|
|
await expect(page.locator('.v-card').first()).toBeVisible({ timeout: 20000 })
|
|
})
|
|
|
|
test('02 servers: list, batch select, add dialog', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/servers')
|
|
await page.getByRole('button', { name: '添加' }).click()
|
|
await expect(page.getByText('添加服务器')).toBeVisible()
|
|
await page.getByRole('button', { name: '取消' }).click()
|
|
const cbs = page.locator('table tbody tr input[type="checkbox"]')
|
|
if (await cbs.count() >= 2) {
|
|
await cbs.nth(0).check()
|
|
await cbs.nth(1).check()
|
|
await expect(page.getByText(/已选择\s*2\s*台/)).toBeVisible()
|
|
}
|
|
})
|
|
|
|
test('03 global search', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/')
|
|
const search = page.getByPlaceholder('搜索服务器、脚本、凭据...')
|
|
await search.fill('a')
|
|
await page.waitForTimeout(400)
|
|
})
|
|
|
|
test('04 terminal page', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/terminal')
|
|
await expect(page.getByText(/终端|服务器|连接/).first()).toBeVisible({ timeout: 20000 })
|
|
})
|
|
|
|
test('05 files page', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/files')
|
|
await expect(page.getByText(/文件|远程|服务器/).first()).toBeVisible({ timeout: 20000 })
|
|
})
|
|
|
|
test('06 push: preview only', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/push')
|
|
await page.getByRole('button', { name: '预览' }).click()
|
|
await page.waitForTimeout(1500)
|
|
})
|
|
|
|
test('07 scripts: new dialog cancel', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/scripts')
|
|
await page.getByRole('button', { name: '新建脚本' }).click()
|
|
await expect(page.getByText('新建脚本').first()).toBeVisible()
|
|
await page.getByRole('button', { name: '取消' }).click()
|
|
})
|
|
|
|
test('08 credentials: tabs', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/credentials')
|
|
await expect(page.getByText('凭据管理')).toBeVisible({ timeout: 20000 })
|
|
await page.getByRole('button', { name: 'SSH 密钥' }).click()
|
|
await page.getByRole('button', { name: '数据库凭据' }).click()
|
|
await page.getByRole('button', { name: '密码预设' }).click()
|
|
await expect(page.getByRole('button', { name: '添加' })).toBeVisible()
|
|
})
|
|
|
|
test('09 schedules: new dialog cancel', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/schedules')
|
|
await page.getByRole('button', { name: '新建调度' }).click()
|
|
await expect(page.getByText('新建调度').first()).toBeVisible()
|
|
await page.getByRole('button', { name: '取消' }).click()
|
|
})
|
|
|
|
test('10 retries list', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/retries')
|
|
await expect(page.getByText(/重试|队列/).first()).toBeVisible({ timeout: 20000 })
|
|
})
|
|
|
|
test('11 commands: command, session and push log views', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/commands')
|
|
await expect(page.getByText('命令日志')).toBeVisible({ timeout: 20000 })
|
|
await page.locator('.v-card-title .v-select').first().click()
|
|
await page.locator('.v-overlay-container').getByText('推送日志').click()
|
|
await expect(page.locator('table').first()).toBeVisible()
|
|
await page.locator('.v-card-title .v-select').first().click()
|
|
await page.locator('.v-overlay-container').getByText('会话视图').click()
|
|
await expect(page.locator('table').first()).toBeVisible()
|
|
})
|
|
|
|
test('12 alerts filters', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/alerts')
|
|
await expect(page.getByText(/告警/).first()).toBeVisible({ timeout: 20000 })
|
|
})
|
|
|
|
test('13 audit pagination', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/audit')
|
|
await expect(page.getByText(/审计/).first()).toBeVisible({ timeout: 20000 })
|
|
})
|
|
|
|
test('14 settings sections', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/settings')
|
|
await expect(page.getByText(/系统|监控|Telegram|安全/).first()).toBeVisible({ timeout: 20000 })
|
|
})
|
|
|
|
test('15 theme toggle + logout', async ({ page }) => {
|
|
await login(page)
|
|
await nav(page, '/')
|
|
await page.getByText(USER, { exact: false }).first().click()
|
|
await page.getByText('切换主题').click()
|
|
await page.getByText(USER, { exact: false }).first().click()
|
|
await page.getByText('退出登录').click()
|
|
await expect(page).toHaveURL(/#\/login/, { timeout: 15000 })
|
|
})
|