Compare commits

..

No commits in common. "135718a8c39c8189d4d60a58fb7726e4490ca1e0" and "c7f923d8348914c2510ba5129166ee392d7f0278" have entirely different histories.

10 changed files with 216 additions and 285 deletions

View File

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

View File

@ -3,13 +3,12 @@ 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 inputLocators: { [key: string]: Locator } = {}; private readonly inputLocator: 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 inputLocator: Locator; private readonly pointInputLocator: Locator;
// private readonly pointInputLocator: Locator; private readonly commonInputLocator: Locator;
// private readonly commonInputLocator: Locator;
/** /**
* *
@ -18,60 +17,41 @@ 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.inputLocators = { this.commonInputLocator = this.popupLocator.getByPlaceholder('');
common: this.popupLocator.getByPlaceholder(''), this.inputLocator = this.popupLocator.getByPlaceholder('请输入内容');
normal: this.popupLocator.getByPlaceholder('请输入内容'), this.pointInputLocator = 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> { async setCommonValue(value: number): Promise<void> {
const locator = this.inputLocators[type]; await this.commonInputLocator.fill(value.toString());
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 setValue(value: number): Promise<void> { async setValue(value: number): Promise<void> {
// await this.inputLocator.fill(value.toString()); await this.inputLocator.fill(value.toString());
// } }
/**
*
* @param value
*/
// async setPointValue(value: number): Promise<void> {
// await this.pointInputLocator.fill(value.toString());
// }
/** /**
* *
*/ */
async setString(value: string): Promise<void> { async setString(value: string): Promise<void> {
// await this.inputLocator.fill(value); await this.inputLocator.fill(value);
await this.inputLocators.normal.fill(value); }
/**
*
* @param value
*/
async setPointValue(value: number): Promise<void> {
await this.pointInputLocator.fill(value.toString());
} }
/** /**

View File

@ -14,23 +14,20 @@ 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
const $dropDown = this.page.locator('.top_tab .tab_item', { hasText: '顾客分析' }).locator('.ant-dropdown-link'); .locator('.top_tab .tab_item', {
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,12 +8,7 @@ export class CustomerDetailsPage {
*/ */
constructor(page: Page) { constructor(page: Page) {
this.page = page; this.page = page;
this.subPages = [ this.subPages = [{ name: '基本资料' }, { name: '流水' }, { name: '动态' }, { name: '日志' }];
{ name: '基本资料' },
{ name: '流水' },
{ name: '动态' },
{ name: '日志' }
];
} }
/** /**
@ -25,22 +20,12 @@ 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`);
} }
const $subPage = this.page.getByRole('tab', { name: subPageName });
try { await $subPage.click();
// 获取子页面元素并点击 await expect($subPage).toHaveClass(/active/);
const $subPage = this.page.getByRole('tab', { name: subPageName }); await this.page.waitForLoadState();
await $subPage.click();
await expect($subPage).toHaveClass(/active/);
} catch (error) {
// 捕获并抛出更明确的错误信息
throw new Error(`Failed to navigate to ${subPageName}: ${error.message}`);
}
}; };
} }

View File

@ -60,14 +60,8 @@ 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} 不存在`);
} }
@ -75,24 +69,18 @@ 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 属性超时`);
}
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 加载失败`);
} }
await Promise.all([
expect(async () => {
await $subPageTab.click();
await expect($subPageTab).toHaveClass(/active/);
}).toPass(),
waitSpecifyApiLoad(this.page, subPage.url),
]);
}; };
/** /**

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'; import { waitSpecifyApiLoad } from '@/utils/utils.js';
export class MarketingInviteGuestsPage { export class MarketingInviteGuestsPage {
page: Page; page: Page;

View File

@ -280,17 +280,9 @@ 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( await expect($slidingMenu.locator('div.item_box').first().getByRole('button', { name: /^取\s单$/ })).not.toBeVisible();
$slidingMenu
.locator('div.item_box')
.first()
.getByRole('button', { name: /^取\s单$/ }),
).not.toBeVisible();
const $delete = $slidingMenu const $delete = $slidingMenu.locator('div.item_box').first().locator('.comment > div:nth-child(2) > .touchIcon');
.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('测试备注');
@ -590,13 +582,13 @@ test.describe('收银-房态', () => {
}); });
test('占用床位', async ({ test('占用床位', async ({
page, page,
homeNavigation, homeNavigation,
createCustomer, createCustomer,
cashierRoomPage, cashierRoomPage,
customerPage, customerPage,
appointmentPage, appointmentPage,
}) => { }) => {
const customer = createCustomer; const customer = createCustomer;
const employee = Employees.FirstShop.Employee_6; const employee = Employees.FirstShop.Employee_6;
@ -880,13 +872,13 @@ test.describe('收银-房态', () => {
test.describe('收银-开单&结算', () => { test.describe('收银-开单&结算', () => {
test('开单-反结算-撤单', async ({ test('开单-反结算-撤单', async ({
page, page,
homeNavigation, homeNavigation,
createCustomer, createCustomer,
customerPage, customerPage,
wasteBookBusinessRecordPage, wasteBookBusinessRecordPage,
numberInput, numberInput,
}) => { }) => {
// 定义一个随机单号 // 定义一个随机单号
const randomBillNo1 = faker.helpers.fromRegExp(/1[3-9][0-9]{8}/); const randomBillNo1 = faker.helpers.fromRegExp(/1[3-9][0-9]{8}/);
const randomBillNo2 = faker.helpers.fromRegExp(/1[3-9][0-9]{9}/); const randomBillNo2 = faker.helpers.fromRegExp(/1[3-9][0-9]{9}/);
@ -1187,12 +1179,12 @@ test.describe('收银-开单&结算', () => {
}); });
test('开卡-使用卡金和赠金-充值卡金', async ({ test('开卡-使用卡金和赠金-充值卡金', async ({
page, page,
homeNavigation, homeNavigation,
createCustomer, createCustomer,
customerPage, customerPage,
numberInput, numberInput,
}) => { }) => {
const c = createCustomer; const c = createCustomer;
const username = c.username; const username = c.username;
const phone = c.phone; const phone = c.phone;
@ -1264,7 +1256,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.setValue('common', 100); await numberInput.setCommonValue(100);
await numberInput.confirmValue(); await numberInput.confirmValue();
// 选择赠送金支付 // 选择赠送金支付
await page.getByText('赠金').nth(2).click(); await page.getByText('赠金').nth(2).click();

View File

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

View File

@ -1,5 +1,3 @@
import { Page } from "playwright";
// 解析二维码 // 解析二维码
const decodeImage = require('jimp').read; const decodeImage = require('jimp').read;
const { readFile, unlinkSync } = require('fs'); const { readFile, unlinkSync } = require('fs');
@ -97,31 +95,17 @@ export function CleanPunctuation(str) {
/** /**
* 等待指定接口加载完成 * 等待指定接口加载完成
* @param {Page} page - Playwright Page 对象 * @param {import('@playwright/test').Page} page
* @param {string[]} apiArray - 接口名称数组 * @param {string[]} apiArray 接口名称数组
* @returns Promise<Response[]> * @returns Promise<Response[]>
*/ */
export const waitSpecifyApiLoad = async (page: Page, apiArray: string[]): Promise<Response[]> => { export const waitSpecifyApiLoad = (page, apiArray) => {
// 输入参数检查 if (apiArray === undefined || apiArray.length === 0) {
if (!page || !Array.isArray(apiArray) || apiArray.length === 0 || !apiArray.every(item => typeof item === 'string')) { return Promise.resolve([]);
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);
}; };
/** /**