first commit
This commit is contained in:
19
tests/e2e/features/governance/governance.data.ts
Normal file
19
tests/e2e/features/governance/governance.data.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { CustomerConfig, TeamConfig } from './pages/governance.page'
|
||||
|
||||
export function createTeamData(overrides: Partial<TeamConfig> = {}): TeamConfig {
|
||||
const timestamp = Date.now()
|
||||
return {
|
||||
name: `E2E Team ${timestamp}`,
|
||||
budget: { maxLimit: 100, resetDuration: '1M' },
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
export function createCustomerData(overrides: Partial<CustomerConfig> = {}): CustomerConfig {
|
||||
const timestamp = Date.now()
|
||||
return {
|
||||
name: `E2E Customer ${timestamp}`,
|
||||
budget: { maxLimit: 50, resetDuration: '1d' },
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
173
tests/e2e/features/governance/governance.spec.ts
Normal file
173
tests/e2e/features/governance/governance.spec.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { expect, test } from '../../core/fixtures/base.fixture'
|
||||
import { createCustomerData, createTeamData } from './governance.data'
|
||||
|
||||
const createdTeams: string[] = []
|
||||
const createdCustomers: string[] = []
|
||||
|
||||
test.describe('Governance - Teams', () => {
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
test.beforeEach(async ({ governancePage }) => {
|
||||
await governancePage.gotoTeams()
|
||||
})
|
||||
|
||||
test.afterEach(async ({ governancePage }) => {
|
||||
await governancePage.closeTeamDialog()
|
||||
for (const name of [...createdTeams]) {
|
||||
try {
|
||||
const exists = await governancePage.teamExists(name)
|
||||
if (exists) {
|
||||
await governancePage.deleteTeam(name)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[CLEANUP] Failed to delete team ${name}:`, e)
|
||||
}
|
||||
}
|
||||
createdTeams.length = 0
|
||||
for (const name of [...createdCustomers]) {
|
||||
try {
|
||||
await governancePage.gotoCustomers()
|
||||
const exists = await governancePage.customerExists(name)
|
||||
if (exists) {
|
||||
await governancePage.deleteCustomer(name)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[CLEANUP] Failed to delete customer ${name}:`, e)
|
||||
}
|
||||
}
|
||||
createdCustomers.length = 0
|
||||
})
|
||||
|
||||
test('should display create team button or empty state', async ({ governancePage }) => {
|
||||
const createVisible = await governancePage.teamsCreateBtn.isVisible().catch(() => false)
|
||||
const emptyAddVisible = await governancePage.page.getByTestId('team-button-add').isVisible().catch(() => false)
|
||||
expect(createVisible || emptyAddVisible).toBe(true)
|
||||
})
|
||||
|
||||
test('should create a team', async ({ governancePage }) => {
|
||||
const teamData = createTeamData({ name: `E2E Test Team ${Date.now()}` })
|
||||
createdTeams.push(teamData.name)
|
||||
|
||||
await governancePage.createTeam(teamData)
|
||||
|
||||
const exists = await governancePage.teamExists(teamData.name)
|
||||
expect(exists).toBe(true)
|
||||
})
|
||||
|
||||
test('should edit a team', async ({ governancePage }) => {
|
||||
const teamData = createTeamData({ name: `E2E Edit Team ${Date.now()}` })
|
||||
createdTeams.push(teamData.name)
|
||||
await governancePage.createTeam(teamData)
|
||||
|
||||
await governancePage.editTeam(teamData.name, { budget: { maxLimit: 129 } })
|
||||
|
||||
const exists = await governancePage.teamExists(teamData.name)
|
||||
expect(exists).toBe(true)
|
||||
})
|
||||
|
||||
test('should create team with customer assignment', async ({ governancePage }) => {
|
||||
// 1. Create a customer (UI)
|
||||
const customerData = createCustomerData({ name: `E2E Customer For Team ${Date.now()}` })
|
||||
createdCustomers.push(customerData.name)
|
||||
await governancePage.gotoCustomers()
|
||||
await governancePage.createCustomer(customerData)
|
||||
|
||||
// 2. Go to Teams and create a team, assign the customer from the create-team dropdown (UI)
|
||||
await governancePage.gotoTeams()
|
||||
const teamData = createTeamData({
|
||||
name: `E2E Team With Customer ${Date.now()}`,
|
||||
customerName: customerData.name,
|
||||
})
|
||||
createdTeams.push(teamData.name)
|
||||
await governancePage.createTeam(teamData)
|
||||
|
||||
// 3. Validate in UI that the customer was assigned (via data-testid)
|
||||
const exists = await governancePage.teamExists(teamData.name)
|
||||
expect(exists).toBe(true)
|
||||
const customerCell = governancePage.getTeamRowCustomerCell(teamData.name)
|
||||
await expect(customerCell).toContainText(customerData.name)
|
||||
})
|
||||
|
||||
test('should delete a team', async ({ governancePage }) => {
|
||||
const teamData = createTeamData({ name: `E2E Delete Team ${Date.now()}` })
|
||||
createdTeams.push(teamData.name)
|
||||
await governancePage.createTeam(teamData)
|
||||
|
||||
let exists = await governancePage.teamExists(teamData.name)
|
||||
expect(exists).toBe(true)
|
||||
|
||||
await governancePage.deleteTeam(teamData.name)
|
||||
const idx = createdTeams.indexOf(teamData.name)
|
||||
if (idx >= 0) createdTeams.splice(idx, 1)
|
||||
|
||||
exists = await governancePage.teamExists(teamData.name)
|
||||
expect(exists).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Governance - Customers', () => {
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
test.beforeEach(async ({ governancePage }) => {
|
||||
await governancePage.gotoCustomers()
|
||||
})
|
||||
|
||||
test.afterEach(async ({ governancePage }) => {
|
||||
for (const name of [...createdCustomers]) {
|
||||
try {
|
||||
const exists = await governancePage.customerExists(name)
|
||||
if (exists) {
|
||||
await governancePage.deleteCustomer(name)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[CLEANUP] Failed to delete customer ${name}:`, e)
|
||||
}
|
||||
}
|
||||
createdCustomers.length = 0
|
||||
})
|
||||
|
||||
test('should display create customer button or empty state', async ({ governancePage }) => {
|
||||
const createVisible = await governancePage.customersCreateBtn.isVisible().catch(() => false)
|
||||
const emptyCreateVisible = await governancePage.page.getByTestId('customer-button-create').isVisible().catch(() => false)
|
||||
expect(createVisible || emptyCreateVisible).toBe(true)
|
||||
})
|
||||
|
||||
test('should create a customer', async ({ governancePage }) => {
|
||||
const customerData = createCustomerData({ name: `E2E Test Customer ${Date.now()}` })
|
||||
createdCustomers.push(customerData.name)
|
||||
|
||||
await governancePage.createCustomer(customerData)
|
||||
|
||||
const exists = await governancePage.customerExists(customerData.name)
|
||||
expect(exists).toBe(true)
|
||||
})
|
||||
|
||||
test('should edit a customer', async ({ governancePage }) => {
|
||||
const customerData = createCustomerData({ name: `E2E Edit Customer ${Date.now()}` })
|
||||
createdCustomers.push(customerData.name)
|
||||
await governancePage.createCustomer(customerData)
|
||||
|
||||
const newName = `E2E Edited Customer ${Date.now()}`
|
||||
createdCustomers[createdCustomers.length - 1] = newName
|
||||
await governancePage.editCustomer(customerData.name, { name: newName })
|
||||
|
||||
const oldExists = await governancePage.customerExists(customerData.name)
|
||||
const newExists = await governancePage.customerExists(newName)
|
||||
expect(oldExists).toBe(false)
|
||||
expect(newExists).toBe(true)
|
||||
})
|
||||
|
||||
test('should delete a customer', async ({ governancePage }) => {
|
||||
const customerData = createCustomerData({ name: `E2E Delete Customer ${Date.now()}` })
|
||||
createdCustomers.push(customerData.name)
|
||||
await governancePage.createCustomer(customerData)
|
||||
|
||||
let exists = await governancePage.customerExists(customerData.name)
|
||||
expect(exists).toBe(true)
|
||||
|
||||
await governancePage.deleteCustomer(customerData.name)
|
||||
const idx = createdCustomers.indexOf(customerData.name)
|
||||
if (idx >= 0) createdCustomers.splice(idx, 1)
|
||||
|
||||
exists = await governancePage.customerExists(customerData.name)
|
||||
expect(exists).toBe(false)
|
||||
})
|
||||
})
|
||||
229
tests/e2e/features/governance/pages/governance.page.ts
Normal file
229
tests/e2e/features/governance/pages/governance.page.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import { Locator, Page } from '@playwright/test'
|
||||
import { expect } from '../../../core/fixtures/base.fixture'
|
||||
import { BasePage } from '../../../core/pages/base.page'
|
||||
import { fillSelect, waitForNetworkIdle } from '../../../core/utils/test-helpers'
|
||||
|
||||
export interface TeamConfig {
|
||||
name: string
|
||||
/** Assign by customer id (from API). Prefer customerName for UI-only flow. */
|
||||
customerId?: string
|
||||
/** Assign by customer name in the create-team dropdown (UI-only, no API). */
|
||||
customerName?: string
|
||||
budget?: { maxLimit: number; resetDuration?: string }
|
||||
rateLimit?: {
|
||||
tokenMaxLimit?: number
|
||||
tokenResetDuration?: string
|
||||
requestMaxLimit?: number
|
||||
requestResetDuration?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomerConfig {
|
||||
name: string
|
||||
budget?: { maxLimit: number; resetDuration?: string }
|
||||
rateLimit?: {
|
||||
tokenMaxLimit?: number
|
||||
tokenResetDuration?: string
|
||||
requestMaxLimit?: number
|
||||
requestResetDuration?: string
|
||||
}
|
||||
}
|
||||
|
||||
export class GovernancePage extends BasePage {
|
||||
// Teams
|
||||
readonly teamsCreateBtn: Locator
|
||||
readonly teamsTable: Locator
|
||||
readonly teamDialog: Locator
|
||||
readonly teamNameInput: Locator
|
||||
|
||||
// Customers
|
||||
readonly customersCreateBtn: Locator
|
||||
readonly customersTable: Locator
|
||||
readonly customerDialog: Locator
|
||||
readonly customerNameInput: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page)
|
||||
|
||||
this.teamsCreateBtn = page.getByTestId('create-team-btn').or(page.getByTestId('team-button-add'))
|
||||
this.teamsTable = page.getByTestId('teams-table')
|
||||
this.teamDialog = page.getByTestId('team-dialog-content')
|
||||
this.teamNameInput = page.getByTestId('team-name-input')
|
||||
|
||||
this.customersCreateBtn = page.getByTestId('customer-button-create')
|
||||
this.customersTable = page.getByTestId('customer-table-container')
|
||||
this.customerDialog = page.getByTestId('customer-dialog-content')
|
||||
this.customerNameInput = page.getByTestId('customer-name-input')
|
||||
}
|
||||
|
||||
async gotoTeams(): Promise<void> {
|
||||
await this.page.goto('/workspace/governance/teams')
|
||||
await waitForNetworkIdle(this.page)
|
||||
}
|
||||
|
||||
async gotoCustomers(): Promise<void> {
|
||||
await this.page.goto('/workspace/governance/customers')
|
||||
await waitForNetworkIdle(this.page)
|
||||
}
|
||||
|
||||
getTeamRow(name: string): Locator {
|
||||
return this.page.getByTestId(`team-row-${name}`)
|
||||
}
|
||||
|
||||
/** Customer cell for a team row (use for asserting assigned customer in UI). */
|
||||
getTeamRowCustomerCell(teamName: string): Locator {
|
||||
return this.page.getByTestId(`team-row-${teamName}-customer`)
|
||||
}
|
||||
|
||||
async teamExists(name: string): Promise<boolean> {
|
||||
const row = this.getTeamRow(name)
|
||||
return (await row.count()) > 0
|
||||
}
|
||||
|
||||
async createTeam(config: TeamConfig): Promise<void> {
|
||||
await this.teamsCreateBtn.click()
|
||||
await expect(this.teamDialog).toBeVisible({ timeout: 5000 })
|
||||
await this.waitForSheetAnimation()
|
||||
|
||||
await this.teamNameInput.fill(config.name)
|
||||
|
||||
if (config.customerId !== undefined || config.customerName !== undefined) {
|
||||
const trigger = this.page.getByTestId('team-customer-select-trigger')
|
||||
await trigger.click()
|
||||
if (config.customerId === '') {
|
||||
await this.page.getByTestId('team-customer-option-none').click()
|
||||
} else if (config.customerName !== undefined) {
|
||||
const customerOption = this.page
|
||||
.locator('[data-testid^="team-customer-option-"]')
|
||||
.filter({ hasText: config.customerName })
|
||||
await customerOption.waitFor({ state: 'visible', timeout: 5000 })
|
||||
await customerOption.click()
|
||||
} else if (config.customerId !== undefined && config.customerId !== '') {
|
||||
const customerOption = this.page.getByTestId(`team-customer-option-${config.customerId}`)
|
||||
await customerOption.waitFor({ state: 'visible', timeout: 5000 })
|
||||
await customerOption.click()
|
||||
}
|
||||
}
|
||||
|
||||
if (config.budget?.maxLimit !== undefined) {
|
||||
const budgetInput = this.page.getByTestId('budget-max-limit-input')
|
||||
await budgetInput.fill(String(config.budget.maxLimit))
|
||||
}
|
||||
|
||||
const saveBtn = this.teamDialog.getByRole('button', { name: /Create Team/i })
|
||||
await expect(saveBtn).toBeEnabled()
|
||||
await saveBtn.click()
|
||||
await this.waitForSuccessToast()
|
||||
await expect(this.teamDialog).not.toBeVisible({ timeout: 10000 })
|
||||
}
|
||||
|
||||
async deleteTeam(name: string): Promise<void> {
|
||||
const deleteBtn = this.page.getByTestId(`team-delete-btn-${name}`)
|
||||
await deleteBtn.click()
|
||||
|
||||
const confirmDialog = this.page.locator('[role="alertdialog"]')
|
||||
await confirmDialog.getByRole('button', { name: /Delete/i }).click()
|
||||
await this.waitForSuccessToast()
|
||||
await expect.poll(() => this.teamExists(name), { timeout: 10000 }).toBe(false)
|
||||
}
|
||||
|
||||
async closeTeamDialog(): Promise<void> {
|
||||
if (await this.teamDialog.isVisible().catch(() => false)) {
|
||||
await this.teamDialog.getByRole('button', { name: /Cancel/i }).click()
|
||||
await expect(this.teamDialog).not.toBeVisible({ timeout: 10000 })
|
||||
}
|
||||
}
|
||||
|
||||
getCustomerRow(name: string): Locator {
|
||||
return this.customersTable.getByTestId(`customer-row-${name}`)
|
||||
}
|
||||
|
||||
async customerExists(name: string): Promise<boolean> {
|
||||
const row = this.getCustomerRow(name)
|
||||
return (await row.count()) > 0
|
||||
}
|
||||
|
||||
async createCustomer(config: CustomerConfig): Promise<void> {
|
||||
await this.customersCreateBtn.click()
|
||||
await expect(this.customerDialog).toBeVisible({ timeout: 5000 })
|
||||
await this.waitForSheetAnimation()
|
||||
|
||||
await this.customerNameInput.fill(config.name)
|
||||
|
||||
if (config.budget?.maxLimit !== undefined) {
|
||||
const budgetInput = this.page.getByTestId('budget-max-limit-input')
|
||||
await budgetInput.fill(String(config.budget.maxLimit))
|
||||
}
|
||||
|
||||
const saveBtn = this.customerDialog.getByRole('button', { name: /Create Customer/i })
|
||||
await expect(saveBtn).toBeEnabled()
|
||||
await saveBtn.click()
|
||||
await this.waitForSuccessToast()
|
||||
await expect(this.customerDialog).not.toBeVisible({ timeout: 10000 })
|
||||
}
|
||||
|
||||
async deleteCustomer(name: string): Promise<void> {
|
||||
const row = this.getCustomerRow(name)
|
||||
const deleteBtn = row.locator('[data-testid^="customer-button-delete-"]')
|
||||
await deleteBtn.click()
|
||||
|
||||
const confirmBtn = this.page.getByTestId('customer-button-delete-confirm')
|
||||
await confirmBtn.waitFor({ state: 'visible', timeout: 5000 })
|
||||
await confirmBtn.click()
|
||||
await this.waitForSuccessToast()
|
||||
await expect.poll(() => this.customerExists(name), { timeout: 10000 }).toBe(false)
|
||||
}
|
||||
|
||||
async editTeam(name: string, updates: Partial<TeamConfig>): Promise<void> {
|
||||
const editBtn = this.page.getByTestId(`team-edit-btn-${name}`)
|
||||
await editBtn.click()
|
||||
await expect(this.teamDialog).toBeVisible({ timeout: 5000 })
|
||||
await this.waitForSheetAnimation()
|
||||
|
||||
if (updates.name) {
|
||||
await this.teamNameInput.clear()
|
||||
await this.teamNameInput.fill(updates.name)
|
||||
}
|
||||
|
||||
if (updates.customerId !== undefined) {
|
||||
const trigger = this.page.getByTestId('team-customer-select-trigger')
|
||||
await trigger.click()
|
||||
if (updates.customerId === '') {
|
||||
await this.page.getByTestId('team-customer-option-none').click()
|
||||
} else {
|
||||
await this.page.getByTestId(`team-customer-option-${updates.customerId}`).click()
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.budget?.maxLimit !== undefined) {
|
||||
const budgetInput = this.page.getByTestId('budget-max-limit-input')
|
||||
await budgetInput.clear()
|
||||
await budgetInput.fill(String(updates.budget.maxLimit))
|
||||
}
|
||||
|
||||
const saveBtn = this.teamDialog.getByRole('button', { name: /Save|Update/i })
|
||||
await expect(saveBtn).toBeEnabled()
|
||||
await saveBtn.click()
|
||||
await this.waitForSuccessToast()
|
||||
await expect(this.teamDialog).not.toBeVisible({ timeout: 10000 })
|
||||
}
|
||||
|
||||
async editCustomer(name: string, updates: Partial<CustomerConfig>): Promise<void> {
|
||||
const row = this.getCustomerRow(name)
|
||||
const editBtn = row.locator('[data-testid^="customer-button-edit-"]')
|
||||
await editBtn.click()
|
||||
await expect(this.customerDialog).toBeVisible({ timeout: 5000 })
|
||||
await this.waitForSheetAnimation()
|
||||
|
||||
if (updates.name) {
|
||||
await this.customerNameInput.clear()
|
||||
await this.customerNameInput.fill(updates.name)
|
||||
}
|
||||
|
||||
const saveBtn = this.customerDialog.getByRole('button', { name: /Save|Update/i })
|
||||
await expect(saveBtn).toBeEnabled()
|
||||
await saveBtn.click()
|
||||
await this.waitForSuccessToast()
|
||||
await expect(this.customerDialog).not.toBeVisible({ timeout: 10000 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user