You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

236 lines
7.3 KiB

  1. import type { Page } from '@playwright/test';
  2. import {
  3. adminStorageStatePath,
  4. expect,
  5. rootStorageStatePath,
  6. test,
  7. } from './fixtures/cluster';
  8. function nextBatchName(prefix: string) {
  9. return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
  10. }
  11. async function gotoUserPage(page: Page) {
  12. await page.goto('/console/user');
  13. await expect(page.getByTestId('user-page')).toBeVisible();
  14. }
  15. async function openMigrationTab(page: Page) {
  16. const migrationTab = page.getByTestId('user-migration-tab');
  17. await expect(migrationTab).toBeVisible();
  18. await migrationTab.click();
  19. await expect(page.getByTestId('user-migration-center')).toBeVisible();
  20. }
  21. async function selectCandidateUser(page: Page, userId: number) {
  22. const row = page.getByTestId(`migration-candidate-row-${userId}`);
  23. await expect(row).toBeVisible();
  24. const checkboxInput = row.locator('input[type="checkbox"]');
  25. if ((await checkboxInput.count()) > 0) {
  26. await checkboxInput.first().check({ force: true });
  27. return;
  28. }
  29. await row.locator('.semi-checkbox').first().click();
  30. }
  31. async function createBatch(
  32. page: Page,
  33. batchName: string,
  34. selectedUserIds: number[] = [],
  35. ) {
  36. await page.getByTestId('migration-create-batch-button').click();
  37. await expect(
  38. page.getByTestId('migration-selection-mode-group'),
  39. ).toBeVisible();
  40. if (selectedUserIds.length > 0) {
  41. await page.getByTestId('migration-selection-mode-explicit').click();
  42. await expect(page.getByTestId('migration-candidate-table')).toBeVisible();
  43. for (const userId of selectedUserIds) {
  44. await selectCandidateUser(page, userId);
  45. }
  46. }
  47. await page.getByTestId('migration-create-batch-submit').click();
  48. const nameField = page.getByTestId('migration-batch-name-field');
  49. await expect(nameField).toBeVisible();
  50. await nameField.fill(batchName);
  51. await page.getByTestId('migration-create-batch-submit').click();
  52. await expect(page.getByTestId('migration-batch-table')).toContainText(
  53. batchName,
  54. );
  55. await page.getByTestId('migration-batch-table').getByText(batchName).click();
  56. await expect(page.getByTestId('migration-execution-panel')).toBeVisible();
  57. await expect(page.getByTestId('migration-batch-detail')).toBeVisible();
  58. }
  59. test.describe('User Migration Access', () => {
  60. test.use({ storageState: adminStorageStatePath });
  61. test('non-root admin cannot see migration tab', async ({ page }) => {
  62. await gotoUserPage(page);
  63. await expect(page.getByTestId('user-migration-tab')).toHaveCount(0);
  64. });
  65. });
  66. test.describe.serial('User Migration Smoke', () => {
  67. test.use({ storageState: rootStorageStatePath });
  68. test('root can open migration center and create a batch', async ({
  69. page,
  70. }) => {
  71. const batchName = nextBatchName('ui-create');
  72. await gotoUserPage(page);
  73. await openMigrationTab(page);
  74. await createBatch(page, batchName);
  75. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  76. 'draft',
  77. );
  78. });
  79. test('root can clear selected batch from the list', async ({ page }) => {
  80. const batchName = nextBatchName('ui-clear');
  81. await gotoUserPage(page);
  82. await openMigrationTab(page);
  83. await createBatch(page, batchName);
  84. await expect(page.getByTestId('migration-batch-detail')).toBeVisible();
  85. await page.getByTestId('migration-clear-batch-selection-button').click();
  86. await expect(page.getByTestId('migration-batch-detail')).toHaveCount(0);
  87. await expect(page.getByTestId('migration-execution-panel')).toHaveCount(0);
  88. });
  89. test('root can create explicit_ids batch from candidate users', async ({
  90. page,
  91. }) => {
  92. const batchName = nextBatchName('ui-explicit');
  93. await gotoUserPage(page);
  94. await openMigrationTab(page);
  95. await createBatch(page, batchName, [10000003, 10000004]);
  96. await expect(page.getByTestId('migration-batch-table')).toContainText(
  97. '指定 2 人',
  98. );
  99. await expect(page.getByTestId('migration-selection-summary')).toContainText(
  100. '已选 2 人',
  101. );
  102. });
  103. test('root can cancel a batch and recreate selected users in a new batch', async ({
  104. page,
  105. }) => {
  106. const firstBatchName = nextBatchName('ui-cancel-source');
  107. const secondBatchName = nextBatchName('ui-cancel-recreate');
  108. await gotoUserPage(page);
  109. await openMigrationTab(page);
  110. await createBatch(page, firstBatchName, [10000001, 10000002]);
  111. await page.getByTestId('migration-cancel-button').click();
  112. await expect(page.getByTestId('migration-cancel-confirm')).toBeVisible();
  113. await page.getByTestId('migration-cancel-confirm').click();
  114. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  115. 'cancelled',
  116. );
  117. await createBatch(page, secondBatchName, [10000001, 10000002]);
  118. await expect(page.getByTestId('migration-batch-table')).toContainText(
  119. secondBatchName,
  120. );
  121. });
  122. test('root can scan, resolve conflicts, execute, and verify a batch', async ({
  123. page,
  124. }) => {
  125. const batchName = nextBatchName('ui-flow');
  126. await gotoUserPage(page);
  127. await openMigrationTab(page);
  128. await createBatch(page, batchName);
  129. await page.getByTestId('migration-scan-button').click();
  130. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  131. /scanning|scanned/,
  132. );
  133. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  134. 'scanned',
  135. {
  136. timeout: 25_000,
  137. },
  138. );
  139. await expect(page.getByTestId('migration-conflict-table')).toBeVisible();
  140. await page.getByTestId('migration-resolve-skip-10000003').click();
  141. await page.getByTestId('migration-resolve-skip-10000004').click();
  142. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  143. 'ready',
  144. );
  145. await page.getByTestId('migration-execute-button').click();
  146. await expect(page.getByTestId('migration-execute-confirm')).toBeVisible();
  147. await page.getByTestId('migration-execute-confirm').click();
  148. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  149. /running|completed/,
  150. );
  151. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  152. 'completed',
  153. {
  154. timeout: 25_000,
  155. },
  156. );
  157. await page.getByTestId('migration-verify-button').click();
  158. await expect(
  159. page.getByTestId('migration-verify-results-table'),
  160. ).toBeVisible();
  161. await expect(
  162. page.getByTestId('migration-verify-results-table'),
  163. ).toContainText('10000001');
  164. await expect(
  165. page.getByTestId('migration-batch-detail-table'),
  166. ).toContainText('migrated');
  167. });
  168. test('page reload keeps batch list and detail loading available', async ({
  169. page,
  170. }) => {
  171. const batchName = nextBatchName('ui-refresh');
  172. await gotoUserPage(page);
  173. await openMigrationTab(page);
  174. await createBatch(page, batchName);
  175. await page.getByTestId('migration-scan-button').click();
  176. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  177. 'scanned',
  178. {
  179. timeout: 25_000,
  180. },
  181. );
  182. await page.reload();
  183. await gotoUserPage(page);
  184. await openMigrationTab(page);
  185. await expect(page.getByTestId('migration-batch-table')).toContainText(
  186. batchName,
  187. );
  188. await page
  189. .getByTestId('migration-batch-table')
  190. .getByText(batchName)
  191. .click();
  192. await expect(page.getByTestId('migration-batch-detail')).toBeVisible();
  193. await expect(page.getByTestId('migration-batch-status')).toHaveText(
  194. 'scanned',
  195. );
  196. });
  197. });