- Removed unnecessary test.skip() calls in various test files, replacing them with comments for clarity. - Enhanced retry logic in TestDataManager for API requests to handle rate limiting more gracefully. - Updated security helper functions to include retry mechanisms for fetching security status and setting module states. - Improved loading completion checks to handle page closure scenarios. - Adjusted WebKit-specific tests to run in all browsers, removing the previous skip logic. - General cleanup and refactoring across multiple test files to enhance readability and maintainability.
316 lines
12 KiB
TypeScript
316 lines
12 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
/**
|
|
* Modal Dropdown Z-Index Triage Tests
|
|
* Verifies that all 7 critical modal components properly expose their dropdowns
|
|
* and interact correctly with the 3-layer modal architecture.
|
|
*
|
|
* Test Reference: /docs/issues/created/20260204-modal_dropdown_handoff_contract.md
|
|
*/
|
|
|
|
test.describe('Modal Dropdown Z-Index Triage', () => {
|
|
// Common setup
|
|
const baseURL = 'http://localhost:8080'
|
|
|
|
// Helper to check if a dropdown can be opened
|
|
async function testDropdownInteraction(page: any, labelText: RegExp, stepName: string) {
|
|
const label = page.locator('label', { hasText: labelText }).first()
|
|
const labelVisible = await label.isVisible({ timeout: 3000 }).catch(() => false)
|
|
|
|
if (!labelVisible) {
|
|
console.log(`❌ ${stepName}: Label not found`)
|
|
return { opened: false, selectedValue: null }
|
|
}
|
|
|
|
// Find the select element - should be near the label
|
|
const select = page.locator('select').first()
|
|
const selectVisible = await select.isVisible().catch(() => false)
|
|
|
|
if (!selectVisible) {
|
|
console.log(`❌ ${stepName}: Select element not visible`)
|
|
return { opened: false, selectedValue: null }
|
|
}
|
|
|
|
// Try to click and open the dropdown
|
|
try {
|
|
await select.click()
|
|
await page.waitForTimeout(300) // Wait for dropdown to open
|
|
|
|
// Check if an option element is visible (indicates dropdown opened)
|
|
const firstOption = select.locator('option').first()
|
|
const optionVisible = await firstOption.isVisible().catch(() => false)
|
|
|
|
if (optionVisible) {
|
|
// Try to get the displayed value
|
|
const selectedValue = await select.locator('option[selected]').first().textContent().catch(() => 'unknown')
|
|
console.log(`✅ ${stepName}: Dropdown OPENED and has options. Selected: "${selectedValue}"`)
|
|
return { opened: true, selectedValue }
|
|
} else {
|
|
console.log(`⚠️ ${stepName}: Click registered but dropdown may not have opened`)
|
|
return { opened: false, selectedValue: null }
|
|
}
|
|
} catch (e) {
|
|
console.log(`❌ ${stepName}: Click failed - ${(e as Error).message}`)
|
|
return { opened: false, selectedValue: null }
|
|
}
|
|
}
|
|
|
|
test('A. ProxyHostForm - ACL Dropdown', async ({ page }) => {
|
|
await test.step('Navigate to Proxy Hosts page', async () => {
|
|
await page.goto(`${baseURL}/proxy-hosts`)
|
|
await page.waitForLoadState('networkidle')
|
|
})
|
|
|
|
await test.step('Click "Add Proxy Host" button', async () => {
|
|
const addButton = page.getByRole('button', { name: /add proxy host|new|create/i })
|
|
await expect(addButton).toBeVisible()
|
|
await addButton.click()
|
|
await page.waitForTimeout(500)
|
|
})
|
|
|
|
await test.step('Verify 3-layer modal structure', async () => {
|
|
const modalBackdrop = page.locator('.fixed.inset-0.bg-black').first()
|
|
const modalContainer = page.locator('.fixed.inset-0.pointer-events-none').first()
|
|
const modalContent = page.locator('[role="dialog"]').first()
|
|
|
|
await expect(modalBackdrop).toBeVisible()
|
|
await expect(modalContainer).toBeVisible()
|
|
await expect(modalContent).toBeVisible()
|
|
console.log('✅ 3-layer modal structure verified')
|
|
})
|
|
|
|
await test.step('Test ACL dropdown', async () => {
|
|
const result = await testDropdownInteraction(page, /access list|acl|access control/i, 'ACL Dropdown')
|
|
if (!result.opened) {
|
|
console.log('⚠️ ProxyHostForm: ACL dropdown may have z-index issue')
|
|
}
|
|
})
|
|
|
|
await test.step('Test Security Headers dropdown', async () => {
|
|
const result = await testDropdownInteraction(page, /security headers/i, 'Security Headers Dropdown')
|
|
if (!result.opened) {
|
|
console.log('⚠️ ProxyHostForm: Security Headers dropdown may have z-index issue')
|
|
}
|
|
})
|
|
|
|
await test.step('Close modal', async () => {
|
|
await page.keyboard.press('Escape')
|
|
await page.waitForTimeout(300)
|
|
})
|
|
})
|
|
|
|
test('B. UsersPage - InviteUserModal Role Dropdown', async ({ page }) => {
|
|
await test.step('Navigate to Users page', async () => {
|
|
await page.goto(`${baseURL}/users`)
|
|
await page.waitForLoadState('networkidle')
|
|
})
|
|
|
|
await test.step('Click "Invite User" button', async () => {
|
|
const inviteButton = page.getByRole('button', { name: /invite|add user|send invite/i })
|
|
await expect(inviteButton).toBeVisible()
|
|
await inviteButton.click()
|
|
await page.waitForTimeout(500)
|
|
})
|
|
|
|
await test.step('Verify modal is displayed', async () => {
|
|
const modal = page.locator('[role="dialog"]')
|
|
await expect(modal).toBeVisible()
|
|
})
|
|
|
|
await test.step('Test Role dropdown', async () => {
|
|
const result = await testDropdownInteraction(page, /role/i, 'Role Dropdown')
|
|
if (!result.opened) {
|
|
console.log('⚠️ UsersPage: Role dropdown may have z-index issue')
|
|
}
|
|
})
|
|
|
|
await test.step('Test Permission Mode dropdown', async () => {
|
|
// There should be a second select for permissions
|
|
const allSelects = page.locator('select')
|
|
const selectCount = await allSelects.count()
|
|
if (selectCount > 1) {
|
|
const result = await testDropdownInteraction(page, /permission|mode/i, 'Permission Dropdown')
|
|
if (!result.opened) {
|
|
console.log('⚠️ UsersPage: Permission dropdown may have z-index issue')
|
|
}
|
|
}
|
|
})
|
|
|
|
await test.step('Close modal', async () => {
|
|
await page.keyboard.press('Escape')
|
|
await page.waitForTimeout(300)
|
|
})
|
|
})
|
|
|
|
test('C. UsersPage - EditPermissionsModal Dropdowns', async ({ page }) => {
|
|
await test.step('Navigate to Users page', async () => {
|
|
await page.goto(`${baseURL}/users`)
|
|
await page.waitForLoadState('networkidle')
|
|
})
|
|
|
|
await test.step('Find and click Edit Permissions for first user', async () => {
|
|
const editButtons = page.getByRole('button', { name: /edit|permissions|manage/i })
|
|
if (await editButtons.first().isVisible()) {
|
|
await editButtons.first().click()
|
|
await page.waitForTimeout(500)
|
|
} else {
|
|
console.log('⚠️ No users found or edit button not visible')
|
|
return
|
|
}
|
|
})
|
|
|
|
await test.step('Test permission dropdowns', async () => {
|
|
const allSelects = page.locator('select')
|
|
const selectCount = await allSelects.count()
|
|
|
|
if (selectCount === 0) {
|
|
console.log('⚠️ No dropdowns found in EditPermissionsModal')
|
|
return
|
|
}
|
|
|
|
console.log(`Found ${selectCount} select elements in EditPermissionsModal`)
|
|
|
|
for (let i = 0; i < selectCount && i < 3; i++) {
|
|
const result = await testDropdownInteraction(page, /role|permission|access/i, `EditPermissions Dropdown ${i + 1}`)
|
|
}
|
|
})
|
|
|
|
await test.step('Close modal', async () => {
|
|
await page.keyboard.press('Escape')
|
|
await page.waitForTimeout(300)
|
|
})
|
|
})
|
|
|
|
test('D. Uptime - CreateMonitorModal Type Dropdown', async ({ page }) => {
|
|
await test.step('Navigate to Uptime page', async () => {
|
|
await page.goto(`${baseURL}/uptime`)
|
|
await page.waitForLoadState('networkidle')
|
|
})
|
|
|
|
await test.step('Click "Create Monitor" button', async () => {
|
|
const createButton = page.getByRole('button', { name: /create|add|new|monitor/i })
|
|
if (await createButton.isVisible()) {
|
|
await createButton.click()
|
|
} else {
|
|
const plusButton = page.locator('button').filter({ hasText: '+' })
|
|
if (await plusButton.isVisible()) {
|
|
await plusButton.click()
|
|
} else {
|
|
console.log('⚠️ Create Monitor button not found')
|
|
return
|
|
}
|
|
}
|
|
await page.waitForTimeout(500)
|
|
})
|
|
|
|
await test.step('Test Monitor Type dropdown', async () => {
|
|
const result = await testDropdownInteraction(page, /monitor type|type|protocol/i, 'Monitor Type Dropdown')
|
|
if (!result.opened) {
|
|
console.log('⚠️ Uptime: Monitor Type dropdown may have z-index issue')
|
|
}
|
|
})
|
|
|
|
await test.step('Close modal', async () => {
|
|
await page.keyboard.press('Escape')
|
|
await page.waitForTimeout(300)
|
|
})
|
|
})
|
|
|
|
test('D. RemoteServerForm - Provider Dropdown', async ({ page }) => {
|
|
await test.step('Navigate to Remote Servers page', async () => {
|
|
await page.goto(`${baseURL}/remote-servers`)
|
|
await page.waitForLoadState('networkidle')
|
|
})
|
|
|
|
await test.step('Click "Add Server" button', async () => {
|
|
const addButton = page.getByRole('button', { name: /add|new|create|server/i })
|
|
if (await addButton.isVisible()) {
|
|
await addButton.click()
|
|
await page.waitForTimeout(500)
|
|
} else {
|
|
console.log('⚠️ Add Server button not found')
|
|
return
|
|
}
|
|
})
|
|
|
|
await test.step('Test Provider dropdown', async () => {
|
|
const result = await testDropdownInteraction(page, /provider|type|docker|generic/i, 'Provider Dropdown')
|
|
if (!result.opened) {
|
|
console.log('⚠️ RemoteServerForm: Provider dropdown may have z-index issue')
|
|
}
|
|
})
|
|
|
|
await test.step('Close modal', async () => {
|
|
await page.keyboard.press('Escape')
|
|
await page.waitForTimeout(300)
|
|
})
|
|
})
|
|
|
|
test('E. CrowdSecConfig - BanIPModal Duration Dropdown', async ({ page }) => {
|
|
await test.step('Navigate to CrowdSec page', async () => {
|
|
await page.goto(`${baseURL}/security/crowdsec`)
|
|
await page.waitForLoadState('networkidle')
|
|
})
|
|
|
|
await test.step('Find and click Ban IP button', async () => {
|
|
const banButton = page.getByRole('button', { name: /ban|block|manual/i })
|
|
if (await banButton.isVisible()) {
|
|
await banButton.click()
|
|
await page.waitForTimeout(500)
|
|
} else {
|
|
console.log('⚠️ Ban IP button not found on CrowdSec page')
|
|
return
|
|
}
|
|
})
|
|
|
|
await test.step('Test Duration dropdown', async () => {
|
|
const result = await testDropdownInteraction(page, /duration|time|ban.*duration/i, 'Duration Dropdown')
|
|
if (!result.opened) {
|
|
console.log('⚠️ CrowdSecConfig: Duration dropdown may have z-index issue')
|
|
}
|
|
})
|
|
|
|
await test.step('Close modal', async () => {
|
|
await page.keyboard.press('Escape')
|
|
await page.waitForTimeout(300)
|
|
})
|
|
})
|
|
|
|
test('Accessibility Verification - Modal 3-Layer Architecture', async ({ page }) => {
|
|
await test.step('Verify 3-layer modal structure in ProxyHostForm', async () => {
|
|
await page.goto(`${baseURL}/proxy-hosts`)
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
const addButton = page.getByRole('button', { name: /add proxy host/i })
|
|
if (await addButton.isVisible()) {
|
|
await addButton.click()
|
|
await page.waitForTimeout(500)
|
|
}
|
|
|
|
// Check for 3-layer structure in DOM
|
|
const layer1 = page.locator('.fixed.inset-0.bg-black').first()
|
|
const layer2 = page.locator('.fixed.inset-0.pointer-events-none').first()
|
|
const layer3 = page.locator('[role="dialog"]').first()
|
|
|
|
const hasLayer1 = await layer1.isVisible({ timeout: 2000 }).catch(() => false)
|
|
const hasLayer2 = await layer2.isVisible({ timeout: 2000 }).catch(() => false)
|
|
const hasLayer3 = await layer3.isVisible({ timeout: 2000 }).catch(() => false)
|
|
|
|
console.log(`
|
|
3-Layer Modal Structure Check:
|
|
- Layer 1 (Overlay z-40): ${hasLayer1 ? '✅' : '❌'}
|
|
- Layer 2 (Container z-50): ${hasLayer2 ? '✅' : '❌'}
|
|
- Layer 3 (Content Dialog): ${hasLayer3 ? '✅' : '❌'}
|
|
`)
|
|
|
|
if (hasLayer1 && hasLayer2 && hasLayer3) {
|
|
console.log('✅ 3-Layer modal architecture VERIFIED in ProxyHostForm')
|
|
} else {
|
|
console.log('❌ 3-Layer modal architecture INCOMPLETE in ProxyHostForm')
|
|
}
|
|
|
|
await page.keyboard.press('Escape')
|
|
})
|
|
})
|
|
})
|