Compare commits

...

2 Commits

Author SHA1 Message Date
135718a8c3 refactor(tests): 优化组件导入路径和输入框处理逻辑,增强代码可读性 2025-03-10 23:36:34 +08:00
e55b84e3cc test(boss_customer): 优化顾客相关测试的结算流程和动态查看功能
- 移除了不必要的日期相关变量和计算
- 优化了结算流程,增加了对结算状态的验证
- 改进了动态查看功能,增加了对当前月份动态的精确查找和验证
-修复了一些测试步骤中的潜在问题,提高了测试的稳定性和准确性
2025-01-04 20:38:12 +08:00
10 changed files with 285 additions and 216 deletions

View File

@ -1,4 +1,4 @@
import { NumberInput } from '@/pages/components/numberInput';
import { PopupContent } from '@/pages/components/popupContent';
import { NumberInput } from './numberInput';
import { PopupContent } from './popupContent';
export { NumberInput, PopupContent };

View File

@ -3,12 +3,13 @@ import { Locator, Page } from '@playwright/test';
export class NumberInput {
private readonly page: Page;
private readonly popupLocator: Locator;
private readonly inputLocator: Locator;
private readonly inputLocators: { [key: string]: Locator } = {};
private readonly confirmButtonLocator: Locator;
private readonly delButtonLocator: Locator;
private readonly delAllButtonLocator: Locator;
private readonly pointInputLocator: Locator;
private readonly commonInputLocator: Locator;
// private readonly inputLocator: Locator;
// private readonly pointInputLocator: Locator;
// private readonly commonInputLocator: Locator;
/**
*
@ -17,41 +18,60 @@ export class NumberInput {
constructor(page: Page) {
this.page = page;
this.popupLocator = this.page.locator('div.popup_content');
this.commonInputLocator = this.popupLocator.getByPlaceholder('');
this.inputLocator = this.popupLocator.getByPlaceholder('请输入内容');
this.pointInputLocator = this.popupLocator.getByPlaceholder('请输入积分');
this.inputLocators = {
common: this.popupLocator.getByPlaceholder(''),
normal: this.popupLocator.getByPlaceholder('请输入内容'),
point: this.popupLocator.getByPlaceholder('请输入积分'),
};
// this.commonInputLocator = this.popupLocator.getByPlaceholder('');
// this.inputLocator = this.popupLocator.getByPlaceholder('请输入内容');
// this.pointInputLocator = this.popupLocator.getByPlaceholder('请输入积分');
this.confirmButtonLocator = this.popupLocator.locator('button.sure');
this.delButtonLocator = this.popupLocator.locator('button.del');
this.delAllButtonLocator = this.popupLocator.locator('button.delAll');
}
/**
*
* @param type (common, normal, point)
* @param value
*/
async setValue(type: 'common' | 'normal' | 'point', value: number | string): Promise<void> {
const locator = this.inputLocators[type];
if (!locator) {
throw new Error(`Invalid input type: ${type}`);
}
await locator.fill(value.toString());
}
/**
*
*/
async setCommonValue(value: number): Promise<void> {
await this.commonInputLocator.fill(value.toString());
}
// async setCommonValue(value: number): Promise<void> {
// await this.commonInputLocator.fill(value.toString());
// }
/**
*
*/
async setValue(value: number): Promise<void> {
await this.inputLocator.fill(value.toString());
}
/**
*
*/
async setString(value: string): Promise<void> {
await this.inputLocator.fill(value);
}
// async setValue(value: number): Promise<void> {
// await this.inputLocator.fill(value.toString());
// }
/**
*
* @param value
*/
async setPointValue(value: number): Promise<void> {
await this.pointInputLocator.fill(value.toString());
// async setPointValue(value: number): Promise<void> {
// await this.pointInputLocator.fill(value.toString());
// }
/**
*
*/
async setString(value: string): Promise<void> {
// await this.inputLocator.fill(value);
await this.inputLocators.normal.fill(value);
}
/**

View File

@ -14,20 +14,23 @@ export class CustomerAnalysisPage {
/**
*
* @param {string} subPageName
* -
* -
* -
* @param {string} subPageName -
* - "项目余量分析"
* - "套餐消耗升单分析"
* - "顾客项目分析"
*/
gotoSubPage = async (subPageName: string) => {
if (!this.subPages.some(({ name }) => name === subPageName)) {
// 输入验证
if (!subPageName || typeof subPageName !== 'string') {
throw new Error('Invalid subPageName parameter');
}
// 异常处理
if (!this.subPages || !this.subPages.some(({ name }) => name === subPageName)) {
throw new Error(`${subPageName} is not a valid sub page name`);
}
const $dropDown = this.page
.locator('.top_tab .tab_item', {
hasText: '顾客分析',
})
.locator('.ant-dropdown-link');
const $dropDown = this.page.locator('.top_tab .tab_item', { hasText: '顾客分析' }).locator('.ant-dropdown-link');
await $dropDown.click();
await this.page.getByRole('menuitem', { name: subPageName }).click();

View File

@ -8,7 +8,12 @@ export class CustomerDetailsPage {
*/
constructor(page: Page) {
this.page = page;
this.subPages = [{ name: '基本资料' }, { name: '流水' }, { name: '动态' }, { name: '日志' }];
this.subPages = [
{ name: '基本资料' },
{ name: '流水' },
{ name: '动态' },
{ name: '日志' }
];
}
/**
@ -20,12 +25,22 @@ export class CustomerDetailsPage {
* -
*/
gotoSubPage = async (subPageName: string) => {
if (!this.subPages.findIndex(e => e.name === subPageName)) {
// 标准化输入字符串,去除前后空格并统一为小写
const normalizedSubPageName = subPageName.trim().toLowerCase();
// 检查子页面是否存在
if (!this.subPages.some(e => e.name.trim().toLowerCase() === normalizedSubPageName)) {
throw new Error(`${subPageName} is not in the subPages list`);
}
const $subPage = this.page.getByRole('tab', { name: subPageName });
await $subPage.click();
await expect($subPage).toHaveClass(/active/);
await this.page.waitForLoadState();
try {
// 获取子页面元素并点击
const $subPage = this.page.getByRole('tab', { name: subPageName });
await $subPage.click();
await expect($subPage).toHaveClass(/active/);
} catch (error) {
// 捕获并抛出更明确的错误信息
throw new Error(`Failed to navigate to ${subPageName}: ${error.message}`);
}
};
}

View File

@ -60,8 +60,14 @@ export class CustomerPage {
* -
*/
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} 不存在`);
}
@ -69,18 +75,24 @@ export class CustomerPage {
await $subPageTab.waitFor();
const classAttribute = await $subPageTab.getAttribute('class', { timeout: 5000 });
if (classAttribute && classAttribute.includes('active')) {
return;
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 属性超时`);
}
await Promise.all([
expect(async () => {
await $subPageTab.click();
await expect($subPageTab).toHaveClass(/active/);
}).toPass(),
waitSpecifyApiLoad(this.page, subPage.url),
]);
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 加载失败`);
}
};
/**

View File

@ -1,5 +1,5 @@
import { type Locator, type Page } from '@playwright/test';
import { waitSpecifyApiLoad } from '@/utils/utils.js';
import { waitSpecifyApiLoad } from '@/utils/utils';
export class MarketingInviteGuestsPage {
page: Page;

View File

@ -280,9 +280,17 @@ test.describe('挂单', () => {
await page.getByText('已过期 / 删除单据').click();
await page.getByText('警告:已过期服务无法取单结算,请至收银台处理').waitFor();
await $slidingMenu.locator('div.item_box').first().waitFor();
await expect($slidingMenu.locator('div.item_box').first().getByRole('button', { name: /^取\s单$/ })).not.toBeVisible();
await expect(
$slidingMenu
.locator('div.item_box')
.first()
.getByRole('button', { name: /^取\s单$/ }),
).not.toBeVisible();
const $delete = $slidingMenu.locator('div.item_box').first().locator('.comment > div:nth-child(2) > .touchIcon');
const $delete = $slidingMenu
.locator('div.item_box')
.first()
.locator('.comment > div:nth-child(2) > .touchIcon');
await $delete.click();
await page.getByPlaceholder('请输入1-100个字符备注内容').click();
await page.getByPlaceholder('请输入1-100个字符备注内容').fill('测试备注');
@ -582,13 +590,13 @@ test.describe('收银-房态', () => {
});
test('占用床位', async ({
page,
homeNavigation,
createCustomer,
cashierRoomPage,
customerPage,
appointmentPage,
}) => {
page,
homeNavigation,
createCustomer,
cashierRoomPage,
customerPage,
appointmentPage,
}) => {
const customer = createCustomer;
const employee = Employees.FirstShop.Employee_6;
@ -872,13 +880,13 @@ test.describe('收银-房态', () => {
test.describe('收银-开单&结算', () => {
test('开单-反结算-撤单', async ({
page,
homeNavigation,
createCustomer,
customerPage,
wasteBookBusinessRecordPage,
numberInput,
}) => {
page,
homeNavigation,
createCustomer,
customerPage,
wasteBookBusinessRecordPage,
numberInput,
}) => {
// 定义一个随机单号
const randomBillNo1 = faker.helpers.fromRegExp(/1[3-9][0-9]{8}/);
const randomBillNo2 = faker.helpers.fromRegExp(/1[3-9][0-9]{9}/);
@ -1179,12 +1187,12 @@ test.describe('收银-开单&结算', () => {
});
test('开卡-使用卡金和赠金-充值卡金', async ({
page,
homeNavigation,
createCustomer,
customerPage,
numberInput,
}) => {
page,
homeNavigation,
createCustomer,
customerPage,
numberInput,
}) => {
const c = createCustomer;
const username = c.username;
const phone = c.phone;
@ -1256,7 +1264,7 @@ test.describe('收银-开单&结算', () => {
await page.getByText('卡金').nth(4).click();
await page.getByRole('button', { name: '增加收款' }).click();
// 输入金额
await numberInput.setCommonValue(100);
await numberInput.setValue('common', 100);
await numberInput.confirmValue();
// 选择赠送金支付
await page.getByText('赠金').nth(2).click();

View File

@ -1041,11 +1041,6 @@ test.describe('顾客详情', () => {
test('操作套餐', async ({ page, homeNavigation, customerPage, createCustomer }) => {
let billNo: string;
const date = new Date();
const currentYear = date.getFullYear();
const currentMonth = date.getMonth() + 1;
const currentDay = date.getDate();
const dayStr = currentDay >= 10 ? `${currentDay}` : `0${currentDay}`;
// 套餐的名称
const $setMeal = page.getByText('护理修护全套');
// 套餐的所有项目
@ -1071,14 +1066,24 @@ test.describe('顾客详情', () => {
// 结算
const [response] = await Promise.all([
page.waitForResponse(async res => {
return res.url().includes('/bill') && (await res.json()).code === 'SUCCESS';
}),
page.waitForResponse(res => res.url().includes('/bill') && res.status() === 200 && res.request().method() === 'POST'),
page.getByRole('button', { name: /跳\s过/ }).click(),
]);
const responseBody = await response.json();
billNo = responseBody?.content?.billNo;
expect(billNo).not.toBeNull();
billNo = (await response.json())?.content?.billNo;
await page.waitForResponse(async res => {
if (
res.url().includes('/bill_status') &&
res.status() === 200 &&
res.request().method() === 'GET'
) {
const responseBody = await res.json(); // 等待解析 JSON
return responseBody?.content?.status === 'SETTLED';
}
return false;
});
await page.getByRole('button', { name: '不寄存' }).click();
});
@ -1103,19 +1108,17 @@ test.describe('顾客详情', () => {
});
await test.step('冻结有效期-解冻有效期', async () => {
// 冻结日期
const freezeStr = `${currentYear}-${currentMonth}-${dayStr}冻结`;
// 冻结有效期
await $$treatCard.first().locator('svg').last().click();
await page.getByText('冻结有效期').click();
await Promise.all([page.getByRole('button', { name: /确\s认/ }).click(), page.waitForLoadState()]);
await expect(page.locator('.ant-message')).toContainText('修改成功');
await expect($$treatCard.first().locator('.deadline_row span')).toContainText(freezeStr);
await expect($$treatCard.first().locator('.deadline_row span')).toContainText('冻结');
// 解冻有效期
await expect(async () => {
await $$treatCard.first().locator('svg').last().click();
await page.getByText('解冻有效期').click({ timeout: 2000 });
await expect($$treatCard.first().locator('.deadline_row span')).not.toContainText(freezeStr, {
await expect($$treatCard.first().locator('.deadline_row span')).not.toContainText('冻结', {
timeout: 2000,
});
}).toPass();
@ -1417,35 +1420,37 @@ test.describe('顾客详情', () => {
const currentMonth = date.getMonth() + 1;
const currentDay = date.getDate();
/**@type {string} */
let billNo = '';
let billNo: string;
await test.step('开单拿取单号', async () => {
await homeNavigation.gotoModule('收银');
await page.getByRole('button', { name: /开\s单/ }).click();
await page.getByPlaceholder('姓名(拼音首字)、手机号、档案号搜索').fill(customer.phone);
await page.getByText('搜索', { exact: true }).click();
await page.locator('.member_list_li').filter({ hasText: customer.phone }).click();
await customerPage.searchCustomer(customer.phone);
await customerPage.selectSearchCustomer(customer.phone);
await page.locator('.list_box .project_list').first().click();
await page
.locator('.pay_btn')
.filter({ hasText: /^结\s算$/ })
.click();
//取消推送消息提醒
await page.locator('div.pay_btn').filter({ hasText: /^结\s算$/ }).click();
await page.getByLabel('推送消费提醒').uncheck();
//取消结算签字
await page.getByLabel('结算签字').uncheck();
await page.locator('.paymentInfoItem').first().click();
// 结算
const [response] = await Promise.all([
page.waitForResponse(async res => {
return res.url().includes('/bill') && (await res.json()).code === 'SUCCESS';
}),
page.getByRole('button', { name: /^结\s算$/ }).click(),
page.waitForResponse(res => res.url().includes('/bill') && res.status() === 200 && res.request().method() === 'POST'),
page.getByRole('button', { name: /结\s算/ }).click(),
]);
billNo = (await response.json())?.content?.billNo;
expect(billNo).not.toBeNull();
await page.waitForResponse(async res => {
if (
res.url().includes('/bill_status') &&
res.status() === 200 &&
res.request().method() === 'GET'
) {
const responseBody = await res.json(); // 等待解析 JSON
return responseBody?.content?.status === 'SETTLED';
}
return false;
});
});
await test.step('进入顾客详情页面', async () => {
@ -1482,36 +1487,32 @@ test.describe('顾客详情', () => {
});
// 今年所有月份的动态
const $$dynamic = page.locator('.calendar_year_list', { hasText: `${currentYear}` }).locator('.dynamic_list');
const $$dynamic = page.locator('div.calendar_year_list', { hasText: `${currentYear}` }).locator('.dynamic_list');
// 当前月份的动态
const $currentForMonthDynamic = $$dynamic.filter({
has: page.locator('.month_box', { hasText: `${currentMonth}` }),
const $currentMonthDynamic = $$dynamic.filter({
has: page.locator('.month_box').getByText(`${currentMonth}`, { exact: true }),
});
await test.step('查看月动态-缩略', async () => {
await page
.locator('div')
.filter({ hasText: /^日月$/ })
.getByRole('switch')
.click();
await page.locator('div').filter({ hasText: /^日月$/ }).getByRole('switch').click();
await expect(page.getByText('月', { exact: true })).toHaveClass('selected_btn');
await expect(async () => {
await $currentForMonthDynamic.click();
await $currentMonthDynamic.click();
await expect(page.getByRole('tabpanel').getByText(billNo)).toBeVisible();
}).toPass();
await page.locator('.action_detail > .container > .m_sliding_menu > .box > .top > .anticon').click();
await page.locator('div.action_detail div.m_sliding_menu div.top i.anticon').click();
});
await test.step('查看月动态-日期', async () => {
await page
.locator('div')
.filter({ hasText: /^缩略日期$/ })
.getByRole('switch')
.click();
await page.locator('div').filter({ hasText: /^缩略日期$/ }).getByRole('switch').click();
await expect(page.getByText('日期', { exact: true })).toHaveClass('selected_btn');
await $currentForMonthDynamic.filter({ hasText: `${currentDay}` }).click();
const $$dateList = $currentMonthDynamic.locator('div.date_list');
await expect(async () => {
await $$dateList.getByText(`${currentDay}`, { exact: true }).click();
await expect(page.getByRole('tabpanel').getByText(billNo)).toBeVisible();
}).toPass();
await expect(page.getByRole('tabpanel').getByText(billNo)).toBeVisible();
});
});
@ -2024,7 +2025,7 @@ test.describe('顾客分配', () => {
const index = titleList.findIndex(text => text === '现金总额');
// 拿取前三个顾客的现金总额
let amountArray: number[];
let amountArray: number[] = new Array(3);
for (let i = 0; i < 3; i++) {
const amountStr = await amountList.nth(i).locator('td').nth(index).innerText();
const amount = convertAmountText(amountStr).amount;
@ -2218,7 +2219,7 @@ test.describe('顾客分配', () => {
test.describe('顾客分析', () => {
test('查看顾客项目分析', async ({ page, homeNavigation, createCustomers, customerPage, numberInput }) => {
let customers: Customer[];
let customers: Customer[] = new Array(2);
await test.step('创建两个顾客', async () => {
customers = await createCustomers(2);
});
@ -2226,38 +2227,32 @@ test.describe('顾客分析', () => {
const ca = customers[0];
const cb = customers[1];
// 获取姓名、手机号、档案号
const usernameA = ca.username;
const phoneA = ca.phone;
const usernameB = cb.username;
const phoneB = cb.phone;
const ProjectA1 = ProjectName.Projects.Projects_1;
const ProjectA1Quantity = 2;
const ProjectA2 = ProjectName.Projects.Projects_239;
const ProjectA2Quantity = 1;
const ProjectA3 = ProjectName.Projects.Projects_2;
const ProjectA3Quantity = 2;
const ProjectA12Quantity = 1;
const projects_a1 = { num: '100012', name: '雪肌晶纯护理', price: 300 };
const projects_a1_quantity = 2;
const projects_a2 = { num: '100258', name: '出水芙蓉SPA水疗', price: 380 };
const projects_a2_quantity = 1;
const projects_a3 = { num: '100013', name: '净透驻氧护理', price: 380 };
const projects_a3_quantity = 2;
const projects_a12_quantity = 1;
// 顾客A
await test.step('顾客A', async () => {
await homeNavigation.gotoModule('收银');
await page.getByRole('button', { name: /开\s单/ }).click();
await customerPage.searchCustomer(phoneA);
await customerPage.selectSearchCustomer(usernameA);
await customerPage.searchCustomer(ca.phone);
await customerPage.selectSearchCustomer(ca.username);
// 购买项目1-普通2次
await page.getByText(ProjectA1.num).click();
await page.getByText(projects_a1.num).click();
await page.locator('.edit_txt div:nth-child(2)').first().click();
await numberInput.setValue(ProjectA1Quantity);
await numberInput.setValue(projects_a1_quantity);
await numberInput.confirmValue();
// 点击面部
await page.getByText('面部').click();
// 购买项目2-普通1次
await page.getByText(ProjectA2.num).click();
await page.getByText(projects_a2.num).click();
await page.locator('.type_btn').first().click();
await page.locator('.type_item', { hasText: '普通' }).click();
@ -2265,21 +2260,21 @@ test.describe('顾客分析', () => {
await page.getByText('护理', { exact: true }).click();
// 购买项目3-赠送3次
await page.getByText(ProjectA3.num).click();
await page.getByText(projects_a3.num).click();
await page.locator('.edit_txt div:nth-child(2)').first().click();
await numberInput.setValue(ProjectA3Quantity);
await numberInput.setValue(projects_a3_quantity);
await numberInput.confirmValue();
await page.locator('.type_btn').first().click();
await page.locator('.type_item', { hasText: '赠送' }).click();
// 选择组合项目2-项目1普通
await page.getByText(ProjectA1.num).first().click();
await page.getByText(projects_a1.num).first().click();
await page.locator('.add_btn', { hasText: '设置' }).last().click();
await page.getByRole('textbox').fill(ProjectA2.num);
await page.getByRole('textbox').fill(projects_a2.num);
await page.getByRole('button', { name: /搜\s索/ }).click();
await expect(async () => {
await page.getByLabel(ProjectA2.name).uncheck();
await page.getByLabel(ProjectA2.name).check();
await page.getByLabel(projects_a2.name).uncheck();
await page.getByLabel(projects_a2.name).check();
await page.locator('.menu-item-dot', { hasText: '2' }).first().waitFor({ timeout: 2000 });
}).toPass();
await page.getByRole('button', { name: '确定选择' }).click();
@ -2299,50 +2294,52 @@ test.describe('顾客分析', () => {
await page.getByRole('button', { name: /^结\s算$/ }).click();
});
const ProjectB3 = ProjectName.Projects.Projects_2;
const ProjectB3Quantity = 20;
const ProjectB4 = ProjectName.Projects.Projects_661;
const ProjectB4Quantity = 30;
const ProjectB5 = ProjectName.Projects.Projects_676;
const ProjectB5Quantity = 9;
const ProjectB1 = ProjectName.Projects.Projects_1;
const ProjectB2 = ProjectName.Projects.Projects_239;
const ProjectB12Quantity = 2;
const ProjectB34Quantity = 3;
const project_b3 = { num: '100013', name: '净透驻氧护理', price: 380 };
const project_b3_quantity = 20;
const project_b4 = { num: '100719', name: '艾灸', price: 0 };
const project_b4_quantity = 30;
const project_b5 = { num: '100735', name: '脱毛', price: 88 };
const project_b5_quantity = 9;
const project_b1 = { num: '100012', name: '雪肌晶纯护理', price: 300 };
const project_b2 = { num: '100258', name: '出水芙蓉SPA水疗', Price: 380 };
const project_b12_quantity = 2;
const project_b34_quantity = 3;
await test.step('顾客B', async () => {
//顾客B
await page.reload();
await page.getByRole('button', { name: /开\s单/ }).click();
await customerPage.searchCustomer(phoneB);
await customerPage.selectSearchCustomer(usernameB);
await customerPage.searchCustomer(cb.phone);
await customerPage.selectSearchCustomer(cb.username);
// 购买项目3-普通20次
await page.getByText(ProjectB3.num).click();
await page.getByText(project_b3.num).click();
await page.locator('.edit_txt div:nth-child(2)').first().click();
await numberInput.setValue(ProjectB3Quantity);
await numberInput.setValue(project_b3_quantity);
await numberInput.confirmValue();
// 点击身体
await page.locator('.type_tab_item', { hasText: '身体' }).click();
// 购买项目4-普通30次
await page.getByText(ProjectB4.num).click();
await page.getByText(project_b4.num).click();
await page.locator('.edit_txt div:nth-child(2)').first().click();
await numberInput.setValue(ProjectB4Quantity);
await numberInput.setValue(project_b4_quantity);
await numberInput.confirmValue();
// 购买项目59次
await page.getByText(ProjectB5.num).click();
await page.getByText(project_b5.num).click();
await page.locator('.edit_txt div:nth-child(2)').first().click();
await numberInput.setValue(ProjectB5Quantity);
await numberInput.setValue(project_b5_quantity);
await numberInput.confirmValue();
// 购买项目B1-B2混合2次
await page.locator('.type_tab_item', { hasText: '护理' }).click();
await page.getByText(ProjectB1.num).first().click();
await page.getByText(project_b1.num).first().click();
await page.locator('.add_btn', { hasText: '设置' }).last().click();
await page.getByRole('textbox').fill(ProjectB2.num);
await page.getByRole('textbox').fill(project_b2.num);
await page.getByRole('button', { name: /搜\s索/ }).click();
await expect(async () => {
await page.locator('.list_box .ant-checkbox-input').click();
@ -2351,13 +2348,13 @@ test.describe('顾客分析', () => {
await page.getByRole('button', { name: '确定选择' }).click();
// 点击选择数量
await page.locator('.edit_txt div:nth-child(2)').first().click();
await numberInput.setValue(ProjectB12Quantity);
await numberInput.setValue(project_b12_quantity);
await numberInput.confirmValue();
// 购买项目B4-B4混合3次
await page.getByText(ProjectB3.num).first().click();
await page.getByText(project_b3.num).first().click();
await page.locator('.add_btn', { hasText: '设置' }).last().click();
await page.getByRole('textbox').fill(ProjectB4.num);
await page.getByRole('textbox').fill(project_b4.num);
await page.getByRole('button', { name: '搜 索' }).click();
await expect(async () => {
await page.locator('.list_box .ant-checkbox-input').click();
@ -2366,7 +2363,7 @@ test.describe('顾客分析', () => {
await page.getByRole('button', { name: '确定选择' }).click();
// 点击选择数量
await page.locator('.edit_txt div:nth-child(2)').first().click();
await numberInput.setValue(ProjectB34Quantity);
await numberInput.setValue(project_b34_quantity);
await numberInput.confirmValue();
await page.locator('.commodity_item').last().click();
@ -2384,15 +2381,13 @@ test.describe('顾客分析', () => {
await page.reload();
await homeNavigation.gotoModule('顾客');
await customerPage.gotoSubPage('顾客分析');
await page
.locator('div')
.filter({ hasText: /^排序$/ })
.nth(1)
.click();
await page.getByRole('menuitem', { name: '上次到店时间从近到远' }).click();
await page.locator('.loading_container').waitFor({ state: 'hidden' });
const Customer1A = page.locator('.m-table__body-wrapper tbody tr', { hasText: phoneA });
const Customer1B = page.locator('.m-table__body-wrapper tbody tr', { hasText: phoneB });
await page.getByText('排序', { exact: true }).click();
await Promise.all([
page.waitForResponse(res => res.url().includes('analysis') && res.ok()),
page.getByRole('menuitem', { name: '上次到店时间从近到远' }).click(),
]);
const Customer1A = page.locator('.m-table__body-wrapper tbody tr', { hasText: ca.phone });
const Customer1B = page.locator('.m-table__body-wrapper tbody tr', { hasText: cb.phone });
// 1 查找到顾客A 顾客B
await expect(Customer1A).toBeVisible();
await expect(Customer1B).toBeVisible();
@ -2405,19 +2400,19 @@ test.describe('顾客分析', () => {
.locator('.m-table__body-wrapper tbody tr')
.allInnerTexts()
.then(async text => {
return text.findIndex(item => item.includes(phoneB));
return text.findIndex(item => item.includes(cb.phone));
});
// 查看各单元格内容
// 护理内容
await expect.soft(allTr.nth(nowRow).locator('td').nth(2)).toContainText(`${ProjectB3Quantity - 1}`);
await expect.soft(allTr.nth(nowRow).locator('td').nth(2)).toContainText(`${project_b3_quantity - 1}`);
// 身体内容
await expect
.soft(allTr.nth(nowRow).locator('td').nth(5))
.toContainText(`${ProjectB12Quantity + ProjectB34Quantity}`);
.toContainText(`${project_b12_quantity + project_b34_quantity}`);
// 组合内容
await expect(allTr.nth(nowRow).locator('td').nth(5)).toContainText(
`${ProjectB12Quantity + ProjectB34Quantity}`,
`${project_b12_quantity + project_b34_quantity}`,
);
const $productName = page.locator('.treat_box .treat_card:nth-child(1) .name_row .auto_desc');
@ -2429,67 +2424,67 @@ test.describe('顾客分析', () => {
// 点击护理
await allTr.nth(nowRow).locator('td').nth(2).click();
// 项目名称
await expect.soft($productName).toContainText(ProjectB3.name);
await expect.soft($productName).toContainText(project_b3.name);
// 剩余次数
await expect($productResidue).toContainText(`${ProjectB3Quantity - 1}`);
await expect($productResidue).toContainText(`${project_b34_quantity - 1}`);
// 关闭弹窗
await page.locator('.close_icon').last().click();
// 点击身体
await allTr.nth(nowRow).locator('td').nth(4).click();
// 项目名称
await expect.soft($productName).toContainText(ProjectB4.name);
await expect.soft($productName).toContainText(project_b4.name);
// 剩余次数
await expect.soft($productResidue).toContainText(`${ProjectB4Quantity}`);
await expect.soft($productResidue).toContainText(`${project_b4_quantity}`);
// 项目名称
await expect.soft($productName2).toContainText(ProjectB5.name);
await expect.soft($productName2).toContainText(project_b5.name);
// 剩余次数
await expect.soft($productResidue2).toContainText(`${ProjectB5Quantity}`);
await expect.soft($productResidue2).toContainText(`${project_b5_quantity}`);
// 关闭弹窗
await page.locator('.close_icon').last().click();
// 点击组合
await allTr.nth(nowRow).locator('td').nth(5).click();
// 项目名称
await expect.soft($productName).toContainText(ProjectB1.name + '' + ProjectB2.name);
await expect.soft($productName).toContainText(project_b1.name + '' + project_b2.name);
// 剩余次数
await expect.soft($productResidue).toContainText(`${ProjectB12Quantity}`);
await expect.soft($productResidue).toContainText(`${project_b12_quantity}`);
// 项目名称
await expect.soft($productName2).toContainText(ProjectB3.name + '' + ProjectB4.name);
await expect.soft($productName2).toContainText(project_b3.name + '' + project_b4.name);
// 剩余次数
await expect($productResidue2).toContainText(`${ProjectB34Quantity}`);
await expect($productResidue2).toContainText(`${project_b34_quantity}`);
// 关闭弹窗
await page.locator('.close_icon').last().click();
// 查看顾客A的各单元格内容
const allTrA = page.locator('.m-table__body-wrapper tbody tr');
const nowRowA = await allTrA.allInnerTexts().then(async text => {
return text.findIndex(item => item.includes(phoneA));
return text.findIndex(item => item.includes(ca.phone));
});
// 点击护理
await allTrA.nth(nowRowA).locator('td').nth(2).click();
// 项目名称
await expect.soft($productName).toContainText(ProjectA1.name);
await expect.soft($productName).toContainText(projects_a1.name);
// 剩余次数
await expect($productResidue).toContainText(`${ProjectA1Quantity - 1}`);
await expect($productResidue).toContainText(`${projects_a1_quantity - 1}`);
// 关闭弹窗
await page.locator('.close_icon').last().click();
// 点击面部
await allTrA.nth(nowRowA).locator('td').nth(3).click();
// 项目名称
await expect.soft($productName).toContainText(ProjectA2.name);
await expect.soft($productName).toContainText(projects_a2.name);
// 剩余次数
await expect($productResidue).toContainText(`${ProjectA2Quantity}`);
await expect($productResidue).toContainText(`${projects_a2_quantity}`);
// 关闭弹窗
await page.locator('.close_icon').last().click();
// 点击组合
await allTrA.nth(nowRowA).locator('td').nth(5).click();
// 项目名称
await expect.soft($productName).toContainText(ProjectA1.name + '' + ProjectA2.name);
await expect.soft($productName).toContainText(projects_a1.name + '' + projects_a2.name);
// 剩余次数
await expect($productResidue).toContainText(`${ProjectA12Quantity}`);
await expect($productResidue).toContainText(`${projects_a12_quantity}`);
// 关闭弹窗
await page.locator('.close_icon').last().click();
@ -2505,18 +2500,18 @@ test.describe('顾客分析', () => {
// 护理内容
const allTrAA = page.locator('.m-table__body-wrapper tbody tr');
const nowRowAA = await allTrAA.allInnerTexts().then(async text => {
return text.findIndex(item => item.includes(phoneA));
return text.findIndex(item => item.includes(ca.phone));
});
// 点击护理
await allTrAA.nth(nowRowAA).locator('td').nth(2).click();
// 项目名称
await expect.soft($productName).toContainText(ProjectA1.name);
await expect.soft($productName).toContainText(projects_a1.name);
// 剩余次数
await expect.soft($productResidue).toContainText(`${ProjectA1Quantity - 1}`);
await expect.soft($productResidue).toContainText(`${projects_a1_quantity - 1}`);
// 项目名称
await expect.soft($productName2).toContainText(ProjectA3.name);
await expect.soft($productName2).toContainText(projects_a3.name);
// 剩余次数
await expect($productResidue2).toContainText(`${ProjectA3Quantity}`);
await expect($productResidue2).toContainText(`${projects_a3_quantity}`);
// 关闭弹窗
await page.locator('.close_icon').last().click();

View File

@ -280,7 +280,7 @@ test.describe('业绩明细表', () => {
test.describe('项目销耗存表', () => {
test('数据校验', async ({ page, createCustomers, homeNavigation, reportPage, customerPage, numberInput }) => {
let customers: Customer[];
let customers: Customer[] = new Array(2);
await test.step('创建两个顾客', async () => {
customers = await createCustomers(2);
});

View File

@ -1,3 +1,5 @@
import { Page } from "playwright";
// 解析二维码
const decodeImage = require('jimp').read;
const { readFile, unlinkSync } = require('fs');
@ -95,17 +97,31 @@ export function CleanPunctuation(str) {
/**
*
* @param {import('@playwright/test').Page} page
* @param {string[]} apiArray
* @param {Page} page - Playwright Page
* @param {string[]} apiArray -
* @returns Promise<Response[]>
*/
export const waitSpecifyApiLoad = (page, apiArray) => {
if (apiArray === undefined || apiArray.length === 0) {
return Promise.resolve([]);
export const waitSpecifyApiLoad = async (page: Page, apiArray: string[]): Promise<Response[]> => {
// 输入参数检查
if (!page || !Array.isArray(apiArray) || apiArray.length === 0 || !apiArray.every(item => typeof item === 'string')) {
return [];
}
return Promise.all(
apiArray.map(api => page.waitForResponse(res => res.url().includes(api) && res.status() === 200)),
// 去重
const uniqueApiArray = Array.from(new Set(apiArray));
// 精确匹配 URL
const urlMatchers = uniqueApiArray.map(api => new RegExp(`^.*${api}$`));
// 等待所有接口加载完成
const responses = await Promise.allSettled(
urlMatchers.map(matcher => page.waitForResponse(res => matcher.test(res.url()) && res.status() === 200))
);
// 处理结果
return responses
.filter(response => response.status === 'fulfilled')
.map(response => (response as unknown as PromiseFulfilledResult<Response>).value);
};
/**