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 { NumberInput } from './numberInput';
import { PopupContent } from '@/pages/components/popupContent'; import { PopupContent } from './popupContent';
export { NumberInput, PopupContent }; export { NumberInput, PopupContent };

View File

@ -3,12 +3,13 @@ import { Locator, Page } from '@playwright/test';
export class NumberInput { export class NumberInput {
private readonly page: Page; private readonly page: Page;
private readonly popupLocator: Locator; private readonly popupLocator: Locator;
private readonly inputLocator: Locator; private readonly inputLocators: { [key: string]: Locator } = {};
private readonly confirmButtonLocator: Locator; private readonly confirmButtonLocator: Locator;
private readonly delButtonLocator: Locator; private readonly delButtonLocator: Locator;
private readonly delAllButtonLocator: Locator; private readonly delAllButtonLocator: Locator;
private readonly pointInputLocator: Locator; // private readonly inputLocator: Locator;
private readonly commonInputLocator: Locator; // private readonly pointInputLocator: Locator;
// private readonly commonInputLocator: Locator;
/** /**
* *
@ -17,41 +18,60 @@ export class NumberInput {
constructor(page: Page) { constructor(page: Page) {
this.page = page; this.page = page;
this.popupLocator = this.page.locator('div.popup_content'); this.popupLocator = this.page.locator('div.popup_content');
this.commonInputLocator = this.popupLocator.getByPlaceholder(''); this.inputLocators = {
this.inputLocator = this.popupLocator.getByPlaceholder('请输入内容'); common: this.popupLocator.getByPlaceholder(''),
this.pointInputLocator = 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.confirmButtonLocator = this.popupLocator.locator('button.sure');
this.delButtonLocator = this.popupLocator.locator('button.del'); this.delButtonLocator = this.popupLocator.locator('button.del');
this.delAllButtonLocator = this.popupLocator.locator('button.delAll'); 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> { // async setCommonValue(value: number): Promise<void> {
await this.commonInputLocator.fill(value.toString()); // await this.commonInputLocator.fill(value.toString());
} // }
/** /**
* *
*/ */
async setValue(value: number): Promise<void> { // async setValue(value: number): Promise<void> {
await this.inputLocator.fill(value.toString()); // await this.inputLocator.fill(value.toString());
} // }
/**
*
*/
async setString(value: string): Promise<void> {
await this.inputLocator.fill(value);
}
/** /**
* *
* @param value * @param value
*/ */
async setPointValue(value: number): Promise<void> { // async setPointValue(value: number): Promise<void> {
await this.pointInputLocator.fill(value.toString()); // 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) => { 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`); throw new Error(`${subPageName} is not a valid sub page name`);
} }
const $dropDown = this.page
.locator('.top_tab .tab_item', { const $dropDown = this.page.locator('.top_tab .tab_item', { hasText: '顾客分析' }).locator('.ant-dropdown-link');
hasText: '顾客分析',
})
.locator('.ant-dropdown-link');
await $dropDown.click(); await $dropDown.click();
await this.page.getByRole('menuitem', { name: subPageName }).click(); await this.page.getByRole('menuitem', { name: subPageName }).click();

View File

@ -8,7 +8,12 @@ export class CustomerDetailsPage {
*/ */
constructor(page: Page) { constructor(page: Page) {
this.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) => { 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`); throw new Error(`${subPageName} is not in the subPages list`);
} }
try {
// 获取子页面元素并点击
const $subPage = this.page.getByRole('tab', { name: subPageName }); const $subPage = this.page.getByRole('tab', { name: subPageName });
await $subPage.click(); await $subPage.click();
await expect($subPage).toHaveClass(/active/); await expect($subPage).toHaveClass(/active/);
await this.page.waitForLoadState(); } 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) => { gotoSubPage = async (subPageName: string) => {
// 输入验证
if (!subPageName || typeof subPageName !== 'string' || subPageName.trim() === '') {
throw new Error('子页面名称不能为空或无效');
}
const subPage = this.subPages.find(e => e.name === subPageName); const subPage = this.subPages.find(e => e.name === subPageName);
if (!subPage) { if (!subPage) {
console.error(`子页面 ${subPageName} 不存在`);
throw new Error(`子页面 ${subPageName} 不存在`); throw new Error(`子页面 ${subPageName} 不存在`);
} }
@ -69,18 +75,24 @@ export class CustomerPage {
await $subPageTab.waitFor(); await $subPageTab.waitFor();
try {
const classAttribute = await $subPageTab.getAttribute('class', { timeout: 5000 }); const classAttribute = await $subPageTab.getAttribute('class', { timeout: 5000 });
if (classAttribute && classAttribute.includes('active')) { if (classAttribute && classAttribute.includes('active')) {
return; return;
} }
} catch (error) {
console.error(`获取子页面 ${subPageName} 的 class 属性超时`);
throw new Error(`获取子页面 ${subPageName} 的 class 属性超时`);
}
await Promise.all([ try {
expect(async () => {
await $subPageTab.click(); await $subPageTab.click();
await expect($subPageTab).toHaveClass(/active/); await expect($subPageTab).toHaveClass(/active/, { timeout: 5000 });
}).toPass(), await waitSpecifyApiLoad(this.page, subPage.url);
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 { type Locator, type Page } from '@playwright/test';
import { waitSpecifyApiLoad } from '@/utils/utils.js'; import { waitSpecifyApiLoad } from '@/utils/utils';
export class MarketingInviteGuestsPage { export class MarketingInviteGuestsPage {
page: Page; page: Page;

View File

@ -280,9 +280,17 @@ test.describe('挂单', () => {
await page.getByText('已过期 / 删除单据').click(); await page.getByText('已过期 / 删除单据').click();
await page.getByText('警告:已过期服务无法取单结算,请至收银台处理').waitFor(); await page.getByText('警告:已过期服务无法取单结算,请至收银台处理').waitFor();
await $slidingMenu.locator('div.item_box').first().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 $delete.click();
await page.getByPlaceholder('请输入1-100个字符备注内容').click(); await page.getByPlaceholder('请输入1-100个字符备注内容').click();
await page.getByPlaceholder('请输入1-100个字符备注内容').fill('测试备注'); await page.getByPlaceholder('请输入1-100个字符备注内容').fill('测试备注');
@ -1256,7 +1264,7 @@ test.describe('收银-开单&结算', () => {
await page.getByText('卡金').nth(4).click(); await page.getByText('卡金').nth(4).click();
await page.getByRole('button', { name: '增加收款' }).click(); await page.getByRole('button', { name: '增加收款' }).click();
// 输入金额 // 输入金额
await numberInput.setCommonValue(100); await numberInput.setValue('common', 100);
await numberInput.confirmValue(); await numberInput.confirmValue();
// 选择赠送金支付 // 选择赠送金支付
await page.getByText('赠金').nth(2).click(); await page.getByText('赠金').nth(2).click();

View File

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

View File

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

View File

@ -1,3 +1,5 @@
import { Page } from "playwright";
// 解析二维码 // 解析二维码
const decodeImage = require('jimp').read; const decodeImage = require('jimp').read;
const { readFile, unlinkSync } = require('fs'); const { readFile, unlinkSync } = require('fs');
@ -95,17 +97,31 @@ export function CleanPunctuation(str) {
/** /**
* *
* @param {import('@playwright/test').Page} page * @param {Page} page - Playwright Page
* @param {string[]} apiArray * @param {string[]} apiArray -
* @returns Promise<Response[]> * @returns Promise<Response[]>
*/ */
export const waitSpecifyApiLoad = (page, apiArray) => { export const waitSpecifyApiLoad = async (page: Page, apiArray: string[]): Promise<Response[]> => {
if (apiArray === undefined || apiArray.length === 0) { // 输入参数检查
return Promise.resolve([]); 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);
}; };
/** /**