304 lines
11 KiB
TypeScript
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 colorStatus = {
|
|
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: colorStatus;
|
|
CONSULT: colorStatus;
|
|
BILL: colorStatus;
|
|
SETTLED: colorStatus;
|
|
EXPIRED: colorStatus;
|
|
};
|
|
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.getByText('预约详情').waitFor();
|
|
};
|
|
|
|
/**
|
|
* 关闭预约详情
|
|
*/
|
|
closeAppointmentDetail = async () => {
|
|
await this.page.locator('.close > svg > use').first().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 $popup = this.page.locator('.popup_content .content');
|
|
// 点击操作
|
|
await expect(async () => {
|
|
await $popup.getByText(operation).click();
|
|
await expect($popup.getByText(operation)).not.toBeInViewport({ timeout: 3000 });
|
|
}).toPass();
|
|
};
|
|
}
|