fix(n+1): 批量查询替代循环单查
- IsAdminBootstrapRequired: userRepo.GetByID 循环 → GetByIDs 批量 - AssignRoles: roleRepo.GetByID 循环 → GetByIDs 批量 - 在 userRepositoryInterface 补充 GetByIDs 方法签名
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import process from 'node:process'
|
||||
import { access, mkdir, mkdtemp, readFile, readdir, rm } from 'node:fs/promises'
|
||||
import { access, mkdtemp, readFile, readdir, rm } from 'node:fs/promises'
|
||||
import { constants as fsConstants } from 'node:fs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createHmac } from 'node:crypto'
|
||||
@@ -8,6 +8,8 @@ import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { chromium, expect } from '@playwright/test'
|
||||
|
||||
import { parseSelectedScenarioNames, selectScenarioNames } from './playwright-e2e-scenarios.mjs'
|
||||
|
||||
const TEXT = {
|
||||
accessControl: '\u8bbf\u95ee\u63a7\u5236',
|
||||
active: '\u542f\u7528',
|
||||
@@ -84,6 +86,10 @@ const TEXT = {
|
||||
permissionsAction: '\u6743\u9650',
|
||||
permissionsHint: '\u9009\u62e9\u8981\u5206\u914d\u7ed9\u8be5\u89d2\u8272\u7684\u6743\u9650',
|
||||
profile: '\u4e2a\u4eba\u8d44\u6599',
|
||||
profileBioPlaceholder: '\u4ecb\u7ecd\u4e00\u4e0b\u81ea\u5df1...',
|
||||
profileNicknamePlaceholder: '\u8bf7\u8f93\u5165\u6635\u79f0',
|
||||
profileRegionPlaceholder: '\u8bf7\u8f93\u5165\u5730\u533a',
|
||||
profileSaveChanges: '\u4fdd\u5b58\u4fee\u6539',
|
||||
profileConfirmPasswordPlaceholder: '\u8bf7\u518d\u6b21\u8f93\u5165\u65b0\u5bc6\u7801',
|
||||
registerEmailPlaceholder: '\u90ae\u7bb1\u5730\u5740\uff08\u9009\u586b\uff09',
|
||||
registerSuccess: '\u6ce8\u518c\u6210\u529f',
|
||||
@@ -426,25 +432,23 @@ async function resolveManagedBrowserPath() {
|
||||
throw new Error('No compatible browser found for Playwright CDP E2E.')
|
||||
}
|
||||
|
||||
async function createManagedBrowserProfileDir(browserPath, port) {
|
||||
if (!isHeadlessShellBrowser(browserPath)) {
|
||||
return await mkdtemp(path.join(tmpdir(), 'pw-playwright-cdp-'))
|
||||
}
|
||||
|
||||
const profileRoot = path.join(process.cwd(), '.cache', 'cdp-profiles')
|
||||
await mkdir(profileRoot, { recursive: true })
|
||||
return path.join(profileRoot, `pw-profile-playwright-cdp-${port}`)
|
||||
async function createManagedBrowserProfileDir() {
|
||||
return await mkdtemp(path.join(tmpdir(), 'pw-playwright-cdp-'))
|
||||
}
|
||||
|
||||
function startManagedBrowser(browserPath, port, profileDir) {
|
||||
const args = [
|
||||
`--remote-debugging-port=${port}`,
|
||||
`--user-data-dir=${profileDir}`,
|
||||
'--noerrdialogs',
|
||||
'--no-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-background-networking',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-renderer-backgrounding',
|
||||
'--disable-breakpad',
|
||||
'--disable-crash-reporter',
|
||||
'--disable-crashpad-for-testing',
|
||||
'--disable-sync',
|
||||
'--disable-gpu',
|
||||
]
|
||||
@@ -951,8 +955,8 @@ async function forceFillInput(locator, value) {
|
||||
}
|
||||
|
||||
await locator.evaluate((element, nextValue) => {
|
||||
if (!(element instanceof HTMLInputElement)) {
|
||||
throw new Error('Target element is not an input.')
|
||||
if (!(element instanceof HTMLInputElement) && !(element instanceof HTMLTextAreaElement)) {
|
||||
throw new Error('Target element is not a text input.')
|
||||
}
|
||||
|
||||
element.focus()
|
||||
@@ -2317,6 +2321,62 @@ async function verifySettings(page) {
|
||||
await expect(page).toHaveURL(/\/login$/)
|
||||
}
|
||||
|
||||
async function verifyProfileManagement(page) {
|
||||
logDebug('verifyProfileManagement: admin login /login')
|
||||
await loginFromLoginPage(page)
|
||||
|
||||
const profileUsername = `e2e_profile_${Date.now()}`
|
||||
const profilePassword = 'Profile123!@#'
|
||||
const profileLandingPattern = /\/profile$/
|
||||
const suffix = Date.now()
|
||||
const updatedNickname = `Profile User ${suffix}`
|
||||
const updatedRegion = `Hangzhou-${suffix}`
|
||||
|
||||
logDebug('verifyProfileManagement: goto /users as admin')
|
||||
await page.goto(appUrl('/users'))
|
||||
await expect(page).toHaveURL(/\/users$/)
|
||||
const createdUser = await createUserFromUsersPage(page, profileUsername, profilePassword)
|
||||
logDebug(`verifyProfileManagement: created user ${createdUser.username}`)
|
||||
|
||||
logDebug('verifyProfileManagement: reset session before profile user login')
|
||||
await resetSessionToLogin(page)
|
||||
|
||||
logDebug(`verifyProfileManagement: profile user login ${createdUser.username}`)
|
||||
await loginWithPassword(page, createdUser.username, profilePassword, profileLandingPattern)
|
||||
await installFetchDiagnostics(page)
|
||||
|
||||
await expect(page).toHaveURL(/\/profile$/)
|
||||
await expect(page.getByRole('heading', { name: TEXT.profile })).toBeVisible({ timeout: 10 * 1000 })
|
||||
await expect(page.locator('body')).toContainText(createdUser.username, { timeout: 10 * 1000 })
|
||||
await expect(page.locator('body')).toContainText(createdUser.email)
|
||||
|
||||
await forceFillInput(page.getByPlaceholder(TEXT.profileNicknamePlaceholder).first(), updatedNickname)
|
||||
await forceFillInput(page.getByPlaceholder(TEXT.profileRegionPlaceholder).first(), updatedRegion)
|
||||
await forceFillInput(page.getByPlaceholder(TEXT.profileBioPlaceholder).first(), `Profile bio ${suffix}`)
|
||||
|
||||
const updateProfileFetchCount = await getFetchDiagnosticsCount(page)
|
||||
const updateProfileFetch = await performActionAndWaitForFetchLogEntry(page, (entry) => {
|
||||
return fetchLogPathMatches(entry, /\/api\/v1\/users\/\d+$/) && entry.method === 'PUT'
|
||||
}, async () => {
|
||||
await forceClick(page.getByRole('button', { name: TEXT.profileSaveChanges }).first())
|
||||
}, {
|
||||
afterCount: updateProfileFetchCount,
|
||||
label: 'update profile fetch',
|
||||
})
|
||||
assertFetchLogSuccess(updateProfileFetch, 'update profile')
|
||||
|
||||
await expect(page.getByPlaceholder(TEXT.profileNicknamePlaceholder).first()).toHaveValue(updatedNickname, { timeout: 20 * 1000 })
|
||||
await expect(page.getByPlaceholder(TEXT.profileRegionPlaceholder).first()).toHaveValue(updatedRegion, { timeout: 20 * 1000 })
|
||||
await expect(page.getByPlaceholder(TEXT.profileBioPlaceholder).first()).toHaveValue(`Profile bio ${suffix}`)
|
||||
|
||||
await forceClick(page.locator('a[href="/profile/security"]').first())
|
||||
await expect(page).toHaveURL(/\/profile\/security$/)
|
||||
await expect(page.getByRole('button', { name: TEXT.changePassword })).toBeVisible({ timeout: 10 * 1000 })
|
||||
|
||||
await resetSessionToLogin(page)
|
||||
logDebug('verifyProfileManagement: completed')
|
||||
}
|
||||
|
||||
async function verifyProfileAndSecurity(page) {
|
||||
logDebug('verifyProfileAndSecurity: admin login /login')
|
||||
await loginFromLoginPage(page)
|
||||
@@ -2436,17 +2496,44 @@ async function main() {
|
||||
let runtime = null
|
||||
let managedBrowser = null
|
||||
let managedProfileDir = null
|
||||
const selectedScenarioNames = new Set(
|
||||
(process.env.E2E_SCENARIOS ?? '')
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean),
|
||||
)
|
||||
const selectedScenarioNames = parseSelectedScenarioNames(process.env.E2E_SCENARIOS ?? '')
|
||||
const scenarioEntries = new Map([
|
||||
['admin-bootstrap', verifyAdminBootstrapWorkflow],
|
||||
['public-registration', verifyPublicRegistration],
|
||||
['email-activation', verifyEmailActivationWorkflow],
|
||||
['password-reset', verifyPasswordResetWorkflow],
|
||||
['login-surface', verifyLoginSurface],
|
||||
['auth-workflow', verifyAuthWorkflow],
|
||||
['responsive-login', verifyResponsiveLogin],
|
||||
['desktop-mobile-navigation', verifyDesktopAndMobileNavigation],
|
||||
['user-management-crud', verifyUserManagementCRUD],
|
||||
['user-management-batch', verifyUserManagementBatch],
|
||||
['role-management-crud', verifyRoleManagementCRUD],
|
||||
['permissions-management-crud', verifyPermissionsManagementCRUD],
|
||||
['device-management', verifyDeviceManagement],
|
||||
['login-logs', verifyLoginLogs],
|
||||
['operation-logs', verifyOperationLogs],
|
||||
['webhook-management', verifyWebhookManagement],
|
||||
['import-export', verifyImportExport],
|
||||
['profile-management', verifyProfileManagement],
|
||||
['profile-and-security', verifyProfileAndSecurity],
|
||||
['settings', verifySettings],
|
||||
['dashboard-stats', verifyDashboardStats],
|
||||
])
|
||||
const scenarioNamesToRun = selectScenarioNames({
|
||||
requestedScenarioNames: selectedScenarioNames,
|
||||
expectAdminBootstrap: process.env.E2E_EXPECT_ADMIN_BOOTSTRAP === '1',
|
||||
})
|
||||
|
||||
if (process.env.E2E_LIST_SCENARIOS === '1') {
|
||||
console.log(scenarioNamesToRun.join('\n'))
|
||||
return
|
||||
}
|
||||
|
||||
if (process.env.E2E_MANAGED_BROWSER === '1') {
|
||||
const browserPath = await resolveManagedBrowserPath()
|
||||
const port = await getFreePort()
|
||||
managedProfileDir = await createManagedBrowserProfileDir(browserPath, port)
|
||||
managedProfileDir = await createManagedBrowserProfileDir()
|
||||
managedBrowser = startManagedBrowser(browserPath, port, managedProfileDir)
|
||||
managedCdpUrl = `http://127.0.0.1:${port}`
|
||||
console.log(`LAUNCH playwright-cdp ${browserPath}`)
|
||||
@@ -2466,35 +2553,13 @@ async function main() {
|
||||
throw new Error('No persistent Chromium context is available through CDP.')
|
||||
}
|
||||
|
||||
const scenarios = []
|
||||
if (process.env.E2E_EXPECT_ADMIN_BOOTSTRAP === '1') {
|
||||
scenarios.push(['admin-bootstrap', verifyAdminBootstrapWorkflow])
|
||||
}
|
||||
scenarios.push(
|
||||
['public-registration', verifyPublicRegistration],
|
||||
['email-activation', verifyEmailActivationWorkflow],
|
||||
['password-reset', verifyPasswordResetWorkflow],
|
||||
['login-surface', verifyLoginSurface],
|
||||
['auth-workflow', verifyAuthWorkflow],
|
||||
['responsive-login', verifyResponsiveLogin],
|
||||
['desktop-mobile-navigation', verifyDesktopAndMobileNavigation],
|
||||
['user-management-crud', verifyUserManagementCRUD],
|
||||
['user-management-batch', verifyUserManagementBatch],
|
||||
['role-management-crud', verifyRoleManagementCRUD],
|
||||
['permissions-management-crud', verifyPermissionsManagementCRUD],
|
||||
['device-management', verifyDeviceManagement],
|
||||
['login-logs', verifyLoginLogs],
|
||||
['operation-logs', verifyOperationLogs],
|
||||
['webhook-management', verifyWebhookManagement],
|
||||
['import-export', verifyImportExport],
|
||||
['profile-and-security', verifyProfileAndSecurity],
|
||||
['settings', verifySettings],
|
||||
['dashboard-stats', verifyDashboardStats],
|
||||
)
|
||||
|
||||
const scenariosToRun = selectedScenarioNames.size === 0
|
||||
? scenarios
|
||||
: scenarios.filter(([name]) => name === 'admin-bootstrap' || selectedScenarioNames.has(name))
|
||||
const scenariosToRun = scenarioNamesToRun.map((name) => {
|
||||
const handler = scenarioEntries.get(name)
|
||||
if (!handler) {
|
||||
throw new Error(`No Playwright CDP scenario handler is registered for ${name}.`)
|
||||
}
|
||||
return [name, handler]
|
||||
})
|
||||
|
||||
if (scenariosToRun.length === 0) {
|
||||
throw new Error(`No E2E scenarios matched E2E_SCENARIOS=${process.env.E2E_SCENARIOS ?? ''}`)
|
||||
|
||||
Reference in New Issue
Block a user