| import { test, expect } from '@playwright/test'
|
| import { gotoAndEnsureAuth, waitForGraphQLOperation } from './auth.utils'
|
|
|
| test.describe('Users - Add to Project', () => {
|
| let testProjectId: string
|
| let testProjectName: string
|
| let testUserEmail: string
|
|
|
| test.beforeAll(async ({ browser }) => {
|
| test.setTimeout(120000)
|
|
|
| const context = await browser.newContext()
|
| const page = await context.newPage()
|
|
|
| await gotoAndEnsureAuth(page, '/projects')
|
|
|
|
|
| const uniqueSuffix = Date.now().toString().slice(-6)
|
| testProjectName = `pw-test-Project for Users ${uniqueSuffix}`
|
|
|
| const createProjectButton = page.getByRole('button', { name: /创建项目|Create Project|新建项目|Add Project/i })
|
| await createProjectButton.click()
|
|
|
| const projectDialog = page.getByRole('dialog')
|
| await projectDialog.getByLabel(/名称|Name/i).fill(testProjectName)
|
| await projectDialog.getByLabel(/描述|Description/i).fill('pw-test project for user assignment tests')
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'CreateProject'),
|
| projectDialog.getByRole('button', { name: /创建|Create|保存|Save/i }).click()
|
| ])
|
|
|
| await page.waitForTimeout(500)
|
|
|
|
|
| await page.goto('/users')
|
| await page.waitForLoadState('domcontentloaded')
|
|
|
| testUserEmail = `pw-test-project-user-${uniqueSuffix}@example.com`
|
|
|
| const createUserButton = page.getByRole('button', { name: /添加用户|Add User|新增用户|Create User/i })
|
| await createUserButton.click()
|
|
|
| const userDialog = page.getByRole('dialog')
|
| await userDialog.getByLabel(/邮箱|Email/i).fill(testUserEmail)
|
| await userDialog.getByLabel(/名|First Name/i).fill('pw-test-ProjectTest')
|
| await userDialog.getByLabel(/姓|Last Name/i).fill(uniqueSuffix)
|
|
|
| const passwordField = userDialog.locator('input[type="password"]').first()
|
| await passwordField.fill('TestPass123!')
|
|
|
| const confirmPasswordField = userDialog.locator('input[type="password"]').last()
|
| await confirmPasswordField.fill('TestPass123!')
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'CreateUser'),
|
| userDialog.getByRole('button', { name: /保存|Save|创建|Create|Save changes/i }).click()
|
| ])
|
|
|
| await page.waitForTimeout(500)
|
| await context.close()
|
| })
|
|
|
| test.beforeEach(async ({ page }) => {
|
| test.setTimeout(60000)
|
| await gotoAndEnsureAuth(page, '/users')
|
| })
|
|
|
| test('can add user to project with owner permission', async ({ page }) => {
|
|
|
| const usersTable = page.locator('[data-testid="users-table"], table:has(th), table').first()
|
| const userRow = usersTable.locator('tbody tr').filter({ hasText: testUserEmail })
|
| await expect(userRow).toBeVisible()
|
|
|
|
|
| const actionsTrigger = userRow.locator('[data-testid="row-actions"], button:has(svg), .dropdown-trigger, .action-button, button:has-text("Open menu")').first()
|
| await actionsTrigger.click()
|
|
|
|
|
| const menu = page.getByRole('menu')
|
| await expect(menu).toBeVisible()
|
| await menu.getByRole('menuitem', { name: /添加到项目|Add to Project|加入项目/i }).focus()
|
| await page.keyboard.press('Enter')
|
|
|
|
|
| const addToProjectDialog = page.getByRole('dialog')
|
| await expect(addToProjectDialog).toBeVisible()
|
| await expect(addToProjectDialog).toContainText(/添加到项目|Add to Project/i)
|
|
|
|
|
| const projectSelect = addToProjectDialog.locator('button[role="combobox"], select, .select-trigger').first()
|
| await projectSelect.click()
|
|
|
|
|
| const projectOption = page.locator('[role="option"], [role="menuitem"]').filter({ hasText: testProjectName })
|
| await expect(projectOption).toBeVisible()
|
| await projectOption.click()
|
|
|
|
|
| const ownerCheckbox = addToProjectDialog.getByRole('checkbox', { name: /所有者|Owner|拥有者/i })
|
| await expect(ownerCheckbox).toBeVisible()
|
| await ownerCheckbox.check()
|
|
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'AddUserToProject'),
|
| addToProjectDialog.getByRole('button', { name: /添加|Add|保存|Save|确认|Confirm/i }).click()
|
| ])
|
|
|
|
|
| await expect(addToProjectDialog).not.toBeVisible()
|
| })
|
|
|
| test('can add user to project with specific roles', async ({ page }) => {
|
|
|
| const uniqueSuffix = Date.now().toString().slice(-6)
|
| const newUserEmail = `pw-test-roles-user-${uniqueSuffix}@example.com`
|
|
|
| const createUserButton = page.getByRole('button', { name: /添加用户|Add User|新增用户|Create User/i })
|
| await createUserButton.click()
|
|
|
| const userDialog = page.getByRole('dialog')
|
| await userDialog.getByLabel(/邮箱|Email/i).fill(newUserEmail)
|
| await userDialog.getByLabel(/名|First Name/i).fill('pw-test-RolesTest')
|
| await userDialog.getByLabel(/姓|Last Name/i).fill(uniqueSuffix)
|
|
|
| const passwordField = userDialog.locator('input[type="password"]').first()
|
| await passwordField.fill('TestPass123!')
|
|
|
| const confirmPasswordField = userDialog.locator('input[type="password"]').last()
|
| await confirmPasswordField.fill('TestPass123!')
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'CreateUser'),
|
| userDialog.getByRole('button', { name: /保存|Save|创建|Create|Save changes/i }).click()
|
| ])
|
|
|
| await page.waitForTimeout(500)
|
|
|
|
|
| const usersTable = page.locator('[data-testid="users-table"], table:has(th), table').first()
|
| const userRow = usersTable.locator('tbody tr').filter({ hasText: newUserEmail })
|
| await expect(userRow).toBeVisible()
|
|
|
|
|
| const actionsTrigger = userRow.locator('[data-testid="row-actions"], button:has(svg), .dropdown-trigger, .action-button, button:has-text("Open menu")').first()
|
| await actionsTrigger.click()
|
|
|
|
|
| const menu = page.getByRole('menu')
|
| await expect(menu).toBeVisible()
|
| await menu.getByRole('menuitem', { name: /添加到项目|Add to Project|加入项目/i }).focus()
|
| await page.keyboard.press('Enter')
|
|
|
|
|
| const addToProjectDialog = page.getByRole('dialog')
|
| await expect(addToProjectDialog).toBeVisible()
|
|
|
|
|
| const projectSelect = addToProjectDialog.locator('button[role="combobox"], select, .select-trigger').first()
|
| await projectSelect.click()
|
|
|
| const projectOption = page.locator('[role="option"], [role="menuitem"]').filter({ hasText: testProjectName })
|
| await expect(projectOption).toBeVisible()
|
| await projectOption.click()
|
|
|
|
|
| await page.waitForTimeout(1000)
|
|
|
|
|
| const rolesSection = addToProjectDialog.locator('text=/项目角色|Project Roles/i')
|
| if (await rolesSection.isVisible()) {
|
|
|
| const firstRoleCheckbox = addToProjectDialog.locator('input[type="checkbox"][id^="role-"]').first()
|
| if (await firstRoleCheckbox.isVisible()) {
|
| await firstRoleCheckbox.check()
|
| }
|
| }
|
|
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'AddUserToProject'),
|
| addToProjectDialog.getByRole('button', { name: /添加|Add|保存|Save|确认|Confirm/i }).click()
|
| ])
|
|
|
|
|
| await expect(addToProjectDialog).not.toBeVisible()
|
| })
|
|
|
| test('can add user to project with specific scopes', async ({ page }) => {
|
|
|
| const uniqueSuffix = Date.now().toString().slice(-6)
|
| const newUserEmail = `pw-test-scopes-user-${uniqueSuffix}@example.com`
|
|
|
| const createUserButton = page.getByRole('button', { name: /添加用户|Add User|新增用户|Create User/i })
|
| await createUserButton.click()
|
|
|
| const userDialog = page.getByRole('dialog')
|
| await userDialog.getByLabel(/邮箱|Email/i).fill(newUserEmail)
|
| await userDialog.getByLabel(/名|First Name/i).fill('pw-test-ScopesTest')
|
| await userDialog.getByLabel(/姓|Last Name/i).fill(uniqueSuffix)
|
|
|
| const passwordField = userDialog.locator('input[type="password"]').first()
|
| await passwordField.fill('TestPass123!')
|
|
|
| const confirmPasswordField = userDialog.locator('input[type="password"]').last()
|
| await confirmPasswordField.fill('TestPass123!')
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'CreateUser'),
|
| userDialog.getByRole('button', { name: /保存|Save|创建|Create|Save changes/i }).click()
|
| ])
|
|
|
| await page.waitForTimeout(500)
|
|
|
|
|
| const usersTable = page.locator('[data-testid="users-table"], table:has(th), table').first()
|
| const userRow = usersTable.locator('tbody tr').filter({ hasText: newUserEmail })
|
| await expect(userRow).toBeVisible()
|
|
|
|
|
| const actionsTrigger = userRow.locator('[data-testid="row-actions"], button:has(svg), .dropdown-trigger, .action-button, button:has-text("Open menu")').first()
|
| await actionsTrigger.click()
|
|
|
|
|
| const menu = page.getByRole('menu')
|
| await expect(menu).toBeVisible()
|
| await menu.getByRole('menuitem', { name: /添加到项目|Add to Project|加入项目/i }).focus()
|
| await page.keyboard.press('Enter')
|
|
|
|
|
| const addToProjectDialog = page.getByRole('dialog')
|
| await expect(addToProjectDialog).toBeVisible()
|
|
|
|
|
| const projectSelect = addToProjectDialog.locator('button[role="combobox"], select, .select-trigger').first()
|
| await projectSelect.click()
|
|
|
| const projectOption = page.locator('[role="option"], [role="menuitem"]').filter({ hasText: testProjectName })
|
| await expect(projectOption).toBeVisible()
|
| await projectOption.click()
|
|
|
|
|
| await page.waitForTimeout(1000)
|
|
|
|
|
| const scopesSection = addToProjectDialog.locator('text=/项目权限|Project Scopes|项目范围/i')
|
| if (await scopesSection.isVisible()) {
|
|
|
| const firstScopeCheckbox = addToProjectDialog.locator('input[type="checkbox"][id^="scope-"]').first()
|
| if (await firstScopeCheckbox.isVisible()) {
|
| await firstScopeCheckbox.check()
|
| }
|
| }
|
|
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'AddUserToProject'),
|
| addToProjectDialog.getByRole('button', { name: /添加|Add|保存|Save|确认|Confirm/i }).click()
|
| ])
|
|
|
|
|
| await expect(addToProjectDialog).not.toBeVisible()
|
| })
|
|
|
| test('shows disabled projects that user is already member of', async ({ page }) => {
|
|
|
| const usersTable = page.locator('[data-testid="users-table"], table:has(th), table').first()
|
| const userRow = usersTable.locator('tbody tr').filter({ hasText: testUserEmail })
|
| await expect(userRow).toBeVisible()
|
|
|
|
|
| const actionsTrigger = userRow.locator('[data-testid="row-actions"], button:has(svg), .dropdown-trigger, .action-button, button:has-text("Open menu")').first()
|
| await actionsTrigger.click()
|
|
|
|
|
| const menu = page.getByRole('menu')
|
| await expect(menu).toBeVisible()
|
| await menu.getByRole('menuitem', { name: /添加到项目|Add to Project|加入项目/i }).focus()
|
| await page.keyboard.press('Enter')
|
|
|
|
|
| const addToProjectDialog = page.getByRole('dialog')
|
| await expect(addToProjectDialog).toBeVisible()
|
|
|
|
|
| const projectSelect = addToProjectDialog.locator('button[role="combobox"], select, .select-trigger').first()
|
| await projectSelect.click()
|
|
|
|
|
|
|
|
|
| const dropdown = page.locator('[role="listbox"], [role="menu"]')
|
| await expect(dropdown).toBeVisible()
|
|
|
|
|
| await page.keyboard.press('Escape')
|
| })
|
|
|
| test('validates project selection is required', async ({ page }) => {
|
|
|
| const uniqueSuffix = Date.now().toString().slice(-6)
|
| const newUserEmail = `playwright-validation-${uniqueSuffix}@example.com`
|
|
|
| const createUserButton = page.getByRole('button', { name: /添加用户|Add User|新增用户|Create User/i })
|
| await expect(createUserButton).toBeVisible({ timeout: 10000 })
|
| await createUserButton.click()
|
|
|
| const userDialog = page.getByRole('dialog')
|
| await userDialog.getByLabel(/邮箱|Email/i).fill(newUserEmail)
|
| await userDialog.getByLabel(/名|First Name/i).fill('ValidationTest')
|
| await userDialog.getByLabel(/姓|Last Name/i).fill(uniqueSuffix)
|
|
|
| const passwordField = userDialog.locator('input[type="password"]').first()
|
| await passwordField.fill('TestPass123!')
|
|
|
| const confirmPasswordField = userDialog.locator('input[type="password"]').last()
|
| await confirmPasswordField.fill('TestPass123!')
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'CreateUser'),
|
| userDialog.getByRole('button', { name: /保存|Save|创建|Create|Save changes/i }).click()
|
| ])
|
|
|
| await page.waitForTimeout(500)
|
|
|
|
|
| const usersTable = page.locator('[data-testid="users-table"], table:has(th), table').first()
|
| const userRow = usersTable.locator('tbody tr').filter({ hasText: newUserEmail })
|
| await expect(userRow).toBeVisible()
|
|
|
|
|
| const actionsTrigger = userRow.locator('[data-testid="row-actions"], button:has(svg), .dropdown-trigger, .action-button, button:has-text("Open menu")').first()
|
| await actionsTrigger.click()
|
|
|
|
|
| const menu = page.getByRole('menu')
|
| await expect(menu).toBeVisible()
|
| await menu.getByRole('menuitem', { name: /添加到项目|Add to Project|加入项目/i }).focus()
|
| await page.keyboard.press('Enter')
|
|
|
|
|
| const addToProjectDialog = page.getByRole('dialog')
|
| await expect(addToProjectDialog).toBeVisible()
|
|
|
|
|
| const submitButton = addToProjectDialog.getByRole('button', { name: /添加|Add|保存|Save|确认|Confirm/i })
|
| await submitButton.click()
|
|
|
|
|
| await expect(addToProjectDialog).toBeVisible()
|
| })
|
|
|
| test('can cancel adding user to project', async ({ page }) => {
|
|
|
| const uniqueSuffix = Date.now().toString().slice(-6)
|
| const newUserEmail = `playwright-cancel-${uniqueSuffix}@example.com`
|
|
|
| const createUserButton = page.getByRole('button', { name: /添加用户|Add User|新增用户|Create User/i })
|
| await createUserButton.click()
|
|
|
| const userDialog = page.getByRole('dialog')
|
| await userDialog.getByLabel(/邮箱|Email/i).fill(newUserEmail)
|
| await userDialog.getByLabel(/名|First Name/i).fill('CancelTest')
|
| await userDialog.getByLabel(/姓|Last Name/i).fill(uniqueSuffix)
|
|
|
| const passwordField = userDialog.locator('input[type="password"]').first()
|
| await passwordField.fill('TestPass123!')
|
|
|
| const confirmPasswordField = userDialog.locator('input[type="password"]').last()
|
| await confirmPasswordField.fill('TestPass123!')
|
|
|
| await Promise.all([
|
| waitForGraphQLOperation(page, 'CreateUser'),
|
| userDialog.getByRole('button', { name: /保存|Save|创建|Create|Save changes/i }).click()
|
| ])
|
|
|
| await page.waitForTimeout(500)
|
|
|
|
|
| const usersTable = page.locator('[data-testid="users-table"], table:has(th), table').first()
|
| const userRow = usersTable.locator('tbody tr').filter({ hasText: newUserEmail })
|
| await expect(userRow).toBeVisible()
|
|
|
|
|
| const actionsTrigger = userRow.locator('[data-testid="row-actions"], button:has(svg), .dropdown-trigger, .action-button, button:has-text("Open menu")').first()
|
| await actionsTrigger.click()
|
|
|
|
|
| const menu = page.getByRole('menu')
|
| await expect(menu).toBeVisible()
|
| await menu.getByRole('menuitem', { name: /添加到项目|Add to Project|加入项目/i }).focus()
|
| await page.keyboard.press('Enter')
|
|
|
|
|
| const addToProjectDialog = page.getByRole('dialog')
|
| await expect(addToProjectDialog).toBeVisible()
|
|
|
|
|
| await page.keyboard.press('Escape')
|
|
|
|
|
| await expect(addToProjectDialog).not.toBeVisible()
|
| })
|
| })
|
|
|