This repository has been archived on 2025-04-22. You can view files and clone it, but cannot push or open issues or pull requests.
hlk_autotest/tests/pages/appointmentPage.ts
LingandRX 6af040e406 refactor(appointment): 重构预约测试流程
- 重命名 colorStatus 类型为 status,简化类型名称
- 优化预约状态检查逻辑,使用统一的 API 进行状态验证
- 添加网络请求等待,确保操作完成后端数据同步
- 优化测试步骤,减少冗余代码,提高测试可读性和稳定性
2025-01-03 20:56:06 +08:00

304 lines
11 KiB
TypeScript

import { expect, type Page, type Locator } from '@playwright/test';
import { Customer } from '@/utils/customer';
import { getNextAppointmentTime } from '@/utils/timeUtils';
/**
* 预约状态颜色
*/
type status = {
name: string;
color: string;
quantity: number;
};
/**
* 预约窗口操作
*/
export enum AppointmentOperation {
CANCEL = '取消预约',
ADD_OCCUPY = '新建占用',
ADD_APPOINT = '新建预约',
ADD_REMARK = '添加备注',
BACK = '返回',
CANCEL_OCCUPY = '取消占用',
}
export class AppointmentPage {
page: Page;
$$name: Locator;
$$time: Locator;
$customer: Locator;
$occupy: Locator;
appointmentStatus: {
NORMAL: status;
CONSULT: status;
BILL: status;
SETTLED: status;
EXPIRED: status;
};
appointmentOperation: typeof AppointmentOperation;
/**
* 预约模块
* @param page 页面
*/
constructor(page: Page) {
this.page = page;
this.$$name = this.page.locator('.header_table tr');
this.$$time = this.page.locator('.left_table td');
this.$customer = page.locator('.a_userInfo .user_name');
this.$occupy = page.locator('.a_userInfo .occupy');
this.appointmentStatus = {
NORMAL: { name: '未到店', color: '', quantity: 0 },
CONSULT: { name: '已咨询', color: '', quantity: 0 },
BILL: { name: '已开单', color: '', quantity: 0 },
SETTLED: { name: '已结算', color: '', quantity: 0 },
EXPIRED: { name: '已过期', color: '', quantity: 0 },
};
}
/**
* 初始化预约页面
*/
init = async () => {
await this.page.getByRole('button', { name: /设\s置/ }).click();
await this.page.getByRole('menuitem', { name: '看板设置' }).click();
const $$btn = this.page.locator('.btnBox').getByRole('switch');
const $$btnCount = await $$btn.count();
// 遍历每个开关并将其状态设置为“开”
for (let i = 0; i < $$btnCount; i++) {
// 获取当前的开关按钮
const btn = $$btn.nth(i);
await expect(async () => {
if ((await btn.innerText()).trim() === '关') {
await btn.click();
}
await expect(btn).toContainText('开', { timeout: 3_000 });
}).toPass({ timeout: 30_000 });
}
// 初始化未到店颜色
await this.page
.locator('li', { hasText: /^未到店$/ })
.locator('svg')
.click();
await this.page.getByLabel('Color:#D0021B').click();
await this.page.locator('div', { hasText: /^看板设置$/ }).click();
await this.page.locator('.close > svg').first().click();
await this.page.reload();
};
/**
* 获取预约状态
*/
getAppointmentStatusSetting = async () => {
await this.page.getByRole('button', { name: /设\s置/ }).waitFor();
await this.page.getByText('已过期').waitFor();
const statusArray = await this.page.locator('.herder_right ul > li').all();
for (const status of statusArray) {
// 获取预约状态class name
const statusClass = await status.locator('.status').getAttribute('class');
let name = statusClass ? statusClass.split(' ')[1] : '';
// 获取预约状态Color
const color = await status.locator('.status').evaluate(e => {
return window.getComputedStyle(e).backgroundColor;
});
// 获取预约数量
const quantity = await status.locator('.number_type').innerText();
if (!name || !this.appointmentStatus[name]) {
throw new Error(`没有获取${name}预约状态`);
}
const appointment = this.appointmentStatus[name];
appointment.color = color;
appointment.quantity = quantity;
}
return this.appointmentStatus;
};
/**
* 获取顾客预约状态
* @param {Customer} customer 顾客
*/
getCustomerAppointmentStatus = async (customer: Customer) => {
const customerLocator = this.page.locator('.a_userInfo', { hasText: customer.username }).first();
// 点击顾客信息并获取状态
await customerLocator.locator('.user_name_info').click();
const customerStatus = await this.page.locator('.userInfo .state').innerText();
await this.page.locator('.close > svg > use').first().click();
// 获取顾客预约颜色
const customerColor = await customerLocator.locator('.appointment').evaluate(e => {
return window.getComputedStyle(e).backgroundColor;
});
return { customerColor, customerStatus };
};
/**
* 根据员工和时间的中心进行点击预约单元格
*/
openAppointmentCell = async (name: string, time?: Date, retry = 10) => {
const currentAppointment = time ? getNextAppointmentTime(time) : getNextAppointmentTime();
const $currentTime = this.$$time.filter({ hasText: currentAppointment });
const currentTimeBoundingBox = await $currentTime.boundingBox();
if (name === '未指定') {
await expect(async () => {
if (await this.page.locator('.showNoAppoint_left').isVisible()) {
await this.page.locator('.showNoAppoint_left').click();
}
await expect(this.page.locator('.showNoAppoint_right')).toBeVisible({ timeout: 1000 });
}).toPass();
}
const $name = this.$$name.filter({ hasText: name }).last();
const nameBoundingBox = await $name.boundingBox();
if (!currentTimeBoundingBox || !nameBoundingBox) {
throw new Error(
`Could not find bounding boxes: currentTimeBoundingBox=${currentTimeBoundingBox}, nameBoundingBox=${nameBoundingBox}`,
);
}
let widthCenter = nameBoundingBox.x + nameBoundingBox.width / 2; // 员工横坐标中心
let heightCenter = currentTimeBoundingBox.y + currentTimeBoundingBox.height / 2; // 时间纵坐标中心
const distance = currentTimeBoundingBox.height / 2; // 纵坐标位移一次的距离
/**
* 获取元素的中心位置
*/
const getElementPoint = async (x: number, y: number) => {
return this.page.evaluate(({ x, y }) => document.elementFromPoint(x, y)?.outerHTML, { x, y });
};
for (let attempt = 0; attempt < retry; attempt++) {
const elementPoint = await getElementPoint(widthCenter, heightCenter);
// 根据是否有 user 单元格进行占用单元格
if (elementPoint?.includes('user') || elementPoint?.includes('occupy')) {
heightCenter += distance;
} else {
// 点击预约单元格,弹出操作窗口,此处进行了重试
await expect(async () => {
await this.page.mouse.click(widthCenter, heightCenter, { delay: 2000 });
await expect(this.page.locator('.popup_content', { hasText: '选择操作' })).toBeVisible({
timeout: 2000,
});
}).toPass();
return;
}
}
throw new Error(`Unable to click on the appointment cell after ${retry} attempts.`);
};
/**
* 打开预约详情
*/
openAppointmentDetail = async (text: string) => {
await this.page.locator('.a_userInfo', { hasText: text }).first().locator('.user_name_info').click();
await this.page.locator('div.m_sliding_menu div.title').waitFor();
};
/**
* 关闭预约详情
*/
closeAppointmentDetail = async () => {
await this.page.locator('div.m_sliding_menu i.close.anticon').click();
};
/**
* 取消预约
*/
cancelAppoint = async () => {
await this.page.locator('.state').waitFor();
const appointmentState = (await this.page.locator('.state').innerText()).trim();
let appointmentTimeStr: string;
let appointmentTime: string;
let hours: any;
let minutes: any;
let date: number | Date;
let currentDate: number | Date;
switch (appointmentState) {
case '未到店':
appointmentTimeStr = await this.page.locator('.time').innerText();
appointmentTime = appointmentTimeStr.split('-')[0];
[hours, minutes] = appointmentTime.split(':').map(Number);
date = new Date();
date.setHours(hours, minutes, 0, 0); // 设置时、分、秒、毫秒
// 获取当前时间
currentDate = new Date();
// 比较时间
if (date >= currentDate) {
// 当前时间小于或等于指定时间,执行操作
await this.page.getByRole('button', { name: '取消预约' }).click();
await this.page.getByRole('button', { name: /确\s认/ }).click();
await expect(this.page.locator('.ant-message', { hasText: '取消预约成功!' })).toBeVisible();
} else {
await this.page.locator('.close > svg').first().click();
}
break;
case '已结算':
await this.page.locator('.close > svg').first().click();
break;
case '已开单':
await this.page.locator('.close > svg').first().click();
break;
case '已过期':
await this.page.locator('.close > svg').first().click();
break;
default:
break;
}
};
/**
* 判断元素的中心位置是否在可见视窗内
*/
elementCenterInViewport = async (element: Locator) => {
const box = await element.boundingBox();
const viewport = this.page.viewportSize();
if (!box) {
return false;
}
const centerX = box.x + box.width / 2;
const centerY = box.y + box.height / 2;
return centerX >= 0 && centerX <= viewport!.width && centerY >= 0 && centerY <= viewport!.height;
};
/**
* 操作预约
* @param operation 操作
* - CANCEL 取消预约
* - ADD_OCCUPY 新建占用
* - ADD_APPOINT 新建预约
* - ADD_REMARK 添加备注
* - BACK 返回
* - CANCEL_OCCUPY 取消占用
*/
operationAppointment = async (operation: AppointmentOperation) => {
// 操作按钮
const $operation = this.page.locator('div.popup_content div.content').getByText(operation);
// 点击操作
await expect(async () => {
await $operation.click();
await expect($operation).not.toBeInViewport({ timeout: 1500 });
}).toPass();
};
}