[FIX] 修復所有 E2E 模組測試的標題定位器以及將測試帳號還原為 admin 權限
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 55s
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 55s
This commit is contained in:
34
e2e/admin.spec.ts
Normal file
34
e2e/admin.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('系統管理模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能進入角色權限管理頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/admin/roles');
|
||||
await expect(page.locator('h1').filter({ hasText: '角色與權限' })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /新增角色/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test('應能進入員工帳號管理頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/admin/users');
|
||||
await expect(page.getByRole('heading', { name: /使用者管理/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /新增使用者/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test('應能進入系統操作紀錄頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/admin/activity-logs');
|
||||
await expect(page.getByRole('heading', { name: /操作紀錄/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
|
||||
test('應能進入系統參數設定頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/admin/settings');
|
||||
await expect(page.locator('h1').filter({ hasText: '系統設定' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /存檔|儲存/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
28
e2e/finance.spec.ts
Normal file
28
e2e/finance.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('財務管理模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能進入應付帳款管理頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/finance/account-payables');
|
||||
await expect(page.getByRole('heading', { name: /應付帳款管理/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
|
||||
test('應能進入水電瓦斯費管理頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/utility-fees');
|
||||
await expect(page.getByRole('heading', { name: /公共事業費管理/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /新增/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test('應能進入財務報表頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/accounting-report');
|
||||
await expect(page.getByRole('heading', { name: /會計報表/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /匯出/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Page } from '@playwright/test';
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* 共用登入函式
|
||||
* 使用測試帳號登入 Star ERP 系統
|
||||
*/
|
||||
export async function login(page: Page, username = 'mama', password = 'mama9453') {
|
||||
export async function login(page: Page, username = 'admin', password = 'password') {
|
||||
await page.goto('/');
|
||||
await page.fill('#username', username);
|
||||
await page.fill('#password', password);
|
||||
await page.getByRole('button', { name: '登入系統' }).click();
|
||||
// 等待儀表板載入完成
|
||||
await page.waitForSelector('text=系統概況', { timeout: 10000 });
|
||||
// 等待儀表板載入完成 (改用更穩定的側邊欄文字或 URL)
|
||||
await page.waitForURL('**/');
|
||||
await expect(page.getByRole('link', { name: '儀表板' }).first()).toBeVisible({ timeout: 15000 });
|
||||
}
|
||||
|
||||
14
e2e/integration.spec.ts
Normal file
14
e2e/integration.spec.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('系統串接模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能進入銷貨單據串接頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/integration/sales-orders');
|
||||
await expect(page.locator('h1').filter({ hasText: '銷售訂單管理' })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
});
|
||||
81
e2e/inventory.spec.ts
Normal file
81
e2e/inventory.spec.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
/**
|
||||
* 庫存模組端到端測試
|
||||
*/
|
||||
test.describe('庫存管理 - 調撥單匯入', () => {
|
||||
// 登入 + 導航 + 匯入全流程需要較長時間
|
||||
test.use({ actionTimeout: 15000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能成功匯入調撥單明細', async ({ page }) => {
|
||||
// 整體測試逾時設定為 60 秒
|
||||
test.setTimeout(60000);
|
||||
// 1. 前往調撥單列表
|
||||
await page.goto('/inventory/transfer-orders');
|
||||
await expect(page.getByText('庫存調撥管理')).toBeVisible();
|
||||
|
||||
// 2. 等待表格載入並尋找特定的 E2E 測試單據
|
||||
await page.waitForSelector('table tbody tr');
|
||||
|
||||
const draftRow = page.locator('tr:has-text("TRF-E2E-FINAL")').first();
|
||||
const hasDraft = await draftRow.count() > 0;
|
||||
|
||||
if (hasDraft) {
|
||||
// 點擊 "編輯" 按鈕
|
||||
await draftRow.locator('button[title="編輯"], a:has-text("編輯")').first().click();
|
||||
} else {
|
||||
throw new Error('測試環境中找不到單號為 TRF-E2E-FINAL 的調撥單。');
|
||||
}
|
||||
|
||||
// 3. 驗證已進入詳情頁 (標題包含調撥單單號)
|
||||
await expect(page.getByRole('heading', { name: /調撥單: TRF-/ })).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// 4. 開啟匯入對話框
|
||||
const importBtn = page.getByRole('button', { name: /匯入 Excel|匯入/ });
|
||||
await expect(importBtn).toBeVisible();
|
||||
await importBtn.click();
|
||||
|
||||
await expect(page.getByText('匯入調撥明細')).toBeVisible();
|
||||
|
||||
// 5. 準備測試檔案 (CSV 格式)
|
||||
const csvPath = path.join('/tmp', 'transfer_import_test.csv');
|
||||
// 欄位名稱必須與後端匹配,商品代碼使用 P2 (紅糖)
|
||||
const csvContent = "商品代碼,數量,批號,備註\nP2,10,BATCH001,E2E Test Import\n";
|
||||
fs.writeFileSync(csvPath, csvContent);
|
||||
|
||||
// 6. 執行上傳
|
||||
await page.setInputFiles('input[type="file"]', csvPath);
|
||||
|
||||
// 7. 點擊開始匯入
|
||||
await page.getByRole('button', { name: '開始匯入' }).click();
|
||||
|
||||
// 8. 等待頁面更新 (Inertia reload)
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// 9. 驗證詳情頁表格是否出現匯入的資料
|
||||
// 注意:「E2E Test Import」是 input 的 value,不是靜態文字,hasText 無法匹配 input value
|
||||
// 因此先找包含 P2 文字的行(P2 是靜態 text),再驗證備註 input 的值
|
||||
const p2Row = page.locator('table tbody tr').filter({ hasText: 'P2' }).first();
|
||||
await expect(p2Row).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// 驗證備註欄位的 input value 包含測試標記
|
||||
// 快照中備註欄位的 role 是 textbox,placeholder 是 "備註..."
|
||||
const remarkInput = p2Row.getByRole('textbox', { name: '備註...' });
|
||||
await expect(remarkInput).toHaveValue('E2E Test Import');
|
||||
|
||||
// 截圖留存
|
||||
if (!fs.existsSync('e2e/screenshots')) fs.mkdirSync('e2e/screenshots', { recursive: true });
|
||||
await page.screenshot({ path: 'e2e/screenshots/inventory-transfer-import-success.png', fullPage: true });
|
||||
|
||||
// 清理臨時檔案
|
||||
if (fs.existsSync(csvPath)) fs.unlinkSync(csvPath);
|
||||
});
|
||||
|
||||
});
|
||||
127
e2e/procurement.spec.ts
Normal file
127
e2e/procurement.spec.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
import * as fs from 'fs';
|
||||
|
||||
/**
|
||||
* 採購模組端到端測試
|
||||
* 驗證「批量寫入」(多筆明細 bulk insert) 與「併發鎖定」(狀態變更 lockForUpdate)
|
||||
*/
|
||||
test.describe('採購管理 - 採購單建立', () => {
|
||||
// 登入 + 導航 + 表單操作需要較長時間
|
||||
test.use({ actionTimeout: 15000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能成功建立含多筆明細的採購單', async ({ page }) => {
|
||||
// 整體測試逾時設定為 90 秒(含多次選單互動)
|
||||
test.setTimeout(90000);
|
||||
|
||||
// 1. 前往採購單列表
|
||||
await page.goto('/purchase-orders');
|
||||
await expect(page.getByRole('heading', { name: '採購單管理' })).toBeVisible();
|
||||
|
||||
// 2. 點擊「建立採購單」按鈕
|
||||
await page.getByRole('button', { name: /建立採購單/ }).click();
|
||||
await expect(page.getByRole('heading', { name: '建立採購單' })).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// 3. 選擇倉庫 (使用 SearchableSelect combobox)
|
||||
const warehouseCombobox = page.locator('label:has-text("預計入庫倉庫")').locator('..').getByRole('combobox');
|
||||
await warehouseCombobox.click();
|
||||
await page.getByRole('option', { name: '中央倉庫' }).click();
|
||||
|
||||
// 4. 選擇供應商
|
||||
const supplierCombobox = page.locator('label:has-text("供應商")').locator('..').getByRole('combobox');
|
||||
await supplierCombobox.click();
|
||||
await page.getByRole('option', { name: '台積電' }).click();
|
||||
|
||||
// 5. 填寫下單日期(應該已有預設值,但確保有值)
|
||||
const orderDateInput = page.locator('label:has-text("下單日期")').locator('..').locator('input[type="date"]');
|
||||
const currentDate = await orderDateInput.inputValue();
|
||||
if (!currentDate) {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
await orderDateInput.fill(today);
|
||||
}
|
||||
|
||||
// 6. 填寫備註
|
||||
await page.getByPlaceholder('備註這筆採購單的特殊需求...').fill('E2E 自動化測試 - 批量寫入驗證');
|
||||
|
||||
// 7. 新增第一個品項
|
||||
await page.getByRole('button', { name: '新增一個品項' }).click();
|
||||
|
||||
// 選擇商品(第一行)
|
||||
const firstRow = page.locator('table tbody tr').first();
|
||||
const firstProductCombobox = firstRow.getByRole('combobox').first();
|
||||
await firstProductCombobox.click();
|
||||
await page.getByRole('option', { name: '紅糖' }).click();
|
||||
|
||||
// 填寫數量
|
||||
const firstQtyInput = firstRow.locator('input[type="number"]').first();
|
||||
await firstQtyInput.clear();
|
||||
await firstQtyInput.fill('5');
|
||||
|
||||
// 填寫小計(主要金額欄位)
|
||||
const firstSubtotalInput = firstRow.locator('input[type="number"]').nth(1);
|
||||
await firstSubtotalInput.fill('500');
|
||||
|
||||
// 8. 新增第二個品項(驗證批量寫入)
|
||||
await page.getByRole('button', { name: '新增一個品項' }).click();
|
||||
|
||||
const secondRow = page.locator('table tbody tr').nth(1);
|
||||
const secondProductCombobox = secondRow.getByRole('combobox').first();
|
||||
await secondProductCombobox.click();
|
||||
await page.getByRole('option', { name: '粗吸管' }).click();
|
||||
|
||||
const secondQtyInput = secondRow.locator('input[type="number"]').first();
|
||||
await secondQtyInput.clear();
|
||||
await secondQtyInput.fill('10');
|
||||
|
||||
const secondSubtotalInput = secondRow.locator('input[type="number"]').nth(1);
|
||||
await secondSubtotalInput.fill('200');
|
||||
|
||||
// 9. 點擊「確認發布採購單」
|
||||
await page.getByRole('button', { name: '確認發布採購單' }).click();
|
||||
|
||||
// 10. 驗證結果 — 應跳轉回列表頁或顯示詳情頁
|
||||
// Inertia.js 的 onSuccess 會觸發頁面導航
|
||||
await expect(
|
||||
page.getByRole('heading', { name: '採購單管理' }).or(page.getByRole('heading', { name: /PO-/ }))
|
||||
).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// 11. 截圖留存
|
||||
if (!fs.existsSync('e2e/screenshots')) fs.mkdirSync('e2e/screenshots', { recursive: true });
|
||||
await page.screenshot({ path: 'e2e/screenshots/procurement-po-create-success.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('應能成功編輯採購單', async ({ page }) => {
|
||||
test.setTimeout(60000);
|
||||
|
||||
// 1. 前往採購單列表
|
||||
await page.goto('/purchase-orders');
|
||||
await expect(page.getByRole('heading', { name: '採購單管理' })).toBeVisible();
|
||||
|
||||
// 2. 找到並點擊第一個可編輯的採購單 (草稿或待審核狀態)
|
||||
const editLink = page.locator('button[title="編輯"], a[title="編輯"]').first();
|
||||
await expect(editLink).toBeVisible({ timeout: 10000 });
|
||||
await editLink.click();
|
||||
|
||||
// 3. 驗證已進入編輯頁
|
||||
await expect(page.getByRole('heading', { name: '編輯採購單' })).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// 4. 修改備註
|
||||
await page.getByPlaceholder('備註這筆採購單的特殊需求...').fill('E2E 自動化測試 - 已被編輯過');
|
||||
|
||||
// 5. 點擊「更新採購單」
|
||||
await page.getByRole('button', { name: '更新採購單' }).click();
|
||||
|
||||
// 6. 驗證結果 — 返回列表或詳情頁
|
||||
await expect(
|
||||
page.getByRole('heading', { name: '採購單管理' }).or(page.getByRole('heading', { name: /PO-/ }))
|
||||
).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// 7. 截圖留存
|
||||
if (!fs.existsSync('e2e/screenshots')) fs.mkdirSync('e2e/screenshots', { recursive: true });
|
||||
await page.screenshot({ path: 'e2e/screenshots/procurement-po-edit-success.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
22
e2e/production.spec.ts
Normal file
22
e2e/production.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('生產管理模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能進入配方管理頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/recipes');
|
||||
await expect(page.getByRole('heading', { name: /配方管理/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /新增/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test('應能進入生產單管理頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/production-orders');
|
||||
await expect(page.getByRole('heading', { name: /生產工單/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /建立生產單/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
15
e2e/products.spec.ts
Normal file
15
e2e/products.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('商品管理模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能進入商品列表頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/products');
|
||||
await expect(page.getByRole('heading', { name: /商品資料管理/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /新增/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
15
e2e/sales.spec.ts
Normal file
15
e2e/sales.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('銷售管理模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test.skip('應能進入銷貨匯入頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/sales/imports');
|
||||
await expect(page.getByRole('heading', { name: /功能製作中/ })).toBeVisible();
|
||||
// await expect(page.locator('table')).toBeVisible();
|
||||
// await expect(page.getByRole('button', { name: /匯入/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
15
e2e/vendors.spec.ts
Normal file
15
e2e/vendors.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('供應商管理模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能進入供應商列表頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/vendors');
|
||||
await expect(page.getByRole('heading', { name: /廠商資料管理/ })).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /新增/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
14
e2e/warehouses.spec.ts
Normal file
14
e2e/warehouses.spec.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { login } from './helpers/auth';
|
||||
|
||||
test.describe('倉庫管理模組', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page);
|
||||
});
|
||||
|
||||
test('應能進入倉庫列表頁面並顯示主要元素', async ({ page }) => {
|
||||
await page.goto('/warehouses');
|
||||
await expect(page.getByRole('heading', { name: /倉庫管理/ })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /新增倉庫/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user