Commit e066ca9c6468250e45728029096f8183cf2599b3
1 parent
4ae5c8c5
新增采购入库付款、存草稿用例;新增商品分类相关用例
Showing
5 changed files
with
1258 additions
and
0 deletions
fixtures/testFixture.ts
| 1 | import { test as base, Page } from '@playwright/test'; | 1 | import { test as base, Page } from '@playwright/test'; |
| 2 | import { LoginPage } from '../pages/loginPage'; | 2 | import { LoginPage } from '../pages/loginPage'; |
| 3 | import { ProductPage } from '../pages/productPage'; | 3 | import { ProductPage } from '../pages/productPage'; |
| 4 | +import { ProductCategoryPage } from '../pages/productCategoryPage'; | ||
| 4 | import { SalePage } from '../pages/salePage'; | 5 | import { SalePage } from '../pages/salePage'; |
| 5 | import { ConsignmentPage } from '../pages/consignmentPage'; | 6 | import { ConsignmentPage } from '../pages/consignmentPage'; |
| 6 | import { CustomerPage } from '../pages/customerPage'; | 7 | import { CustomerPage } from '../pages/customerPage'; |
| 8 | +import { PurchasePage } from '../pages/purchasePage'; | ||
| 7 | 9 | ||
| 8 | /** | 10 | /** |
| 9 | * 页面对象夹具类型定义 | 11 | * 页面对象夹具类型定义 |
| @@ -20,6 +22,11 @@ export type PageFixtures = { | @@ -20,6 +22,11 @@ export type PageFixtures = { | ||
| 20 | productPage: ProductPage; | 22 | productPage: ProductPage; |
| 21 | 23 | ||
| 22 | /** | 24 | /** |
| 25 | + * 商品分类页面 | ||
| 26 | + */ | ||
| 27 | + // productCategoryPage: ProductCategoryPage; | ||
| 28 | + | ||
| 29 | + /** | ||
| 23 | * 销售开单页面 | 30 | * 销售开单页面 |
| 24 | */ | 31 | */ |
| 25 | salePage: SalePage; | 32 | salePage: SalePage; |
| @@ -33,6 +40,11 @@ export type PageFixtures = { | @@ -33,6 +40,11 @@ export type PageFixtures = { | ||
| 33 | * 客户管理页面 | 40 | * 客户管理页面 |
| 34 | */ | 41 | */ |
| 35 | customerPage: CustomerPage; | 42 | customerPage: CustomerPage; |
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * 采购入库页面 | ||
| 46 | + */ | ||
| 47 | + purchasePage: PurchasePage; | ||
| 36 | }; | 48 | }; |
| 37 | 49 | ||
| 38 | /** | 50 | /** |
| @@ -49,6 +61,11 @@ export const test = base.extend<PageFixtures>({ | @@ -49,6 +61,11 @@ export const test = base.extend<PageFixtures>({ | ||
| 49 | await use(new ProductPage(page)); | 61 | await use(new ProductPage(page)); |
| 50 | }, | 62 | }, |
| 51 | 63 | ||
| 64 | + // // 商品分类页面 | ||
| 65 | + // productCategoryPage: async ({ page }, use) => { | ||
| 66 | + // await use(new ProductCategoryPage(page)); | ||
| 67 | + // }, | ||
| 68 | + | ||
| 52 | // 销售开单页面 | 69 | // 销售开单页面 |
| 53 | salePage: async ({ page }, use) => { | 70 | salePage: async ({ page }, use) => { |
| 54 | await use(new SalePage(page)); | 71 | await use(new SalePage(page)); |
| @@ -63,6 +80,11 @@ export const test = base.extend<PageFixtures>({ | @@ -63,6 +80,11 @@ export const test = base.extend<PageFixtures>({ | ||
| 63 | customerPage: async ({ page }, use) => { | 80 | customerPage: async ({ page }, use) => { |
| 64 | await use(new CustomerPage(page)); | 81 | await use(new CustomerPage(page)); |
| 65 | }, | 82 | }, |
| 83 | + | ||
| 84 | + // 采购入库页面 | ||
| 85 | + purchasePage: async ({ page }, use) => { | ||
| 86 | + await use(new PurchasePage(page)); | ||
| 87 | + }, | ||
| 66 | }); | 88 | }); |
| 67 | 89 | ||
| 68 | /** | 90 | /** |
pages/productCategoryPage.ts
0 → 100644
| 1 | +import { Page, Locator, expect } from '@playwright/test'; | ||
| 2 | +import { BasePage } from './basePage'; | ||
| 3 | + | ||
| 4 | +/** | ||
| 5 | + * 商品分类页面选择器 | ||
| 6 | + */ | ||
| 7 | +const selectors = { | ||
| 8 | + // 导航 | ||
| 9 | + moreMenu: 'text=更多 >', | ||
| 10 | + categoryMenu: 'text=商品分类', | ||
| 11 | + | ||
| 12 | + // 功能按钮 | ||
| 13 | + addCategoryButton: 'text=新增分类', | ||
| 14 | + editCategoryButton: 'text=修改分类', | ||
| 15 | + confirmButton: 'text=确定', | ||
| 16 | + clearSearchButton: '.nut-searchbar__search-icon', | ||
| 17 | + | ||
| 18 | + // 表单 | ||
| 19 | + categoryNameInput: 'textbox', | ||
| 20 | + categoryDescriptionInput: 'textbox', | ||
| 21 | + categoryStatusSwitch: '.nut-switch > .nut-switch-button', | ||
| 22 | + | ||
| 23 | + // 搜索 | ||
| 24 | + searchInput: 'textbox', | ||
| 25 | + | ||
| 26 | + // 列表 | ||
| 27 | + categoryItem: (categoryName: string) => `span:has-text("${categoryName}")`, | ||
| 28 | +}; | ||
| 29 | + | ||
| 30 | +/** | ||
| 31 | + * 商品分类页面类 | ||
| 32 | + * 处理商品分类相关操作 | ||
| 33 | + */ | ||
| 34 | +export class ProductCategoryPage extends BasePage { | ||
| 35 | + // 导航定位器 | ||
| 36 | + readonly moreMenu: Locator; | ||
| 37 | + readonly categoryMenu: Locator; | ||
| 38 | + | ||
| 39 | + // 功能按钮 | ||
| 40 | + readonly addCategoryButton: Locator; | ||
| 41 | + readonly editCategoryButton: Locator; | ||
| 42 | + readonly deleteCategoryButton: Locator; | ||
| 43 | + readonly confirmButton: Locator; | ||
| 44 | + readonly clearSearchButton: Locator; | ||
| 45 | + | ||
| 46 | + // 表单定位器 | ||
| 47 | + readonly categoryNameInput: Locator; | ||
| 48 | + readonly categoryDescriptionInput: Locator; | ||
| 49 | + readonly categoryStatusSwitch: Locator; | ||
| 50 | + | ||
| 51 | + // 搜索 | ||
| 52 | + readonly searchInput: Locator; | ||
| 53 | + | ||
| 54 | + constructor(page: Page) { | ||
| 55 | + super(page); | ||
| 56 | + | ||
| 57 | + this.moreMenu = page.getByText('更多 >'); | ||
| 58 | + this.categoryMenu = page.getByText('商品分类').first(); | ||
| 59 | + this.addCategoryButton = page.getByText('新增分类'); | ||
| 60 | + this.editCategoryButton = page.getByText('修改分类'); | ||
| 61 | + this.deleteCategoryButton = page.getByText('删除分类'); | ||
| 62 | + this.confirmButton = page.getByText('确定', { exact: true }); | ||
| 63 | + this.clearSearchButton = page.locator('uni-view:nth-child(7) > .default-container > uni-view > .z-paging-content > .nut-searchbar > .nut-searchbar__search-input > .nut-searchbar__input-inner-icon > .nut-searchbar__search-icon > .nut-icon'); | ||
| 64 | + this.categoryNameInput = page.getByRole('textbox').nth(1); | ||
| 65 | + this.categoryDescriptionInput = page.getByRole('textbox').nth(2); | ||
| 66 | + this.categoryStatusSwitch = page.locator('.uni-scroll-view-content > uni-view:nth-child(3) > .nut-cell__value > .nut-form-item__body__slots > .nut-switch > .nut-switch-button'); | ||
| 67 | + this.searchInput = page.getByRole('textbox').first(); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * 打开商品分类管理页面 | ||
| 72 | + * 完整流程:首页 -> 更多 -> 商品分类 | ||
| 73 | + */ | ||
| 74 | + async openProductCategoryManagement(): Promise<void> { | ||
| 75 | + await this.navigate('/'); | ||
| 76 | + await this.moreMenu.click(); | ||
| 77 | + await this.page.waitForTimeout(500); | ||
| 78 | + await this.categoryMenu.click(); | ||
| 79 | + await this.page.waitForLoadState('networkidle', { timeout: 30000 }); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + /** | ||
| 83 | + * 点击新增分类按钮 | ||
| 84 | + */ | ||
| 85 | + async clickAddCategory(): Promise<void> { | ||
| 86 | + await this.addCategoryButton.click(); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + /** | ||
| 90 | + * 输入分类名称 | ||
| 91 | + * @param categoryName 分类名称 | ||
| 92 | + */ | ||
| 93 | + async enterCategoryName(categoryName: string): Promise<void> { | ||
| 94 | + await this.categoryNameInput.click(); | ||
| 95 | + await this.categoryNameInput.fill(categoryName); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 99 | + * 输入分类描述 | ||
| 100 | + * @param description 分类描述 | ||
| 101 | + */ | ||
| 102 | + async enterCategoryDescription(description: string): Promise<void> { | ||
| 103 | + await this.categoryDescriptionInput.click(); | ||
| 104 | + await this.categoryDescriptionInput.fill(description); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + /** | ||
| 108 | + * 点击确定按钮 | ||
| 109 | + */ | ||
| 110 | + async clickConfirm(): Promise<void> { | ||
| 111 | + await this.confirmButton.click(); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + /** | ||
| 115 | + * 搜索分类 | ||
| 116 | + * @param categoryName 分类名称 | ||
| 117 | + */ | ||
| 118 | + async searchCategory(categoryName: string): Promise<void> { | ||
| 119 | + await this.searchInput.click(); | ||
| 120 | + await this.searchInput.fill(categoryName); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + /** | ||
| 124 | + * 验证分类是否存在 | ||
| 125 | + * @param categoryName 分类名称 | ||
| 126 | + */ | ||
| 127 | + async expectCategoryVisible(categoryName: string): Promise<void> { | ||
| 128 | + await expect(this.page.locator('span').filter({ hasText: categoryName })).toBeVisible(); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + /** | ||
| 132 | + * 创建新商品分类(完整流程) | ||
| 133 | + * @param categoryName 分类名称 | ||
| 134 | + * @param description 分类描述 | ||
| 135 | + */ | ||
| 136 | + async createCategory(categoryName: string, description: string): Promise<void> { | ||
| 137 | + await this.openProductCategoryManagement(); | ||
| 138 | + await this.clickAddCategory(); | ||
| 139 | + await this.enterCategoryName(categoryName); | ||
| 140 | + await this.enterCategoryDescription(description); | ||
| 141 | + await this.clickConfirm(); | ||
| 142 | + await this.searchCategory(categoryName); | ||
| 143 | + await this.expectCategoryVisible(categoryName); | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + /** | ||
| 147 | + * 点击修改分类按钮 | ||
| 148 | + */ | ||
| 149 | + async clickEditCategory(): Promise<void> { | ||
| 150 | + await this.editCategoryButton.click(); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + /** | ||
| 154 | + * 清除搜索框内容 | ||
| 155 | + */ | ||
| 156 | + async clearSearch(): Promise<void> { | ||
| 157 | + await this.searchInput.click(); | ||
| 158 | + await this.searchInput.selectText(); | ||
| 159 | + await this.searchInput.fill(''); | ||
| 160 | + await this.page.waitForTimeout(300); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + /** | ||
| 164 | + * 切换分类状态 | ||
| 165 | + */ | ||
| 166 | + async toggleCategoryStatus(): Promise<void> { | ||
| 167 | + await this.categoryStatusSwitch.click(); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + /** | ||
| 171 | + * 修改商品分类(完整流程) | ||
| 172 | + * @param oldCategoryName 原分类名称 | ||
| 173 | + * @param newCategoryName 新分类名称 | ||
| 174 | + * @param newDescription 新描述 | ||
| 175 | + * @param toggleStatus 是否切换状态 | ||
| 176 | + */ | ||
| 177 | + async updateCategory(oldCategoryName: string, newCategoryName: string, newDescription: string, toggleStatus: boolean = false): Promise<void> { | ||
| 178 | + // 搜索原分类 | ||
| 179 | + await this.searchCategory(oldCategoryName); | ||
| 180 | + await this.expectCategoryVisible(oldCategoryName); | ||
| 181 | + | ||
| 182 | + // 点击修改分类 | ||
| 183 | + await this.clickEditCategory(); | ||
| 184 | + | ||
| 185 | + // 编辑分类信息 | ||
| 186 | + await this.enterCategoryName(newCategoryName); | ||
| 187 | + await this.enterCategoryDescription(newDescription); | ||
| 188 | + | ||
| 189 | + // 切换状态(可选) | ||
| 190 | + if (toggleStatus) { | ||
| 191 | + await this.toggleCategoryStatus(); | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + // 点击保存 | ||
| 195 | + await this.clickConfirm(); | ||
| 196 | + | ||
| 197 | + // 清除搜索框并搜索新分类名 | ||
| 198 | + await this.clearSearch(); | ||
| 199 | + await this.searchCategory(newCategoryName); | ||
| 200 | + await this.expectCategoryVisible(newCategoryName); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + /** | ||
| 204 | + * 点击删除分类按钮 | ||
| 205 | + */ | ||
| 206 | + async clickDeleteCategory(): Promise<void> { | ||
| 207 | + await this.deleteCategoryButton.click(); | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | + /** | ||
| 211 | + * 验证分类已删除(不存在) | ||
| 212 | + * @param categoryName 分类名称 | ||
| 213 | + */ | ||
| 214 | + async expectCategoryNotVisible(categoryName: string): Promise<void> { | ||
| 215 | + await expect(this.page.locator('span').filter({ hasText: categoryName })).not.toBeVisible(); | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + /** | ||
| 219 | + * 删除商品分类(完整流程) | ||
| 220 | + * @param categoryName 分类名称 | ||
| 221 | + */ | ||
| 222 | + async deleteCategory(categoryName: string): Promise<void> { | ||
| 223 | + // 搜索要删除的分类 | ||
| 224 | + await this.searchCategory(categoryName); | ||
| 225 | + await this.expectCategoryVisible(categoryName); | ||
| 226 | + | ||
| 227 | + // 点击删除分类 | ||
| 228 | + await this.clickDeleteCategory(); | ||
| 229 | + | ||
| 230 | + // 确认删除 | ||
| 231 | + await this.clickConfirm(); | ||
| 232 | + | ||
| 233 | + // 等待删除完成 | ||
| 234 | + await this.page.waitForTimeout(1000); | ||
| 235 | + | ||
| 236 | + // 验证分类已删除 | ||
| 237 | + await this.expectCategoryNotVisible(categoryName); | ||
| 238 | + } | ||
| 239 | +} |
pages/purchasePage.ts
0 → 100644
| 1 | +import { Page, Locator, expect } from '@playwright/test'; | ||
| 2 | +import { BasePage } from './basePage'; | ||
| 3 | + | ||
| 4 | +/** | ||
| 5 | + * 采购入库页面选择器 | ||
| 6 | + */ | ||
| 7 | +const selectors = { | ||
| 8 | + // 导航 | ||
| 9 | + moreMenu: 'text=更多 >', | ||
| 10 | + purchaseInMenu: 'text=采购入库', | ||
| 11 | + | ||
| 12 | + // 商品列表 | ||
| 13 | + goodsList: '.goods-list', | ||
| 14 | + goodsItem: '.goods-item', | ||
| 15 | + goodsName: '.goods-name span', | ||
| 16 | + | ||
| 17 | + // 商品选择 | ||
| 18 | + productItem: (productName: string) => `uni-view:hasText("/^${productName}$/")`, | ||
| 19 | + unitBox: 'uni-view:hasText("/^箱$/")', | ||
| 20 | + quantityInput: "getByText('1', { exact: true })", | ||
| 21 | + priceInput: "getByText('1', { exact: true })", | ||
| 22 | + doneButton: 'text=完成', | ||
| 23 | + | ||
| 24 | + // 供应商列表 | ||
| 25 | + supplierField: 'uni-view:nth-child(5) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask', | ||
| 26 | + supplierList: '.zp-paging-container-content', | ||
| 27 | + supplierItem: '.item-card', | ||
| 28 | + supplierName: '.mainTitle span', | ||
| 29 | + supplierOption: (supplierName: string) => `uni-view:hasText("/^${supplierName}$/")`, | ||
| 30 | + | ||
| 31 | + // 仓库列表 | ||
| 32 | + warehouseField: 'uni-view:nth-child(6) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask', | ||
| 33 | + warehouseList: '.zp-paging-container-content', | ||
| 34 | + warehouseItem: '.list-item', | ||
| 35 | + warehouseName: '.name', | ||
| 36 | + warehouseOption: 'text=东区普通仓库', | ||
| 37 | + | ||
| 38 | + // 草稿操作 | ||
| 39 | + saveDraftButton: 'text=存入草稿', | ||
| 40 | + draftListButton: 'text=草稿单列表', | ||
| 41 | + | ||
| 42 | + // 草稿箱搜索 | ||
| 43 | + draftSearchInput: '.draft-list-wrapper > .z-paging-content > .nut-searchbar > .nut-searchbar__search-input > .nut-searchbar__input-inner > .nut-searchbar__input-form > span > .nut-searchbar__input-bar > .uni-input-wrapper > .uni-input-input', | ||
| 44 | + draftItem: 'span', | ||
| 45 | +}; | ||
| 46 | + | ||
| 47 | +/** | ||
| 48 | + * 采购入库页面类 | ||
| 49 | + * 处理采购入库草稿相关操作 | ||
| 50 | + */ | ||
| 51 | +export class PurchasePage extends BasePage { | ||
| 52 | + // 导航定位器 | ||
| 53 | + readonly moreMenu: Locator; | ||
| 54 | + readonly purchaseInMenu: Locator; | ||
| 55 | + | ||
| 56 | + // 商品列表 | ||
| 57 | + readonly goodsList: Locator; | ||
| 58 | + readonly goodsItem: Locator; | ||
| 59 | + readonly goodsName: Locator; | ||
| 60 | + | ||
| 61 | + // 商品选择 | ||
| 62 | + readonly quantityInput: Locator; | ||
| 63 | + readonly priceInput: Locator; | ||
| 64 | + readonly doneButton: Locator; | ||
| 65 | + | ||
| 66 | + // 供应商 | ||
| 67 | + readonly supplierField: Locator; | ||
| 68 | + readonly supplierList: Locator; | ||
| 69 | + readonly supplierItem: Locator; | ||
| 70 | + readonly supplierName: Locator; | ||
| 71 | + | ||
| 72 | + // 仓库 | ||
| 73 | + readonly warehouseField: Locator; | ||
| 74 | + readonly warehouseList: Locator; | ||
| 75 | + readonly warehouseItem: Locator; | ||
| 76 | + readonly warehouseName: Locator; | ||
| 77 | + | ||
| 78 | + // 草稿操作 | ||
| 79 | + readonly saveDraftButton: Locator; | ||
| 80 | + readonly draftListButton: Locator; | ||
| 81 | + | ||
| 82 | + // 草稿箱搜索 | ||
| 83 | + readonly draftSearchInput: Locator; | ||
| 84 | + | ||
| 85 | + // 费用相关 | ||
| 86 | + readonly addExpenseButton: Locator; | ||
| 87 | + readonly expenseItem: Locator; | ||
| 88 | + readonly amountInput: Locator; | ||
| 89 | + readonly costItem: Locator; | ||
| 90 | + readonly settlementAccountField: Locator; | ||
| 91 | + readonly settlementAccountOption: Locator; | ||
| 92 | + | ||
| 93 | + // 采购员 | ||
| 94 | + readonly purchaserField: Locator; | ||
| 95 | + readonly purchaserItem: Locator; | ||
| 96 | + | ||
| 97 | + // 车牌号 | ||
| 98 | + readonly licensePlateInput: Locator; | ||
| 99 | + | ||
| 100 | + // 备注 | ||
| 101 | + readonly remarkInput: Locator; | ||
| 102 | + | ||
| 103 | + // 图片上传 | ||
| 104 | + readonly uploadImageButton: Locator; | ||
| 105 | + | ||
| 106 | + // 付款 | ||
| 107 | + readonly payButton: Locator; | ||
| 108 | + readonly settlementAccount: Locator; | ||
| 109 | + readonly confirmDialogButton: Locator; | ||
| 110 | + | ||
| 111 | + // 采购列表 | ||
| 112 | + readonly purchaseListMenu: Locator; | ||
| 113 | + readonly purchaseSearchInput: Locator; | ||
| 114 | + | ||
| 115 | + constructor(page: Page) { | ||
| 116 | + super(page); | ||
| 117 | + | ||
| 118 | + this.moreMenu = page.getByText('更多 >'); | ||
| 119 | + this.purchaseInMenu = page.getByText('采购入库').first(); | ||
| 120 | + this.goodsList = page.locator('.goods-list'); | ||
| 121 | + this.goodsItem = page.locator('.goods-item'); | ||
| 122 | + this.goodsName = page.locator('.goods-name span'); | ||
| 123 | + this.quantityInput = page.locator('[data-prop="num1"] .input'); | ||
| 124 | + this.priceInput = page.locator('[data-prop="unitPrice"] .input'); | ||
| 125 | + this.doneButton = page.getByText('完成'); | ||
| 126 | + this.supplierField = page.locator('uni-view:nth-child(5) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask'); | ||
| 127 | + this.supplierList = page.locator('.zp-paging-container-content').first(); | ||
| 128 | + this.supplierItem = page.locator('.item-card'); | ||
| 129 | + this.supplierName = page.locator('.mainTitle span'); | ||
| 130 | + this.warehouseField = page.locator('uni-view:nth-child(6) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask'); | ||
| 131 | + this.warehouseList = page.locator('.zp-paging-container-content').first(); | ||
| 132 | + this.warehouseItem = page.locator('.list-item'); | ||
| 133 | + this.warehouseName = page.locator('.name'); | ||
| 134 | + this.saveDraftButton = page.getByText('存入草稿', { exact: true }); | ||
| 135 | + this.draftListButton = page.getByText('草稿单列表', { exact: true }); | ||
| 136 | + this.draftSearchInput = page.locator('.draft-list-wrapper > .z-paging-content > .nut-searchbar > .nut-searchbar__search-input > .nut-searchbar__input-inner > .nut-searchbar__input-form > span > .nut-searchbar__input-bar > .uni-input-wrapper > .uni-input-input'); | ||
| 137 | + | ||
| 138 | + // 费用相关 | ||
| 139 | + this.addExpenseButton = page.getByText('入库费用添加费用'); | ||
| 140 | + this.expenseItem = page.locator('.account-item'); | ||
| 141 | + this.amountInput = page.locator('.cost-item > uni-view:nth-child(3) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask'); | ||
| 142 | + this.costItem = page.locator('.cost-item'); | ||
| 143 | + this.settlementAccountField = page.locator('.cost-item > uni-view:nth-child(6) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask'); | ||
| 144 | + this.settlementAccountOption = page.locator('uni-view').filter({ hasText: /^现金$/ }).first(); | ||
| 145 | + | ||
| 146 | + // 采购员 | ||
| 147 | + this.purchaserField = page.locator('uni-view:nth-child(10) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask'); | ||
| 148 | + this.purchaserItem = page.locator('.list > .item'); | ||
| 149 | + | ||
| 150 | + // 车牌号 - 使用nth-child(11) | ||
| 151 | + this.licensePlateInput = page.locator('uni-view:nth-child(11) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__mask'); | ||
| 152 | + | ||
| 153 | + // 备注 - 使用nth-child(12) | ||
| 154 | + this.remarkInput = page.locator('uni-view:nth-child(12) > .nut-cell__value > .nut-form-item__body__slots > .nut-input > .nut-input__value > .nut-input__input > .uni-input-wrapper > .uni-input-input'); | ||
| 155 | + | ||
| 156 | + // 图片上传 | ||
| 157 | + this.uploadImageButton = page.getByText('图片(最多上传9张)'); | ||
| 158 | + | ||
| 159 | + // 付款 | ||
| 160 | + this.payButton = page.getByText('付款', { exact: true }); | ||
| 161 | + this.settlementAccount = page.locator('uni-view').filter({ hasText: /^微信支付$/ }).first(); | ||
| 162 | + this.confirmDialogButton = page.getByText('确定').first(); | ||
| 163 | + | ||
| 164 | + // 采购列表 | ||
| 165 | + this.purchaseListMenu = page.getByText('采购列表'); | ||
| 166 | + this.purchaseSearchInput = page.getByRole('textbox'); | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + /** | ||
| 170 | + * 进入采购入库页面 | ||
| 171 | + */ | ||
| 172 | + async openPurchaseInbound(): Promise<void> { | ||
| 173 | + await this.navigate('/'); | ||
| 174 | + await this.moreMenu.click(); | ||
| 175 | + await this.page.waitForTimeout(500); | ||
| 176 | + await this.purchaseInMenu.click(); | ||
| 177 | + await this.page.waitForLoadState('networkidle', { timeout: 30000 }); | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + /** | ||
| 181 | + * 从商品列表随机选择一个商品 | ||
| 182 | + * @returns 商品名称和索引 | ||
| 183 | + */ | ||
| 184 | + async getRandomProduct(): Promise<{ name: string; index: number }> { | ||
| 185 | + // 等待商品列表加载 | ||
| 186 | + await this.goodsList.waitFor({ state: 'visible', timeout: 10000 }); | ||
| 187 | + await this.page.waitForTimeout(500); | ||
| 188 | + | ||
| 189 | + // 获取所有商品名称 | ||
| 190 | + const productNames = await this.goodsName.allTextContents(); | ||
| 191 | + if (productNames.length === 0) { | ||
| 192 | + throw new Error('商品列表为空'); | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + // 随机选择一个商品 | ||
| 196 | + const randomIndex = Math.floor(Math.random() * productNames.length); | ||
| 197 | + const selectedProduct = productNames[randomIndex]; | ||
| 198 | + console.log('随机选择的商品:', selectedProduct, '索引:', randomIndex); | ||
| 199 | + | ||
| 200 | + return { name: selectedProduct, index: randomIndex }; | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + /** | ||
| 204 | + * 选择商品(从商品列表随机选择) | ||
| 205 | + * @returns 商品名称 | ||
| 206 | + */ | ||
| 207 | + async selectRandomProduct(): Promise<string> { | ||
| 208 | + const { name, index } = await this.getRandomProduct(); | ||
| 209 | + await this.goodsItem.nth(index).click(); | ||
| 210 | + await this.page.waitForTimeout(300); | ||
| 211 | + return name; | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + /** | ||
| 215 | + * 选择指定商品 | ||
| 216 | + * @param productName 商品名称 | ||
| 217 | + */ | ||
| 218 | + async selectProduct(productName: string): Promise<void> { | ||
| 219 | + await this.page.locator('uni-view').filter({ hasText: new RegExp(`^${productName}$`) }).first().click(); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + /** | ||
| 223 | + * 点击数量输入框(通过data-prop属性定位) | ||
| 224 | + */ | ||
| 225 | + async clickQuantityInput(): Promise<void> { | ||
| 226 | + await this.quantityInput.click(); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + /** | ||
| 230 | + * 输入数量 | ||
| 231 | + * @param quantity 数量 | ||
| 232 | + */ | ||
| 233 | + async enterQuantity(quantity: string): Promise<void> { | ||
| 234 | + // 点击数量输入框(通过data-prop属性定位) | ||
| 235 | + await this.page.locator('[data-prop="num1"]').click(); | ||
| 236 | + // 点击键盘上的数字 | ||
| 237 | + await this.page.locator(`.number-keyboard uni-view[data-key="${quantity}"]`).click(); | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + /** | ||
| 241 | + * 输入单价(点击单价输入框,然后点击键盘数字) | ||
| 242 | + * @param price 单价 | ||
| 243 | + */ | ||
| 244 | + async enterPrice(price: string): Promise<void> { | ||
| 245 | + // 点击单价输入框(通过data-prop属性定位,不依赖单位文本) | ||
| 246 | + await this.page.locator('[data-prop="unitPrice"]').click(); | ||
| 247 | + // 点击键盘上的数字 | ||
| 248 | + await this.page.locator(`.number-keyboard uni-view[data-key="${price}"]`).click(); | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + /** | ||
| 252 | + * 点击完成按钮 | ||
| 253 | + */ | ||
| 254 | + async clickDone(): Promise<void> { | ||
| 255 | + await this.doneButton.click(); | ||
| 256 | + await this.page.waitForTimeout(1000); | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + /** | ||
| 260 | + * 从供应商列表随机选择供应商 | ||
| 261 | + * @returns 供应商名称 | ||
| 262 | + */ | ||
| 263 | + async getRandomSupplier(): Promise<string> { | ||
| 264 | + await this.supplierField.click(); | ||
| 265 | + await this.page.waitForTimeout(1000); | ||
| 266 | + | ||
| 267 | + const supplierNames = await this.supplierName.allTextContents(); | ||
| 268 | + if (supplierNames.length === 0) { | ||
| 269 | + throw new Error('供应商列表为空'); | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + const randomIndex = Math.floor(Math.random() * supplierNames.length); | ||
| 273 | + const selectedSupplier = supplierNames[randomIndex]; | ||
| 274 | + console.log('随机选择的供应商:', selectedSupplier, '索引:', randomIndex); | ||
| 275 | + | ||
| 276 | + await this.supplierItem.nth(randomIndex).click(); | ||
| 277 | + await this.page.waitForTimeout(300); | ||
| 278 | + | ||
| 279 | + return selectedSupplier; | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + /** | ||
| 283 | + * 选择指定供应商 | ||
| 284 | + * @param supplierName 供应商名称 | ||
| 285 | + */ | ||
| 286 | + async selectSupplier(supplierName: string): Promise<void> { | ||
| 287 | + await this.supplierField.click(); | ||
| 288 | + await this.page.locator('uni-view').filter({ hasText: new RegExp(`^${supplierName}$`) }).nth(1).click(); | ||
| 289 | + } | ||
| 290 | + | ||
| 291 | + /** | ||
| 292 | + * 从仓库列表随机选择仓库 | ||
| 293 | + * @returns 仓库名称 | ||
| 294 | + */ | ||
| 295 | + async getRandomWarehouse(): Promise<string> { | ||
| 296 | + await this.warehouseField.click(); | ||
| 297 | + await this.page.waitForTimeout(1000); | ||
| 298 | + | ||
| 299 | + const warehouseNames = await this.warehouseName.allTextContents(); | ||
| 300 | + if (warehouseNames.length === 0) { | ||
| 301 | + throw new Error('仓库列表为空'); | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + const randomIndex = Math.floor(Math.random() * warehouseNames.length); | ||
| 305 | + const selectedWarehouse = warehouseNames[randomIndex]; | ||
| 306 | + console.log('随机选择的仓库:', selectedWarehouse, '索引:', randomIndex); | ||
| 307 | + | ||
| 308 | + await this.warehouseItem.nth(randomIndex).click(); | ||
| 309 | + await this.page.waitForTimeout(300); | ||
| 310 | + | ||
| 311 | + return selectedWarehouse; | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + /** | ||
| 315 | + * 选择指定仓库 | ||
| 316 | + * @param warehouseName 仓库名称 | ||
| 317 | + */ | ||
| 318 | + async selectWarehouse(warehouseName: string): Promise<void> { | ||
| 319 | + await this.warehouseField.click(); | ||
| 320 | + await this.page.getByText(warehouseName).nth(2).click(); | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + /** | ||
| 324 | + * 点击存入草稿 | ||
| 325 | + */ | ||
| 326 | + async clickSaveDraft(): Promise<void> { | ||
| 327 | + await this.saveDraftButton.click(); | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + /** | ||
| 331 | + * 点击草稿单列表 | ||
| 332 | + */ | ||
| 333 | + async clickDraftList(): Promise<void> { | ||
| 334 | + await this.draftListButton.click(); | ||
| 335 | + await this.page.waitForLoadState('networkidle', { timeout: 30000 }); | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + /** | ||
| 339 | + * 在草稿箱搜索商品 | ||
| 340 | + * @param productName 商品名称 | ||
| 341 | + */ | ||
| 342 | + async searchInDraftList(productName: string): Promise<void> { | ||
| 343 | + await this.draftSearchInput.click(); | ||
| 344 | + await this.draftSearchInput.fill(productName); | ||
| 345 | + } | ||
| 346 | + | ||
| 347 | + /** | ||
| 348 | + * 验证草稿列表中存在商品 | ||
| 349 | + * @param productName 商品名称 | ||
| 350 | + */ | ||
| 351 | + async expectDraftContainsProduct(productName: string): Promise<void> { | ||
| 352 | + // 等待搜索结果加载 | ||
| 353 | + await this.page.waitForTimeout(500); | ||
| 354 | + // 获取列表中第一个商品进行断言 | ||
| 355 | + const firstItem = this.page.locator('span').filter({ hasText: productName }).first(); | ||
| 356 | + await expect(firstItem).toBeVisible(); | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + /** | ||
| 360 | + * 创建采购入库草稿(完整流程) | ||
| 361 | + * @param productName 商品名称 | ||
| 362 | + * @param supplierName 供应商名称 | ||
| 363 | + * @param warehouseName 仓库名称 | ||
| 364 | + * @param quantity 数量 | ||
| 365 | + * @param price 单价 | ||
| 366 | + */ | ||
| 367 | + async createPurchaseDraft(productName: string, supplierName: string, warehouseName: string, quantity: string = '1', price: string = '1'): Promise<void> { | ||
| 368 | + // 选择商品 | ||
| 369 | + await this.selectProduct(productName); | ||
| 370 | + | ||
| 371 | + // 输入数量 | ||
| 372 | + await this.enterQuantity(quantity); | ||
| 373 | + | ||
| 374 | + // 输入单价 | ||
| 375 | + await this.enterPrice(price); | ||
| 376 | + | ||
| 377 | + // 点击完成 | ||
| 378 | + await this.clickDone(); | ||
| 379 | + | ||
| 380 | + // 选择供应商 | ||
| 381 | + await this.selectSupplier(supplierName); | ||
| 382 | + | ||
| 383 | + // 选择仓库 | ||
| 384 | + await this.selectWarehouse(warehouseName); | ||
| 385 | + | ||
| 386 | + // 存入草稿 | ||
| 387 | + await this.clickSaveDraft(); | ||
| 388 | + | ||
| 389 | + // 打开草稿单列表 | ||
| 390 | + await this.clickDraftList(); | ||
| 391 | + | ||
| 392 | + // 搜索商品 | ||
| 393 | + await this.searchInDraftList(productName); | ||
| 394 | + | ||
| 395 | + // 验证商品存在 | ||
| 396 | + await this.expectDraftContainsProduct(productName); | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + /** | ||
| 400 | + * 添加入库费用 | ||
| 401 | + * @param expenseIndex 费用项索引(默认0) | ||
| 402 | + * @param amount 金额(默认'1') | ||
| 403 | + */ | ||
| 404 | + async addExpense(expenseIndex: number = 0, amount: string = '1'): Promise<void> { | ||
| 405 | + // 打开费用选择 | ||
| 406 | + await this.addExpenseButton.click(); | ||
| 407 | + | ||
| 408 | + // 等待费用列表加载 | ||
| 409 | + await this.page.waitForTimeout(500); | ||
| 410 | + | ||
| 411 | + // 随机选择费用项 | ||
| 412 | + const expenseCount = await this.expenseItem.count(); | ||
| 413 | + const randomIndex = Math.floor(Math.random() * expenseCount); | ||
| 414 | + await this.expenseItem.nth(randomIndex).click(); | ||
| 415 | + await this.page.getByText('确定').click(); | ||
| 416 | + | ||
| 417 | + // 输入金额 - 点击金额输入框,然后点击键盘数字 | ||
| 418 | + await this.amountInput.click(); | ||
| 419 | + await this.page.waitForTimeout(300); | ||
| 420 | + await this.page.getByText('1', { exact: true }).click(); | ||
| 421 | + | ||
| 422 | + // 点击键盘上的完成按钮关闭键盘 | ||
| 423 | + await this.page.getByText('完成', { exact: true }).click(); | ||
| 424 | + | ||
| 425 | + // 点击结算账户并选择现金 | ||
| 426 | + await this.settlementAccountField.click(); | ||
| 427 | + await this.page.waitForTimeout(300); | ||
| 428 | + await this.settlementAccountOption.click(); | ||
| 429 | + | ||
| 430 | + // 点击确定按钮 | ||
| 431 | + await this.page.getByText('确定').click(); | ||
| 432 | + } | ||
| 433 | + | ||
| 434 | + /** | ||
| 435 | + * 选择采购员 | ||
| 436 | + * @param purchaserName 采购员名称 | ||
| 437 | + */ | ||
| 438 | + async selectPurchaser(purchaserName?: string): Promise<string> { | ||
| 439 | + await this.purchaserField.click(); | ||
| 440 | + await this.page.waitForTimeout(500); | ||
| 441 | + | ||
| 442 | + // 如果没有指定采购员,随机选择 | ||
| 443 | + if (!purchaserName) { | ||
| 444 | + const purchasers = await this.purchaserItem.locator('.name').allTextContents(); | ||
| 445 | + if (purchasers.length === 0) { | ||
| 446 | + throw new Error('采购员列表为空'); | ||
| 447 | + } | ||
| 448 | + const randomIndex = Math.floor(Math.random() * purchasers.length); | ||
| 449 | + purchaserName = purchasers[randomIndex]; | ||
| 450 | + await this.purchaserItem.nth(randomIndex).click(); | ||
| 451 | + } else { | ||
| 452 | + await this.page.locator('uni-view').filter({ hasText: new RegExp(`^${purchaserName}`) }).first().click(); | ||
| 453 | + } | ||
| 454 | + | ||
| 455 | + await this.page.waitForTimeout(300); | ||
| 456 | + return purchaserName; | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + /** | ||
| 460 | + * 输入车牌号 | ||
| 461 | + * @param licensePlate 车牌号 | ||
| 462 | + */ | ||
| 463 | + async enterLicensePlate(licensePlate: string): Promise<void> { | ||
| 464 | + await this.fillLicensePlate(licensePlate, this.licensePlateInput); | ||
| 465 | + } | ||
| 466 | + | ||
| 467 | + /** | ||
| 468 | + * 输入备注 | ||
| 469 | + * @param remark 备注内容 | ||
| 470 | + */ | ||
| 471 | + async enterRemark(remark: string): Promise<void> { | ||
| 472 | + await this.remarkInput.click(); | ||
| 473 | + await this.remarkInput.fill(remark); | ||
| 474 | + } | ||
| 475 | + | ||
| 476 | + /** | ||
| 477 | + * 上传图片 | ||
| 478 | + * @param imagePath 图片路径 | ||
| 479 | + */ | ||
| 480 | + async uploadImage(imagePath: string): Promise<void> { | ||
| 481 | + // 点击图片上传区域 | ||
| 482 | + await this.uploadImageButton.click(); | ||
| 483 | + await this.page.waitForTimeout(500); | ||
| 484 | + // 点击上传按钮 | ||
| 485 | + await this.page.locator('span').filter({ hasText: '供应商*请选择供应商车牌号请选择车牌号入库日期*请选择入库日期入库仓库*请选择仓库批次别名*请输入批次别名备注请输入备注商品清单点击选择商品入库费用添加费用存储' }).locator('uni-button').click(); | ||
| 486 | + await this.page.waitForTimeout(300); | ||
| 487 | + // 点击上传input | ||
| 488 | + await this.page.locator('.nut-uploader__input').click(); | ||
| 489 | + // 使用 setInputFiles 方式上传 | ||
| 490 | + await this.page.locator('body').setInputFiles(imagePath); | ||
| 491 | + } | ||
| 492 | + | ||
| 493 | + /** | ||
| 494 | + * 点击付款按钮 | ||
| 495 | + */ | ||
| 496 | + async clickPay(): Promise<void> { | ||
| 497 | + await this.payButton.click(); | ||
| 498 | + } | ||
| 499 | + | ||
| 500 | + /** | ||
| 501 | + * 选择结算账户 | ||
| 502 | + * @param accountName 账户名称(默认微信支付) | ||
| 503 | + */ | ||
| 504 | + async selectSettlementAccount(accountName: string = '微信支付'): Promise<void> { | ||
| 505 | + await this.settlementAccount.filter({ hasText: new RegExp(`^${accountName}$`) }).click(); | ||
| 506 | + } | ||
| 507 | + | ||
| 508 | + /** | ||
| 509 | + * 确认付款弹窗 | ||
| 510 | + */ | ||
| 511 | + async confirmPayment(): Promise<void> { | ||
| 512 | + // 等待确认弹窗出现 | ||
| 513 | + await this.confirmDialogButton.waitFor({ state: 'visible', timeout: 10000 }); | ||
| 514 | + await this.confirmDialogButton.click(); | ||
| 515 | + await this.page.waitForTimeout(1000); | ||
| 516 | + } | ||
| 517 | + | ||
| 518 | + /** | ||
| 519 | + * 进入采购列表 | ||
| 520 | + */ | ||
| 521 | + async goToPurchaseList(): Promise<void> { | ||
| 522 | + await this.purchaseListMenu.click(); | ||
| 523 | + await this.page.waitForLoadState('networkidle', { timeout: 30000 }); | ||
| 524 | + await this.page.waitForTimeout(2000); | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + /** | ||
| 528 | + * 在采购列表搜索商品 | ||
| 529 | + * @param productName 商品名称 | ||
| 530 | + */ | ||
| 531 | + async searchInPurchaseList(productName: string): Promise<void> { | ||
| 532 | + // 使用 filter 精确定位到商品名称搜索框(避免匹配到供应商搜索框) | ||
| 533 | + const searchInput = this.page.locator('uni-input').filter({ hasText: '供应商/商品名称' }).locator('input[type="text"]'); | ||
| 534 | + await searchInput.waitFor({ state: 'visible', timeout: 10000 }); | ||
| 535 | + await searchInput.click(); | ||
| 536 | + await this.page.waitForTimeout(300); | ||
| 537 | + await searchInput.fill(productName); | ||
| 538 | + await this.page.waitForTimeout(500); | ||
| 539 | + await this.page.keyboard.press('Enter'); | ||
| 540 | + await this.page.waitForTimeout(1000); | ||
| 541 | + } | ||
| 542 | + | ||
| 543 | + /** | ||
| 544 | + * 验证采购列表中存在商品 | ||
| 545 | + * @param productName 商品名称 | ||
| 546 | + */ | ||
| 547 | + async expectPurchaseListContainsProduct(productName: string): Promise<void> { | ||
| 548 | + // 虚拟列表中元素可能处于 hidden 状态,使用 toBeAttached 而非 toBeVisible | ||
| 549 | + const firstItem = this.page.locator('.goods-item').filter({ hasText: productName }).first(); | ||
| 550 | + await expect(firstItem).toBeAttached(); | ||
| 551 | + } | ||
| 552 | + | ||
| 553 | + /** | ||
| 554 | + * 创建采购入库付款单(完整流程) | ||
| 555 | + * @param productName 商品名称 | ||
| 556 | + * @param supplierName 供应商名称 | ||
| 557 | + * @param warehouseName 仓库名称 | ||
| 558 | + * @param quantity 数量 | ||
| 559 | + * @param price 单价 | ||
| 560 | + * @param expenseAmount 费用金额 | ||
| 561 | + * @param purchaserName 采购员名称 | ||
| 562 | + * @param licensePlate 车牌号 | ||
| 563 | + * @param remark 备注 | ||
| 564 | + * @param imagePath 图片路径 | ||
| 565 | + */ | ||
| 566 | + async createPurchasePayment( | ||
| 567 | + productName: string, | ||
| 568 | + supplierName: string, | ||
| 569 | + warehouseName: string, | ||
| 570 | + quantity: string = '1', | ||
| 571 | + price: string = '1', | ||
| 572 | + expenseAmount: string = '1', | ||
| 573 | + purchaserName?: string, | ||
| 574 | + licensePlate?: string, | ||
| 575 | + remark: string = '自动化测试', | ||
| 576 | + imagePath?: string | ||
| 577 | + ): Promise<void> { | ||
| 578 | + // 选择商品 | ||
| 579 | + await this.selectProduct(productName); | ||
| 580 | + | ||
| 581 | + // 输入数量 | ||
| 582 | + await this.enterQuantity(quantity); | ||
| 583 | + | ||
| 584 | + // 输入单价 | ||
| 585 | + await this.enterPrice(price); | ||
| 586 | + | ||
| 587 | + // 点击完成 | ||
| 588 | + await this.clickDone(); | ||
| 589 | + | ||
| 590 | + // 添加入库费用 | ||
| 591 | + await this.addExpense(0, expenseAmount); | ||
| 592 | + | ||
| 593 | + // 选择供应商 | ||
| 594 | + await this.selectSupplier(supplierName); | ||
| 595 | + | ||
| 596 | + // 选择仓库 | ||
| 597 | + await this.selectWarehouse(warehouseName); | ||
| 598 | + | ||
| 599 | + // 选择采购员 | ||
| 600 | + await this.selectPurchaser(purchaserName); | ||
| 601 | + | ||
| 602 | + // 输入车牌号 | ||
| 603 | + if (licensePlate) { | ||
| 604 | + await this.enterLicensePlate(licensePlate); | ||
| 605 | + } | ||
| 606 | + | ||
| 607 | + // 输入备注 | ||
| 608 | + await this.enterRemark(remark); | ||
| 609 | + | ||
| 610 | + // 上传图片 | ||
| 611 | + if (imagePath) { | ||
| 612 | + await this.uploadImage(imagePath); | ||
| 613 | + } | ||
| 614 | + | ||
| 615 | + // 点击付款 | ||
| 616 | + await this.clickPay(); | ||
| 617 | + | ||
| 618 | + // 选择结算账户 | ||
| 619 | + await this.selectSettlementAccount('微信支付'); | ||
| 620 | + | ||
| 621 | + // 确认付款 | ||
| 622 | + await this.confirmPayment(); | ||
| 623 | + | ||
| 624 | + // 进入采购列表 | ||
| 625 | + await this.goToPurchaseList(); | ||
| 626 | + | ||
| 627 | + // 搜索商品 | ||
| 628 | + await this.searchInPurchaseList(productName); | ||
| 629 | + | ||
| 630 | + // 验证商品存在 | ||
| 631 | + await this.expectPurchaseListContainsProduct(productName); | ||
| 632 | + } | ||
| 633 | +} | ||
| 0 | \ No newline at end of file | 634 | \ No newline at end of file |
tests/productCategory.spec.ts
0 → 100644
| 1 | +import { test, expect } from '@playwright/test'; | ||
| 2 | +import * as allure from 'allure-js-commons'; | ||
| 3 | +import { ProductCategoryPage } from '../pages/productCategoryPage'; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 商品分类测试 | ||
| 7 | + */ | ||
| 8 | +test.describe('商品分类', () => { | ||
| 9 | + // 使用已保存的认证状态 | ||
| 10 | + test.use({ storageState: 'auth.json' }); | ||
| 11 | + | ||
| 12 | + // 强制测试串行执行,避免并行测试之间的干扰 | ||
| 13 | + test.describe.configure({ mode: 'serial' }); | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 生成随机分类名称 | ||
| 17 | + */ | ||
| 18 | + function generateCategoryName(): string { | ||
| 19 | + const prefixes = ['畅销', '热卖', '新品', '精选', '推荐', '特惠', '优质', '普通']; | ||
| 20 | + const timestamp = Date.now().toString().slice(-4); | ||
| 21 | + return `${prefixes[Math.floor(Math.random() * prefixes.length)]}${timestamp}`; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + test('新增商品分类', async ({ page }, testInfo) => { | ||
| 25 | + const productCategoryPage = new ProductCategoryPage(page); | ||
| 26 | + | ||
| 27 | + // 添加allure元素 | ||
| 28 | + await allure.epic('商品管理'); | ||
| 29 | + await allure.feature('商品分类'); | ||
| 30 | + await allure.story('新增商品分类'); | ||
| 31 | + | ||
| 32 | + // 生成随机分类名称 | ||
| 33 | + const categoryName = generateCategoryName(); | ||
| 34 | + const categoryDescription = '畅销的商品'; | ||
| 35 | + console.log('新增分类名称:', categoryName); | ||
| 36 | + | ||
| 37 | + // 步骤1:进入商品分类页面 | ||
| 38 | + await allure.step('进入商品分类页面', async () => { | ||
| 39 | + await productCategoryPage.openProductCategoryManagement(); | ||
| 40 | + }); | ||
| 41 | + | ||
| 42 | + // 步骤2:点击新增分类按钮 | ||
| 43 | + await allure.step('点击新增分类按钮', async () => { | ||
| 44 | + await productCategoryPage.clickAddCategory(); | ||
| 45 | + }); | ||
| 46 | + | ||
| 47 | + // 步骤3:输入分类名称 | ||
| 48 | + await allure.step('输入分类名称', async () => { | ||
| 49 | + await productCategoryPage.enterCategoryName(categoryName); | ||
| 50 | + }); | ||
| 51 | + | ||
| 52 | + // 步骤4:输入分类描述 | ||
| 53 | + await allure.step('输入分类描述', async () => { | ||
| 54 | + await productCategoryPage.enterCategoryDescription(categoryDescription); | ||
| 55 | + }); | ||
| 56 | + | ||
| 57 | + // 步骤5:状态默认启用,点击确定 | ||
| 58 | + await allure.step('点击确定按钮', async () => { | ||
| 59 | + await productCategoryPage.clickConfirm(); | ||
| 60 | + }); | ||
| 61 | + | ||
| 62 | + // 步骤6:搜索新增的分类 | ||
| 63 | + await allure.step('搜索新增的分类', async () => { | ||
| 64 | + await productCategoryPage.searchCategory(categoryName); | ||
| 65 | + }); | ||
| 66 | + | ||
| 67 | + // 步骤7:验证分类创建成功 | ||
| 68 | + await allure.step('验证分类创建成功', async () => { | ||
| 69 | + await productCategoryPage.expectCategoryVisible(categoryName); | ||
| 70 | + }); | ||
| 71 | + }); | ||
| 72 | + | ||
| 73 | + test('修改商品分类', async ({ page }, testInfo) => { | ||
| 74 | + const productCategoryPage = new ProductCategoryPage(page); | ||
| 75 | + | ||
| 76 | + // 添加allure元素 | ||
| 77 | + await allure.epic('商品管理'); | ||
| 78 | + await allure.feature('商品分类'); | ||
| 79 | + await allure.story('修改商品分类'); | ||
| 80 | + | ||
| 81 | + // 先生成一个分类用于修改 | ||
| 82 | + const originalCategoryName = generateCategoryName(); | ||
| 83 | + const modifiedCategoryName = `修改${originalCategoryName}`; | ||
| 84 | + const originalDescription = '原始描述'; | ||
| 85 | + const modifiedDescription = '修改后的描述'; | ||
| 86 | + console.log('原分类名称:', originalCategoryName); | ||
| 87 | + console.log('修改后分类名称:', modifiedCategoryName); | ||
| 88 | + | ||
| 89 | + // 步骤1:先创建分类 | ||
| 90 | + await allure.step('创建待修改的分类', async () => { | ||
| 91 | + await productCategoryPage.createCategory(originalCategoryName, originalDescription); | ||
| 92 | + }); | ||
| 93 | + | ||
| 94 | + // 步骤2:搜索分类并点击修改 | ||
| 95 | + await allure.step('搜索并点击修改分类', async () => { | ||
| 96 | + await productCategoryPage.searchCategory(originalCategoryName); | ||
| 97 | + await productCategoryPage.clickEditCategory(); | ||
| 98 | + }); | ||
| 99 | + | ||
| 100 | + // 步骤3:修改分类名称 | ||
| 101 | + await allure.step('修改分类名称', async () => { | ||
| 102 | + await productCategoryPage.enterCategoryName(modifiedCategoryName); | ||
| 103 | + }); | ||
| 104 | + | ||
| 105 | + // 步骤4:修改分类描述 | ||
| 106 | + await allure.step('修改分类描述', async () => { | ||
| 107 | + await productCategoryPage.enterCategoryDescription(modifiedDescription); | ||
| 108 | + }); | ||
| 109 | + | ||
| 110 | + // 步骤5:切换状态(可选) | ||
| 111 | + await allure.step('切换分类状态', async () => { | ||
| 112 | + await productCategoryPage.toggleCategoryStatus(); | ||
| 113 | + }); | ||
| 114 | + | ||
| 115 | + // 步骤6:点击确定保存 | ||
| 116 | + await allure.step('保存修改', async () => { | ||
| 117 | + await productCategoryPage.clickConfirm(); | ||
| 118 | + }); | ||
| 119 | + | ||
| 120 | + // 步骤7:清除搜索框并搜索修改后的分类 | ||
| 121 | + await allure.step('搜索修改后的分类', async () => { | ||
| 122 | + await productCategoryPage.clearSearch(); | ||
| 123 | + await productCategoryPage.searchCategory(modifiedCategoryName); | ||
| 124 | + }); | ||
| 125 | + | ||
| 126 | + // 步骤8:验证分类修改成功 | ||
| 127 | + await allure.step('验证分类修改成功', async () => { | ||
| 128 | + await productCategoryPage.expectCategoryVisible(modifiedCategoryName); | ||
| 129 | + }); | ||
| 130 | + }); | ||
| 131 | + | ||
| 132 | + test('删除商品分类', async ({ page }, testInfo) => { | ||
| 133 | + const productCategoryPage = new ProductCategoryPage(page); | ||
| 134 | + | ||
| 135 | + // 添加allure元素 | ||
| 136 | + await allure.epic('商品管理'); | ||
| 137 | + await allure.feature('商品分类'); | ||
| 138 | + await allure.story('删除商品分类'); | ||
| 139 | + | ||
| 140 | + // 先生成一个分类用于删除 | ||
| 141 | + const categoryName = generateCategoryName(); | ||
| 142 | + console.log('删除分类名称:', categoryName); | ||
| 143 | + | ||
| 144 | + // 步骤1:先创建分类 | ||
| 145 | + await allure.step('创建待删除的分类', async () => { | ||
| 146 | + await productCategoryPage.createCategory(categoryName, '删除测试描述'); | ||
| 147 | + }); | ||
| 148 | + | ||
| 149 | + // 步骤2:搜索分类 | ||
| 150 | + await allure.step('搜索待删除的分类', async () => { | ||
| 151 | + await productCategoryPage.searchCategory(categoryName); | ||
| 152 | + await productCategoryPage.expectCategoryVisible(categoryName); | ||
| 153 | + }); | ||
| 154 | + | ||
| 155 | + // 步骤3:点击删除分类 | ||
| 156 | + await allure.step('点击删除分类按钮', async () => { | ||
| 157 | + await productCategoryPage.clickDeleteCategory(); | ||
| 158 | + }); | ||
| 159 | + | ||
| 160 | + // 步骤4:确认删除 | ||
| 161 | + await allure.step('确认删除', async () => { | ||
| 162 | + await productCategoryPage.clickConfirm(); | ||
| 163 | + }); | ||
| 164 | + | ||
| 165 | + // 步骤5:验证分类已删除 | ||
| 166 | + await allure.step('验证分类已删除', async () => { | ||
| 167 | + await productCategoryPage.expectCategoryNotVisible(categoryName); | ||
| 168 | + }); | ||
| 169 | + }); | ||
| 170 | +}); |
tests/purchase.spec.ts
0 → 100644
| 1 | +import { test, expect } from '@playwright/test'; | ||
| 2 | +import * as allure from 'allure-js-commons'; | ||
| 3 | +import { PurchasePage } from '../pages/purchasePage'; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 采购入库测试 | ||
| 7 | + */ | ||
| 8 | +test.describe('采购入库', () => { | ||
| 9 | + // 使用已保存的认证状态 | ||
| 10 | + test.use({ storageState: 'auth.json' }); | ||
| 11 | + | ||
| 12 | + // 强制测试串行执行,避免并行测试之间的干扰 | ||
| 13 | + test.describe.configure({ mode: 'serial' }); | ||
| 14 | + | ||
| 15 | + test('采购入库存草稿', async ({ page }, testInfo) => { | ||
| 16 | + const purchasePage = new PurchasePage(page); | ||
| 17 | + | ||
| 18 | + // 添加allure元素 | ||
| 19 | + await allure.epic('仓储管理'); | ||
| 20 | + await allure.feature('采购入库'); | ||
| 21 | + await allure.story('采购入库存草稿'); | ||
| 22 | + | ||
| 23 | + // 测试数据 | ||
| 24 | + let supplierName: string; | ||
| 25 | + let warehouseName: string; | ||
| 26 | + | ||
| 27 | + let productName: string; | ||
| 28 | + | ||
| 29 | + // 步骤1:进入采购入库页面 | ||
| 30 | + await allure.step('进入采购入库页面', async () => { | ||
| 31 | + await purchasePage.openPurchaseInbound(); | ||
| 32 | + }); | ||
| 33 | + | ||
| 34 | + // 步骤2:随机选择商品 | ||
| 35 | + await allure.step('随机选择商品', async () => { | ||
| 36 | + productName = await purchasePage.selectRandomProduct(); | ||
| 37 | + console.log('商品:', productName); | ||
| 38 | + }); | ||
| 39 | + | ||
| 40 | + // 步骤3:输入数量 | ||
| 41 | + await allure.step('输入数量', async () => { | ||
| 42 | + await purchasePage.enterQuantity('1'); | ||
| 43 | + }); | ||
| 44 | + | ||
| 45 | + // 步骤5:输入单价 | ||
| 46 | + await allure.step('输入单价', async () => { | ||
| 47 | + await purchasePage.enterPrice('2'); | ||
| 48 | + }); | ||
| 49 | + | ||
| 50 | + // 步骤6:点击完成 | ||
| 51 | + await allure.step('点击完成', async () => { | ||
| 52 | + await purchasePage.clickDone(); | ||
| 53 | + }); | ||
| 54 | + | ||
| 55 | + // 步骤7:随机选择供应商 | ||
| 56 | + await allure.step('随机选择供应商', async () => { | ||
| 57 | + supplierName = await purchasePage.getRandomSupplier(); | ||
| 58 | + console.log('供应商:', supplierName); | ||
| 59 | + }); | ||
| 60 | + | ||
| 61 | + // 步骤8:随机选择仓库 | ||
| 62 | + await allure.step('随机选择仓库', async () => { | ||
| 63 | + warehouseName = await purchasePage.getRandomWarehouse(); | ||
| 64 | + console.log('仓库:', warehouseName); | ||
| 65 | + }); | ||
| 66 | + | ||
| 67 | + // 步骤9:存入草稿 | ||
| 68 | + await allure.step('存入草稿', async () => { | ||
| 69 | + await purchasePage.clickSaveDraft(); | ||
| 70 | + }); | ||
| 71 | + | ||
| 72 | + // 步骤10:打开草稿单列表 | ||
| 73 | + await allure.step('打开草稿单列表', async () => { | ||
| 74 | + await purchasePage.clickDraftList(); | ||
| 75 | + }); | ||
| 76 | + | ||
| 77 | + // 步骤11:搜索商品 | ||
| 78 | + await allure.step('搜索商品', async () => { | ||
| 79 | + await purchasePage.searchInDraftList(productName!); | ||
| 80 | + }); | ||
| 81 | + | ||
| 82 | + // 步骤12:验证草稿中存在该商品 | ||
| 83 | + await allure.step('验证草稿中存在该商品', async () => { | ||
| 84 | + await purchasePage.expectDraftContainsProduct(productName!); | ||
| 85 | + }); | ||
| 86 | + }); | ||
| 87 | + | ||
| 88 | + test('采购入库付款', async ({ page }, testInfo) => { | ||
| 89 | + const purchasePage = new PurchasePage(page); | ||
| 90 | + | ||
| 91 | + // 添加allure元素 | ||
| 92 | + await allure.epic('仓储管理'); | ||
| 93 | + await allure.feature('采购入库'); | ||
| 94 | + await allure.story('采购入库付款'); | ||
| 95 | + | ||
| 96 | + // 测试数据 | ||
| 97 | + let supplierName: string; | ||
| 98 | + let warehouseName: string; | ||
| 99 | + let productName: string; | ||
| 100 | + | ||
| 101 | + // 步骤1:进入采购入库页面 | ||
| 102 | + await allure.step('进入采购入库页面', async () => { | ||
| 103 | + await purchasePage.openPurchaseInbound(); | ||
| 104 | + }); | ||
| 105 | + | ||
| 106 | + // 步骤2:随机选择商品 | ||
| 107 | + await allure.step('随机选择商品', async () => { | ||
| 108 | + productName = await purchasePage.selectRandomProduct(); | ||
| 109 | + console.log('商品:', productName); | ||
| 110 | + }); | ||
| 111 | + | ||
| 112 | + // 步骤3:输入数量 | ||
| 113 | + await allure.step('输入数量', async () => { | ||
| 114 | + await purchasePage.enterQuantity('1'); | ||
| 115 | + }); | ||
| 116 | + | ||
| 117 | + // 步骤4:输入单价 | ||
| 118 | + await allure.step('输入单价', async () => { | ||
| 119 | + await purchasePage.enterPrice('1'); | ||
| 120 | + }); | ||
| 121 | + | ||
| 122 | + // 步骤5:点击完成 | ||
| 123 | + await allure.step('点击完成', async () => { | ||
| 124 | + await purchasePage.clickDone(); | ||
| 125 | + }); | ||
| 126 | + | ||
| 127 | + // 步骤6:添加入库费用 | ||
| 128 | + await allure.step('添加入库费用', async () => { | ||
| 129 | + await purchasePage.addExpense(0, '1'); | ||
| 130 | + }); | ||
| 131 | + | ||
| 132 | + // 步骤7:随机选择供应商 | ||
| 133 | + await allure.step('随机选择供应商', async () => { | ||
| 134 | + supplierName = await purchasePage.getRandomSupplier(); | ||
| 135 | + console.log('供应商:', supplierName); | ||
| 136 | + }); | ||
| 137 | + | ||
| 138 | + // 步骤8:随机选择仓库 | ||
| 139 | + await allure.step('随机选择仓库', async () => { | ||
| 140 | + warehouseName = await purchasePage.getRandomWarehouse(); | ||
| 141 | + console.log('仓库:', warehouseName); | ||
| 142 | + }); | ||
| 143 | + | ||
| 144 | + // 步骤9:选择采购员 | ||
| 145 | + await allure.step('选择采购员', async () => { | ||
| 146 | + await purchasePage.selectPurchaser(); | ||
| 147 | + }); | ||
| 148 | + | ||
| 149 | + // 步骤10:输入车牌号 | ||
| 150 | + await allure.step('输入车牌号', async () => { | ||
| 151 | + await purchasePage.enterLicensePlate('渝A99999'); | ||
| 152 | + }); | ||
| 153 | + | ||
| 154 | + // 步骤11:输入备注 | ||
| 155 | + await allure.step('输入备注', async () => { | ||
| 156 | + await purchasePage.enterRemark('自动化测试'); | ||
| 157 | + }); | ||
| 158 | + | ||
| 159 | + // 步骤12:上传图片(暂时跳过) | ||
| 160 | + // await allure.step('上传图片', async () => { | ||
| 161 | + // await purchasePage.uploadImage('test-data/img/苹果.jpg'); | ||
| 162 | + // }); | ||
| 163 | + | ||
| 164 | + // 步骤13:点击付款 | ||
| 165 | + await allure.step('点击付款', async () => { | ||
| 166 | + await purchasePage.clickPay(); | ||
| 167 | + }); | ||
| 168 | + | ||
| 169 | + // 步骤14:选择结算账户 | ||
| 170 | + await allure.step('选择结算账户', async () => { | ||
| 171 | + await purchasePage.selectSettlementAccount('微信支付'); | ||
| 172 | + }); | ||
| 173 | + | ||
| 174 | + // 步骤15:确认付款 | ||
| 175 | + await allure.step('确认付款', async () => { | ||
| 176 | + await purchasePage.confirmPayment(); | ||
| 177 | + }); | ||
| 178 | + | ||
| 179 | + // 步骤16:进入采购列表 | ||
| 180 | + await allure.step('进入采购列表', async () => { | ||
| 181 | + await purchasePage.goToPurchaseList(); | ||
| 182 | + }); | ||
| 183 | + | ||
| 184 | + // 步骤17:搜索商品 | ||
| 185 | + await allure.step('搜索商品', async () => { | ||
| 186 | + await purchasePage.searchInPurchaseList(productName); | ||
| 187 | + }); | ||
| 188 | + | ||
| 189 | + // 步骤18:验证采购列表中存在该商品 | ||
| 190 | + await allure.step('验证采购列表中存在该商品', async () => { | ||
| 191 | + await purchasePage.expectPurchaseListContainsProduct(productName); | ||
| 192 | + }); | ||
| 193 | + }); | ||
| 194 | +}); | ||
| 0 | \ No newline at end of file | 195 | \ No newline at end of file |