region-edit.spec.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. import { test, expect } from '../../utils/test-setup';
  2. import { readFileSync } from 'fs';
  3. import { join, dirname } from 'path';
  4. import { fileURLToPath } from 'url';
  5. const __filename = fileURLToPath(import.meta.url);
  6. const __dirname = dirname(__filename);
  7. const testUsers = JSON.parse(readFileSync(join(__dirname, '../../fixtures/test-users.json'), 'utf-8'));
  8. /**
  9. * 生成唯一区域名称
  10. * @param prefix - 名称前缀
  11. * @returns 唯一的区域名称
  12. */
  13. function generateUniqueRegionName(prefix: string = '测试区域'): string {
  14. const timestamp = Date.now();
  15. const random = Math.floor(Math.random() * 1000);
  16. return `${prefix}_${timestamp}_${random}`;
  17. }
  18. /**
  19. * 生成唯一区域代码
  20. * @param level - 区域层级
  21. * @returns 唯一的区域代码
  22. */
  23. function generateUniqueRegionCode(level: string): string {
  24. const timestamp = Date.now();
  25. return `${level.toUpperCase()}_${timestamp}`;
  26. }
  27. test.describe.serial('编辑区域测试', () => {
  28. // 用于跟踪测试创建的区域,以便清理
  29. const createdProvinces: string[] = [];
  30. test.beforeEach(async ({ adminLoginPage, regionManagementPage }) => {
  31. // 以管理员身份登录后台
  32. await adminLoginPage.goto();
  33. await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password);
  34. await adminLoginPage.expectLoginSuccess();
  35. await regionManagementPage.goto();
  36. await regionManagementPage.waitForTreeLoaded();
  37. });
  38. test.afterEach(async ({ regionManagementPage }) => {
  39. // 清理测试创建的数据
  40. let cleanupSuccessCount = 0;
  41. let cleanupFailCount = 0;
  42. for (const provinceName of createdProvinces) {
  43. try {
  44. // 等待树形结构就绪后检查区域是否存在
  45. await regionManagementPage.waitForTreeLoaded();
  46. const exists = await regionManagementPage.regionExists(provinceName);
  47. if (exists) {
  48. const deleteSuccess = await regionManagementPage.deleteRegion(provinceName);
  49. if (deleteSuccess) {
  50. cleanupSuccessCount++;
  51. console.debug(`✅ 已清理测试数据: ${provinceName}`);
  52. } else {
  53. cleanupFailCount++;
  54. console.debug(`⚠️ 删除失败(无成功提示): ${provinceName}`);
  55. }
  56. } else {
  57. console.debug(`ℹ️ 区域不存在,跳过删除: ${provinceName}`);
  58. }
  59. } catch (error) {
  60. cleanupFailCount++;
  61. console.debug(`❌ 清理异常: ${provinceName}`, error);
  62. }
  63. }
  64. // 记录清理结果摘要
  65. console.debug(`🧹 测试数据清理: 成功 ${cleanupSuccessCount}, 失败 ${cleanupFailCount}`);
  66. // 如果有清理失败,记录警告但不阻塞测试
  67. if (cleanupFailCount > 0) {
  68. console.debug(`⚠️ 有 ${cleanupFailCount} 个区域清理失败,可能产生脏数据`);
  69. }
  70. // 清空跟踪列表
  71. createdProvinces.length = 0;
  72. });
  73. test.describe('编辑区域名称', () => {
  74. test('应该成功编辑区域名称', async ({ regionManagementPage }) => {
  75. // 首先创建一个测试省份
  76. const originalName = generateUniqueRegionName('测试省');
  77. await regionManagementPage.createProvince({
  78. name: originalName,
  79. code: generateUniqueRegionCode('PROV'),
  80. level: 1,
  81. });
  82. createdProvinces.push(originalName);
  83. // 刷新树形结构以显示新创建的省份
  84. await regionManagementPage.refreshTree();
  85. // 编辑区域名称
  86. const newName = generateUniqueRegionName('编辑后的省');
  87. const result = await regionManagementPage.editRegion(originalName, {
  88. name: newName,
  89. });
  90. // 验证编辑成功
  91. expect(result.success).toBe(true);
  92. expect(result.hasError).toBe(false);
  93. // 等待树形结构刷新
  94. await regionManagementPage.waitForTreeLoaded();
  95. // 验证列表中显示新名称
  96. const exists = await regionManagementPage.regionExists(newName);
  97. expect(exists).toBe(true);
  98. // 更新清理列表中的名称
  99. const index = createdProvinces.indexOf(originalName);
  100. if (index > -1) {
  101. createdProvinces[index] = newName;
  102. }
  103. });
  104. test('编辑后原名称不应存在', async ({ regionManagementPage }) => {
  105. const originalName = generateUniqueRegionName('测试省');
  106. await regionManagementPage.createProvince({
  107. name: originalName,
  108. code: generateUniqueRegionCode('PROV'),
  109. level: 1,
  110. });
  111. createdProvinces.push(originalName);
  112. // 刷新树形结构以显示新创建的省份
  113. await regionManagementPage.refreshTree();
  114. const newName = generateUniqueRegionName('编辑后的省');
  115. const result = await regionManagementPage.editRegion(originalName, { name: newName });
  116. expect(result.success).toBe(true);
  117. await regionManagementPage.waitForTreeLoaded();
  118. // 验证原名称不存在
  119. const originalExists = await regionManagementPage.regionExists(originalName);
  120. expect(originalExists).toBe(false);
  121. // 验证新名称存在
  122. const newExists = await regionManagementPage.regionExists(newName);
  123. expect(newExists).toBe(true);
  124. // 更新清理列表
  125. const index = createdProvinces.indexOf(originalName);
  126. if (index > -1) {
  127. createdProvinces[index] = newName;
  128. }
  129. });
  130. });
  131. test.describe('修改区域代码', () => {
  132. test('应该成功修改行政区划代码', async ({ regionManagementPage }) => {
  133. const provinceName = generateUniqueRegionName('测试省');
  134. await regionManagementPage.createProvince({
  135. name: provinceName,
  136. code: 'OLD_CODE',
  137. level: 1,
  138. });
  139. createdProvinces.push(provinceName);
  140. await regionManagementPage.waitForTreeLoaded();
  141. // 修改代码
  142. const newCode = generateUniqueRegionCode('NEW');
  143. const result = await regionManagementPage.editRegion(provinceName, {
  144. code: newCode,
  145. });
  146. expect(result.success).toBe(true);
  147. expect(result.hasError).toBe(false);
  148. });
  149. test('应该能同时修改名称和代码', async ({ regionManagementPage }) => {
  150. const originalName = generateUniqueRegionName('测试省');
  151. await regionManagementPage.createProvince({
  152. name: originalName,
  153. code: 'OLD_CODE',
  154. level: 1,
  155. });
  156. createdProvinces.push(originalName);
  157. await regionManagementPage.waitForTreeLoaded();
  158. const newName = generateUniqueRegionName('新省名');
  159. const newCode = generateUniqueRegionCode('NEW');
  160. const result = await regionManagementPage.editRegion(originalName, {
  161. name: newName,
  162. code: newCode,
  163. });
  164. expect(result.success).toBe(true);
  165. expect(result.hasError).toBe(false);
  166. await regionManagementPage.waitForTreeLoaded();
  167. expect(await regionManagementPage.regionExists(newName)).toBe(true);
  168. // 更新清理列表
  169. const index = createdProvinces.indexOf(originalName);
  170. if (index > -1) {
  171. createdProvinces[index] = newName;
  172. }
  173. });
  174. });
  175. test.describe('区域状态切换', () => {
  176. test('应该成功禁用已启用的区域', async ({ regionManagementPage }) => {
  177. const provinceName = generateUniqueRegionName('测试省');
  178. console.debug(`创建省份: ${provinceName}`);
  179. await regionManagementPage.createProvince({
  180. name: provinceName,
  181. code: generateUniqueRegionCode('PROV'),
  182. level: 1,
  183. });
  184. createdProvinces.push(provinceName);
  185. // 等待树形结构加载完成
  186. await regionManagementPage.waitForTreeLoaded();
  187. // 验证区域存在并获取初始状态
  188. const exists = await regionManagementPage.regionExists(provinceName);
  189. expect(exists).toBe(true);
  190. const initialStatus = await regionManagementPage.getRegionStatus(provinceName);
  191. expect(initialStatus).toBe('启用');
  192. // 禁用区域
  193. const success = await regionManagementPage.toggleRegionStatus(provinceName);
  194. expect(success).toBe(true);
  195. // 等待树形结构刷新(toggleRegionStatus 内部已等待)
  196. await regionManagementPage.waitForTreeLoaded();
  197. // 验证状态已更新
  198. const newStatus = await regionManagementPage.getRegionStatus(provinceName);
  199. expect(newStatus).toBe('禁用');
  200. });
  201. test('应该成功启用已禁用的区域', async ({ regionManagementPage }) => {
  202. const provinceName = generateUniqueRegionName('测试省');
  203. await regionManagementPage.createProvince({
  204. name: provinceName,
  205. code: generateUniqueRegionCode('PROV'),
  206. level: 1,
  207. });
  208. createdProvinces.push(provinceName);
  209. await regionManagementPage.waitForTreeLoaded();
  210. // 先禁用
  211. await regionManagementPage.toggleRegionStatus(provinceName);
  212. await regionManagementPage.waitForTreeLoaded();
  213. // 验证已禁用
  214. const disabledStatus = await regionManagementPage.getRegionStatus(provinceName);
  215. expect(disabledStatus).toBe('禁用');
  216. // 再启用
  217. const success = await regionManagementPage.toggleRegionStatus(provinceName);
  218. expect(success).toBe(true);
  219. await regionManagementPage.waitForTreeLoaded();
  220. // 验证状态已恢复为启用
  221. const status = await regionManagementPage.getRegionStatus(provinceName);
  222. expect(status).toBe('启用');
  223. });
  224. test('取消状态切换应保持原状态', async ({ regionManagementPage }) => {
  225. const provinceName = generateUniqueRegionName('测试省');
  226. await regionManagementPage.createProvince({
  227. name: provinceName,
  228. code: generateUniqueRegionCode('PROV'),
  229. level: 1,
  230. });
  231. createdProvinces.push(provinceName);
  232. await regionManagementPage.waitForTreeLoaded();
  233. const initialStatus = await regionManagementPage.getRegionStatus(provinceName);
  234. expect(initialStatus).toBe('启用');
  235. // 打开状态切换对话框但取消
  236. await regionManagementPage.openToggleStatusDialog(provinceName);
  237. await regionManagementPage.cancelToggleStatus();
  238. // 验证状态未改变(不需要刷新页面)
  239. const currentStatus = await regionManagementPage.getRegionStatus(provinceName);
  240. expect(currentStatus).toBe(initialStatus);
  241. });
  242. });
  243. test.describe.skip('编辑子区域 - TODO: 需要修复 createChildRegion 功能', () => {
  244. test('应该成功编辑市级区域名称', async ({ regionManagementPage, page }) => {
  245. const provinceName = generateUniqueRegionName('测试省');
  246. const originalCityName = generateUniqueRegionName('测试市');
  247. // 创建省和市
  248. await regionManagementPage.createProvince({
  249. name: provinceName,
  250. code: generateUniqueRegionCode('PROV'),
  251. level: 1,
  252. });
  253. createdProvinces.push(provinceName);
  254. await page.goto('/admin/areas');
  255. await regionManagementPage.waitForTreeLoaded();
  256. // 创建市后,先展开省节点验证市已创建
  257. const cityResult = await regionManagementPage.createChildRegion(provinceName, '市', {
  258. name: originalCityName,
  259. code: generateUniqueRegionCode('CITY'),
  260. level: 2,
  261. });
  262. expect(cityResult.success).toBe(true);
  263. await page.goto('/admin/areas');
  264. await regionManagementPage.waitForTreeLoaded();
  265. // 直接展开新创建的省节点(滚动到可见区域)
  266. await regionManagementPage.expandNode(provinceName);
  267. await page.waitForTimeout(1000);
  268. // 验证市级区域可见
  269. const cityVisible = await regionManagementPage.regionExists(originalCityName);
  270. console.debug(`市级区域 "${originalCityName}" 可见: ${cityVisible}`);
  271. expect(cityVisible).toBe(true);
  272. // 编辑城市名称
  273. const newCityName = generateUniqueRegionName('编辑后的市');
  274. const result = await regionManagementPage.editRegion(originalCityName, {
  275. name: newCityName,
  276. });
  277. expect(result.success).toBe(true);
  278. expect(result.hasError).toBe(false);
  279. });
  280. test('应该成功编辑区级区域状态', async ({ regionManagementPage, page }) => {
  281. const provinceName = generateUniqueRegionName('测试省');
  282. const cityName = generateUniqueRegionName('测试市');
  283. const districtName = generateUniqueRegionName('测试区');
  284. // 创建省市区三级结构
  285. await regionManagementPage.createProvince({
  286. name: provinceName,
  287. code: generateUniqueRegionCode('PROV'),
  288. level: 1,
  289. });
  290. createdProvinces.push(provinceName);
  291. await page.goto('/admin/areas');
  292. await regionManagementPage.waitForTreeLoaded();
  293. const cityResult = await regionManagementPage.createChildRegion(provinceName, '市', {
  294. name: cityName,
  295. code: generateUniqueRegionCode('CITY'),
  296. level: 2,
  297. });
  298. expect(cityResult.success).toBe(true);
  299. await page.goto('/admin/areas');
  300. await regionManagementPage.waitForTreeLoaded();
  301. const districtResult = await regionManagementPage.createChildRegion(provinceName, '市', {
  302. name: districtName,
  303. code: generateUniqueRegionCode('DISTRICT'),
  304. level: 3,
  305. });
  306. expect(districtResult.success).toBe(true);
  307. await page.goto('/admin/areas');
  308. await regionManagementPage.waitForTreeLoaded();
  309. // 展开省节点
  310. await regionManagementPage.expandNode(provinceName);
  311. await page.waitForTimeout(1000);
  312. // 切换区的状态
  313. const success = await regionManagementPage.toggleRegionStatus(districtName);
  314. expect(success).toBe(true);
  315. });
  316. });
  317. test.describe('表单验证', () => {
  318. test('清空名称时应显示错误提示', async ({ regionManagementPage }) => {
  319. const provinceName = generateUniqueRegionName('测试省');
  320. await regionManagementPage.createProvince({
  321. name: provinceName,
  322. code: generateUniqueRegionCode('PROV'),
  323. level: 1,
  324. });
  325. createdProvinces.push(provinceName);
  326. await regionManagementPage.waitForTreeLoaded();
  327. // 打开编辑对话框并清空名称
  328. await regionManagementPage.openEditDialog(provinceName);
  329. await regionManagementPage.page.getByLabel('区域名称').fill('');
  330. // 提交表单
  331. const submitButton = regionManagementPage.page.getByRole('button', { name: '更新' });
  332. await submitButton.click();
  333. // 验证错误提示 - 可能是内联错误或 toast
  334. await regionManagementPage.page.waitForTimeout(500);
  335. // 检查内联错误
  336. const nameError = regionManagementPage.page.getByText('区域名称不能为空');
  337. const hasInlineError = await nameError.count() > 0;
  338. // 检查 toast 错误
  339. const errorToast = regionManagementPage.page.locator('[data-sonner-toast][data-type="error"]');
  340. const hasToastError = await errorToast.count() > 0;
  341. // 至少应该有一种错误提示
  342. expect(hasInlineError || hasToastError).toBe(true);
  343. // 取消对话框
  344. await regionManagementPage.cancelDialog();
  345. });
  346. test('应该支持取消编辑操作', async ({ regionManagementPage }) => {
  347. const provinceName = generateUniqueRegionName('测试省');
  348. await regionManagementPage.createProvince({
  349. name: provinceName,
  350. code: generateUniqueRegionCode('PROV'),
  351. level: 1,
  352. });
  353. createdProvinces.push(provinceName);
  354. await regionManagementPage.waitForTreeLoaded();
  355. // 打开编辑对话框
  356. await regionManagementPage.openEditDialog(provinceName);
  357. // 修改名称
  358. const newName = generateUniqueRegionName('修改后的省');
  359. await regionManagementPage.page.getByLabel('区域名称').fill(newName);
  360. // 点击取消按钮
  361. await regionManagementPage.cancelDialog();
  362. // 验证对话框已关闭
  363. const dialog = regionManagementPage.page.locator('[role="dialog"]');
  364. await expect(dialog).not.toBeVisible();
  365. // 验证数据未修改 - 原名称仍存在
  366. await regionManagementPage.waitForTreeLoaded();
  367. const originalExists = await regionManagementPage.regionExists(provinceName);
  368. expect(originalExists).toBe(true);
  369. });
  370. });
  371. test.describe('连续编辑操作', () => {
  372. test('应该能连续编辑多个区域', async ({ regionManagementPage }) => {
  373. // 创建多个省份
  374. const province1 = generateUniqueRegionName('测试省1');
  375. const province2 = generateUniqueRegionName('测试省2');
  376. await regionManagementPage.createProvince({
  377. name: province1,
  378. code: generateUniqueRegionCode('PROV1'),
  379. level: 1,
  380. });
  381. createdProvinces.push(province1);
  382. await regionManagementPage.waitForTreeLoaded();
  383. await regionManagementPage.createProvince({
  384. name: province2,
  385. code: generateUniqueRegionCode('PROV2'),
  386. level: 1,
  387. });
  388. createdProvinces.push(province2);
  389. await regionManagementPage.waitForTreeLoaded();
  390. // 编辑第一个省份
  391. const newProvince1Name = generateUniqueRegionName('编辑后的省1');
  392. const result1 = await regionManagementPage.editRegion(province1, { name: newProvince1Name });
  393. expect(result1.success).toBe(true);
  394. // 更新清理列表
  395. const index1 = createdProvinces.indexOf(province1);
  396. if (index1 > -1) {
  397. createdProvinces[index1] = newProvince1Name;
  398. }
  399. // 编辑第二个省份
  400. const newProvince2Name = generateUniqueRegionName('编辑后的省2');
  401. const result2 = await regionManagementPage.editRegion(province2, { name: newProvince2Name });
  402. expect(result2.success).toBe(true);
  403. // 更新清理列表
  404. const index2 = createdProvinces.indexOf(province2);
  405. if (index2 > -1) {
  406. createdProvinces[index2] = newProvince2Name;
  407. }
  408. });
  409. });
  410. });