优化代码
This commit is contained in:
parent
8c910d9086
commit
28430d0c9b
@ -2,6 +2,7 @@
|
|||||||
const { defineConfig, devices } = require('@playwright/test');
|
const { defineConfig, devices } = require('@playwright/test');
|
||||||
// import dotenv from "dotenv";
|
// import dotenv from "dotenv";
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { firstAccount, secondAccount } from './tests/common/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
@ -15,9 +16,9 @@ require('dotenv').config({ path: path.resolve(__dirname, '.env') });
|
|||||||
// ".env" + `${process.env.NODE_ENV ? "." + process.env.NODE_ENV : ""}`
|
// ".env" + `${process.env.NODE_ENV ? "." + process.env.NODE_ENV : ""}`
|
||||||
// ),
|
// ),
|
||||||
// });
|
// });
|
||||||
const authPath = '.auth/user.json';
|
|
||||||
const firstAuthFile = '.auth/admin_first.json';
|
const firstAuthFile = firstAccount.authFile;
|
||||||
const secondAuthFile = '.auth/admin_second.json';
|
const secondAuthFile = secondAccount.authFile;
|
||||||
/**
|
/**
|
||||||
* @see https://playwright.dev/docs/test-configuration
|
* @see https://playwright.dev/docs/test-configuration
|
||||||
*/
|
*/
|
||||||
|
|||||||
52
tests/common/auth.ts
Normal file
52
tests/common/auth.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { getEnvVar } from '@/utils/envUtils';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
const authConfig = {
|
||||||
|
authFilePath: '.auth/',
|
||||||
|
indexedDBFilePath: '.auth/',
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuthAccount {
|
||||||
|
account: string;
|
||||||
|
password: string;
|
||||||
|
authFile: string;
|
||||||
|
indexedDBFile: string;
|
||||||
|
|
||||||
|
constructor(account: string, password: string, authFile: string, indexedDBFile: string) {
|
||||||
|
this.account = account;
|
||||||
|
this.password = password;
|
||||||
|
this.authFile = authFile;
|
||||||
|
this.indexedDBFile = indexedDBFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadIndexedDBFile(account: string, accountArray: AuthAccount[]) {
|
||||||
|
try {
|
||||||
|
for (const item of accountArray) {
|
||||||
|
if (item.account === account) {
|
||||||
|
return JSON.parse(readFileSync(item.indexedDBFile, 'utf-8'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('indexedDB文件读取失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('未找到该账户');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstAccount = new AuthAccount(
|
||||||
|
getEnvVar('boss_account'),
|
||||||
|
getEnvVar('boss_password'),
|
||||||
|
`${authConfig.authFilePath}${getEnvVar('boss_account')}.json`,
|
||||||
|
`${authConfig.indexedDBFilePath}${getEnvVar('boss_account')}_indexedDB.json`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const secondAccount = new AuthAccount(
|
||||||
|
getEnvVar('boss_account_2'),
|
||||||
|
getEnvVar('boss_password_2'),
|
||||||
|
`${authConfig.authFilePath}${getEnvVar('boss_account_2')}.json`,
|
||||||
|
`${authConfig.indexedDBFilePath}${getEnvVar('boss_account_2')}_indexedDB.json`,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export { firstAccount, secondAccount, AuthAccount };
|
||||||
@ -1,7 +1,7 @@
|
|||||||
//@ts-check
|
//@ts-check
|
||||||
// 默认为生产
|
// 默认为生产
|
||||||
let nodeEnv = process.env.NODE_ENV || "production";
|
let nodeEnv = process.env.NODE_ENV || 'production';
|
||||||
nodeEnv = nodeEnv === "staging" ? "production" : nodeEnv;
|
nodeEnv = nodeEnv === 'staging' ? 'production' : nodeEnv;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - 员工数据 门店 部门 员工 姓名
|
* - 员工数据 门店 部门 员工 姓名
|
||||||
@ -14,163 +14,163 @@ nodeEnv = nodeEnv === "staging" ? "production" : nodeEnv;
|
|||||||
let staffData = {
|
let staffData = {
|
||||||
firstStore: {
|
firstStore: {
|
||||||
firstSector: {
|
firstSector: {
|
||||||
name: "美容部",
|
name: '美容部',
|
||||||
employee_1: {
|
employee_1: {
|
||||||
name: "张伟",
|
name: '张伟',
|
||||||
phone: "13812345678",
|
phone: '13812345678',
|
||||||
id: { production: 3, test: 1 },
|
id: { production: 3, test: 1 },
|
||||||
},
|
},
|
||||||
employee_2: {
|
employee_2: {
|
||||||
name: "李娜",
|
name: '李娜',
|
||||||
phone: "13987654321",
|
phone: '13987654321',
|
||||||
id: { production: 4, test: 2 },
|
id: { production: 4, test: 2 },
|
||||||
},
|
},
|
||||||
employee_3: {
|
employee_3: {
|
||||||
name: "王芳",
|
name: '王芳',
|
||||||
phone: "13723456789",
|
phone: '13723456789',
|
||||||
id: { production: 5, test: 3 },
|
id: { production: 5, test: 3 },
|
||||||
},
|
},
|
||||||
employee_4: {
|
employee_4: {
|
||||||
name: "陈刚",
|
name: '陈刚',
|
||||||
phone: "13698765432",
|
phone: '13698765432',
|
||||||
id: { production: 6, test: 4 },
|
id: { production: 6, test: 4 },
|
||||||
},
|
},
|
||||||
employee_5: {
|
employee_5: {
|
||||||
name: "赵军",
|
name: '赵军',
|
||||||
phone: "13512349876",
|
phone: '13512349876',
|
||||||
id: { production: 7, test: 5 },
|
id: { production: 7, test: 5 },
|
||||||
},
|
},
|
||||||
employee_6: {
|
employee_6: {
|
||||||
name: "刘强",
|
name: '刘强',
|
||||||
phone: "13498761234",
|
phone: '13498761234',
|
||||||
id: { production: 8, test: 6 },
|
id: { production: 8, test: 6 },
|
||||||
},
|
},
|
||||||
employee_7: {
|
employee_7: {
|
||||||
name: "周萍",
|
name: '周萍',
|
||||||
phone: "13365432109",
|
phone: '13365432109',
|
||||||
id: { production: 9, test: 7 },
|
id: { production: 9, test: 7 },
|
||||||
},
|
},
|
||||||
employee_8: {
|
employee_8: {
|
||||||
name: "吴浩",
|
name: '吴浩',
|
||||||
phone: "13287654329",
|
phone: '13287654329',
|
||||||
id: { production: 10, test: 8 },
|
id: { production: 10, test: 8 },
|
||||||
},
|
},
|
||||||
employee_9: {
|
employee_9: {
|
||||||
name: "徐亮",
|
name: '徐亮',
|
||||||
phone: "13123459876",
|
phone: '13123459876',
|
||||||
id: { production: 11, test: 9 },
|
id: { production: 11, test: 9 },
|
||||||
},
|
},
|
||||||
employee_10: {
|
employee_10: {
|
||||||
name: "杨雪",
|
name: '杨雪',
|
||||||
phone: "13098761234",
|
phone: '13098761234',
|
||||||
id: { production: 12, test: 10 },
|
id: { production: 12, test: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
secondSector: {
|
secondSector: {
|
||||||
name: "医美部",
|
name: '医美部',
|
||||||
employee_1: {
|
employee_1: {
|
||||||
name: "赵伟",
|
name: '赵伟',
|
||||||
phone: "13923456789",
|
phone: '13923456789',
|
||||||
id: { production: 13, test: 11 },
|
id: { production: 13, test: 11 },
|
||||||
},
|
},
|
||||||
employee_2: {
|
employee_2: {
|
||||||
name: "钱丽",
|
name: '钱丽',
|
||||||
phone: "13898765432",
|
phone: '13898765432',
|
||||||
id: { production: 14, test: 12 },
|
id: { production: 14, test: 12 },
|
||||||
},
|
},
|
||||||
employee_3: {
|
employee_3: {
|
||||||
name: "孙峰",
|
name: '孙峰',
|
||||||
phone: "13712349876",
|
phone: '13712349876',
|
||||||
id: { production: 15, test: 13 },
|
id: { production: 15, test: 13 },
|
||||||
},
|
},
|
||||||
employee_4: {
|
employee_4: {
|
||||||
name: "李涛",
|
name: '李涛',
|
||||||
phone: "13687654321",
|
phone: '13687654321',
|
||||||
id: { production: 16, test: 14 },
|
id: { production: 16, test: 14 },
|
||||||
},
|
},
|
||||||
employee_5: {
|
employee_5: {
|
||||||
name: "周慧",
|
name: '周慧',
|
||||||
phone: "13598761234",
|
phone: '13598761234',
|
||||||
id: { production: 17, test: 15 },
|
id: { production: 17, test: 15 },
|
||||||
},
|
},
|
||||||
employee_6: {
|
employee_6: {
|
||||||
name: "吴凯",
|
name: '吴凯',
|
||||||
phone: "13465432109",
|
phone: '13465432109',
|
||||||
id: { production: 18, test: 16 },
|
id: { production: 18, test: 16 },
|
||||||
},
|
},
|
||||||
employee_7: {
|
employee_7: {
|
||||||
name: "郑翔",
|
name: '郑翔',
|
||||||
phone: "13387654329",
|
phone: '13387654329',
|
||||||
id: { production: 19, test: 17 },
|
id: { production: 19, test: 17 },
|
||||||
},
|
},
|
||||||
employee_8: {
|
employee_8: {
|
||||||
name: "冯敏",
|
name: '冯敏',
|
||||||
phone: "13223459876",
|
phone: '13223459876',
|
||||||
id: { production: 20, test: 18 },
|
id: { production: 20, test: 18 },
|
||||||
},
|
},
|
||||||
employee_9: {
|
employee_9: {
|
||||||
name: "朱强",
|
name: '朱强',
|
||||||
phone: "13198761234",
|
phone: '13198761234',
|
||||||
id: { production: 21, test: 19 },
|
id: { production: 21, test: 19 },
|
||||||
},
|
},
|
||||||
employee_10: {
|
employee_10: {
|
||||||
name: "何平",
|
name: '何平',
|
||||||
phone: "13065432198",
|
phone: '13065432198',
|
||||||
id: { production: 22, test: 20 },
|
id: { production: 22, test: 20 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
secondStore: {
|
secondStore: {
|
||||||
name: "美容部",
|
name: '美容部',
|
||||||
firstSector: {
|
firstSector: {
|
||||||
employee_1: {
|
employee_1: {
|
||||||
name: "张凯",
|
name: '张凯',
|
||||||
phone: "13865432198",
|
phone: '13865432198',
|
||||||
id: { production: 1, test: 1 },
|
id: { production: 1, test: 1 },
|
||||||
},
|
},
|
||||||
employee_2: {
|
employee_2: {
|
||||||
name: "李军",
|
name: '李军',
|
||||||
phone: "13923459876",
|
phone: '13923459876',
|
||||||
id: { production: 2, test: 2 },
|
id: { production: 2, test: 2 },
|
||||||
},
|
},
|
||||||
employee_3: {
|
employee_3: {
|
||||||
name: "王涛",
|
name: '王涛',
|
||||||
phone: "13798761234",
|
phone: '13798761234',
|
||||||
id: { production: 3, test: 3 },
|
id: { production: 3, test: 3 },
|
||||||
},
|
},
|
||||||
employee_4: {
|
employee_4: {
|
||||||
name: "陈敏",
|
name: '陈敏',
|
||||||
phone: "13654321987",
|
phone: '13654321987',
|
||||||
id: { production: 4, test: 4 },
|
id: { production: 4, test: 4 },
|
||||||
},
|
},
|
||||||
employee_5: {
|
employee_5: {
|
||||||
name: "赵峰",
|
name: '赵峰',
|
||||||
phone: "13523456789",
|
phone: '13523456789',
|
||||||
id: { production: 5, test: 5 },
|
id: { production: 5, test: 5 },
|
||||||
},
|
},
|
||||||
employee_6: {
|
employee_6: {
|
||||||
name: "刘丽",
|
name: '刘丽',
|
||||||
phone: "13487654321",
|
phone: '13487654321',
|
||||||
id: { production: 6, test: 6 },
|
id: { production: 6, test: 6 },
|
||||||
},
|
},
|
||||||
employee_7: {
|
employee_7: {
|
||||||
name: "周亮",
|
name: '周亮',
|
||||||
phone: "13398765432",
|
phone: '13398765432',
|
||||||
id: { production: 7, test: 7 },
|
id: { production: 7, test: 7 },
|
||||||
},
|
},
|
||||||
employee_8: {
|
employee_8: {
|
||||||
name: "吴平",
|
name: '吴平',
|
||||||
phone: "13212349876",
|
phone: '13212349876',
|
||||||
id: { production: 8, test: 8 },
|
id: { production: 8, test: 8 },
|
||||||
},
|
},
|
||||||
employee_9: {
|
employee_9: {
|
||||||
name: "徐浩",
|
name: '徐浩',
|
||||||
phone: "13165432109",
|
phone: '13165432109',
|
||||||
id: { production: 9, test: 9 },
|
id: { production: 9, test: 9 },
|
||||||
},
|
},
|
||||||
employee_10: {
|
employee_10: {
|
||||||
name: "孙杰",
|
name: '孙杰',
|
||||||
phone: "13087654329",
|
phone: '13087654329',
|
||||||
id: { production: 10, test: 10 },
|
id: { production: 10, test: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -200,7 +200,7 @@ function init(staffData, nodeEnv) {
|
|||||||
const store = updatedData[storeKey];
|
const store = updatedData[storeKey];
|
||||||
Object.keys(store).forEach(sectorKey => {
|
Object.keys(store).forEach(sectorKey => {
|
||||||
const sector = store[sectorKey];
|
const sector = store[sectorKey];
|
||||||
if (typeof sector === "object" && sector !== null && sector.name) {
|
if (typeof sector === 'object' && sector !== null && sector.name) {
|
||||||
updateIds(sector); // 更新每个 sector 的员工 id
|
updateIds(sector); // 更新每个 sector 的员工 id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
31
tests/fixtures/baseFixture.ts
vendored
31
tests/fixtures/baseFixture.ts
vendored
@ -1,7 +1,7 @@
|
|||||||
import { test as base, expect } from '@playwright/test';
|
import { test as base, expect } from '@playwright/test';
|
||||||
import { HomeNavigation } from '@/pages/homeNavigationPage';
|
import { HomeNavigation } from '@/pages/homeNavigationPage';
|
||||||
import { writeIndexedDB } from '@/utils/indexedDBUtils';
|
import { writeIndexedDB } from '@/utils/indexedDBUtils';
|
||||||
import { readFileSync } from 'fs';
|
import { firstAccount, secondAccount, AuthAccount } from '@/common/auth';
|
||||||
|
|
||||||
type MyFixture = {
|
type MyFixture = {
|
||||||
homeNavigation: HomeNavigation;
|
homeNavigation: HomeNavigation;
|
||||||
@ -17,7 +17,6 @@ export const test = base.extend<MyFixture>({
|
|||||||
|
|
||||||
if (!baseURL) throw new Error('baseURL is required');
|
if (!baseURL) throw new Error('baseURL is required');
|
||||||
await page.goto(baseURL);
|
await page.goto(baseURL);
|
||||||
// await page.getByRole('button', { name: /开\s单/ }).waitFor();
|
|
||||||
|
|
||||||
const mobileObject = await page.evaluate(() => {
|
const mobileObject = await page.evaluate(() => {
|
||||||
return window.localStorage.getItem('hlk_touch_mobile');
|
return window.localStorage.getItem('hlk_touch_mobile');
|
||||||
@ -26,36 +25,26 @@ export const test = base.extend<MyFixture>({
|
|||||||
throw new Error('localStorage does not contain boss_account or boss_account2');
|
throw new Error('localStorage does not contain boss_account or boss_account2');
|
||||||
}
|
}
|
||||||
const mobileString = JSON.parse(mobileObject);
|
const mobileString = JSON.parse(mobileObject);
|
||||||
const bossAccount = process.env.boss_account || '';
|
const data = AuthAccount.loadIndexedDBFile(mobileString.val, [firstAccount, secondAccount]);
|
||||||
const bossAccount2 = process.env.boss_account2 || '';
|
|
||||||
|
|
||||||
let jsonData: [];
|
|
||||||
if (mobileString.val?.includes(bossAccount)) {
|
|
||||||
jsonData = JSON.parse(readFileSync('.auth/admin_first_indexeddb.json', 'utf-8'));
|
|
||||||
} else if (mobileString.val?.includes(bossAccount2)) {
|
|
||||||
jsonData = JSON.parse(readFileSync('.auth/admin_second_indexeddb.json', 'utf-8'));
|
|
||||||
} else {
|
|
||||||
throw new Error('localStorage does not contain boss_account or boss_account2');
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
async ({ fnString, jsonData }) => {
|
async ({ fnString, data }) => {
|
||||||
// 在浏览器上下文中动态创建函数
|
|
||||||
const writeIndexedDBFn = new Function('return ' + fnString)();
|
const writeIndexedDBFn = new Function('return ' + fnString)();
|
||||||
|
return await writeIndexedDBFn(data);
|
||||||
// 调用函数并传递参数
|
|
||||||
return await writeIndexedDBFn(jsonData);
|
|
||||||
},
|
},
|
||||||
{ fnString: writeIndexedDB.toString(), jsonData },
|
{ fnString: writeIndexedDB.toString(), data },
|
||||||
);
|
);
|
||||||
// await page.reload();
|
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
async () => {
|
async () => {
|
||||||
const databases = await indexedDB.databases(); // 获取所有数据库
|
const databases = await indexedDB.databases(); // 获取所有数据库
|
||||||
return databases.some(db => db.name === 'hlk_touch_test_init'); // 判断是否存在目标数据库
|
return databases.some(db => db.name === 'hlk_touch_test_init'); // 判断是否存在目标数据库
|
||||||
},
|
},
|
||||||
{ timeout: 30000 }, // 设置超时时间,默认为 30 秒
|
{ timeout: 30000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
await page.getByRole('button', { name: /开\s单/ }).waitFor();
|
await page.getByRole('button', { name: /开\s单/ }).waitFor();
|
||||||
|
|
||||||
await use(page);
|
await use(page);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { expect, type Page, type Locator } from '@playwright/test';
|
import { expect, type Page, type Locator } from '@playwright/test';
|
||||||
import { Customer } from '@/utils/customer';
|
import { Customer } from '@/utils/customer';
|
||||||
|
import { getNextAppointmentTime } from '@/utils/timeUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预约状态颜色
|
* 预约状态颜色
|
||||||
@ -88,22 +89,6 @@ export class AppointmentPage {
|
|||||||
await this.page.reload();
|
await this.page.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取预约时间
|
|
||||||
* - 8:00 --> 8:30
|
|
||||||
* - 8:30 --> 9:00
|
|
||||||
* - 8:50 --> 9:00
|
|
||||||
*/
|
|
||||||
getAppointmentTimesAvailable = (data = new Date()) => {
|
|
||||||
if (!(data instanceof Date)) {
|
|
||||||
throw new Error(`传入的${data}不是时间类型`);
|
|
||||||
}
|
|
||||||
const currentHour = data.getHours();
|
|
||||||
const nextTime = data.getMinutes() > 28 ? ':00' : ':30';
|
|
||||||
const hour = String(currentHour + (nextTime === ':00' ? 1 : 0)).padStart(2, '0');
|
|
||||||
return `${hour}${nextTime}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取预约状态
|
* 获取预约状态
|
||||||
*/
|
*/
|
||||||
@ -159,7 +144,7 @@ export class AppointmentPage {
|
|||||||
* 根据员工和时间的中心进行点击预约单元格
|
* 根据员工和时间的中心进行点击预约单元格
|
||||||
*/
|
*/
|
||||||
openAppointmentCell = async (name: string, time?: Date, retry = 10) => {
|
openAppointmentCell = async (name: string, time?: Date, retry = 10) => {
|
||||||
const currentAppointment = time ? this.getAppointmentTimesAvailable(time) : this.getAppointmentTimesAvailable();
|
const currentAppointment = time ? getNextAppointmentTime(time) : getNextAppointmentTime();
|
||||||
|
|
||||||
const $currentTime = this.$$time.filter({ hasText: currentAppointment });
|
const $currentTime = this.$$time.filter({ hasText: currentAppointment });
|
||||||
const currentTimeBoundingBox = await $currentTime.boundingBox();
|
const currentTimeBoundingBox = await $currentTime.boundingBox();
|
||||||
|
|||||||
@ -1,37 +1,21 @@
|
|||||||
import { test as setup } from '@playwright/test';
|
import { test as setup } from '@playwright/test';
|
||||||
import { readIndexedDB } from '@/utils/indexedDBUtils';
|
import { readIndexedDB } from '@/utils/indexedDBUtils';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { firstAccount, secondAccount } from '@/common/auth';
|
||||||
|
|
||||||
const firstAuthFile = '.auth/admin_first.json';
|
const regex = /^https?:\/\/(?:www\.)?hlk\.meiguanjia\.net\/$/;
|
||||||
const secondAuthFile = '.auth/admin_second.json';
|
|
||||||
|
|
||||||
const regex = /^https?:\/\/(www\.)?hlk\.meiguanjia\.net\/?(#\s)?$/;
|
|
||||||
|
|
||||||
/** @param personalizedPages 营销-个性化页面*/
|
/** @param personalizedPages 营销-个性化页面*/
|
||||||
const personalizedPages = '/#/marketing/brand/personalized';
|
const personalizedPages = '/#/marketing/brand/personalized';
|
||||||
|
|
||||||
class testAccount {
|
const testAccountArray = [firstAccount, secondAccount];
|
||||||
constructor(account: string, password: string, path: string) {
|
|
||||||
this.account = account;
|
|
||||||
this.password = password;
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
account: string;
|
for (let testAccount of testAccountArray) {
|
||||||
password: string;
|
setup(`租户${testAccount.account}总部管理员登录`, async ({ page, baseURL }) => {
|
||||||
path: string;
|
const account = testAccount.account;
|
||||||
}
|
const password = testAccount.password;
|
||||||
|
const authFile = testAccount.authFile;
|
||||||
const allAccounts = [
|
const indexedDBFile = testAccount.indexedDBFile;
|
||||||
new testAccount(process.env.boss_account!, process.env.boss_password!, firstAuthFile),
|
|
||||||
new testAccount(process.env.boss_account_2!, process.env.boss_password_2!, secondAuthFile),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let a of allAccounts) {
|
|
||||||
setup(`租户${a.account}总部管理员登录`, async ({ page, baseURL }) => {
|
|
||||||
const account = a.account;
|
|
||||||
const password = a.password;
|
|
||||||
const savePath = a.path;
|
|
||||||
|
|
||||||
const $phonePassIcon = page
|
const $phonePassIcon = page
|
||||||
.locator('div', { has: page.getByRole('textbox', { name: '请输入您的手机号码' }) })
|
.locator('div', { has: page.getByRole('textbox', { name: '请输入您的手机号码' }) })
|
||||||
@ -64,7 +48,7 @@ for (let a of allAccounts) {
|
|||||||
const readIndexedDBFn = new Function('return ' + readIndexedDBFnString)();
|
const readIndexedDBFn = new Function('return ' + readIndexedDBFnString)();
|
||||||
return await readIndexedDBFn();
|
return await readIndexedDBFn();
|
||||||
}, readIndexedDB.toString());
|
}, readIndexedDB.toString());
|
||||||
fs.writeFileSync('.auth/admin_first_indexeddb.json', JSON.stringify(result));
|
fs.writeFileSync(indexedDBFile, JSON.stringify(result));
|
||||||
await page.context().storageState({ path: savePath });
|
await page.context().storageState({ path: authFile });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { test as setup, expect } from "@playwright/test";
|
import { test as setup, expect } from '@playwright/test';
|
||||||
const authFileArray = [".auth/user_1.json", ".auth/user_2.json"];
|
import { getEnvVar } from '@/utils/envUtils';
|
||||||
|
|
||||||
|
const authFileArray = ['.auth/user_1.json', '.auth/user_2.json'];
|
||||||
const employee = [
|
const employee = [
|
||||||
{
|
{
|
||||||
phone: process.env.staff1_account,
|
phone: process.env.staff1_account,
|
||||||
@ -10,21 +12,21 @@ const employee = [
|
|||||||
password: process.env.staff2_password,
|
password: process.env.staff2_password,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
setup.describe("员工登录", () => {
|
setup.describe('员工登录', () => {
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
setup(`门店员工登录_${i}`, async ({ page }) => {
|
setup(`门店员工登录_${i}`, async ({ page }) => {
|
||||||
const baseUrl = process.env.BASE_URL;
|
const baseUrl = getEnvVar('BASE_URL');
|
||||||
const $phone = page.getByRole("textbox", { name: "请输入您的手机号码" });
|
const $phone = page.getByRole('textbox', { name: '请输入您的手机号码' });
|
||||||
const $password = page.getByRole("textbox", { name: "请输入登录密码" });
|
const $password = page.getByRole('textbox', { name: '请输入登录密码' });
|
||||||
const $login = page.getByRole("button", { name: /登\s录/ });
|
const $login = page.getByRole('button', { name: /登\s录/ });
|
||||||
await page.goto(baseUrl);
|
await page.goto(baseUrl);
|
||||||
await $phone.fill(employee[i].phone);
|
await $phone.fill(employee[i].phone);
|
||||||
const checkPhone = page.locator(".ant-row", { has: $phone }).locator(".pass_svg");
|
const checkPhone = page.locator('.ant-row', { has: $phone }).locator('.pass_svg');
|
||||||
await expect(checkPhone).toBeVisible();
|
await expect(checkPhone).toBeVisible();
|
||||||
await $password.fill(employee[i].password);
|
await $password.fill(employee[i].password);
|
||||||
await page.getByLabel("请同意慧来客隐私政策和用户协议").check();
|
await page.getByLabel('请同意慧来客隐私政策和用户协议').check();
|
||||||
await $login.click();
|
await $login.click();
|
||||||
await expect(page.getByRole("button", { name: /开\s单/ })).toBeEnabled();
|
await expect(page.getByRole('button', { name: /开\s单/ })).toBeEnabled();
|
||||||
await page.context().storageState({ path: authFileArray[i] });
|
await page.context().storageState({ path: authFileArray[i] });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,19 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { faker } from '@faker-js/faker/locale/zh_CN';
|
import { faker } from '@faker-js/faker/locale/zh_CN';
|
||||||
import { test, expect } from '@/fixtures/boss_common.js';
|
import { test, expect } from '@/fixtures/boss_common.js';
|
||||||
import { staffData } from '@/fixtures/staff.js';
|
import { staffData } from '@/common/staff';
|
||||||
import { HomeNavigation } from '@/pages/homeNavigationPage.js';
|
import { AppointmentOperation } from '@/pages/appointmentPage';
|
||||||
import { AppointmentPage } from '@/pages/appointmentPage.js';
|
import { getNextAppointmentTime } from '@/utils/timeUtils';
|
||||||
import { AppointmentOperation } from '@/pages/appointmentPage.js';
|
|
||||||
|
|
||||||
test('使用预约单元格', async ({ page, homeNavigation, createCustomer, appointmentPage, customerPage }) => {
|
test('使用预约单元格', async ({ page, homeNavigation, createCustomer, appointmentPage, customerPage }) => {
|
||||||
const employee = staffData.firstStore.firstSector.employee_1;
|
const employee = staffData.firstStore.firstSector.employee_1;
|
||||||
const setAppointmentTime = appointmentPage.getAppointmentTimesAvailable();
|
const appointmentTime = getNextAppointmentTime();
|
||||||
|
|
||||||
const customer = createCustomer;
|
const customer = createCustomer;
|
||||||
|
|
||||||
/** 当前可预约时间定位器 */
|
/** 当前可预约时间定位器 */
|
||||||
const $time = page.locator('.times_table td').filter({ hasText: setAppointmentTime });
|
const $time = page.locator('.times_table td').filter({ hasText: appointmentTime });
|
||||||
/** 顾客预约定位器 */
|
/** 顾客预约定位器 */
|
||||||
const $customerAppointment = page
|
const $customerAppointment = page.locator('.a_userInfo').filter({ hasText: customer.phone }).filter({ hasText: customer.username });
|
||||||
.locator('.a_userInfo')
|
|
||||||
.filter({ hasText: customer.phone })
|
|
||||||
.filter({ hasText: customer.username });
|
|
||||||
|
|
||||||
await test.step('进行未指定预约', async () => {
|
await test.step('进行未指定预约', async () => {
|
||||||
await homeNavigation.gotoModule('预约');
|
await homeNavigation.gotoModule('预约');
|
||||||
@ -96,7 +91,7 @@ test('使用预约单元格', async ({ page, homeNavigation, createCustomer, app
|
|||||||
|
|
||||||
test('占用预约单元格', async ({ page, homeNavigation, createCustomer, appointmentPage, customerPage }) => {
|
test('占用预约单元格', async ({ page, homeNavigation, createCustomer, appointmentPage, customerPage }) => {
|
||||||
// 获取当前可预约时间
|
// 获取当前可预约时间
|
||||||
let setAppointmentTime = appointmentPage.getAppointmentTimesAvailable();
|
let appointmentTime = getNextAppointmentTime();
|
||||||
|
|
||||||
// 创建顾客
|
// 创建顾客
|
||||||
const customer = createCustomer;
|
const customer = createCustomer;
|
||||||
@ -106,7 +101,7 @@ test('占用预约单元格', async ({ page, homeNavigation, createCustomer, app
|
|||||||
const remark = '占用预约单元格' + faker.string.alpha(2);
|
const remark = '占用预约单元格' + faker.string.alpha(2);
|
||||||
|
|
||||||
// 当前可预约时间定位器
|
// 当前可预约时间定位器
|
||||||
const $time = page.locator('.times_table td').filter({ hasText: setAppointmentTime });
|
const $time = page.locator('.times_table td').filter({ hasText: appointmentTime });
|
||||||
// 员工定位器
|
// 员工定位器
|
||||||
const $employee = page.locator('.room_table .tr .name_th').filter({ hasText: employee.name });
|
const $employee = page.locator('.room_table .tr .name_th').filter({ hasText: employee.name });
|
||||||
// 顾客预约定位器
|
// 顾客预约定位器
|
||||||
@ -202,18 +197,15 @@ test('占用预约单元格', async ({ page, homeNavigation, createCustomer, app
|
|||||||
|
|
||||||
test.describe('预约状态', () => {
|
test.describe('预约状态', () => {
|
||||||
test('预约-挂单-结算', async ({ page, homeNavigation, createCustomer, appointmentPage, customerPage, billSet }) => {
|
test('预约-挂单-结算', async ({ page, homeNavigation, createCustomer, appointmentPage, customerPage, billSet }) => {
|
||||||
// 员工4
|
|
||||||
const employee = staffData.firstStore.firstSector.employee_4;
|
const employee = staffData.firstStore.firstSector.employee_4;
|
||||||
// 使用的项目
|
|
||||||
const project = { no: '100018', name: '苹果精萃护理', shortName: '精萃护理', price: 980 };
|
const project = { no: '100018', name: '苹果精萃护理', shortName: '精萃护理', price: 980 };
|
||||||
// 获取当前可预约时间
|
const appointmentTime = getNextAppointmentTime();
|
||||||
const setAppointmentTime = appointmentPage.getAppointmentTimesAvailable();
|
const customer = createCustomer;
|
||||||
|
|
||||||
// 当前可预约时间定位器
|
// 当前可预约时间定位器
|
||||||
const $time = page.locator('.times_table td').filter({ hasText: setAppointmentTime });
|
const $time = page.locator('.times_table td').filter({ hasText: appointmentTime });
|
||||||
// 员工定位器
|
// 员工定位器
|
||||||
const $employee = page.locator('.room_table .tr .name_th').filter({ hasText: employee.name });
|
const $employee = page.locator('.room_table .tr .name_th').filter({ hasText: employee.name });
|
||||||
// 创建顾客
|
|
||||||
const customer = createCustomer;
|
|
||||||
|
|
||||||
// 获取设置的预约状态
|
// 获取设置的预约状态
|
||||||
let appointmentStatusSetting;
|
let appointmentStatusSetting;
|
||||||
@ -311,74 +303,29 @@ test.describe('预约状态', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.afterEach(async ({ homeNavigation, wasteBookBusinessRecordPage, billSet }) => {
|
test('测试预约操作', async ({ page, homeNavigation }) => {
|
||||||
// 撤回预约开的单
|
|
||||||
await homeNavigation.gotoModule('流水');
|
|
||||||
await wasteBookBusinessRecordPage.revokeOrder(billSet);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 清除当前时间之后所有的预约和占用
|
|
||||||
test.afterAll(async ({ browser, baseURL }) => {
|
|
||||||
const page = await browser.newPage();
|
|
||||||
await page.goto(baseURL ?? '');
|
|
||||||
|
|
||||||
const homeNavigation = new HomeNavigation(page);
|
|
||||||
const appointmentPage = new AppointmentPage(page);
|
|
||||||
|
|
||||||
await homeNavigation.gotoModule('预约');
|
await homeNavigation.gotoModule('预约');
|
||||||
await expect(page.getByText('张伟')).toBeVisible();
|
await page.getByRole('button', { name: /设\s置/ }).waitFor();
|
||||||
const $$userInfo = page.locator('.a_userInfo');
|
await page.waitForTimeout(3000);
|
||||||
// 获取顾客占用预约单元格信息
|
// await page.mouse.wheel(0, 500);
|
||||||
const $$appointmentBox = $$userInfo.locator('.appointment_content');
|
|
||||||
|
|
||||||
const names = await $$appointmentBox.locator('.name').allInnerTexts();
|
const $table = page.locator('.content_table');
|
||||||
const userPhones = await $$appointmentBox.locator('.user_phone').allInnerTexts();
|
const tableBox = await $table.boundingBox();
|
||||||
|
if (tableBox) {
|
||||||
|
const startX = tableBox.x + tableBox.width / 2; // 元素中心点 X 坐标
|
||||||
|
const startY = tableBox.y + tableBox.height / 2; // 元素中心点 Y 坐标
|
||||||
|
|
||||||
const userInfoData = names.map((name, index) => {
|
// 移动鼠标到起始位置
|
||||||
return {
|
await page.mouse.move(startX, startY);
|
||||||
name,
|
|
||||||
phone: userPhones[index],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 取消预约占用
|
// 按下鼠标左键
|
||||||
// 获取占用框的数量
|
await page.mouse.down();
|
||||||
let occupyBoxCount = await $$userInfo.locator('.occupy').count();
|
|
||||||
while (occupyBoxCount > 0) {
|
|
||||||
// 每次都使用 first() 获取第一个占用框
|
|
||||||
const $occupyBox = $$userInfo.locator('.occupy').first();
|
|
||||||
await $occupyBox.click();
|
|
||||||
await page
|
|
||||||
.locator('.popup_content .class_content')
|
|
||||||
.getByText(/^取消占用$/)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
try {
|
// 向左拖动 (X 坐标减小,Y 坐标保持不变)
|
||||||
// 等待请求成功,确认按钮和响应并行处理
|
await page.mouse.move(startX - 400, startY, { steps: 10 });
|
||||||
await Promise.all([
|
|
||||||
page.waitForResponse(res => res.url().includes('/reservation') && res.status() === 200, {
|
|
||||||
timeout: 5000,
|
|
||||||
}), // 添加超时
|
|
||||||
page.getByRole('button', { name: /确\s认/ }).click(),
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('取消预约时出现错误: ', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.waitForTimeout(500);
|
// 松开鼠标左键
|
||||||
occupyBoxCount = await $$userInfo.locator('.occupy').count();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消有取消预约按钮的预约
|
|
||||||
for (const user of userInfoData) {
|
|
||||||
const { name } = user;
|
|
||||||
const $appointmentBox = $$appointmentBox.filter({ has: page.getByText(name) }).locator('.user_name_info');
|
|
||||||
const appointCount = await $appointmentBox.count();
|
|
||||||
if (appointCount === 1 && (await appointmentPage.elementCenterInViewport($appointmentBox.first()))) {
|
|
||||||
await $appointmentBox.first().click();
|
|
||||||
await appointmentPage.cancelAppoint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.close();
|
|
||||||
});
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { expect, test } from '@/fixtures/boss_common.js';
|
import { expect, test } from '@/fixtures/boss_common.js';
|
||||||
import { staffData } from '@/fixtures/staff.js';
|
import { staffData } from '@/common/staff';
|
||||||
import { Employees } from '@/fixtures/userconfig.js';
|
import { Employees } from '@/fixtures/userconfig.js';
|
||||||
import { Customer } from '@/utils/customer';
|
import { Customer } from '@/utils/customer';
|
||||||
import { KeepOnlyNumbers } from '@/utils/utils.js';
|
import { KeepOnlyNumbers } from '@/utils/utils.js';
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { test, expect } from '@/fixtures/boss_common.js';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { decodeQR } from '@/utils/utils.js';
|
import { decodeQR } from '@/utils/utils.js';
|
||||||
import { Customer } from '@/utils/customer';
|
import { Customer } from '@/utils/customer';
|
||||||
import { staffData } from '@/fixtures/staff.js';
|
import { staffData } from '@/common/staff';
|
||||||
import { HomeNavigation } from '@/pages/homeNavigationPage.js';
|
import { HomeNavigation } from '@/pages/homeNavigationPage.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { Page } from '@playwright/test';
|
import { Page } from '@playwright/test';
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { KeepOnlyNumbers } from '@/utils/utils.js';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Employees, ProjectName } from '@/fixtures/userconfig.js';
|
import { Employees, ProjectName } from '@/fixtures/userconfig.js';
|
||||||
import { staffData } from '@/fixtures/staff.js';
|
import { staffData } from '@/common/staff';
|
||||||
|
|
||||||
test.describe('营业记录', () => {
|
test.describe('营业记录', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
|
|||||||
7
tests/utils/envUtils.ts
Normal file
7
tests/utils/envUtils.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export function getEnvVar(key: string): string {
|
||||||
|
const value = process.env[key];
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Environment variable ${key} is not set`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
14
tests/utils/timeUtils.ts
Normal file
14
tests/utils/timeUtils.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 获取下一个预约时间段的时间
|
||||||
|
* - 8:00 --> 8:30
|
||||||
|
* - 8:30 --> 9:00
|
||||||
|
* - 8:50 --> 9:00
|
||||||
|
*/
|
||||||
|
function getNextAppointmentTime(date = new Date()) {
|
||||||
|
const currentHour = date.getHours();
|
||||||
|
const nextTime = date.getMinutes() > 28 ? ':00' : ':30';
|
||||||
|
const hour = String(currentHour + (nextTime === ':00' ? 1 : 0)).padStart(2, '0');
|
||||||
|
return `${hour}${nextTime}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getNextAppointmentTime };
|
||||||
Reference in New Issue
Block a user