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/customer/customerPage.ts
LingandRX bac0dbfdbe ci(test): 更新 Playwright 版本并优化测试脚本
- 将 Playwright 版本从 v1.43.0 升级到 v1.51.0
- 优化了 baseFixture 中的登录流程
- 改进了 customerPage 中的顾客创建逻辑
- 调整了 boss_cashier 和 staff_goal 测试中的操作方式
2025-03-16 19:10:03 +08:00

447 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { expect, type Locator, type Page } from '@playwright/test';
import { Customer, employee } from '@/utils/customer';
import { waitSpecifyApiLoad } from '@/utils/utils';
type SubPage = {
name: string;
url: string[];
};
/**
* `CustomerPage` 类提供了与应用程序的顾客页面交互的方法。
* 它包括导航到子页面、搜索顾客、打开和关闭顾客详情、创建新顾客以及将顾客设置为无效的功能。
*/
export class CustomerPage {
page: Page;
private readonly subPages: SubPage[] = [
{ name: '顾客概要', url: ['summary', 'todo'] },
{ name: '顾客分配', url: ['search_new', 'distribution'] },
{ name: '顾客动态', url: ['daily_action'] },
{ name: '顾客分析', url: ['analysis'] },
{ name: '服务日志', url: ['service_log'] },
];
private readonly firstStore = {
firstDepartment: { no: 1, name: '美容部' },
secondDepartment: { no: 2, name: '医美部' },
};
private readonly secondStore = {
firstDepartment: { no: 1, name: '美容部' },
};
private readonly sourceChannel: string[] = [
'邀客',
'员工带客',
'美团',
'大众点评',
'客带客',
'上门客人',
'百度糯米',
'支付宝',
];
$register: Locator;
$tabItem: Locator;
$searchInput: Locator;
$searchBtn: Locator;
constructor(page: Page) {
this.page = page;
this.$tabItem = this.page.locator('.top_tab .tab_item');
this.$register = this.page.locator('.regmeber_warp', { hasText: '创建会员' });
this.$searchInput = this.page.getByPlaceholder('姓名(拼音首字)、手机号、档案号搜索');
this.$searchBtn = this.page.getByText('搜索', { exact: true });
}
/**
* 跳转子页面,并且等待页面接口加载完成
* @param {string} subPageName 顾客模块子页面
* - 顾客概要
* - 顾客分配
* - 顾客动态
* - 顾客分析
* - 服务日志
*/
gotoSubPage = async (subPageName: string) => {
// 输入验证
if (!subPageName || typeof subPageName !== 'string' || subPageName.trim() === '') {
throw new Error('子页面名称不能为空或无效');
}
const subPage = this.subPages.find(e => e.name === subPageName);
if (!subPage) {
console.error(`子页面 ${subPageName} 不存在`);
throw new Error(`子页面 ${subPageName} 不存在`);
}
const $subPageTab = this.$tabItem.filter({ hasText: subPageName });
await $subPageTab.waitFor();
try {
const classAttribute = await $subPageTab.getAttribute('class', { timeout: 5000 });
if (classAttribute && classAttribute.includes('active')) {
return;
}
} catch (error) {
console.error(`获取子页面 ${subPageName} 的 class 属性超时`);
throw new Error(`获取子页面 ${subPageName} 的 class 属性超时`);
}
try {
await $subPageTab.click();
await expect($subPageTab).toHaveClass(/active/, { timeout: 5000 });
await waitSpecifyApiLoad(this.page, subPage.url);
} catch (error) {
console.error(`点击子页面 ${subPageName} 或等待 API 加载失败`);
throw new Error(`点击子页面 ${subPageName} 或等待 API 加载失败`);
}
};
/**
* 搜索顾客
* @param text 姓名(拼音首字)、手机号、档案号搜索
*/
searchCustomer = async (text: string) => {
const $$customerContent = this.page.locator('.member_list_li .custom_content');
const $customerInfoCard = $$customerContent.filter({ hasText: text }).first();
await this.$searchInput.fill(text);
await this.$searchBtn.click();
await $customerInfoCard.waitFor();
};
/**
* 选择搜索顾客
* @param text 姓名(拼音首字)、手机号、档案号搜索
*/
selectSearchCustomer = async (text: string) => {
const $$customerContent = this.page.locator('.member_list_li .custom_content');
const $customerInfoCard = $$customerContent.filter({ hasText: text }).first();
await $customerInfoCard.click();
await $customerInfoCard.waitFor({ state: 'detached' });
};
/**
* 打开顾客详情页面
* @param {string} username
* @param {string} phone
*/
openCustomerDetail = async (username: string, phone: string) => {
const $customer = this.page
.locator('.m-table__fixed-left td')
.filter({ hasText: username })
.filter({ hasText: phone })
.first();
const $username = this.page.getByRole('tabpanel').getByText(username);
const $phone = this.page.getByRole('tabpanel').getByText(phone);
await Promise.all([
await $customer.click(),
this.page.waitForResponse(res => res.url().includes('/api/customer') && res.status() === 200),
]);
await $username.waitFor();
await $phone.waitFor();
};
/**
* 关闭顾客详情页面
*/
closeCustomerDetail = async () => {
const $closeBtn = this.page.locator('.member_info_box .close_icons > svg');
await $closeBtn.click();
await $closeBtn.waitFor({ state: 'detached' });
};
/**
* 创建顾客
*/
createCustomer = async (customer: Customer) => {
const customerPage = '/#/member/member-schame';
await this.page.goto(customerPage);
await expect(this.page.getByRole('button', { name: '新增顾客' })).toBeEnabled();
await this.selectStore(customer.store);
await this.fillCustomerDetails(customer);
await this.selectDepartment(customer);
if (customer.employees.length !== 0) {
await this.selectEmployee(customer.employees);
}
await this.selectGender(customer.gender);
if (customer.birthday) {
await this.selectBirthday(customer.birthday);
}
await this.selectSource(customer.source);
await this.fillRemark(customer.remark);
await this.confirmCreation(customer.phone);
await this.page.locator('.person_content').waitFor();
console.log(`username:${customer.username}, phone:${customer.phone}创建成功`);
await this.closeCustomerDetail();
};
/**
* 选择门店
* @param store 门店
*/
private readonly selectStore = async (store: number) => {
await this.page.locator('.search_store > div').click();
await this.page.getByText('部门', { exact: true }).waitFor();
await this.page.locator('.shopSelect_box .shopSelect_shop_content').click();
await this.page
.locator('.com_picker .label')
.nth(store - 1)
.click();
await this.page.getByRole('button', { name: /保\s存/ }).click();
await this.page.getByRole('button', { name: '新增顾客' }).click();
};
/**
* 填写顾客信息
* @param customer 顾客
*/
private readonly fillCustomerDetails = async (customer: Customer) => {
await this.$register.getByPlaceholder('请输入姓名').fill(customer.username);
await this.$register.getByPlaceholder('请输入会员手机号').fill(customer.phone);
await this.$register.getByPlaceholder('请输入会员手机号').click();
const checkPhoneLocator = this.$register
.locator('.ant-form-item', { hasText: '手机号' })
.locator('.ant-form-explain');
if (await checkPhoneLocator.isVisible()) {
const phoneStr = await checkPhoneLocator.innerText();
if (phoneStr.includes('非法手机号码') || phoneStr.includes('请输入会员手机号')) {
throw new Error(`手机号码:${customer.phone}不正确`);
}
}
if (customer.archive) {
await this.$register.getByPlaceholder('请输入12位以内的数字或字母').fill(customer.archive);
}
};
/**
* 选择部门
* @param customer 顾客
*/
private readonly selectDepartment = async (customer: Customer) => {
await this.$register.locator('#register_departmentNo').getByRole('combobox').click();
if (customer.store === 1) {
if (customer.department === 1) {
await this.page.getByRole('option', { name: this.firstStore.firstDepartment.name }).click();
} else if (customer.department === 2) {
await this.page.getByRole('option', { name: this.firstStore.secondDepartment.name }).click();
}
} else if (customer.store === 2) {
if (customer.department === 1) {
await this.page.getByRole('option', { name: this.secondStore.firstDepartment.name }).click();
} else {
throw new Error(`部门:${customer.department}不存在`);
}
} else {
throw new Error(`门店:${customer.store}不存在`);
}
};
/**
* 选择员工
* @param employees 员工
*/
private readonly selectEmployee = async (employees: employee[]) => {
await this.$register.locator('.ant-form-item', { hasText: '员工' }).getByRole('list').click();
for (const employee of employees) {
await this.page
.getByRole('treeitem', { name: employee.level })
.getByRole('treeitem', { name: employee.name })
.click();
}
await this.page.getByRole('button', { name: '确认分配' }).click();
};
/**
* 选择性别
* @param gender 性别
*/
private readonly selectGender = async (gender: number) => {
// 定义性别选项的映射
const GENDER_OPTIONS = {
FEMALE: 0,
MALE: 1,
};
// 定义对应的文本
const GENDER_TEXTS = {
[GENDER_OPTIONS.FEMALE]: '女性',
[GENDER_OPTIONS.MALE]: '男性',
};
if (gender === 0) {
return;
}
await this.$register.locator('label').filter({ hasText: GENDER_TEXTS[gender] }).click();
};
/**
* 选择生日
* @param {object} birthday 生日
* @param {number} birthday.year 年份
* @param {number} birthday.month 月份
* @param {number} birthday.day 日期
*/
private readonly selectBirthday = async (birthday: { year: number; month: number; day: number }) => {
if (!birthday) {
throw new Error('birthday参数为空');
}
const { year, month, day } = birthday;
if (!Number.isInteger(year) || year < 1900 || year > new Date().getFullYear()) {
throw new Error(`年份 ${year} 不合法`);
}
if (!Number.isInteger(month) || month < 1 || month > 12) {
throw new Error(`月份 ${month} 不合法`);
}
if (!Number.isInteger(day) || day < 1 || day > 31) {
throw new Error(`日期 ${day} 不合法`);
}
const $birthday = this.$register.locator('.ant-form-item', { hasText: '生日' });
if (year) {
await $birthday.getByText('年份').click();
await this.page.getByRole('option', { name: `${year}` }).click();
await expect(this.page.getByRole('option', { name: `${year}` })).not.toBeVisible();
}
if (month && day) {
await $birthday.getByText('日期').click();
await this.page.getByRole('option', { name: new RegExp(`^${month}\\s月$`) }).click();
await this.page.getByRole('option', { name: new RegExp(`^${day}\\s日$`) }).click();
await this.page.getByRole('button', { name: /确\s认/ }).click();
} else {
throw new Error(`month:${month}, day:${day}其中一个为空`);
}
};
/**
* 选择顾客来源
* @param {number} source 来源
*/
private readonly selectSource = async (source: number) => {
await this.$register.getByLabel(this.sourceChannel[source]).click();
};
/**
* 填写备注
* @param {string} remark 备注
*/
private readonly fillRemark = async (remark: string) => {
if (!remark) return;
await this.$register.getByPlaceholder('请输入1-100个字符备注内容').fill(remark);
};
/**
* 确认创建
* @param {string} phone 手机号
*/
private readonly confirmCreation = async (phone: string) => {
try {
// 等待响应并点击按钮
const [response] = await Promise.all([
this.page.waitForResponse(async (res) => {
const urlMatch = res.url().includes('/invalid_check');
if (!urlMatch) return false;
try {
const json = await res.json();
return json.code === 'SUCCESS';
} catch (e) {
console.error('解析响应体失败:', e);
return false;
}
}),
this.page.getByRole('button', { name: '确认创建' }).click(),
]);
// 解析响应体
let responseBody: any;
try {
responseBody = await response.json();
} catch (e) {
throw new Error('无法解析服务器响应体,请检查网络连接或服务器状态');
}
// 检查响应体内容
if (responseBody && typeof responseBody.content === 'object') {
const phoneStatus = responseBody.content?.status;
if (phoneStatus != null) {
throw new Error(`手机号码 ${phone} 已被使用,无法创建新顾客`);
}
} else {
console.warn('响应体格式不符合预期content 不是对象');
}
} catch (error) {
// 捕获并记录异常
console.error('检查手机号码时发生错误:', error);
throw error; // 重新抛出异常以便调用方处理
}
// if (phoneStatus === undefined || phoneStatus === null) {
// 创建顾客成功
// return;
// }
// await this.page.getByText('系统查询到当前手机号被建档后转为无效客,是否要恢复无效客?').waitFor();
// await this.page.getByRole('button', { name: '重新建档' }).click();
// const popupWindow = this.page.locator('.ant-message');
// await expect(popupWindow).not.toContainText('该手机号码已经被使用');
// await popupWindow.waitFor();
// const popupContent = (await popupWindow.innerText()).trim();
// if (popupContent.includes('该手机号码已经被使用')) {
// throw new Error(`手机号码 ${phone} 已被使用,无法创建新顾客`);
// }
};
/**
* 设置顾客为无效客
* @param {Customer} customer 顾客
*/
setInvalidCustomer = async (customer: Customer) => {
await this.page.getByRole('button', { name: '新增顾客' }).waitFor();
// 根据手机号进行搜索,进入顾客详情页面
await this.page.getByPlaceholder('姓名(拼音首字)、手机号、档案号搜索').fill(customer.phone);
await this.page.locator('.ant-input-suffix .search_btn', { hasText: '搜索' }).click();
await this.page.locator('.custom_content', { hasText: customer.phone }).click();
await this.page.locator('.m-table__fixed-left').getByText(customer.username).first().click();
// 设置无效客
await this.page.locator('.person_content .tag_box .more_icon svg').click();
await this.page.getByRole('menuitem', { name: '设为无效客' }).click();
const [response] = await Promise.all([
this.page.waitForResponse(
response => response.url().includes('/customer') && response.request().method() === 'PATCH',
),
this.page.getByRole('button', { name: /确\s认/ }).click(),
]);
const responseBody = await response.json();
const code = responseBody?.code;
expect(code).toBe('SUCCESS');
};
/**
* 批量创建顾客
* @param customerArray
*/
createMoreCustomer = async (customerArray: Customer[]) => {
for (const customer of customerArray) {
await this.createCustomer(customer);
}
};
/**
* 批量设置无效客
* @param customerArray
*/
setMoreInvalidCustomer = async (customerArray: Customer[]) => {
for (const customer of customerArray) {
await this.page.reload();
await this.setInvalidCustomer(customer);
}
};
}