//@ts-check const { expect } = require('@playwright/test'); const { Customer } = require('./customer'); class CustomerPage { /** * @param {import("@playwright/test").Page} page */ constructor(page) { this.page = page; this.$module = this.page.locator('.left_box .link_item').filter({ hasText: /顾客/ }); this.$tabItem = this.page.locator('.top_tab .tab_item'); this.$summary = this.$tabItem.filter({ hasText: '顾客概要' }); this.$distribution = this.$tabItem.filter({ hasText: '顾客分配' }); this.$dynamic = this.$tabItem.filter({ hasText: '顾客动态' }); this.$analysis = this.$tabItem.filter({ hasText: '顾客分析' }); this.$serviceLog = this.$tabItem.filter({ hasText: '服务日志' }); this.$register = this.page.locator('.regmeber_warp', { hasText: '创建会员' }); this.firstStore = { firstDepartment: { no: 1, name: '美容部' }, secondDepartment: { no: 2, name: '医美部' }, }; this.secondStore = { firstDepartment: { no: 1, name: '美容部' }, }; this.source = [ '邀客', '员工带客', '美团', '大众点评', '客带客', '上门客人', '百度糯米', '支付宝', ]; } /** * 跳转子页面,并且等待页面接口加载完成 * @param {import("@playwright/test").Locator} locator * @param {string[]} apiList */ gotoSubPage = async (locator, apiList) => { await expect(async () => { if (!(await locator.getAttribute('class'))?.includes('active')) { await locator.click(); } await expect(locator).toHaveClass(/active/); }).toPass({ timeout: 30000 }); await Promise.all( apiList.map((api) => this.page.waitForResponse((res) => res.url().includes(api) && res.status() === 200) ) ); }; /** * 跳转到顾客概要 */ gotoSummary = async () => { await this.gotoSubPage(this.$summary, ['summary', 'todo']); }; /** * 跳转到顾客分配 */ gotoDistribution = async () => { await this.gotoSubPage(this.$distribution, ['search_new', 'distribution']); }; /** * 跳转到顾客动态 */ gotoDynamic = async () => { await this.gotoSubPage(this.$dynamic, ['daily_action']); }; /** * 跳转到顾客分析 */ gotoAnalysis = async () => { await this.gotoSubPage(this.$analysis, ['analysis']); }; /** * 跳转到服务日志 */ gotoServiceLog = async () => { await this.gotoSubPage(this.$serviceLog, ['service_log']); }; /** * 搜索顾客 * @param {Customer} customer */ searchCustomer = async (customer) => { const searchLocator = this.page.locator('.search_normal'); const searchInput = this.page.getByPlaceholder('姓名(拼音首字)、手机号、档案号搜索'); await searchInput.fill(customer.phone); await searchLocator.filter({ has: searchInput }).getByText('搜索', { exact: true }).click(); const customerLocator = this.page .locator('.member_list .member_list_li') .filter({ hasText: customer.username }); await customerLocator.click(); await expect(customerLocator).not.toBeVisible(); }; /** * 打开顾客详情页面 * @param {Customer} customer */ openCustomerDetails = async (customer) => { const $username = this.page.getByText(customer.username).last(); await $username.click(); const infoBox = this.page.locator('.member_info_box'); await Promise.all([ expect(infoBox.getByText(customer.username)).toBeVisible(), this.page.waitForFunction(() => { return document.readyState === 'complete'; }), ]); }; /** * 关闭顾客详情页面 */ closeCustomerDetails = async () => { const closeButton = this.page.locator('.member_info_box .close_icons > svg'); await closeButton.click(); await expect(closeButton).not.toBeVisible(); }; /** * 创建顾客 * @param {Customer} customer 顾客 * @returns {Promise} */ createCustomer = async (customer) => { await expect(async () => { await this.$module.click({ clickCount: 1 }); await expect(this.$module).toHaveClass(/active/, { timeout: 2000 }); }).toPass({ timeout: 30000 }); // 选择门店 await this.page.locator('.search_store > div').click(); await this.page.getByText('部门', { exact: true }).waitFor(); await this.page.locator('.shopSelect_box .shopSelect_shop_content').click(); await this.page .locator('.com_picker .label') .nth(customer.store - 1) .click(); await this.page.getByRole('button', { name: /保.*存/ }).click(); await this.page.getByRole('button', { name: '新增顾客' }).click(); await this.$register.getByPlaceholder('请输入姓名').fill(customer.username); await this.$register.getByPlaceholder('请输入会员手机号').fill(customer.phone); await this.$register.getByPlaceholder('请输入会员手机号').click(); const checkPhoneLocator = this.$register .locator('.ant-form-item', { hasText: '手机号' }) .locator('.ant-form-explain'); if (await checkPhoneLocator.isVisible()) { const phoneStr = await checkPhoneLocator.innerText(); if (phoneStr.includes('非法手机号码') || phoneStr.includes('请输入会员手机号')) { throw new Error(`手机号码:${customer.phone}不正确`); } } // 输入档案号 if (customer.archive) { await this.$register.getByPlaceholder('请输入12位以内的数字或字母').fill(customer.archive); } // 选择部门 await this.$register.locator('#register_departmentNo').getByRole('combobox').click(); if (customer.store === 1) { if (customer.department === 1) { await this.page.getByRole('option', { name: this.firstStore.firstDepartment.name }).click(); } else if (customer.department === 2) { await this.page .getByRole('option', { name: this.firstStore.secondDepartment.name }) .click(); } } else if (customer.store === 2) { if (customer.department === 1) { await this.page .getByRole('option', { name: this.secondStore.firstDepartment.name }) .click(); } else { throw new Error(`部门:${customer.department}不存在`); } } else { throw new Error(`门店:${customer.store}不存在`); } // 选择员工 // 选择性别 if (customer.gender) { if (customer.gender === 0) { await this.$register.locator('label').filter({ hasText: '女性' }).click(); } else if (customer.gender === 1) { await this.$register.locator('label').filter({ hasText: '男性' }).click(); } } // 选择生日 const birthday = customer.birthday; const birthdayLocator = this.$register.locator('.ant-form-item', { hasText: '生日' }); if (birthday) { const { year, month, day } = birthday; if (year) { await birthdayLocator.getByText('年份').click(); await this.page.getByRole('option', { name: `${year}` }).click(); await expect(this.page.getByRole('option', { name: `${year}` })).not.toBeVisible(); } if (month && day) { await birthdayLocator.getByText('日期').click(); await this.page.getByRole('option', { name: new RegExp(`^${month}\\s月$`) }).click(); await this.page.getByRole('option', { name: new RegExp(`^${day}\\s日$`) }).click(); await this.page.getByRole('button', { name: /确\s认/ }).click(); } else { throw new Error(`month:${month}, day:${day}其中一个为空`); } } // 选择顾客来源 // await this.$register.getByLabel(this.source[customer.source]).click(); await this.$register.getByText('请选择顾客来源').click(); await this.page.getByRole('option', { name: this.source[customer.source] }).first().click(); // 选择备注 if (customer.remark) { await this.$register.getByPlaceholder('请输入1-100个字符备注内容').fill(customer.remark); } const [response] = await Promise.all([ this.page.waitForResponse( async (res) => res.url().includes('/invalid_check') && (await res.json()).code === 'SUCCESS' ), this.page.getByRole('button', { name: '确认创建' }).click(), ]); const responseBody = await response.json(); const phoneStatus = responseBody?.content?.status; if (phoneStatus) { await this.page .getByText('系统查询到当前手机号被建档后转为无效客,是否要恢复无效客?') .waitFor(); await this.page.getByRole('button', { name: '重新建档' }).click(); // 检查弹窗信息 const popupWindow = this.page.locator('.ant-message'); await popupWindow.waitFor(); const popupContent = (await popupWindow.innerText()).trim(); if (popupContent.includes('该手机号码已经被使用')) { throw new Error(`该手机号码:${customer.phone}已经被使用`); } } await this.page.locator('.person_content').waitFor(); }; /** * 设置顾客为无效客 * @param {Customer} customer 顾客 * @returns {Promise} */ setInvalidCustomer = async (customer) => { await this.page.goto(process.env.BASE_URL || '', { waitUntil: 'load' }); const moduleLocator = this.$module; const activeLocator = moduleLocator.locator('.active_arrow'); await expect(async () => { if (!(await activeLocator.isVisible())) { await moduleLocator.click({ clickCount: 1 }); } await expect(activeLocator).toBeVisible({ timeout: 2_000 }); }).toPass({ timeout: 30_000 }); // 根据手机号进行搜索,进入顾客详情页面 await this.page.getByPlaceholder('姓名(拼音首字)、手机号、档案号搜索').fill(customer.phone); await this.page.locator('.ant-input-suffix .search_btn', { hasText: '搜索' }).click(); await this.page.locator('.custom_content', { hasText: customer.phone }).click(); await this.page.locator('.m-table__fixed-left').getByText(customer.username).first().click(); // 设置无效客 await this.page.locator('.person_content').waitFor(); await this.page.locator('.person_content .tag_box .more_icon svg').click(); await this.page.getByRole('menuitem', { name: '设为无效客' }).click(); const [response] = await Promise.all([ this.page.waitForResponse( (response) => response.url().includes('/customer') && response.request().method() === 'PATCH' ), this.page.getByRole('button', { name: /确\s认/ }).click(), ]); const responseBody = await response.json(); const code = responseBody?.code; expect(code).toBe('SUCCESS'); }; /** * 批量创建顾客 * @param {Array} customerArray */ createMoreCustomer = async (customerArray) => { for (const customer of customerArray) { await this.createCustomer(customer); } }; /** * 批量设置无效客 * @param {Array} customerArray */ setMoreInvalidCustomer = async (customerArray) => { for (const customer of customerArray) { await this.setInvalidCustomer(customer); } }; } module.exports = { CustomerPage };