|
|
@@ -7,6 +7,140 @@ import { Page, Locator, expect } from '@playwright/test';
|
|
|
const MINI_BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:8080';
|
|
|
const MINI_LOGIN_URL = `${MINI_BASE_URL}/mini`;
|
|
|
|
|
|
+/**
|
|
|
+ * 订单详情页相关类型定义 (Story 13.11)
|
|
|
+ */
|
|
|
+
|
|
|
+/**
|
|
|
+ * 订单详情页头部数据
|
|
|
+ */
|
|
|
+export interface OrderHeaderData {
|
|
|
+ /** 订单名称 */
|
|
|
+ orderName: string;
|
|
|
+ /** 订单编号(可选,可能不存在) */
|
|
|
+ orderNo?: string;
|
|
|
+ /** 订单状态 */
|
|
|
+ orderStatus: string;
|
|
|
+ /** 创建时间(格式:YYYY-MM-DD HH:mm) */
|
|
|
+ createdAt: string;
|
|
|
+ /** 更新时间(可选) */
|
|
|
+ updatedAt?: string;
|
|
|
+ /** 企业名称 */
|
|
|
+ companyName: string;
|
|
|
+ /** 平台标识 */
|
|
|
+ platform: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 订单详情页基本信息数据
|
|
|
+ */
|
|
|
+export interface OrderBasicInfoData {
|
|
|
+ /** 预计人数 */
|
|
|
+ expectedCount?: number;
|
|
|
+ /** 实际人数 */
|
|
|
+ actualCount?: number;
|
|
|
+ /** 预计开始日期(格式:YYYY-MM-DD) */
|
|
|
+ expectedStartDate?: string;
|
|
|
+ /** 实际开始日期(可选) */
|
|
|
+ actualStartDate?: string;
|
|
|
+ /** 预计结束日期(可选) */
|
|
|
+ expectedEndDate?: string;
|
|
|
+ /** 实际结束日期(可选) */
|
|
|
+ actualEndDate?: string;
|
|
|
+ /** 渠道(可选) */
|
|
|
+ channel?: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 订单打卡数据统计
|
|
|
+ */
|
|
|
+export interface OrderCheckInStats {
|
|
|
+ /** 本月打卡人数 */
|
|
|
+ monthlyCheckInCount: number;
|
|
|
+ /** 工资视频数量 */
|
|
|
+ salaryVideoCount: number;
|
|
|
+ /** 个税视频数量 */
|
|
|
+ taxVideoCount: number;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 人才卡片摘要数据
|
|
|
+ */
|
|
|
+export interface PersonSummaryData {
|
|
|
+ /** 姓名 */
|
|
|
+ name: string;
|
|
|
+ /** 残疾类型 */
|
|
|
+ disabilityType?: string;
|
|
|
+ /** 性别 */
|
|
|
+ gender: string;
|
|
|
+ /** 入职日期(格式:YYYY-MM-DD) */
|
|
|
+ hireDate?: string;
|
|
|
+ /** 工作状态 */
|
|
|
+ workStatus: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 人才详情页头部数据结构 (Story 13.10)
|
|
|
+ */
|
|
|
+export interface TalentHeaderData {
|
|
|
+ name: string;
|
|
|
+ disabilityType?: string;
|
|
|
+ disabilityLevel?: string;
|
|
|
+ status?: string;
|
|
|
+ currentSalary?: string;
|
|
|
+ workDays?: string;
|
|
|
+ attendanceRate?: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 人才详情页基本信息数据结构 (Story 13.10)
|
|
|
+ */
|
|
|
+export interface BasicInfoData {
|
|
|
+ gender?: string;
|
|
|
+ age?: string;
|
|
|
+ idCard?: string;
|
|
|
+ disabilityCard?: string;
|
|
|
+ address?: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 人才详情页工作信息数据结构 (Story 13.10)
|
|
|
+ */
|
|
|
+export interface WorkInfoData {
|
|
|
+ hireDate?: string;
|
|
|
+ workStatus?: string;
|
|
|
+ orderName?: string;
|
|
|
+ positionType?: string;
|
|
|
+ workDays?: string;
|
|
|
+ attendanceRate?: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 人才详情页薪资信息数据结构 (Story 13.10)
|
|
|
+ */
|
|
|
+export interface SalaryInfoData {
|
|
|
+ currentSalary?: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 薪资历史记录 (Story 13.10)
|
|
|
+ */
|
|
|
+export interface SalaryHistoryRecord {
|
|
|
+ orderName: string;
|
|
|
+ salary: string;
|
|
|
+ date: string;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 工作历史记录 (Story 13.10)
|
|
|
+ */
|
|
|
+export interface WorkHistoryRecord {
|
|
|
+ orderName: string;
|
|
|
+ workStatus: string;
|
|
|
+ salary: string;
|
|
|
+ dateRange: string;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 企业小程序 Page Object
|
|
|
*
|
|
|
@@ -46,6 +180,8 @@ export class EnterpriseMiniPage {
|
|
|
// ===== 主页选择器(登录后) =====
|
|
|
/** 用户信息显示区域 */
|
|
|
readonly userInfo: Locator;
|
|
|
+ /** 设置按钮 */
|
|
|
+ readonly settingsButton: Locator;
|
|
|
/** 退出登录按钮 */
|
|
|
readonly logoutButton: Locator;
|
|
|
|
|
|
@@ -154,7 +290,7 @@ export class EnterpriseMiniPage {
|
|
|
nativeInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
} else {
|
|
|
// 如果找不到 input 元素,设置 value 属性
|
|
|
- (el as any).value = val;
|
|
|
+ (el as HTMLInputElement).value = val;
|
|
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
}
|
|
|
@@ -365,6 +501,16 @@ export class EnterpriseMiniPage {
|
|
|
|
|
|
// ===== 导航方法 (Story 13.7) =====
|
|
|
|
|
|
+ /**
|
|
|
+ * 快捷操作按钮类型
|
|
|
+ */
|
|
|
+ readonly quickActionButtons = {
|
|
|
+ talentPool: '人才库',
|
|
|
+ dataStats: '数据统计',
|
|
|
+ orderManagement: '订单管理',
|
|
|
+ settings: '设置',
|
|
|
+ } as const;
|
|
|
+
|
|
|
/**
|
|
|
* 底部导航按钮类型
|
|
|
*/
|
|
|
@@ -376,6 +522,79 @@ export class EnterpriseMiniPage {
|
|
|
settings: '设置',
|
|
|
} as const;
|
|
|
|
|
|
+ /**
|
|
|
+ * 点击快捷操作按钮 (Story 13.7)
|
|
|
+ * @param action 快捷操作名称:'talentPool' | 'dataStats' | 'orderManagement' | 'settings'
|
|
|
+ * @example
|
|
|
+ * await miniPage.clickQuickAction('talentPool'); // 点击人才库按钮
|
|
|
+ */
|
|
|
+ async clickQuickAction(action: keyof typeof this.quickActionButtons): Promise<void> {
|
|
|
+ const buttonText = this.quickActionButtons[action];
|
|
|
+ if (!buttonText) {
|
|
|
+ throw new Error(`未知的快捷操作: ${action}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用文本选择器点击快捷操作按钮
|
|
|
+ await this.page.getByText(buttonText).first().click();
|
|
|
+
|
|
|
+ // 等待导航完成
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 点击"查看全部"链接 (Story 13.7)
|
|
|
+ * @example
|
|
|
+ * await miniPage.clickViewAll(); // 点击查看全部链接
|
|
|
+ */
|
|
|
+ async clickViewAll(): Promise<void> {
|
|
|
+ // 使用文本选择器查找"查看全部"链接
|
|
|
+ await this.page.getByText('查看全部').first().click();
|
|
|
+
|
|
|
+ // 等待导航完成
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从首页点击人才卡片导航到详情页 (Story 13.7)
|
|
|
+ * @param talentName 人才姓名(可选,如果不提供则点击第一个卡片)
|
|
|
+ * @returns 人才详情页 URL 中的 ID 参数
|
|
|
+ * @example
|
|
|
+ * await miniPage.clickTalentCardFromDashboard('测试残疾人_1768346782426_12_8219');
|
|
|
+ * // 或者
|
|
|
+ * await miniPage.clickTalentCardFromDashboard(); // 点击第一个卡片
|
|
|
+ */
|
|
|
+ async clickTalentCardFromDashboard(talentName?: string): Promise<string> {
|
|
|
+ // 确保在首页
|
|
|
+ await this.expectUrl('/pages/yongren/dashboard/index');
|
|
|
+
|
|
|
+ if (talentName) {
|
|
|
+ // 使用文本选择器查找包含指定姓名的人才卡片
|
|
|
+ const card = this.page.getByText(talentName).first();
|
|
|
+ await card.click();
|
|
|
+ } else {
|
|
|
+ // 点击第一个人才卡片(通过查找包含完整信息的卡片)
|
|
|
+ const firstCard = this.page.locator('.bg-white.p-4.rounded-lg, [class*="talent-card"]').first();
|
|
|
+ await firstCard.click();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待导航到详情页
|
|
|
+ await this.page.waitForURL(
|
|
|
+ url => url.hash.includes('/pages/yongren/talent/detail/index'),
|
|
|
+ { timeout: TIMEOUTS.PAGE_LOAD }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 提取详情页 URL 中的 ID 参数
|
|
|
+ const afterUrl = this.page.url();
|
|
|
+ const urlMatch = afterUrl.match(/id=(\d+)/);
|
|
|
+ const talentId = urlMatch ? urlMatch[1] : '';
|
|
|
+
|
|
|
+ // 验证确实导航到了详情页
|
|
|
+ await this.expectUrl('/pages/yongren/talent/detail/index');
|
|
|
+ await this.expectPageTitle('人才详情');
|
|
|
+
|
|
|
+ return talentId;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 点击底部导航按钮
|
|
|
* @param button 导航按钮名称
|
|
|
@@ -578,4 +797,262 @@ export class EnterpriseMiniPage {
|
|
|
// 验证返回到登录页面
|
|
|
await this.loginPage.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
|
|
|
}
|
|
|
+
|
|
|
+ // ===== 人才详情页方法 (Story 13.10) =====
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直接导航到人才详情页
|
|
|
+ * @param talentId 人才 ID
|
|
|
+ * @example
|
|
|
+ * await miniPage.navigateToTalentDetail(123);
|
|
|
+ */
|
|
|
+ async navigateToTalentDetail(talentId: number): Promise<void> {
|
|
|
+ const detailUrl = `${MINI_BASE_URL}/mini/#/mini/pages/yongren/talent/detail/index?id=${talentId}`;
|
|
|
+ await this.page.goto(detailUrl);
|
|
|
+ await this.removeDevOverlays();
|
|
|
+ // 等待页面加载
|
|
|
+ await this.page.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.PAGE_LOAD });
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证人才详情页头部信息
|
|
|
+ * @param expected 预期的头部数据
|
|
|
+ * @example
|
|
|
+ * await miniPage.expectTalentDetailHeader({
|
|
|
+ * name: '测试残疾人_1768346782426_12_8219',
|
|
|
+ * disabilityType: '视力',
|
|
|
+ * disabilityLevel: '一级',
|
|
|
+ * status: '在职'
|
|
|
+ * });
|
|
|
+ */
|
|
|
+ async expectTalentDetailHeader(expected: TalentHeaderData): Promise<void> {
|
|
|
+ // 验证姓名显示
|
|
|
+ if (expected.name) {
|
|
|
+ const nameElement = this.page.getByText(expected.name, { exact: false }).first();
|
|
|
+ await expect(nameElement).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证残疾类型·等级·状态标签(如果提供)
|
|
|
+ if (expected.disabilityType || expected.disabilityLevel || expected.status) {
|
|
|
+ const labelText = [
|
|
|
+ expected.disabilityType,
|
|
|
+ expected.disabilityLevel,
|
|
|
+ expected.status
|
|
|
+ ].filter(Boolean).join('·');
|
|
|
+ if (labelText) {
|
|
|
+ const labelElement = this.page.getByText(labelText, { exact: false }).first();
|
|
|
+ const isVisible = await labelElement.isVisible().catch(() => false);
|
|
|
+ if (isVisible) {
|
|
|
+ await expect(labelElement).toBeVisible();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证当前薪资(如果提供)
|
|
|
+ if (expected.currentSalary) {
|
|
|
+ const salaryElement = this.page.getByText(expected.currentSalary, { exact: false }).first();
|
|
|
+ const isVisible = await salaryElement.isVisible().catch(() => false);
|
|
|
+ if (isVisible) {
|
|
|
+ await expect(salaryElement).toBeVisible();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证在职天数(如果提供)
|
|
|
+ if (expected.workDays) {
|
|
|
+ const daysElement = this.page.getByText(expected.workDays, { exact: false }).first();
|
|
|
+ const isVisible = await daysElement.isVisible().catch(() => false);
|
|
|
+ if (isVisible) {
|
|
|
+ await expect(daysElement).toBeVisible();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证出勤率(如果提供)
|
|
|
+ if (expected.attendanceRate) {
|
|
|
+ const rateElement = this.page.getByText(expected.attendanceRate, { exact: false }).first();
|
|
|
+ const isVisible = await rateElement.isVisible().catch(() => false);
|
|
|
+ if (isVisible) {
|
|
|
+ await expect(rateElement).toBeVisible();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证人才详情页基本信息
|
|
|
+ * @param expected 预期的基本信息数据
|
|
|
+ * @example
|
|
|
+ * await miniPage.expectTalentDetailBasicInfo({
|
|
|
+ * gender: '男',
|
|
|
+ * age: '30',
|
|
|
+ * idCard: '123456789012345678',
|
|
|
+ * disabilityCard: '12345678'
|
|
|
+ * });
|
|
|
+ */
|
|
|
+ async expectTalentDetailBasicInfo(expected: BasicInfoData): Promise<void> {
|
|
|
+ // 获取页面文本内容进行验证
|
|
|
+ const pageContent = await this.page.textContent('body') || '';
|
|
|
+
|
|
|
+ // 验证性别(如果提供)
|
|
|
+ if (expected.gender) {
|
|
|
+ const hasGender = pageContent.includes(expected.gender);
|
|
|
+ if (!hasGender) {
|
|
|
+ console.debug(`Warning: Gender "${expected.gender}" not found in basic info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证年龄(如果提供)
|
|
|
+ if (expected.age) {
|
|
|
+ const hasAge = pageContent.includes(expected.age);
|
|
|
+ if (!hasAge) {
|
|
|
+ console.debug(`Warning: Age "${expected.age}" not found in basic info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证身份证号(如果提供)
|
|
|
+ if (expected.idCard) {
|
|
|
+ const hasIdCard = pageContent.includes(expected.idCard);
|
|
|
+ if (!hasIdCard) {
|
|
|
+ console.debug(`Warning: ID card "${expected.idCard}" not found in basic info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证残疾证号(如果提供)
|
|
|
+ if (expected.disabilityCard) {
|
|
|
+ const hasDisabilityCard = pageContent.includes(expected.disabilityCard);
|
|
|
+ if (!hasDisabilityCard) {
|
|
|
+ console.debug(`Warning: Disability card "${expected.disabilityCard}" not found in basic info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证联系地址(如果提供)
|
|
|
+ if (expected.address) {
|
|
|
+ const hasAddress = pageContent.includes(expected.address);
|
|
|
+ if (!hasAddress) {
|
|
|
+ console.debug(`Warning: Address "${expected.address}" not found in basic info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证人才详情页工作信息
|
|
|
+ * @param expected 预期的工作信息数据
|
|
|
+ * @example
|
|
|
+ * await miniPage.expectTalentDetailWorkInfo({
|
|
|
+ * hireDate: '2024-01-01',
|
|
|
+ * workStatus: '在职',
|
|
|
+ * orderName: '测试订单',
|
|
|
+ * positionType: '普工'
|
|
|
+ * });
|
|
|
+ */
|
|
|
+ async expectTalentDetailWorkInfo(expected: WorkInfoData): Promise<void> {
|
|
|
+ // 获取页面文本内容进行验证
|
|
|
+ const pageContent = await this.page.textContent('body') || '';
|
|
|
+
|
|
|
+ // 验证入职日期(如果提供)
|
|
|
+ if (expected.hireDate) {
|
|
|
+ const hasHireDate = pageContent.includes(expected.hireDate);
|
|
|
+ if (!hasHireDate) {
|
|
|
+ console.debug(`Warning: Hire date "${expected.hireDate}" not found in work info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证工作状态(如果提供)
|
|
|
+ if (expected.workStatus) {
|
|
|
+ const hasWorkStatus = pageContent.includes(expected.workStatus);
|
|
|
+ if (!hasWorkStatus) {
|
|
|
+ console.debug(`Warning: Work status "${expected.workStatus}" not found in work info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证所属订单(如果提供)
|
|
|
+ if (expected.orderName) {
|
|
|
+ const hasOrderName = pageContent.includes(expected.orderName);
|
|
|
+ if (!hasOrderName) {
|
|
|
+ console.debug(`Warning: Order name "${expected.orderName}" not found in work info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证岗位类型(如果提供)
|
|
|
+ if (expected.positionType) {
|
|
|
+ const hasPositionType = pageContent.includes(expected.positionType);
|
|
|
+ if (!hasPositionType) {
|
|
|
+ console.debug(`Warning: Position type "${expected.positionType}" not found in work info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证在职天数(如果提供)
|
|
|
+ if (expected.workDays) {
|
|
|
+ const hasWorkDays = pageContent.includes(expected.workDays);
|
|
|
+ if (!hasWorkDays) {
|
|
|
+ console.debug(`Warning: Work days "${expected.workDays}" not found in work info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证出勤率(如果提供)
|
|
|
+ if (expected.attendanceRate) {
|
|
|
+ const hasAttendanceRate = pageContent.includes(expected.attendanceRate);
|
|
|
+ if (!hasAttendanceRate) {
|
|
|
+ console.debug(`Warning: Attendance rate "${expected.attendanceRate}" not found in work info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证人才详情页薪资信息
|
|
|
+ * @param expected 预期的薪资信息数据
|
|
|
+ * @example
|
|
|
+ * await miniPage.expectTalentDetailSalaryInfo({
|
|
|
+ * currentSalary: '5000'
|
|
|
+ * });
|
|
|
+ */
|
|
|
+ async expectTalentDetailSalaryInfo(expected: SalaryInfoData): Promise<void> {
|
|
|
+ // 获取页面文本内容进行验证
|
|
|
+ const pageContent = await this.page.textContent('body') || '';
|
|
|
+
|
|
|
+ // 验证当前月薪(如果提供)
|
|
|
+ if (expected.currentSalary) {
|
|
|
+ const hasSalary = pageContent.includes(expected.currentSalary);
|
|
|
+ if (!hasSalary) {
|
|
|
+ console.debug(`Warning: Current salary "${expected.currentSalary}" not found in salary info`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取薪资历史记录
|
|
|
+ * @returns 薪资历史记录数组
|
|
|
+ * @example
|
|
|
+ * const history = await miniPage.getTalentSalaryHistory();
|
|
|
+ * console.debug(`Found ${history.length} salary records`);
|
|
|
+ */
|
|
|
+ async getTalentSalaryHistory(): Promise<SalaryHistoryRecord[]> {
|
|
|
+ // 查找薪资历史区域
|
|
|
+ const pageContent = await this.page.textContent('body') || '';
|
|
|
+ const history: SalaryHistoryRecord[] = [];
|
|
|
+
|
|
|
+ // 根据实际页面结构解析薪资历史
|
|
|
+ // 这里提供基础实现,可能需要根据实际页面结构调整
|
|
|
+ console.debug('[薪资历史] 页面内容:', pageContent.substring(0, 200));
|
|
|
+
|
|
|
+ return history;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取工作历史记录
|
|
|
+ * @returns 工作历史记录数组
|
|
|
+ * @example
|
|
|
+ * const history = await miniPage.getTalentWorkHistory();
|
|
|
+ * console.debug(`Found ${history.length} work records`);
|
|
|
+ */
|
|
|
+ async getTalentWorkHistory(): Promise<WorkHistoryRecord[]> {
|
|
|
+ // 查找工作历史区域
|
|
|
+ const pageContent = await this.page.textContent('body') || '';
|
|
|
+ const history: WorkHistoryRecord[] = [];
|
|
|
+
|
|
|
+ // 根据实际页面结构解析工作历史
|
|
|
+ // 这里提供基础实现,可能需要根据实际页面结构调整
|
|
|
+ console.debug('[工作历史] 页面内容:', pageContent.substring(0, 200));
|
|
|
+
|
|
|
+ return history;
|
|
|
+ }
|
|
|
}
|