|
@@ -1803,49 +1803,84 @@ export class EnterpriseMiniPage {
|
|
|
/**
|
|
/**
|
|
|
* 选择年份 (Story 13.12)
|
|
* 选择年份 (Story 13.12)
|
|
|
*
|
|
*
|
|
|
- * Taro Picker 组件在 H5 模式下会渲染为原生 select 元素
|
|
|
|
|
- * 实现方式:查找包含年份文本(如"2026年")的 select 元素并选择对应选项
|
|
|
|
|
|
|
+ * Taro Picker 组件在 H5 模式下的实现:
|
|
|
|
|
+ * - Picker 触发元素显示当前选中的值(如"2026年")
|
|
|
|
|
+ * - 点击后会触发原生的 select 选择器
|
|
|
|
|
+ * - 需要通过点击和选择来操作
|
|
|
*
|
|
*
|
|
|
* @param year 要选择的年份(如 2026)
|
|
* @param year 要选择的年份(如 2026)
|
|
|
*/
|
|
*/
|
|
|
async selectYear(year: number): Promise<void> {
|
|
async selectYear(year: number): Promise<void> {
|
|
|
- // Taro Picker 在 H5 模式下渲染为 select 元素
|
|
|
|
|
- // 年份选择器包含年份文本,我们通过查找包含年份文本的元素来定位
|
|
|
|
|
- const yearText = `${year}年`;
|
|
|
|
|
|
|
+ const currentYear = new Date().getFullYear();
|
|
|
|
|
+ const years = Array.from({ length: 5 }, (_, i) => currentYear - 4 + i);
|
|
|
|
|
+ const yearIndex = years.indexOf(year);
|
|
|
|
|
|
|
|
- // 方法1: 查找包含年份文本的 Picker 并触发选择
|
|
|
|
|
- // Taro Picker 的子元素包含当前选中的值
|
|
|
|
|
- const yearPickerElements = this.page.locator('select').filter({
|
|
|
|
|
- has: this.page.getByText(yearText, { exact: false })
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ if (yearIndex === -1) {
|
|
|
|
|
+ console.debug(`[数据统计页] 警告: 年份 ${year} 不在可选范围内 (${years.join(', ')})`);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 方法1: Taro Picker 在 H5 模式下会渲染隐藏的 select 元素
|
|
|
|
|
+ // 查找所有 select 元素
|
|
|
|
|
+ const allSelects = this.page.locator('select');
|
|
|
|
|
+ const selectCount = await allSelects.count();
|
|
|
|
|
|
|
|
- const count = await yearPickerElements.count();
|
|
|
|
|
- if (count > 0) {
|
|
|
|
|
- // 找到年份选择器,使用 selectOption 选择目标年份
|
|
|
|
|
- // select 选项是年份数组中的值
|
|
|
|
|
- const currentYear = new Date().getFullYear();
|
|
|
|
|
- const years = Array.from({ length: 5 }, (_, i) => currentYear - 4 + i);
|
|
|
|
|
- const yearIndex = years.indexOf(year);
|
|
|
|
|
-
|
|
|
|
|
- if (yearIndex !== -1) {
|
|
|
|
|
- await yearPickerElements.first().selectOption(yearIndex.toString());
|
|
|
|
|
- console.debug(`[数据统计页] 选择年份: ${year} (使用 selectOption)`);
|
|
|
|
|
|
|
+ // 尝试找到年份选择器(第一个 select 通常是年份)
|
|
|
|
|
+ if (selectCount > 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await allSelects.first().selectOption(yearIndex.toString());
|
|
|
|
|
+ console.debug(`[数据统计页] 选择年份: ${year} (索引 ${yearIndex})`);
|
|
|
await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
return;
|
|
return;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.debug(`[数据统计页] selectOption 失败: ${e}`);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 方法2: 如果找不到 select,尝试查找可点击的元素并使用 JS 模拟
|
|
|
|
|
- // 某些 Taro 版本可能使用自定义渲染
|
|
|
|
|
- const yearClickableElements = this.page.locator('*').filter({
|
|
|
|
|
- hasText: yearText
|
|
|
|
|
- }).and(this.page.locator('[class*="picker"], [class*="select"]'));
|
|
|
|
|
|
|
+ /// 方法2: 查找包含年份文本的 View 元素并点击
|
|
|
|
|
+ // Taro Picker 的触发元素通常包含当前选中的年份文本
|
|
|
|
|
+ const yearText = `${year}年`;
|
|
|
|
|
+ const yearTextElements = this.page.locator('View').filter({
|
|
|
|
|
+ hasText: /\d{4}年/
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- const clickableCount = await yearClickableElements.count();
|
|
|
|
|
- if (clickableCount > 0) {
|
|
|
|
|
- await yearClickableElements.first().click();
|
|
|
|
|
- console.debug(`[数据统计页] 点击年份选择器: ${year}`);
|
|
|
|
|
|
|
+ const yearTextCount = await yearTextElements.count();
|
|
|
|
|
+ if (yearTextCount > 0) {
|
|
|
|
|
+ // 尝试点击第一个包含年份文本的元素
|
|
|
|
|
+ await yearTextElements.first().click();
|
|
|
|
|
+ console.debug(`[数据统计页] 点击年份选择器元素`);
|
|
|
await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果弹出了原生选择器,再次尝试 selectOption
|
|
|
|
|
+ try {
|
|
|
|
|
+ await allSelects.first().selectOption(yearIndex.toString());
|
|
|
|
|
+ console.debug(`[数据统计页] 选择年份: ${year} (点击后选择)`);
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
|
|
+ return;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // 忽略错误
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 方法3: 使用 JS 直接设置状态并触发事件
|
|
|
|
|
+ const selected = await this.page.evaluate((params) => {
|
|
|
|
|
+ // 查找所有可能的选择器元素
|
|
|
|
|
+ const selects = document.querySelectorAll('select');
|
|
|
|
|
+
|
|
|
|
|
+ if (selects.length > 0) {
|
|
|
|
|
+ const yearSelect = selects[0]; // 第一个 select 通常是年份
|
|
|
|
|
+ yearSelect.value = params.yearIdx;
|
|
|
|
|
+ yearSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
|
+ yearSelect.dispatchEvent(new Event('input', { bubbles: true }));
|
|
|
|
|
+ return { success: true, value: yearSelect.value };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { success: false };
|
|
|
|
|
+ }, { year, yearIdx: yearIndex });
|
|
|
|
|
+
|
|
|
|
|
+ if (selected.success) {
|
|
|
|
|
+ console.debug(`[数据统计页] 选择年份: ${year} (使用 JS 直接设置)`);
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1856,50 +1891,83 @@ export class EnterpriseMiniPage {
|
|
|
/**
|
|
/**
|
|
|
* 选择月份 (Story 13.12)
|
|
* 选择月份 (Story 13.12)
|
|
|
*
|
|
*
|
|
|
- * Taro Picker 组件在 H5 模式下会渲染为原生 select 元素
|
|
|
|
|
- * 实现方式:查找包含月份文本(如"1月")的 select 元素并选择对应选项
|
|
|
|
|
|
|
+ * Taro Picker 组件在 H5 模式下的实现:
|
|
|
|
|
+ * - Picker 触发元素显示当前选中的值(如"1月")
|
|
|
|
|
+ * - 点击后会触发原生的 select 选择器
|
|
|
|
|
+ * - 需要通过点击和选择来操作
|
|
|
*
|
|
*
|
|
|
* @param month 要选择的月份(1-12)
|
|
* @param month 要选择的月份(1-12)
|
|
|
*/
|
|
*/
|
|
|
async selectMonth(month: number): Promise<void> {
|
|
async selectMonth(month: number): Promise<void> {
|
|
|
- // Taro Picker 在 H5 模式下渲染为 select 元素
|
|
|
|
|
- const monthText = `${month}月`;
|
|
|
|
|
|
|
+ if (month < 1 || month > 12) {
|
|
|
|
|
+ console.debug(`[数据统计页] 警告: 月份 ${month} 不在有效范围内 (1-12)`);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const monthIndex = month - 1; // 月份索引从 0 开始
|
|
|
|
|
|
|
|
- // 方法1: 查找包含月份文本的 Picker 并触发选择
|
|
|
|
|
- // 注意:月份选择器需要特别处理,因为可能有多个包含数字和"月"的元素
|
|
|
|
|
- // 我们通过查找所有 select 元素,然后找到包含月份文本的那个
|
|
|
|
|
|
|
+ /// 方法1: Taro Picker 在 H5 模式下会渲染隐藏的 select 元素
|
|
|
|
|
+ // 查找所有 select 元素(第二个 select 通常是月份)
|
|
|
const allSelects = this.page.locator('select');
|
|
const allSelects = this.page.locator('select');
|
|
|
const selectCount = await allSelects.count();
|
|
const selectCount = await allSelects.count();
|
|
|
|
|
|
|
|
- for (let i = 0; i < selectCount; i++) {
|
|
|
|
|
- const select = allSelects.nth(i);
|
|
|
|
|
- const selectParent = select.locator('..');
|
|
|
|
|
|
|
+ // 尝试找到月份选择器(第二个 select 通常是月份)
|
|
|
|
|
+ if (selectCount >= 2) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await allSelects.nth(1).selectOption(monthIndex.toString());
|
|
|
|
|
+ console.debug(`[数据统计页] 选择月份: ${month} (索引 ${monthIndex})`);
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
|
|
+ return;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.debug(`[数据统计页] selectOption 失败: ${e}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 方法2: 查找包含月份文本的 View 元素并点击
|
|
|
|
|
+ // Taro Picker 的触发元素通常包含当前选中的月份文本
|
|
|
|
|
+ const monthText = `${month}月`;
|
|
|
|
|
+ const monthTextElements = this.page.locator('View').filter({
|
|
|
|
|
+ hasText: /\d+月/
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- // 检查 select 的父元素是否包含月份文本
|
|
|
|
|
- const hasMonthText = await selectParent.filter({
|
|
|
|
|
- hasText: monthText
|
|
|
|
|
- }).count() > 0;
|
|
|
|
|
|
|
+ const monthTextCount = await monthTextElements.count();
|
|
|
|
|
+ if (monthTextCount > 0) {
|
|
|
|
|
+ // 尝试点击第二个包含月份文本的元素(第一个可能是年份)
|
|
|
|
|
+ const targetIndex = monthTextCount > 1 ? 1 : 0;
|
|
|
|
|
+ await monthTextElements.nth(targetIndex).click();
|
|
|
|
|
+ console.debug(`[数据统计页] 点击月份选择器元素`);
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
|
|
|
|
|
- if (hasMonthText) {
|
|
|
|
|
- // 找到月份选择器,选择目标月份(月份索引从 0 开始)
|
|
|
|
|
- const monthIndex = month - 1;
|
|
|
|
|
- await select.selectOption(monthIndex.toString());
|
|
|
|
|
- console.debug(`[数据统计页] 选择月份: ${month} (使用 selectOption)`);
|
|
|
|
|
|
|
+ // 如果弹出了原生选择器,再次尝试 selectOption
|
|
|
|
|
+ try {
|
|
|
|
|
+ await allSelects.nth(1).selectOption(monthIndex.toString());
|
|
|
|
|
+ console.debug(`[数据统计页] 选择月份: ${month} (点击后选择)`);
|
|
|
await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
return;
|
|
return;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // 忽略错误
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 方法2: 如果找不到 select,尝试查找可点击的元素
|
|
|
|
|
- const monthClickableElements = this.page.locator('*').filter({
|
|
|
|
|
- hasText: monthText
|
|
|
|
|
- }).and(this.page.locator('[class*="picker"], [class*="select"]'));
|
|
|
|
|
|
|
+ /// 方法3: 使用 JS 直接设置状态并触发事件
|
|
|
|
|
+ const selected = await this.page.evaluate((params) => {
|
|
|
|
|
+ // 查找所有可能的选择器元素
|
|
|
|
|
+ const selects = document.querySelectorAll('select');
|
|
|
|
|
|
|
|
- const clickableCount = await monthClickableElements.count();
|
|
|
|
|
- if (clickableCount > 0) {
|
|
|
|
|
- await monthClickableElements.first().click();
|
|
|
|
|
- console.debug(`[数据统计页] 点击月份选择器: ${month}`);
|
|
|
|
|
- await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
|
|
|
|
+ if (selects.length >= 2) {
|
|
|
|
|
+ const monthSelect = selects[1]; // 第二个 select 通常是月份
|
|
|
|
|
+ monthSelect.value = params.monthIdx;
|
|
|
|
|
+ monthSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
|
+ monthSelect.dispatchEvent(new Event('input', { bubbles: true }));
|
|
|
|
|
+ return { success: true, value: monthSelect.value };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { success: false };
|
|
|
|
|
+ }, { month, monthIdx: monthIndex });
|
|
|
|
|
+
|
|
|
|
|
+ if (selected.success) {
|
|
|
|
|
+ console.debug(`[数据统计页] 选择月份: ${month} (使用 JS 直接设置)`);
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2048,10 +2116,29 @@ export class EnterpriseMiniPage {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 等待统计页数据加载完成 (Story 13.12)
|
|
* 等待统计页数据加载完成 (Story 13.12)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 修复说明:等待所有 4 个 stat-card 元素都可见并加载完成
|
|
|
|
|
+ * 原实现只等待第一个卡片,导致某些测试在卡片未完全加载时失败
|
|
|
*/
|
|
*/
|
|
|
async waitForStatisticsDataLoaded(): Promise<void> {
|
|
async waitForStatisticsDataLoaded(): Promise<void> {
|
|
|
- const cards = this.page.locator('.bg-white.p-4.rounded-lg.shadow-sm, [class*="stat-card"]');
|
|
|
|
|
- await cards.first().waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
|
|
|
|
|
|
|
+ // 等待所有 stat-card 元素出现并可见(应该有 4 个:在职人数、平均薪资、在职率、新增人数)
|
|
|
|
|
+ // 使用 first() 避免 strict mode violation
|
|
|
|
|
+ const firstCard = this.page.locator('.stat-card').first();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待第一个卡片出现
|
|
|
|
|
+ await firstCard.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
|
|
|
|
|
+
|
|
|
|
|
+ // 等待至少 4 个卡片元素存在
|
|
|
|
|
+ await this.page.waitForFunction(
|
|
|
|
|
+ (count) => {
|
|
|
|
|
+ const cardElements = document.querySelectorAll('.stat-card');
|
|
|
|
|
+ return cardElements.length >= count;
|
|
|
|
|
+ },
|
|
|
|
|
+ 4,
|
|
|
|
|
+ { timeout: TIMEOUTS.PAGE_LOAD }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 额外等待 API 数据加载完成
|
|
|
await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
}
|
|
}
|
|
|
|
|
|