Files
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

567 lines
16 KiB
TypeScript

import { Page, expect } from '@playwright/test';
import { testLogger } from '../core/test-logger';
export async function waitForElement(page: Page, selector: string, timeout: number = 10000): Promise<void> {
testLogger.debug(`等待元素: ${selector}, 超时: ${timeout}ms`);
try {
await page.waitForSelector(selector, {
state: 'visible',
timeout
});
testLogger.debug(`元素已可见: ${selector}`);
} catch (error) {
testLogger.error(`等待元素超时: ${selector}`, error as Error);
throw error;
}
}
export async function waitForElementHidden(page: Page, selector: string, timeout: number = 10000): Promise<void> {
testLogger.debug(`等待元素隐藏: ${selector}, 超时: ${timeout}ms`);
try {
await page.waitForSelector(selector, {
state: 'hidden',
timeout
});
testLogger.debug(`元素已隐藏: ${selector}`);
} catch (error) {
testLogger.error(`等待元素隐藏超时: ${selector}`, error as Error);
throw error;
}
}
export async function waitForText(page: Page, selector: string, text: string, timeout: number = 10000): Promise<void> {
testLogger.debug(`等待文本: ${selector} 包含 "${text}", 超时: ${timeout}ms`);
try {
const locator = page.locator(selector);
await expect(locator).toHaveText(text, { timeout });
testLogger.debug(`文本已出现: ${selector} 包含 "${text}"`);
} catch (error) {
testLogger.error(`等待文本超时: ${selector} 包含 "${text}"`, error as Error);
throw error;
}
}
export async function waitForURL(page: Page, urlPattern: string | RegExp, timeout: number = 10000): Promise<void> {
testLogger.debug(`等待URL匹配: ${urlPattern}, 超时: ${timeout}ms`);
try {
await page.waitForURL(urlPattern, { timeout });
testLogger.debug(`URL已匹配: ${page.url()}`);
} catch (error) {
testLogger.error(`等待URL超时: ${urlPattern}`, error as Error);
throw error;
}
}
export async function clickElement(page: Page, selector: string, options?: { timeout?: number; force?: boolean }): Promise<void> {
testLogger.debug(`点击元素: ${selector}`);
try {
const locator = page.locator(selector);
await locator.click(options);
testLogger.debug(`元素点击成功: ${selector}`);
} catch (error) {
testLogger.error(`点击元素失败: ${selector}`, error as Error);
throw error;
}
}
export async function fillInput(page: Page, selector: string, value: string, options?: { timeout?: number }): Promise<void> {
testLogger.debug(`填充输入框: ${selector}, 值: ${value}`);
try {
const locator = page.locator(selector);
await locator.fill(value, options);
testLogger.debug(`输入框填充成功: ${selector}`);
} catch (error) {
testLogger.error(`填充输入框失败: ${selector}`, error as Error);
throw error;
}
}
export async function selectOption(page: Page, selector: string, value: string | string[]): Promise<void> {
testLogger.debug(`选择下拉选项: ${selector}, 值: ${value}`);
try {
const locator = page.locator(selector);
await locator.selectOption(value);
testLogger.debug(`下拉选项选择成功: ${selector}`);
} catch (error) {
testLogger.error(`选择下拉选项失败: ${selector}`, error as Error);
throw error;
}
}
export async function checkCheckbox(page: Page, selector: string): Promise<void> {
testLogger.debug(`勾选复选框: ${selector}`);
try {
const locator = page.locator(selector);
await locator.check();
testLogger.debug(`复选框勾选成功: ${selector}`);
} catch (error) {
testLogger.error(`勾选复选框失败: ${selector}`, error as Error);
throw error;
}
}
export async function uncheckCheckbox(page: Page, selector: string): Promise<void> {
testLogger.debug(`取消勾选复选框: ${selector}`);
try {
const locator = page.locator(selector);
await locator.uncheck();
testLogger.debug(`复选框取消勾选成功: ${selector}`);
} catch (error) {
testLogger.error(`取消勾选复选框失败: ${selector}`, error as Error);
throw error;
}
}
export async function getText(page: Page, selector: string): Promise<string> {
testLogger.debug(`获取元素文本: ${selector}`);
try {
const locator = page.locator(selector);
const text = await locator.textContent();
testLogger.debug(`元素文本: ${selector} = ${text}`);
return text || '';
} catch (error) {
testLogger.error(`获取元素文本失败: ${selector}`, error as Error);
throw error;
}
}
export async function getAttribute(page: Page, selector: string, attributeName: string): Promise<string | null> {
testLogger.debug(`获取元素属性: ${selector}, 属性名: ${attributeName}`);
try {
const locator = page.locator(selector);
const attribute = await locator.getAttribute(attributeName);
testLogger.debug(`元素属性: ${selector}[${attributeName}] = ${attribute}`);
return attribute;
} catch (error) {
testLogger.error(`获取元素属性失败: ${selector}`, error as Error);
throw error;
}
}
export async function isVisible(page: Page, selector: string): Promise<boolean> {
try {
const locator = page.locator(selector);
return await locator.isVisible({ timeout: 5000 });
} catch {
return false;
}
}
export async function isEnabled(page: Page, selector: string): Promise<boolean> {
try {
const locator = page.locator(selector);
return await locator.isEnabled({ timeout: 5000 });
} catch {
return false;
}
}
export async function isHidden(page: Page, selector: string): Promise<boolean> {
try {
const locator = page.locator(selector);
return await locator.isHidden({ timeout: 5000 });
} catch {
return false;
}
}
export async function isDisabled(page: Page, selector: string): Promise<boolean> {
try {
const locator = page.locator(selector);
return await locator.isDisabled({ timeout: 5000 });
} catch {
return false;
}
}
export async function scrollToElement(page: Page, selector: string): Promise<void> {
testLogger.debug(`滚动到元素: ${selector}`);
try {
const locator = page.locator(selector);
await locator.scrollIntoViewIfNeeded();
testLogger.debug(`滚动到元素成功: ${selector}`);
} catch (error) {
testLogger.error(`滚动到元素失败: ${selector}`, error as Error);
throw error;
}
}
export async function hoverElement(page: Page, selector: string): Promise<void> {
testLogger.debug(`悬停在元素上: ${selector}`);
try {
const locator = page.locator(selector);
await locator.hover();
testLogger.debug(`悬停成功: ${selector}`);
} catch (error) {
testLogger.error(`悬停失败: ${selector}`, error as Error);
throw error;
}
}
export async function doubleClickElement(page: Page, selector: string): Promise<void> {
testLogger.debug(`双击元素: ${selector}`);
try {
const locator = page.locator(selector);
await locator.dblclick();
testLogger.debug(`双击成功: ${selector}`);
} catch (error) {
testLogger.error(`双击失败: ${selector}`, error as Error);
throw error;
}
}
export async function rightClickElement(page: Page, selector: string): Promise<void> {
testLogger.debug(`右键点击元素: ${selector}`);
try {
const locator = page.locator(selector);
await locator.click({ button: 'right' });
testLogger.debug(`右键点击成功: ${selector}`);
} catch (error) {
testLogger.error(`右键点击失败: ${selector}`, error as Error);
throw error;
}
}
export async function uploadFile(page: Page, selector: string, filePath: string): Promise<void> {
testLogger.debug(`上传文件: ${selector}, 路径: ${filePath}`);
try {
const locator = page.locator(selector);
await locator.setInputFiles(filePath);
testLogger.debug(`文件上传成功: ${filePath}`);
} catch (error) {
testLogger.error(`文件上传失败: ${filePath}`, error as Error);
throw error;
}
}
export async function clearInput(page: Page, selector: string): Promise<void> {
testLogger.debug(`清空输入框: ${selector}`);
try {
const locator = page.locator(selector);
await locator.clear();
testLogger.debug(`输入框已清空: ${selector}`);
} catch (error) {
testLogger.error(`清空输入框失败: ${selector}`, error as Error);
throw error;
}
}
export async function pressKey(page: Page, key: string): Promise<void> {
testLogger.debug(`按键: ${key}`);
try {
await page.keyboard.press(key);
testLogger.debug(`按键成功: ${key}`);
} catch (error) {
testLogger.error(`按键失败: ${key}`, error as Error);
throw error;
}
}
export async function typeText(page: Page, selector: string, text: string, delay?: number): Promise<void> {
testLogger.debug(`输入文本: ${selector}, 文本: ${text}`);
try {
const locator = page.locator(selector);
await locator.type(text, { delay });
testLogger.debug(`文本输入成功: ${selector}`);
} catch (error) {
testLogger.error(`文本输入失败: ${selector}`, error as Error);
throw error;
}
}
export async function waitForNetworkIdle(page: Page, timeout: number = 30000): Promise<void> {
testLogger.debug(`等待网络空闲, 超时: ${timeout}ms`);
try {
await page.waitForLoadState('networkidle', { timeout });
testLogger.debug('网络已空闲');
} catch (error) {
testLogger.error('等待网络空闲超时', error as Error);
throw error;
}
}
export async function waitForLoadState(page: Page, state: 'load' | 'domcontentloaded' | 'networkidle' = 'load', timeout: number = 30000): Promise<void> {
testLogger.debug(`等待加载状态: ${state}, 超时: ${timeout}ms`);
try {
await page.waitForLoadState(state, { timeout });
testLogger.debug(`加载状态已达到: ${state}`);
} catch (error) {
testLogger.error(`等待加载状态超时: ${state}`, error as Error);
throw error;
}
}
export async function executeScript(page: Page, script: string, ...args: any[]): Promise<any> {
testLogger.debug('执行JavaScript脚本');
try {
const result = await page.evaluate(script, ...args);
testLogger.debug('JavaScript脚本执行成功');
return result;
} catch (error) {
testLogger.error('JavaScript脚本执行失败', error as Error);
throw error;
}
}
export async function takeScreenshot(page: Page, name: string, fullPage: boolean = false): Promise<string> {
testLogger.debug(`截图: ${name}, 全页: ${fullPage}`);
try {
const path = `test-results/screenshots/${name}-${Date.now()}.png`;
await page.screenshot({ path, fullPage });
testLogger.debug(`截图已保存: ${path}`);
return path;
} catch (error) {
testLogger.error(`截图失败: ${name}`, error as Error);
throw error;
}
}
export async function waitForTimeout(ms: number): Promise<void> {
testLogger.debug(`等待 ${ms}ms`);
await new Promise(resolve => setTimeout(resolve, ms));
}
export async function retryOperation<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000,
description: string = '操作'
): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
testLogger.debug(`${description} 尝试 ${attempt}/${maxRetries}`);
const result = await operation();
if (attempt > 1) {
testLogger.info(`${description} 在第 ${attempt} 次尝试后成功`);
}
return result;
} catch (error) {
lastError = error as Error;
testLogger.warn(`${description}${attempt} 次尝试失败: ${error}`);
if (attempt < maxRetries) {
await waitForTimeout(delay);
}
}
}
throw lastError || new Error(`${description}${maxRetries} 次尝试后仍然失败`);
}
export async function waitUntil(
condition: () => boolean | Promise<boolean>,
timeout: number = 10000,
interval: number = 100,
description: string = '条件'
): Promise<void> {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const result = await condition();
if (result) {
testLogger.debug(`${description} 已满足`);
return;
}
await waitForTimeout(interval);
}
throw new Error(`${description}${timeout}ms 内未满足`);
}
export function generateRandomString(length: number = 10): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
export function generateRandomEmail(): string {
const username = generateRandomString(8).toLowerCase();
const domains = ['example.com', 'test.com', 'demo.com'];
const domain = domains[Math.floor(Math.random() * domains.length)];
return `${username}@${domain}`;
}
export function generateRandomPhoneNumber(): string {
const prefix = ['138', '139', '150', '151', '186', '188'];
const selectedPrefix = prefix[Math.floor(Math.random() * prefix.length)];
const suffix = Math.floor(Math.random() * 100000000).toString().padStart(8, '0');
return `${selectedPrefix}${suffix}`;
}
export function generateRandomId(): string {
return `${Date.now()}-${generateRandomString(6)}`;
}
export function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return format
.replace('YYYY', year.toString())
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
}
export function parseDate(dateString: string, format: string = 'YYYY-MM-DD'): Date {
const parts = dateString.match(/(\d+)/g);
if (!parts) {
throw new Error(`无效的日期格式: ${dateString}`);
}
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) - 1;
const day = parseInt(parts[2], 10);
return new Date(year, month, day);
}
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function debounce(func: Function, wait: number): Function {
let timeout: NodeJS.Timeout | null = null;
return function(...args: any[]) {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
export function throttle(func: Function, limit: number): Function {
let inThrottle: boolean = false;
return function(...args: any[]) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
export function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
export function isEmpty(value: any): boolean {
if (value === null || value === undefined) {
return true;
}
if (typeof value === 'string' || Array.isArray(value)) {
return value.length === 0;
}
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}
return false;
}
export function isNotEmpty(value: any): boolean {
return !isEmpty(value);
}
export function pick<T extends Record<string, any>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
for (const key of keys) {
if (key in obj) {
result[key] = obj[key];
}
}
return result;
}
export function omit<T extends Record<string, any>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
const result = { ...obj };
for (const key of keys) {
delete result[key];
}
return result;
}