feat(tests): add global setup and test specification for identity verification

- Created a global setup file to handle user login and merchant selection based on identity verification.
- Implemented a test specification to verify the identity verification tool and log merchant details.
This commit is contained in:
雨霖铃 2025-06-25 22:45:26 +08:00
parent 5e33762358
commit 729b6de180
11 changed files with 843 additions and 9 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
BASE_URL="https://px.meiguanjia.net/prestg/web/#/login"

View File

@ -0,0 +1,10 @@
{
"setting": {
"name": "设置",
"selector": "#app > div.routerView.page_view > div > div.navStore_view > div.left_menu_area > div.menu_area.scroll0 > ul > li:nth-child(3)",
"content": [
{ "name": "微信设置", "selector": "" },
{ "name": "支付设置", "selector": "" }
]
}
}

13
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"dotenv": "^16.5.0",
"playwright": "^1.53.1"
},
"devDependencies": {
@ -42,6 +43,18 @@
"undici-types": "~7.8.0"
}
},
"node_modules/dotenv": {
"version": "16.5.0",
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.5.0.tgz",
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",

View File

@ -8,6 +8,7 @@
"license": "ISC",
"description": "",
"dependencies": {
"dotenv": "^16.5.0",
"playwright": "^1.53.1"
},
"devDependencies": {

View File

@ -1,4 +1,6 @@
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
dotenv.config();
/**
* Read environment variables from file.
@ -30,24 +32,32 @@ export default defineConfig({
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
baseURL: process.env.BASE_URL || 'http://localhost:3000',
},
/* Configure projects for major browsers */
projects: [
{
name: 'setup',
testMatch: /global\.setup\.ts/,
},
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
use: {
...devices['Desktop Chrome'],
},
dependencies: ['setup'],
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {

View File

@ -28,6 +28,24 @@ export class LoginPage {
await this.login_button.click();
}
async user_select_merchant(merchantName: string) {
const merchantLocator = this.page.getByText(merchantName, { exact: true });
await expect(merchantLocator).toBeVisible();
await merchantLocator.click();
await this.page.waitForLoadState('networkidle');
await this.page.waitForTimeout(1000); // 等待页面加载完成
console.log(`已选择租户: ${merchantName}`);
}
async user_select_employee(employeeName: string) {
const employeeLocator = this.page.getByText(employeeName).first();
await expect(employeeLocator).toBeVisible();
await employeeLocator.click();
await this.page.waitForLoadState('networkidle');
await this.page.waitForTimeout(1000); // 等待页面加载完成
console.log(`已选择员工: ${employeeName}`);
}
user_login_success() {
return this.first_menu;
}

12
src/page/SidebarPage.ts Normal file
View File

@ -0,0 +1,12 @@
import { expect, type Locator, type Page } from '@playwright/test';
export class SidebarPage {
readonly page: Page;
readonly first_menu: Locator;
readonly second_menu: Locator;
constructor(page: Page) {
this.page = page;
this.first_menu
this.second_menu
}
}

67
src/util/authUtil.ts Normal file
View File

@ -0,0 +1,67 @@
import { request } from '@playwright/test';
async function getApiRequestContext() {
return await request.newContext({
baseURL: 'https://px.meiguanjia.net',
extraHTTPHeaders: {
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Content-Type': 'application/json',
DNT: '1',
Origin: 'https://px.meiguanjia.net',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0',
currVersion: 'PX-PC_20250625155429',
lang: 'zh_CN',
'req-type': 'json',
'sec-ch-ua': '"Microsoft Edge";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'system-code': 'MGJ',
zoneId: 'UTC+08:00',
},
});
}
/**
*
* @param mobile
* @param password
* @returns false true
* @description
* @example
* const result = await identityVerificationTool('17770898274', 'a123456');
* console.log(result.status); // true or false
* console.log(result.content); // 返回的内容对象
* @throws {Error}
*/
export const identityVerificationTool = async (mobile: string, password: string) => {
const apiRequestContext = await getApiRequestContext();
const response = await apiRequestContext.post('/prestg/pixiu/api/login', {
data: {
mobile: mobile,
password: password,
sn: '8ebc3002-b398-4c11-8323-100e729fcf39',
clientId: 'touch',
deviceType: 'windows',
},
});
const responseBody = await response.json();
if (responseBody?.code === 'ACCOUNT_NEED_CHOOSE_MERCHANT') {
return new verifyContent(false, responseBody?.content);
} else if (responseBody?.code === 'SUCCESS') {
return new verifyContent(true, {});
}
return new verifyContent(true, {});
};
function verifyContent(status: boolean, content: Object) {
this.status = status;
this.content = content;
}

647
state.json Normal file

File diff suppressed because one or more lines are too long

40
tests/global.setup.ts Normal file
View File

@ -0,0 +1,40 @@
import { test as setup } from '@playwright/test';
import { LoginPage } from '../src/page/LoginPage';
import { identityVerificationTool } from '../src/util/authUtil';
setup('state setup', async ({ page }) => {
const body = await identityVerificationTool('17770898274', 'a123456');
if (!body.status) {
console.log('身份验证失败,请选择租户');
let merchanName: string | undefined = undefined;
let employeeName: string | undefined = undefined;
const content = body?.content;
for (const merchant in content) {
if (content[merchant]?.merchantId === 373) {
merchanName = content[merchant]?.merchantName;
if (content[merchant]?.users.length > 0) {
employeeName = content[merchant]?.users[0]?.userName;
}
}
}
await page.goto(process.env.BASE_URL || 'http://localhost:3000');
const loginPage = new LoginPage(page);
await loginPage.user_login('17770898274', 'a123456');
await loginPage.user_select_merchant(merchanName || '美管家');
console.log(employeeName);
if (employeeName) {
await loginPage.user_select_employee(employeeName);
}
await page.pause();
await loginPage.user_login_success().waitFor();
await page.context().storageState({ path: 'state.json', indexedDB: true });
return;
}
console.log('身份验证成功');
await page.goto(process.env.BASE_URL || 'http://localhost:3000');
const loginPage = new LoginPage(page);
await loginPage.user_login('17770898274', 'a123456');
await page.pause();
await loginPage.user_login_success().waitFor();
await page.context().storageState({ path: 'state.json', indexedDB: true });
});

15
tests/test.spec.ts Normal file
View File

@ -0,0 +1,15 @@
import { expect, test } from '@playwright/test';
import { identityVerificationTool } from '../src/util/authUtil';
test('test1', async ({ page, baseURL }) => {
const body = await identityVerificationTool('17770898274', 'a123456');
const content = body?.content;
for (const merchant in content) {
if (content[merchant]?.merchantId === 373) {
console.log(
`租户名称:${content[merchant]?.merchantName}租户ID${content[merchant]?.merchantId}`
);
console.log(content[merchant]?.users);
}
}
});