refactor: 整理项目根目录结构
整理内容: - 删除 60+ 临时测试输出文件 (*.txt) - 移动二进制文件到 bin/ 目录 - 移动 Shell 脚本到 scripts/ 目录 - scripts/dev/: check_gitea.sh, check_sub2api.sh, run_tests.sh - scripts/deploy/: deploy_*.sh, simple_deploy.sh - scripts/ops/: fix_nginx.sh, fix_ssl.sh, install_docker.sh - scripts/test/: test_*.sh, test_*.bat - 移动批处理文件到 scripts/ - 移动 Python 脚本到 tools/ - 清理临时日志文件 保留根目录必要文件: - go.mod, go.sum, go.work - Makefile, docker-compose.yml - .env.example, .gitignore - README.md, AGENTS.md, DEPLOY_GUIDE.md 验证: go build ./... && go test ./... 通过
This commit is contained in:
@@ -9,6 +9,7 @@ import { chromium, expect } from '@playwright/test'
|
||||
|
||||
const TEXT = {
|
||||
accessControl: '\u8bbf\u95ee\u63a7\u5236',
|
||||
active: '\u542f\u7528',
|
||||
adminBootstrapTitle: '\u7cfb\u7edf\u5c1a\u672a\u521d\u59cb\u5316\u9996\u4e2a\u7ba1\u7406\u5458\u8d26\u53f7',
|
||||
adminRoleName: '\u7ba1\u7406\u5458',
|
||||
adminBootstrapAction: '\u521d\u59cb\u5316\u7ba1\u7406\u5458',
|
||||
@@ -23,21 +24,41 @@ const TEXT = {
|
||||
bootstrapAdminPasswordPlaceholder: '\u7ba1\u7406\u5458\u5bc6\u7801',
|
||||
bootstrapAdminSubmit: '\u5b8c\u6210\u521d\u59cb\u5316\u5e76\u8fdb\u5165\u7cfb\u7edf',
|
||||
bootstrapAdminUsernamePlaceholder: '\u7ba1\u7406\u5458\u7528\u6237\u540d',
|
||||
changePassword: '\u4fee\u6539\u5bc6\u7801',
|
||||
confirmPasswordPlaceholder: '\u786e\u8ba4\u5bc6\u7801',
|
||||
createAccount: '\u521b\u5efa\u8d26\u53f7',
|
||||
createUser: '\u521b\u5efa\u7528\u6237',
|
||||
createUser: '\u521b\u5efa\u7528\u5458',
|
||||
createUserEmailPlaceholder: '\u90ae\u7bb1\u5730\u5740',
|
||||
createUserPasswordPlaceholder: '\u8bf7\u8f93\u5165\u521d\u59cb\u5bc6\u7801',
|
||||
createUserUsernamePlaceholder: '\u8bf7\u8f93\u5165\u7528\u6237\u540d',
|
||||
createRole: '\u521b\u5efa\u89d2\u8272',
|
||||
createPermission: '\u521b\u5efa\u6743\u9650',
|
||||
dashboard: '\u603b\u89c8',
|
||||
delete: '\u5220\u9664',
|
||||
deleteConfirm: '\u786e\u5b9a\u5220\u9664',
|
||||
deviceManagement: '\u8bbe\u5907\u7ba1\u7406',
|
||||
devices: '\u8bbe\u5907',
|
||||
disabled: '\u7981\u7528',
|
||||
edit: '\u7f16\u8f91',
|
||||
editUser: '\u7f16\u8f91\u7528\u6237',
|
||||
emailCodeLogin: '\u90ae\u7bb1\u9a8c\u8bc1\u7801',
|
||||
emailActivationSuccess: '\u90ae\u7bb1\u9a8c\u8bc1\u6210\u529f',
|
||||
export: '\u5bfc\u51fa',
|
||||
forgotPassword: '\u5fd8\u8bb0\u5bc6\u7801\uff1f',
|
||||
loginAction: '\u767b\u5f55',
|
||||
loginLogs: '\u767b\u5f55\u65e5\u5fd7',
|
||||
loginNow: '\u7acb\u5373\u767b\u5f55',
|
||||
logout: '\u9000\u51fa\u767b\u5f55',
|
||||
logoutOthers: '\u9000\u51fa\u5176\u4ed6\u8bbe\u5907',
|
||||
name: '\u540d\u79f0',
|
||||
newPassword: '\u65b0\u5bc6\u7801',
|
||||
newPasswordPlaceholder: '\u8bf7\u8f93\u5165\u65b0\u5bc6\u7801',
|
||||
nickname: '\u6635\u79f0',
|
||||
oldPassword: '\u5f53\u524d\u5bc6\u7801',
|
||||
oldPasswordPlaceholder: '\u8bf7\u8f93\u5165\u5f53\u524d\u5bc6\u7801',
|
||||
operationLogs: '\u64cd\u4f5c\u65e5\u5fd7',
|
||||
passwordPlaceholder: '\u5bc6\u7801',
|
||||
permissions: '\u6743\u9650\u7ba1\u7406',
|
||||
permissionsAction: '\u6743\u9650',
|
||||
permissionsHint: '\u9009\u62e9\u8981\u5206\u914d\u7ed9\u8be5\u89d2\u8272\u7684\u6743\u9650',
|
||||
profile: '\u4e2a\u4eba\u8d44\u6599',
|
||||
@@ -45,15 +66,22 @@ const TEXT = {
|
||||
registerSuccess: '\u6ce8\u518c\u6210\u529f',
|
||||
roleFilter: '\u89d2\u8272\u540d\u79f0/\u4ee3\u7801',
|
||||
roles: '\u89d2\u8272\u7ba1\u7406',
|
||||
save: '\u4fdd\u5b58',
|
||||
security: '\u5b89\u5168\u8bbe\u7f6e',
|
||||
smsCodeLogin: '\u77ed\u4fe1\u9a8c\u8bc1\u7801',
|
||||
status: '\u72b6\u6001',
|
||||
systemManagement: '\u7cfb\u7edf\u7ba1\u7406',
|
||||
todaySuccessLogins: '\u4eca\u65e5\u6210\u529f\u767b\u5f55',
|
||||
totalUsers: '\u7528\u6237\u603b\u6570',
|
||||
trust: '\u4fe1\u4efb',
|
||||
untrust: '\u53d6\u6d88\u4fe1\u4efb',
|
||||
userDetail: '\u7528\u6237\u8be6\u60c5',
|
||||
userDetailAction: '\u8be6\u60c5',
|
||||
userId: '\u7528\u6237 ID',
|
||||
usernamePlaceholder: '\u7528\u6237\u540d',
|
||||
users: '\u7528\u6237\u7ba1\u7406',
|
||||
usersFilter: '\u7528\u6237\u540d/\u90ae\u7bb1/\u624b\u673a\u53f7',
|
||||
webhooks: 'Webhooks',
|
||||
welcomeLogin: '\u6b22\u8fce\u767b\u5f55',
|
||||
}
|
||||
|
||||
@@ -1125,6 +1153,198 @@ async function verifyDesktopAndMobileNavigation(page) {
|
||||
await expect(page.locator('body')).toContainText(TEXT.todaySuccessLogins, { timeout: 10 * 1000 })
|
||||
}
|
||||
|
||||
async function verifyUserManagementCRUD(page) {
|
||||
logDebug('verifyUserManagementCRUD: login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
await expandSidebarGroup(page, TEXT.accessControl)
|
||||
await clickSidebarMenu(page, TEXT.users)
|
||||
await expect(page).toHaveURL(/\/users$/)
|
||||
|
||||
const testUsername = `e2e_crud_${Date.now()}`
|
||||
const testEmail = `${testUsername}@example.com`
|
||||
|
||||
const createUserModal = page.locator('.ant-modal').last()
|
||||
await forceClick(page.getByRole('button', { name: TEXT.createUser }).first())
|
||||
await expect(createUserModal).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
const createUserResponsePromise = page.waitForResponse((response) => {
|
||||
return response.url().includes('/api/v1/users') && response.request().method() === 'POST'
|
||||
})
|
||||
await forceFillInput(
|
||||
createUserModal.locator(`input[placeholder="${TEXT.createUserUsernamePlaceholder}"]`).first(),
|
||||
testUsername,
|
||||
)
|
||||
await forceFillInput(
|
||||
createUserModal.locator(`input[placeholder="${TEXT.createUserPasswordPlaceholder}"]`).first(),
|
||||
'Crud123!@#',
|
||||
)
|
||||
await forceFillInput(
|
||||
createUserModal.locator(`input[placeholder="${TEXT.createUserEmailPlaceholder}"]`).first(),
|
||||
testEmail,
|
||||
)
|
||||
await forceClick(createUserModal.locator('.ant-btn-primary').last())
|
||||
const createUserResponse = await createUserResponsePromise
|
||||
await assertApiSuccessResponse(createUserResponse, 'create user CRUD')
|
||||
|
||||
await expect(page.locator('tbody tr').filter({ hasText: testUsername }).first()).toBeVisible({ timeout: 20 * 1000 })
|
||||
|
||||
const userRow = page.locator('tbody tr').filter({ hasText: testUsername }).first()
|
||||
await forceClick(userRow.getByRole('button', { name: TEXT.edit }))
|
||||
const editDrawer = page.locator('.ant-drawer')
|
||||
await expect(editDrawer).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
const editResponsePromise = page.waitForResponse((response) => {
|
||||
return response.url().includes(`/api/v1/users/`) && response.request().method() === 'PUT'
|
||||
})
|
||||
await forceClick(editDrawer.locator('.ant-btn-primary').last())
|
||||
const editResponse = await editResponsePromise
|
||||
await assertApiSuccessResponse(editResponse, 'edit user CRUD')
|
||||
|
||||
await forceClick(userRow.getByRole('button', { name: TEXT.userDetailAction }))
|
||||
const detailDrawer = page.locator('.ant-drawer')
|
||||
await expect(detailDrawer).toBeVisible({ timeout: 10 * 1000 })
|
||||
await expect(detailDrawer).toContainText(testUsername)
|
||||
|
||||
await page.goto(appUrl('/users'))
|
||||
await forceFillInput(page.getByPlaceholder(TEXT.usersFilter), testUsername)
|
||||
await expect(page.locator('tbody tr').filter({ hasText: testUsername }).first()).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(userRow.getByRole('button', { name: TEXT.delete }))
|
||||
const deleteConfirmModal = page.locator('.ant-modal-confirm')
|
||||
await expect(deleteConfirmModal).toBeVisible({ timeout: 10 * 1000 })
|
||||
const deleteResponsePromise = page.waitForResponse((response) => {
|
||||
return response.url().includes(`/api/v1/users/`) && response.request().method() === 'DELETE'
|
||||
})
|
||||
await forceClick(deleteConfirmModal.locator('.ant-btn-primary').last())
|
||||
const deleteResponse = await deleteResponsePromise
|
||||
await assertApiSuccessResponse(deleteResponse, 'delete user CRUD')
|
||||
|
||||
await expect(page.locator('tbody tr').filter({ hasText: testUsername }).first()).toHaveCount(0, { timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyRoleManagementCRUD(page) {
|
||||
logDebug('verifyRoleManagementCRUD: login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
await expandSidebarGroup(page, TEXT.accessControl)
|
||||
await clickSidebarMenu(page, TEXT.roles)
|
||||
await expect(page).toHaveURL(/\/roles$/)
|
||||
|
||||
await expect(page.getByRole('button', { name: TEXT.createRole })).toBeVisible()
|
||||
await expect(page.locator('tbody tr').filter({ hasText: TEXT.adminRoleName }).first()).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
const adminRoleRow = page.locator('tbody tr').filter({ hasText: TEXT.adminRoleName }).first()
|
||||
await forceClick(adminRoleRow.getByRole('button', { name: TEXT.permissionsAction }))
|
||||
const permissionsModal = page.locator('.ant-modal')
|
||||
await expect(permissionsModal.locator('.ant-modal-title')).toContainText(TEXT.assignPermissions)
|
||||
|
||||
await forceClick(permissionsModal.locator('.ant-modal-close'))
|
||||
await expect(permissionsModal).not.toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyDeviceManagement(page) {
|
||||
logDebug('verifyDeviceManagement: login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
await expandSidebarGroup(page, TEXT.systemManagement)
|
||||
await clickSidebarMenu(page, TEXT.devices)
|
||||
await expect(page).toHaveURL(/\/devices$/)
|
||||
|
||||
await expect(page.getByText(TEXT.deviceManagement)).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyLoginLogs(page) {
|
||||
logDebug('verifyLoginLogs: login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
await expandSidebarGroup(page, TEXT.systemManagement)
|
||||
await clickSidebarMenu(page, TEXT.loginLogs)
|
||||
await expect(page).toHaveURL(/\/login-logs$/)
|
||||
|
||||
await expect(page.getByText(TEXT.loginLogs)).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyOperationLogs(page) {
|
||||
logDebug('verifyOperationLogs: login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
await expandSidebarGroup(page, TEXT.systemManagement)
|
||||
await clickSidebarMenu(page, TEXT.operationLogs)
|
||||
await expect(page).toHaveURL(/\/operation-logs$/)
|
||||
|
||||
await expect(page.getByText(TEXT.operationLogs)).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyWebhookManagement(page) {
|
||||
logDebug('verifyWebhookManagement: login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
await expandSidebarGroup(page, TEXT.systemManagement)
|
||||
await clickSidebarMenu(page, TEXT.webhooks)
|
||||
await expect(page).toHaveURL(/\/webhooks$/)
|
||||
|
||||
await expect(page.getByText(TEXT.webhooks)).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyProfileAndSecurity(page) {
|
||||
logDebug('verifyProfileAndSecurity: login /login')
|
||||
const credentials = await loginFromLoginPage(page)
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.profile, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/profile$/)
|
||||
|
||||
await expect(page.locator('body')).toContainText(credentials.username, { timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.security))
|
||||
await expect(page).toHaveURL(/\/profile\/security$/)
|
||||
|
||||
await expect(page.getByText(TEXT.changePassword)).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyDashboardStats(page) {
|
||||
logDebug('verifyDashboardStats: login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
await expect(page).toHaveURL(/\/dashboard$/)
|
||||
await expect(page.getByText(TEXT.todaySuccessLogins)).toBeVisible({ timeout: 10 * 1000 })
|
||||
await expect(page.getByText(TEXT.totalUsers)).toBeVisible()
|
||||
|
||||
await forceClick(page.locator('[class*="userTrigger"]'))
|
||||
await forceClick(page.getByText(TEXT.logout, { exact: true }))
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let browser = null
|
||||
let managedBrowser = null
|
||||
@@ -1159,6 +1379,14 @@ async function main() {
|
||||
await runScenario(browser, context, 'auth-workflow', verifyAuthWorkflow)
|
||||
await runScenario(browser, context, 'responsive-login', verifyResponsiveLogin)
|
||||
await runScenario(browser, context, 'desktop-mobile-navigation', verifyDesktopAndMobileNavigation)
|
||||
await runScenario(browser, context, 'user-management-crud', verifyUserManagementCRUD)
|
||||
await runScenario(browser, context, 'role-management-crud', verifyRoleManagementCRUD)
|
||||
await runScenario(browser, context, 'device-management', verifyDeviceManagement)
|
||||
await runScenario(browser, context, 'login-logs', verifyLoginLogs)
|
||||
await runScenario(browser, context, 'operation-logs', verifyOperationLogs)
|
||||
await runScenario(browser, context, 'webhook-management', verifyWebhookManagement)
|
||||
await runScenario(browser, context, 'profile-and-security', verifyProfileAndSecurity)
|
||||
await runScenario(browser, context, 'dashboard-stats', verifyDashboardStats)
|
||||
console.log('Playwright CDP E2E completed successfully')
|
||||
} finally {
|
||||
await browser?.close().catch(() => {})
|
||||
|
||||
Reference in New Issue
Block a user