Commit 33bc2f2f88ab7800fbf3b853292968d1cd4b6f9c

Authored by 赵旭婷
1 parent 30d1471a

去掉硬编码

.gitignore
... ... @@ -10,4 +10,5 @@ node_modules/
10 10 /.env
11 11 /allure-results/
12 12 /plans/
13   -/reports/
14 13 \ No newline at end of file
  14 +/reports/
  15 +/screenshots/
15 16 \ No newline at end of file
... ...
Jenkinsfile 0 → 100644
  1 +// Jenkinsfile - UI自动化测试流水线配置
  2 +// 使用方法:在 Jenkins 中创建 Pipeline 任务,选择 "Pipeline script from SCM" 或直接粘贴此文件内容
  3 +
  4 +pipeline {
  5 + agent any
  6 +
  7 + tools {
  8 + nodejs 'NodeJS-18' // Jenkins 中配置的 Node.js 工具名称
  9 + allure 'Allure' // Jenkins 中配置的 Allure 工具名称
  10 + }
  11 +
  12 + environment {
  13 + // 环境变量配置
  14 + NODE_ENV = 'test'
  15 + // 测试环境 URL(可在 Jenkins 中覆盖)
  16 + BASE_URL = credentials('test-base-url') ?: 'https://erp-pad.test.gszdtop.com'
  17 + }
  18 +
  19 + parameters {
  20 + choice(
  21 + name: 'TEST_SUITE',
  22 + choices: ['all', 'customer', 'product', 'sale', 'consignment'],
  23 + description: '选择要执行的测试套件'
  24 + )
  25 + choice(
  26 + name: 'BROWSER',
  27 + choices: ['chromium', 'firefox', 'webkit'],
  28 + description: '选择浏览器'
  29 + )
  30 + booleanParam(
  31 + name: 'HEADLESS',
  32 + defaultValue: true,
  33 + description: '是否使用无头模式'
  34 + )
  35 + string(
  36 + name: 'GREP',
  37 + defaultValue: '',
  38 + description: '测试用例过滤(留空执行全部)'
  39 + )
  40 + }
  41 +
  42 + stages {
  43 + stage('Checkout') {
  44 + steps {
  45 + echo '📥 拉取代码...'
  46 + git branch: 'main', url: 'https://your-git-repo-url/xfbhAutoTest.git'
  47 + }
  48 + }
  49 +
  50 + stage('Install Dependencies') {
  51 + steps {
  52 + echo '📦 安装依赖...'
  53 + sh 'npm install'
  54 + }
  55 + }
  56 +
  57 + stage('Install Playwright Browsers') {
  58 + steps {
  59 + echo '🌐 安装浏览器...'
  60 + sh "npx playwright install ${params.BROWSER}"
  61 + sh "npx playwright install-deps ${params.BROWSER}"
  62 + }
  63 + }
  64 +
  65 + stage('Run Tests') {
  66 + steps {
  67 + echo '🧪 执行测试...'
  68 + script {
  69 + def testCommand = "npx playwright test"
  70 +
  71 + // 选择测试套件
  72 + if (params.TEST_SUITE != 'all') {
  73 + testCommand += " tests/${params.TEST_SUITE}.spec.ts"
  74 + }
  75 +
  76 + // 选择浏览器
  77 + testCommand += " --project=${params.BROWSER}"
  78 +
  79 + // 无头模式
  80 + if (!params.HEADLESS) {
  81 + testCommand += " --headed"
  82 + }
  83 +
  84 + // 过滤测试用例
  85 + if (params.GREP) {
  86 + testCommand += " -g \"${params.GREP}\""
  87 + }
  88 +
  89 + sh testCommand
  90 + }
  91 + }
  92 + }
  93 + }
  94 +
  95 + post {
  96 + always {
  97 + echo '📊 生成测试报告...'
  98 +
  99 + // 发布 Allure 报告
  100 + allure includeProperties: false,
  101 + jdk: '',
  102 + results: [[path: 'allure-results']]
  103 +
  104 + // 归档 Playwright HTML 报告
  105 + publishHTML(target: [
  106 + allowMissing: false,
  107 + alwaysLinkToLastBuild: false,
  108 + keepAll: true,
  109 + reportDir: 'playwright-report',
  110 + reportFiles: 'index.html',
  111 + reportName: 'Playwright Report'
  112 + ])
  113 +
  114 + // 归档测试结果
  115 + archiveArtifacts artifacts: 'playwright-report/**, test-results/**',
  116 + allowEmptyArchive: true
  117 + }
  118 +
  119 + success {
  120 + echo '✅ 测试执行成功!'
  121 + }
  122 +
  123 + failure {
  124 + echo '❌ 测试执行失败,请检查测试报告!'
  125 + }
  126 +
  127 + unstable {
  128 + echo '⚠️ 测试执行完成,但存在失败的用例!'
  129 + }
  130 + }
  131 +}
  132 +
  133 +// ============ 定时执行配置 ============
  134 +// 如需定时执行,在 pipeline 块内添加以下 triggers 配置:
  135 +//
  136 +// triggers {
  137 +// // 每天 6:00 执行(北京时间)
  138 +// cron('0 22 * * *') // UTC 22:00 = 北京时间 06:00
  139 +//
  140 +// // 或者使用轮询 SCM(代码变更时触发)
  141 +// pollSCM('H/5 * * * *') // 每5分钟检查一次代码变更
  142 +// }
  143 +
  144 +// ============ 邮件通知配置 ============
  145 +// 如需邮件通知,在 post 块中添加:
  146 +//
  147 +// emailext(
  148 +// subject: "UI自动化测试报告 - ${env.JOB_NAME} #${env.BUILD_NUMBER}",
  149 +// body: """
  150 +// <h2>测试执行完成</h2>
  151 +// <p>构建状态: ${currentBuild.currentResult}</p>
  152 +// <p>构建链接: ${env.BUILD_URL}</p>
  153 +// <p>Allure报告: ${env.BUILD_URL}allure/</p>
  154 +// <p>HTML报告: ${env.BUILD_URL}Playwright_Report/</p>
  155 +// """,
  156 +// to: 'team@example.com',
  157 +// from: 'jenkins@example.com'
  158 +// )
0 159 \ No newline at end of file
... ...
pages/basePage.ts
... ... @@ -32,7 +32,10 @@ export abstract class BasePage {
32 32 * @param path 相对路径
33 33 */
34 34 async navigate(path: string = '/'): Promise<void> {
35   - const baseURL = process.env.BASE_URL || 'https://erp-pad.test.gszdtop.com';
  35 + const baseURL = process.env.BASE_URL;
  36 + if (!baseURL) {
  37 + throw new Error('BASE_URL 环境变量未设置,请设置 BASE_URL 后再运行');
  38 + }
36 39 await this.page.goto(`${baseURL}/#${path}`);
37 40 }
38 41  
... ... @@ -172,7 +175,17 @@ export abstract class BasePage {
172 175 * @param name 截图名称
173 176 */
174 177 async takeScreenshot(name: string): Promise<Buffer> {
175   - return this.page.screenshot({ path: `screenshots/${name}.png`, fullPage: true });
  178 + // 使用项目根目录下的 screenshots 文件夹
  179 + const fs = require('fs');
  180 + const path = require('path');
  181 + const screenshotsDir = path.join(process.cwd(), 'screenshots');
  182 +
  183 + // 确保 screenshots 目录存在
  184 + if (!fs.existsSync(screenshotsDir)) {
  185 + fs.mkdirSync(screenshotsDir, { recursive: true });
  186 + }
  187 +
  188 + return this.page.screenshot({ path: path.join(screenshotsDir, `${name}.png`), fullPage: true });
176 189 }
177 190  
178 191 /**
... ...
pages/customerPage.ts
... ... @@ -80,7 +80,7 @@ export class CustomerPage extends BasePage {
80 80 constructor(page: Page) {
81 81 super(page);
82 82  
83   - this.customerMenu = page.getByText('客户管理');
  83 + this.customerMenu = page.getByText('客户管理').first();
84 84 this.addCustomerButton = page.getByText('新增客户');
85 85 this.customerNameInput = page.getByRole('textbox').nth(1);
86 86 this.phoneInput = page.getByRole('spinbutton').first();
... ... @@ -109,7 +109,7 @@ export class CustomerPage extends BasePage {
109 109 * 打开客户管理页面
110 110 */
111 111 async openCustomerManagement(): Promise<void> {
112   - await this.customerMenu.click();
  112 + await this.customerMenu.click({ force: true });
113 113 await this.page.waitForLoadState('networkidle', { timeout: 30000 });
114 114 }
115 115  
... ... @@ -131,6 +131,17 @@ export class CustomerPage extends BasePage {
131 131 }
132 132  
133 133 /**
  134 + * 清除并填写客户名称
  135 + * @param name 客户名称
  136 + */
  137 + async clearAndFillCustomerName(name: string): Promise<void> {
  138 + await this.customerNameInput.click();
  139 + // 点击清除图标
  140 + await this.page.locator('.nut-input__clear-icon').first().click();
  141 + await this.customerNameInput.fill(name);
  142 + }
  143 +
  144 + /**
134 145 * 填写手机号
135 146 * @param phone 手机号
136 147 */
... ... @@ -140,6 +151,17 @@ export class CustomerPage extends BasePage {
140 151 }
141 152  
142 153 /**
  154 + * 清除并填写手机号
  155 + * @param phone 手机号
  156 + */
  157 + async clearAndFillPhone(phone: string): Promise<void> {
  158 + await this.phoneInput.click();
  159 + // 点击手机号清除图标
  160 + await this.page.locator('uni-view:nth-child(3) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__clear > .nut-input__clear-icon').click();
  161 + await this.phoneInput.fill(phone);
  162 + }
  163 +
  164 + /**
143 165 * 填写身份证号
144 166 * @param idCard 身份证号
145 167 */
... ... @@ -149,6 +171,17 @@ export class CustomerPage extends BasePage {
149 171 }
150 172  
151 173 /**
  174 + * 清除并填写身份证号
  175 + * @param idCard 身份证号
  176 + */
  177 + async clearAndFillIdCard(idCard: string): Promise<void> {
  178 + await this.idCardInput.click();
  179 + // 点击身份证清除图标
  180 + await this.page.locator('uni-view:nth-child(4) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__clear > .nut-input__clear-icon').click();
  181 + await this.idCardInput.fill(idCard);
  182 + }
  183 +
  184 + /**
152 185 * 选择客户分组(选择"普通客户")
153 186 */
154 187 async selectCustomerGroup(): Promise<void> {
... ... @@ -320,4 +353,246 @@ export class CustomerPage extends BasePage {
320 353 return false;
321 354 }
322 355 }
323   -}
324 356 \ No newline at end of file
  357 +
  358 + // ==================== 修改/删除客户相关方法 ====================
  359 +
  360 + /**
  361 + * 搜索客户
  362 + * @param customerName 客户名称
  363 + */
  364 + async searchCustomer(customerName: string): Promise<void> {
  365 + // 点击搜索框区域(使用 force: true 绕过遮罩层)
  366 + await this.page.locator('uni-view').filter({ hasText: /^客户名称\/手机号$/ }).nth(2).click({ force: true });
  367 + // 在输入框中填写客户名称
  368 + await this.page.getByRole('textbox').fill(customerName);
  369 + await this.page.waitForTimeout(1000);
  370 + }
  371 +
  372 + /**
  373 + * 点击客户进入详情页面
  374 + * @param customerName 客户名称
  375 + */
  376 + async clickCustomerItem(customerName: string): Promise<void> {
  377 + await this.page.getByText(customerName).first().click();
  378 + await this.page.waitForLoadState('networkidle', { timeout: 30000 });
  379 + }
  380 +
  381 + /**
  382 + * 点击编辑按钮
  383 + */
  384 + async clickEditButton(): Promise<void> {
  385 + await this.page.getByText('修改客户').click();
  386 + await this.page.waitForLoadState('networkidle', { timeout: 30000 });
  387 + }
  388 +
  389 + /**
  390 + * 点击删除按钮
  391 + */
  392 + async clickDeleteButton(): Promise<void> {
  393 + await this.page.getByText('删除客户').click();
  394 + await this.page.waitForTimeout(500);
  395 + }
  396 +
  397 + /**
  398 + * 确认删除
  399 + */
  400 + async confirmDelete(): Promise<void> {
  401 + await this.page.getByText('确定', { exact: true }).click();
  402 + await this.page.waitForLoadState('networkidle', { timeout: 30000 });
  403 + }
  404 +
  405 + /**
  406 + * 修改客户 - 复用新增方法
  407 + * @param originalName 原客户名称(用于搜索)
  408 + * @param customerInfo 新的客户信息
  409 + * @param options 可选配置
  410 + */
  411 + async updateCustomer(
  412 + originalName: string,
  413 + customerInfo: {
  414 + name: string;
  415 + phone: string;
  416 + idCard: string;
  417 + detailedAddress?: string;
  418 + },
  419 + options?: {
  420 + creditLimit?: string;
  421 + licensePlate?: string;
  422 + province?: string;
  423 + city?: string;
  424 + district?: string;
  425 + imagePath?: string;
  426 + }
  427 + ): Promise<void> {
  428 + // 先返回首页确保页面状态干净(避免遮罩层阻挡)
  429 + await this.gotoHome();
  430 + // 打开客户管理并搜索
  431 + await this.openCustomerManagement();
  432 + await this.searchCustomer(originalName);
  433 + await this.clickCustomerItem(originalName);
  434 + await this.clickEditButton();
  435 +
  436 + // 使用清除并填写方法修改信息
  437 + await this.clearAndFillCustomerName(customerInfo.name);
  438 + await this.clearAndFillPhone(customerInfo.phone);
  439 + await this.clearAndFillIdCard(customerInfo.idCard);
  440 +
  441 + if (options?.creditLimit) {
  442 + // 清除并填写赊欠额度(使用 force: true 绕过遮罩层)
  443 + await this.creditLimitInput.click({ force: true });
  444 + await this.page.locator('uni-view:nth-child(8) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__clear > .nut-input__clear-icon').click({ force: true });
  445 + await this.creditLimitInput.fill(options.creditLimit);
  446 + }
  447 +
  448 + if (options?.licensePlate) {
  449 + await this.fillLicensePlate(options.licensePlate);
  450 + }
  451 +
  452 + if (options?.province && options?.city && options?.district) {
  453 + await this.selectRegion(options.province, options.city, options.district);
  454 + }
  455 +
  456 + if (customerInfo.detailedAddress) {
  457 + await this.fillDetailedAddress(customerInfo.detailedAddress);
  458 + }
  459 +
  460 + if (options?.imagePath) {
  461 + await this.uploadCustomerImage(options.imagePath);
  462 + }
  463 +
  464 + await this.saveButton.click();
  465 + await this.page.waitForLoadState('networkidle', { timeout: 30000 });
  466 + }
  467 +
  468 + /**
  469 + * 删除客户
  470 + * @param customerName 客户名称
  471 + */
  472 + async deleteCustomer(customerName: string): Promise<void> {
  473 + // 先返回首页确保页面状态干净(避免遮罩层阻挡)
  474 + await this.gotoHome();
  475 + await this.openCustomerManagement();
  476 + await this.searchCustomer(customerName);
  477 + await this.clickCustomerItem(customerName);
  478 + await this.clickDeleteButton();
  479 + await this.confirmDelete();
  480 + }
  481 +
  482 + /**
  483 + * 验证客户是否已被删除
  484 + * @param customerName 客户名称
  485 + */
  486 + async verifyCustomerDeleted(customerName: string): Promise<boolean> {
  487 + await this.gotoHome();
  488 + await this.openCustomerManagement();
  489 + await this.searchCustomer(customerName);
  490 +
  491 + const count = await this.page.getByText(customerName).count();
  492 + return count === 0;
  493 + }
  494 +
  495 + // ==================== 录入欠款相关方法 ====================
  496 +
  497 + /**
  498 + * 点击录入欠款按钮
  499 + */
  500 + async clickRecordDebt(): Promise<void> {
  501 + await this.page.getByText('录入欠款').click();
  502 + await this.page.waitForTimeout(500);
  503 + }
  504 +
  505 + /**
  506 + * 选择欠款类型
  507 + * @param typeIndex 类型索引(默认选择第三个,索引为2)
  508 + */
  509 + async selectDebtType(typeIndex: number = 2): Promise<void> {
  510 + await this.page.locator('uni-view:nth-child(4) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask').click();
  511 + await this.page.locator('uni-view').filter({ hasText: /^2$/ }).nth(typeIndex).click();
  512 + }
  513 +
  514 + /**
  515 + * 填写欠款金额
  516 + * @param amount 欠款金额
  517 + */
  518 + async fillDebtAmount(amount: string): Promise<void> {
  519 + await this.page.getByRole('spinbutton').click();
  520 + await this.page.getByRole('spinbutton').fill(amount);
  521 + }
  522 +
  523 + /**
  524 + * 填写欠款备注
  525 + * @param remark 备注
  526 + */
  527 + async fillDebtRemark(remark: string): Promise<void> {
  528 + await this.page.getByRole('textbox').nth(4).click();
  529 + await this.page.getByRole('textbox').nth(4).fill(remark);
  530 + }
  531 +
  532 + /**
  533 + * 保存欠款
  534 + */
  535 + async saveDebt(): Promise<void> {
  536 + await this.page.getByText('保存', { exact: true }).click();
  537 + await this.page.waitForLoadState('networkidle', { timeout: 30000 });
  538 + }
  539 +
  540 + /**
  541 + * 录入欠款 - 完整流程
  542 + * @param customerName 客户名称
  543 + * @param amount 欠款金额
  544 + * @param options 可选配置
  545 + */
  546 + async recordDebt(
  547 + customerName: string,
  548 + amount: string,
  549 + options?: {
  550 + typeIndex?: number; // 欠款类型索引
  551 + remark?: string; // 备注
  552 + imagePath?: string; // 图片路径
  553 + }
  554 + ): Promise<void> {
  555 + // 先返回首页确保页面状态干净(避免遮罩层阻挡)
  556 + await this.gotoHome();
  557 + // 打开客户管理并搜索
  558 + await this.openCustomerManagement();
  559 + await this.searchCustomer(customerName);
  560 + await this.clickCustomerItem(customerName);
  561 +
  562 + // 点击录入欠款
  563 + await this.clickRecordDebt();
  564 +
  565 + // 选择欠款类型
  566 + if (options?.typeIndex !== undefined) {
  567 + await this.selectDebtType(options.typeIndex);
  568 + }
  569 +
  570 + // 填写欠款金额
  571 + await this.fillDebtAmount(amount);
  572 +
  573 + // 填写备注
  574 + if (options?.remark) {
  575 + await this.fillDebtRemark(options.remark);
  576 + }
  577 +
  578 + // 上传图片 - 使用基类封装的方法
  579 + if (options?.imagePath) {
  580 + await this.uploadCustomerImage(options.imagePath);
  581 + }
  582 +
  583 + // 保存
  584 + await this.saveDebt();
  585 + }
  586 +
  587 + /**
  588 + * 验证欠款是否录入成功
  589 + * @param amount 欠款金额
  590 + */
  591 + async verifyDebtRecorded(amount: string): Promise<boolean> {
  592 + try {
  593 + await this.page.getByText(`欠款:${amount}.00元`).waitFor({ timeout: 10000 });
  594 + return true;
  595 + } catch {
  596 + return false;
  597 + }
  598 + }
  599 +}
... ...
pages/loginPage.ts
... ... @@ -131,16 +131,20 @@ export class LoginPage extends BasePage {
131 131 /**
132 132 * 完整的登录流程(半自动)
133 133 * @param phone 手机号
134   - * @param userName 期望的用户名(用于验证登录成功
  134 + * @param userName 期望的用户名(用于验证登录成功,可选,默认从环境变量 TEST_USER_NAME 获取
135 135 * @param authFilePath 认证文件保存路径
136 136 */
137 137 async performLogin(
138   - phone: string,
139   - userName: string = '赵xt',
  138 + phone: string,
  139 + userName?: string,
140 140 authFilePath: string = 'auth.json'
141 141 ): Promise<void> {
  142 + const finalUserName = userName ?? process.env.TEST_USER_NAME;
  143 + if (!finalUserName) {
  144 + throw new Error('userName 参数未提供且 TEST_USER_NAME 环境变量未设置,请设置其中之一后再运行');
  145 + }
142 146 await this.loginWithPhone(phone);
143   - await this.waitForLoginSuccess(userName);
  147 + await this.waitForLoginSuccess(finalUserName);
144 148 await this.saveAuthState(authFilePath);
145 149 }
146 150 }
147 151 \ No newline at end of file
... ...
scripts/save-auth.ts
1 1 // 半自动登录,登录存放auth.json
2 2 import { test, expect } from '@playwright/test';
3 3  
  4 +// 从环境变量获取配置
  5 +const TEST_PHONE = process.env.phone;
  6 +const TEST_USER_NAME = process.env.TEST_USER_NAME;
  7 +
  8 +if (!TEST_PHONE) {
  9 + throw new Error('phone 环境变量未设置,请设置 phone 后再运行');
  10 +}
  11 +if (!TEST_USER_NAME) {
  12 + throw new Error('TEST_USER_NAME 环境变量未设置,请设置 TEST_USER_NAME 后再运行');
  13 +}
  14 +
4 15 test('半自动登录(自动填手机号,手动输验证码)', async ({ page }) => {
5 16 await page.goto('/#/pages/login/index');
6 17 await page.getByText('手机号登录/注册').click();
7 18 await page.getByText('确定').click();
8 19 // --- 这里执行登录操作 ---
9 20 await page.locator('uni-input').filter({ hasText: '请输入手机号' }).getByRole('textbox').click();
10   - await page.locator('uni-input').filter({ hasText: '请输入手机号' }).getByRole('textbox').fill('13548301969');
  21 + await page.locator('uni-input').filter({ hasText: '请输入手机号' }).getByRole('textbox').fill(TEST_PHONE);
11 22 // 此处假设出现了验证码,你需要手动输入或处理
12 23 await page.locator('uni-button').filter({ hasText: '获取验证码' }).click();
13 24 console.log('请手动输入验证码,然后点击登录按钮...');
14 25 await page.locator('uni-button').filter({ hasText: '登录' }).click();
15 26 // --- 等待登录成功,跳转到首页 ---
16   - await expect(page.getByText('赵xt')).toBeVisible({ timeout: 0 });
  27 + await expect(page.getByText(TEST_USER_NAME)).toBeVisible({ timeout: 0 });
17 28  
18 29 // 4. 保存存储状态(Cookie 和 LocalStorage)
19 30 await page.context().storageState({ path: 'auth.json' });
... ...
tests/customer.spec.ts
1 1 import { test, expect } from '../fixtures';
2   -import { generateCustomerName, generateIdCard, generatePhoneNumber, generateDetailedAddress, generateCustomerInfo, getRandomImage } from '../utils/dataGenerator';
  2 +import { generateCustomerName, generateIdCard, generatePhoneNumber, generateDetailedAddress, generateCustomerInfo, getRandomImage, generateRemark, generateAmount } from '../utils/dataGenerator';
3 3 import * as allure from 'allure-js-commons';
4 4  
5 5 /**
6 6 * 客户管理测试
7 7 */
  8 +// 新增客户
8 9 test.describe('客户管理', () => {
9 10 // 使用已保存的认证状态
10 11 test.use({ storageState: 'auth.json' });
... ... @@ -61,75 +62,145 @@ test.describe(&#39;客户管理&#39;, () =&gt; {
61 62 });
62 63 });
63 64  
64   -// test('新增客户 - 仅必填信息', async ({ customerPage }, testInfo) => {
  65 +// test('修改客户', async ({ customerPage }, testInfo) => {
65 66 // // 添加allure元素
66 67 // await allure.epic('客户管理');
67 68 // await allure.feature('客户信息');
68   -// await allure.story('创建新客户(仅必填信息)');
  69 +// await allure.story('修改客户');
69 70  
70   -// // 步骤1:生成随机客户信息
71   -// const customerInfo = await allure.step('生成随机客户信息', async (step) => {
72   -// const info = generateCustomerInfo();
73   -// console.log('生成的客户信息:', info);
74   -// return info;
75   -// });
  71 +// // 先创建一个客户用于修改
  72 +// const originalName = generateCustomerName();
  73 +// const originalPhone = generatePhoneNumber();
  74 +// const originalIdCard = generateIdCard();
  75 +
  76 +// console.log('原客户名称:', originalName);
76 77  
77   -// // 步骤2:执行新增客户操作
78   -// await allure.step('填写并提交客户表单(仅必填信息)', async () => {
  78 +// // 步骤1:先创建客户
  79 +// await allure.step('创建待修改的客户', async () => {
79 80 // await customerPage.gotoHome();
80 81 // await customerPage.createCustomer({
81   -// name: customerInfo.name,
82   -// phone: customerInfo.phone,
83   -// idCard: customerInfo.idCard,
  82 +// name: originalName,
  83 +// phone: originalPhone,
  84 +// idCard: originalIdCard,
84 85 // });
85   -// await customerPage.attachScreenshot(testInfo, '新增客户成功截图');
86 86 // });
87 87  
88   -// // 步骤3:验证客户创建成功
89   -// await allure.step('验证客户创建成功', async () => {
90   -// const isCreated = await customerPage.verifyCustomerCreated(customerInfo.name);
91   -// expect(isCreated).toBeTruthy();
  88 +// // 生成新的客户信息
  89 +// const newName = generateCustomerName();
  90 +// const newPhone = generatePhoneNumber();
  91 +// const newIdCard = generateIdCard();
  92 +// const randomImage = getRandomImage();
  93 +
  94 +// console.log('新客户名称:', newName);
  95 +
  96 +// // 步骤2:执行修改客户操作
  97 +// await allure.step('修改客户信息', async () => {
  98 +// await customerPage.updateCustomer(
  99 +// originalName,
  100 +// {
  101 +// name: newName,
  102 +// phone: newPhone,
  103 +// idCard: newIdCard,
  104 +// },
  105 +// {
  106 +// creditLimit: '400',
  107 +// licensePlate: '渝YUNI99',
  108 +// province: '江苏省',
  109 +// city: '连云港市',
  110 +// district: '海州区',
  111 +// imagePath: randomImage || undefined,
  112 +// }
  113 +// );
  114 +// await customerPage.attachScreenshot(testInfo, '修改客户成功截图');
  115 +// });
  116 +
  117 +// // 步骤3:验证客户修改成功
  118 +// await allure.step('验证客户修改成功', async () => {
  119 +// const isUpdated = await customerPage.verifyCustomerCreated(newName);
  120 +// expect(isUpdated).toBeTruthy();
92 121 // });
93 122 // });
94 123  
95   -// test('新增客户 - 带详细地址', async ({ customerPage }, testInfo) => {
  124 +// test('删除客户', async ({ customerPage }, testInfo) => {
96 125 // // 添加allure元素
97 126 // await allure.epic('客户管理');
98 127 // await allure.feature('客户信息');
99   -// await allure.story('创建新客户(带详细地址)');
100   -
101   -// // 步骤1:生成随机客户信息
102   -// const customerInfo = await allure.step('生成随机客户信息', async (step) => {
103   -// const name = generateCustomerName();
104   -// const phone = generatePhoneNumber();
105   -// const idCard = generateIdCard();
106   -// const detailedAddress = generateDetailedAddress();
107   -
108   -// console.log('生成的客户信息:', { name, phone, idCard, detailedAddress });
109   -
110   -// return { name, phone, idCard, detailedAddress };
  128 +// await allure.story('删除客户');
  129 +
  130 +// // 先创建一个客户用于删除
  131 +// const customerName = generateCustomerName();
  132 +// const phone = generatePhoneNumber();
  133 +// const idCard = generateIdCard();
  134 +
  135 +// console.log('待删除客户名称:', customerName);
  136 +
  137 +// // 步骤1:先创建客户
  138 +// await allure.step('创建待删除的客户', async () => {
  139 +// await customerPage.gotoHome();
  140 +// await customerPage.createCustomer({
  141 +// name: customerName,
  142 +// phone: phone,
  143 +// idCard: idCard,
  144 +// });
  145 +// });
  146 +
  147 +// // 步骤2:执行删除客户操作
  148 +// await allure.step('删除客户', async () => {
  149 +// await customerPage.deleteCustomer(customerName);
  150 +// await customerPage.attachScreenshot(testInfo, '删除客户成功截图');
111 151 // });
112 152  
113   -// // 步骤2:执行新增客户操作
114   -// await allure.step('填写并提交客户表单', async () => {
  153 +// // 步骤3:验证客户删除成功
  154 +// await allure.step('验证客户删除成功', async () => {
  155 +// const isDeleted = await customerPage.verifyCustomerDeleted(customerName);
  156 +// expect(isDeleted).toBeTruthy();
  157 +// });
  158 +// });
  159 +
  160 +// test('录入欠款', async ({ customerPage }, testInfo) => {
  161 +// // 添加allure元素
  162 +// await allure.epic('客户管理');
  163 +// await allure.feature('客户欠款');
  164 +// await allure.story('录入欠款');
  165 +
  166 +// // 先创建一个客户用于录入欠款
  167 +// const customerName = generateCustomerName();
  168 +// const phone = generatePhoneNumber();
  169 +// const idCard = generateIdCard();
  170 +
  171 +// console.log('待录入欠款的客户名称:', customerName);
  172 +
  173 +// // 步骤1:先创建客户
  174 +// await allure.step('创建客户', async () => {
115 175 // await customerPage.gotoHome();
116   -// await customerPage.createCustomer(
117   -// {
118   -// name: customerInfo.name,
119   -// phone: customerInfo.phone,
120   -// idCard: customerInfo.idCard,
121   -// },
122   -// {
123   -// creditLimit: '10000',
124   -// }
125   -// );
126   -// await customerPage.attachScreenshot(testInfo, '新增客户成功截图');
  176 +// await customerPage.createCustomer({
  177 +// name: customerName,
  178 +// phone: phone,
  179 +// idCard: idCard,
  180 +// });
  181 +// });
  182 +
  183 +// // 生成欠款金额和备注
  184 +// const debtAmount = generateAmount(100, 9999);
  185 +// const remark = generateRemark(50);
  186 +// const randomImage = getRandomImage();
  187 +
  188 +// console.log('欠款金额:', debtAmount);
  189 +// console.log('备注:', remark);
  190 +
  191 +// // 步骤2:执行录入欠款操作
  192 +// await allure.step('录入欠款', async () => {
  193 +// await customerPage.recordDebt(customerName, debtAmount, {
  194 +// remark: remark,
  195 +// imagePath: randomImage || undefined,
  196 +// });
  197 +// await customerPage.attachScreenshot(testInfo, '录入欠款成功截图');
127 198 // });
128 199  
129   -// // 步骤3:验证客户创建成功
130   -// await allure.step('验证客户创建成功', async () => {
131   -// const isCreated = await customerPage.verifyCustomerCreated(customerInfo.name);
132   -// expect(isCreated).toBeTruthy();
  200 +// // 步骤3:验证欠款录入成功
  201 +// await allure.step('验证欠款录入成功', async () => {
  202 +// const isRecorded = await customerPage.verifyDebtRecorded(debtAmount);
  203 +// expect(isRecorded).toBeTruthy();
133 204 // });
134 205 // });
135 206 });
136 207 \ No newline at end of file
... ...
tests/login.setup.ts
... ... @@ -3,6 +3,18 @@ import path from &#39;path&#39;;
3 3  
4 4 const authFile = path.join(__dirname, '../auth.json');
5 5  
  6 +// 从环境变量获取配置
  7 +const BASE_URL = process.env.BASE_URL;
  8 +const TEST_USER_NAME = process.env.TEST_USER_NAME;
  9 +
  10 +if (!BASE_URL) {
  11 + throw new Error('BASE_URL 环境变量未设置,请设置 BASE_URL 后再运行');
  12 +}
  13 +
  14 +if (!TEST_USER_NAME) {
  15 + throw new Error('TEST_USER_NAME 环境变量未设置,请设置 TEST_USER_NAME 后再运行');
  16 +}
  17 +
6 18 setup('authenticate', async ({ page }) => {
7 19 // 1. 先检查 auth.json 是否存在
8 20 // 如果存在,并且你想让它自动跳过,可以加一个判断。这里简单起见,总是执行手动登录。
... ... @@ -11,13 +23,13 @@ setup(&#39;authenticate&#39;, async ({ page }) =&gt; {
11 23 console.log('开始执行认证设置...');
12 24  
13 25 // 2. 访问登录页
14   - await page.goto('https://erp-pad.test.gszdtop.com/#/');
  26 + await page.goto(`${BASE_URL}/#/`);
15 27  
16 28 // 3. 手动登录(这里可以加一些提示)
17 29 //console.log('请手动完成手机验证码登录...');
18 30  
19 31 // 4. 等待登录成功,检测某个登录后才会出现的元素
20   - await expect(page.getByText('赵xt')).toBeVisible();
  32 + await expect(page.getByText(TEST_USER_NAME)).toBeVisible();
21 33 // await page.waitForSelector('text=个人中心', { timeout: 60000 });
22 34  
23 35 // 5. 登录成功后,保存状态
... ...
utils/dataGenerator.ts
... ... @@ -173,3 +173,32 @@ export function getRandomImage(imageDir: string = &#39;test-data/img&#39;): string | nul
173 173 return null;
174 174 }
175 175 }
  176 +
  177 +// 随机生成备注(50字以内)
  178 +export function generateRemark(maxLength: number = 50): string {
  179 + const phrases = [
  180 + '自动化测试数据',
  181 + '测试备注信息',
  182 + '系统自动生成',
  183 + '功能验证数据',
  184 + '接口测试记录',
  185 + '数据录入测试',
  186 + '业务流程验证',
  187 + '质量保证测试',
  188 + '回归测试数据',
  189 + '冒烟测试记录'
  190 + ];
  191 +
  192 + const randomPhrase = phrases[Math.floor(Math.random() * phrases.length)];
  193 + const timestamp = Date.now().toString().slice(-6);
  194 + const remark = `${randomPhrase}_${timestamp}`;
  195 +
  196 + // 确保不超过最大长度
  197 + return remark.slice(0, maxLength);
  198 +}
  199 +
  200 +// 随机生成金额(1-9999)
  201 +export function generateAmount(min: number = 1, max: number = 9999): string {
  202 + const amount = Math.floor(Math.random() * (max - min + 1)) + min;
  203 + return amount.toString();
  204 +}
... ...