supplierPage.ts
15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
import { Page, Locator, expect } from '@playwright/test';
import { BasePage } from './basePage';
import { generateSupplierName, generatePhoneNumber, generateChineseName } from '../utils/dataGenerator';
/**
* 供应商信息接口
*/
export interface SupplierInfo {
name: string;
managerName: string;
phone: string;
remark?: string;
}
/**
* 供应商选项接口
*/
export interface SupplierOptions {
enableParkCard?: boolean;
parkCardNumber?: string;
parkCardChannel?: string;
}
/**
* 供应商管理页面选择器
*/
const selectors = {
// 导航
moreMenu: 'text=更多 >',
supplierMenu: 'text=供应商管理',
addSupplierButton: 'text=新建供应商',
// 表单字段
supplierNameInput: 'uni-input:has-text("请输入供应商名称") input',
managerNameInput: 'uni-input:has-text("请输入负责人名称") input',
phoneInput: 'spinbutton',
// 供应商分组选择器
groupPicker: '.nut-input__mask',
groupOption: 'text=普通供应商',
// 备注
remarkInput: 'uni-input:has-text("请输入备注") input',
// 园区卡
parkCardSearchInput: 'uni-view:nth-child(2) > .nut-searchbar__search-input',
queryButton: 'text=查询',
bindButton: 'uni-button',
// 操作按钮
confirmButton: 'text=确定',
saveButton: 'text=确定',
// 供应商列表
supplierList: '.supplier-list',
supplierItem: '.supplier-list .item',
supplierItemClick: '.supplier-list .item-click',
};
/**
* 供应商管理页面类
* 处理供应商管理相关操作
*/
export class SupplierPage extends BasePage {
// 导航定位器
readonly moreMenu!: Locator;
readonly supplierMenu: Locator;
readonly addSupplierButton: Locator;
// 表单字段
readonly supplierNameInput: Locator;
readonly managerNameInput: Locator;
readonly phoneInput: Locator;
// 供应商分组
readonly groupPicker: Locator;
readonly groupOption: Locator;
// 备注
readonly remarkInput: Locator;
// 园区卡
readonly parkCardSearchInput: Locator;
readonly queryButton: Locator;
readonly bindButton: Locator;
// 操作按钮
readonly confirmButton: Locator;
readonly saveButton: Locator;
constructor(page: Page) {
super(page);
// 导航
this.moreMenu = page.getByText('更多 >');
this.supplierMenu = page.getByText('供应商管理').first();
this.addSupplierButton = page.getByText('新建供应商');
// 表单字段
this.supplierNameInput = page.locator('uni-input').filter({ hasText: '请输入供应商名称' }).getByRole('textbox');
this.managerNameInput = page.locator('uni-input').filter({ hasText: '请输入负责人名称' }).getByRole('textbox');
this.phoneInput = page.getByRole('spinbutton');
this.remarkInput = page.locator('uni-input').filter({ hasText: '请输入备注' }).getByRole('textbox');
// 供应商分组
this.groupPicker = page.locator('.nut-input__mask').first();
this.groupOption = page.locator('.select-item');
// 园区卡
this.parkCardSearchInput = page.locator('uni-view:nth-child(2) > .nut-searchbar__search-input > .nut-searchbar__input-inner > .nut-searchbar__input-form > span > .nut-searchbar__input-bar > .uni-input-wrapper > .uni-input-input');
this.queryButton = page.getByText('查询', { exact: true });
this.bindButton = page.locator('uni-button').filter({ hasText: '绑定' });
// 操作按钮
this.confirmButton = page.getByText('确定');
this.saveButton = page.getByText('确定');
}
/**
* 导航到首页并等待加载
*/
async gotoHome(): Promise<void> {
await this.navigate('/');
await this.waitForPageLoad();
}
/**
* 打开更多菜单
*/
async openMoreMenu(): Promise<void> {
await this.moreMenu.click();
await this.wait(500);
}
/**
* 打开供应商管理页面
*/
async openSupplierManagement(): Promise<void> {
await this.supplierMenu.click({ force: true });
await this.waitForPageLoad();
await this.wait(500);
}
/**
* 点击新建供应商按钮
*/
async clickAddSupplier(): Promise<void> {
await this.addSupplierButton.click();
await this.waitForPageLoad();
}
/**
* 点击编辑按钮
*/
async clickEditButton(): Promise<void> {
await this.page.getByText('编辑').click();
await this.waitForPageLoad();
}
/**
* 点击确定/保存按钮
*/
async clickConfirmButton(): Promise<void> {
await this.confirmButton.click();
await this.waitForPageLoad();
}
/**
* 填写表单字段(通用方法)
* @param locator 表单字段定位器
* @param value 填写值
*/
async fillFormField(locator: Locator, value: string): Promise<void> {
await locator.click();
await locator.fill(value);
}
/**
* 清除并填写表单字段(通用方法)
* @param locator 表单字段定位器
* @param value 填写值
*/
async clearAndFillFormField(locator: Locator, value: string): Promise<void> {
await locator.click();
await locator.fill('');
await locator.fill(value);
}
/**
* 点击选择器弹窗(通用方法,用于供应商分组、园区卡等)
* @param index 选择器索引(0=第一个,1=第二个)
*/
async clickPickerSelector(index: number = 0): Promise<void> {
await this.page.locator('.nut-form-item__body__slots .nut-input__mask').nth(index).click();
await this.wait(500);
}
/**
* 选择供应商类型(必选字段)
* @param type 供应商类型(默认"代卖")
*/
async selectSupplierType(type: string = '代卖'): Promise<void> {
await this.page.locator('.nut-form-item__body__slots > .nut-checkbox-group > uni-view').first().click();
await this.wait(500);
}
/**
* 选择供应商分组
*/
async selectGroup(): Promise<void> {
await this.groupPicker.first().click();
await this.wait(500);
await this.groupOption.click();
await this.wait(500);
}
/**
* 选择园区卡渠道
* @param channel 渠道名称
*/
async selectParkCardChannel(channel: string = '地利'): Promise<void> {
await this.page.locator('uni-view').filter({ hasText: new RegExp(`^${channel}$`) }).first().click();
await this.wait(500);
}
/**
* 搜索园区卡
* @param cardNumber 园区卡号
*/
async searchParkCard(cardNumber: string): Promise<void> {
await this.parkCardSearchInput.click();
await this.parkCardSearchInput.fill(cardNumber);
await this.queryButton.click();
await this.wait(500);
}
/**
* 绑定园区卡
*/
async bindParkCard(): Promise<void> {
await this.bindButton.click();
await this.waitForPageLoad();
await this.wait(500);
}
/**
* 选择园区卡(完整流程)
* @param cardNumber 园区卡号
* @param channel 渠道名称(可选)
*/
async selectParkCard(cardNumber: string, channel?: string): Promise<void> {
// 点击园区卡输入框(第二个,因为第一个是供应商分组)
await this.clickPickerSelector(1);
// 选择渠道(如果指定)
if (channel) {
await this.selectParkCardChannel(channel);
}
// 搜索并绑定
await this.searchParkCard(cardNumber);
await this.bindParkCard();
}
/**
* 点击供应商列表项
* @param index 列表索引(0=第一个.item,1=第二个.item)
*/
async clickSupplierItemByIndex(index: number = 0): Promise<void> {
await this.page.locator(selectors.supplierItem).nth(index).click();
}
/**
* 搜索供应商
* @param supplierName 供应商名称
*/
async searchSupplier(supplierName: string): Promise<void> {
await this.page.getByRole('textbox').click();
const input = this.page.locator('uni-input').filter({ hasText: '供应商名称/联系电话' }).getByRole('textbox');
await input.fill(supplierName);
await this.page.waitForTimeout(1500);
await input.press('Enter');
await this.page.waitForTimeout(1000);
}
/**
* 进入供应商管理页面(完整流程)
*/
async navigateToSupplierManagement(): Promise<void> {
await this.gotoHome();
await this.openMoreMenu();
await this.openSupplierManagement();
}
/**
* 验证供应商是否创建成功
* @param supplierName 供应商名称
*/
async verifySupplierCreated(supplierName: string): Promise<boolean> {
try {
await this.navigateToSupplierManagement();
await this.searchSupplier(supplierName);
await this.page.waitForSelector(`uni-view:has-text("${supplierName}")`, { timeout: 10000 });
return true;
} catch {
return false;
}
}
/**
* 点击删除按钮
*/
async clickDeleteButton(): Promise<void> {
await this.page.getByText('删除').click();
}
/**
* 确认删除
*/
async confirmDelete(): Promise<void> {
await this.page.getByText('确定', { exact: true }).click();
}
/**
* 点击录入欠款按钮
*/
async clickRecordDebt(): Promise<void> {
await this.page.getByText('录入欠款').click();
}
/**
* 点击返回按钮
*/
async clickBackButton(): Promise<void> {
await this.page.locator('.uni-icons.uniui-left').click();
await this.wait(300);
}
/**
* 选择欠款方向
* @param direction 欠款方向('我方欠供应商' 或 '供应商欠我方')
*/
async selectDebtDirection(direction: 'oweSupplier' | 'supplierOwe'): Promise<void> {
const text = direction === 'oweSupplier' ? '我方欠供应商' : '供应商欠我方';
await this.page.getByText(text, { exact: true }).click();
}
/**
* 选择赊欠日期
* @param day 日期(1-31),不传则选择今日
*/
async selectDebtDate(day?: number): Promise<void> {
await this.page.getByText('赊欠日期*请选择赊欠日期').click();
await this.wait(300);
if (day) {
await this.page.getByText(`${day}日`).click();
} else {
const today = new Date().getDate();
await this.page.getByText(`${today}today`).click();
}
await this.wait(300);
}
/**
* 填写赊欠金额
* @param amount 金额
*/
async fillDebtAmount(amount: string): Promise<void> {
const amountMask = this.page.locator('uni-view:nth-child(6) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask');
await amountMask.click();
await this.wait(300);
await this.page.getByText('1', { exact: true }).click();
await this.page.getByText('0', { exact: true }).nth(0).click();
await this.page.getByText('0', { exact: true }).nth(0).click();
await this.page.getByText('完成').click();
}
/**
* 填写备注
* @param remark 备注内容
*/
async fillRemark(remark: string): Promise<void> {
const remarkInput = this.page.locator('uni-view:nth-child(7) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__input > .uni-input-wrapper > .uni-input-input');
await remarkInput.click();
await remarkInput.fill(remark);
}
/**
* 上传欠款凭证
* @param imagePath 图片路径
*/
async uploadDebtVoucher(imagePath: string): Promise<void> {
await this.page.locator('body').setInputFiles(imagePath);
}
/**
* 保存欠款记录
*/
async saveDebt(): Promise<void> {
await this.page.getByText('保存').click();
await this.waitForPageLoad();
}
/**
* 获取供应商名称(从详情页)
*/
async getSupplierName(): Promise<string> {
// 供应商名称通常显示在页面顶部,可能是 "供应商名称" 标签后面
const nameElement = this.page.locator('uni-view').filter({ hasText: /^供应商名称/ }).locator('..').locator('uni-view').last();
const name = await nameElement.textContent();
return name || '';
}
/**
* 获取应付金额
* @returns 应付金额字符串,如 '¥ 221300.01'
*/
async getPayableAmount(): Promise<string> {
const amountText = await this.page.getByText(/应付 ¥/).textContent();
return amountText || '';
}
/**
* 解析应付金额为数字
* @returns 应付金额数值
*/
async parsePayableAmount(): Promise<number> {
const amountText = await this.getPayableAmount();
const match = amountText.match(/¥ ?([\d.]+)/);
return match ? parseFloat(match[1]) : 0;
}
/**
* 验证欠款录入成功(检查应付金额是否增加)
* @param originalAmount 原始应付金额
* @param addedAmount 新增金额
* @returns 是否验证成功
*/
async verifyDebtRecorded(originalAmount: number, addedAmount: number): Promise<boolean> {
try {
const currentAmount = await this.parsePayableAmount();
// 允许小幅误差(由于金额格式或四舍五入)
const expectedAmount = originalAmount + addedAmount;
return Math.abs(currentAmount - expectedAmount) < 0.01;
} catch {
return false;
}
}
/**
* 点击供应商的金额显示区域
* @param index 供应商索引
*/
async clickSupplierAmount(index: number): Promise<void> {
await this.page.getByText('¥').nth(index).click();
}
/**
* 获取供应商列表中所有供应商的金额信息
* @returns 金额数组
*/
async getSupplierAmounts(): Promise<number[]> {
const yuanElements = await this.page.getByText('¥').all();
const amounts: number[] = [];
for (const element of yuanElements) {
const text = await element.textContent();
const match = text?.match(/¥(\d+)/);
if (match) {
amounts.push(parseInt(match[1], 10));
}
}
return amounts;
}
/**
* 查找符合条件的供应商(所有金额都是0)
* @returns 是否找到符合条件的供应商
*/
async clickSupplierWithZeroAmounts(): Promise<boolean> {
// 获取所有供应商项
const supplierItems = await this.page.locator(selectors.supplierItem).all();
for (let i = 0; i < supplierItems.length; i++) {
// 点击供应商列表项
await this.clickSupplierItemByIndex(i);
await this.wait(300);
// 获取所有金额文本,检查是否都为'¥ 0'
const yuanTexts = await this.page.locator('text=¥').allTextContents();
// 检查所有金额是否都是'¥ 0'
const allZero = yuanTexts.every(text => {
const trimmed = text.trim();
return trimmed === '¥ 0.00' || trimmed === '¥0.00';
});
// 如果全为0,返回true(保持在当前页面继续删除流程)
if (allZero && yuanTexts.length > 0) {
return true;
}
// 如果不全为0,继续尝试下一个供应商(不返回页面,直接点击下一个)
}
return false;
}
/**
* 生成随机供应商信息
* @param options 可选配置
*/
async generateRandomSupplierInfo(options?: {
namePrefix?: string;
managerName?: string;
}): Promise<SupplierInfo> {
const timestamp = Date.now().toString().slice(-6);
const name = options?.namePrefix
? `${options.namePrefix}_${timestamp}`
: generateSupplierName();
const managerName = options?.managerName || generateChineseName();
return {
name,
managerName,
phone: generatePhoneNumber(),
remark: `自动化测试_${timestamp}`,
};
}
}