mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 07:30:11 +00:00
## Summary Major refactoring of browser tests to improve reliability, maintainability, and type safety. ## Changes ### Test Infrastructure Decomposition - Decomposed `ComfyPage.ts` (~1000 lines) into focused helpers: - `CanvasHelper`, `DebugHelper`, `SubgraphHelper`, `NodeOperationsHelper` - `SettingsHelper`, `WorkflowHelper`, `ClipboardHelper`, `KeyboardHelper` - Created `ContextMenu` page object, `BaseDialog` base class, and `BottomPanel` page object - Extracted `DefaultGraphPositions` constants ### Locator Stability - Added `data-testid` attributes to Vue components (sidebar, dialogs, node library) - Created centralized `selectors.ts` with test ID constants - Replaced fragile CSS selectors (`.nth()`, `:nth-child()`) with `getByTestId`/`getByRole` ### Performance & Reliability - Removed `setTimeout` anti-patterns (replaced with `waitForFunction`) - Replaced `waitForTimeout` with retrying assertions - Replaced hardcoded coordinates with computed `NodeReference` positions - Enforced LF line endings for all text files ### Type Safety - Enabled `no-explicit-any` lint rule for browser_tests via oxlint - Purged `as any` casts from browser_tests - Added Window type augmentation for standardized window access - Added proper type annotations throughout ### Bug Fixes - Restored `ExtensionManager` API contract - Removed test-only settings from production schema - Fixed flaky selectors and missing test setup ## Testing - All browser tests pass - Typecheck passes <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Overhauled browser E2E test infrastructure with many new helpers/fixtures, updated test APIs, and CI test container image bumped for consistency. * **Chores** * Standardized line endings and applied stricter lint rules for browser tests; workspace dependency version updated. * **Documentation** * Updated Playwright and TypeScript testing guidance and test-run commands. * **UI** * Added stable data-testids to multiple components to improve testability. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
168 lines
4.5 KiB
TypeScript
168 lines
4.5 KiB
TypeScript
import type { Locator, Page, TestInfo } from '@playwright/test'
|
|
|
|
import type { Position } from '../types'
|
|
|
|
export interface DebugScreenshotOptions {
|
|
fullPage?: boolean
|
|
element?: 'canvas' | 'page'
|
|
markers?: Array<{ position: Position; id?: string }>
|
|
}
|
|
|
|
export class DebugHelper {
|
|
constructor(
|
|
private page: Page,
|
|
private canvas: Locator
|
|
) {}
|
|
|
|
async addMarker(
|
|
position: Position,
|
|
id: string = 'debug-marker'
|
|
): Promise<void> {
|
|
await this.page.evaluate(
|
|
([pos, markerId]) => {
|
|
const existing = document.getElementById(markerId)
|
|
if (existing) existing.remove()
|
|
|
|
const marker = document.createElement('div')
|
|
marker.id = markerId
|
|
marker.style.position = 'fixed'
|
|
marker.style.left = `${pos.x - 10}px`
|
|
marker.style.top = `${pos.y - 10}px`
|
|
marker.style.width = '20px'
|
|
marker.style.height = '20px'
|
|
marker.style.border = '2px solid red'
|
|
marker.style.borderRadius = '50%'
|
|
marker.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'
|
|
marker.style.pointerEvents = 'none'
|
|
marker.style.zIndex = '10000'
|
|
document.body.appendChild(marker)
|
|
},
|
|
[position, id] as const
|
|
)
|
|
}
|
|
|
|
async removeMarkers(): Promise<void> {
|
|
await this.page.evaluate(() => {
|
|
document
|
|
.querySelectorAll('[id^="debug-marker"]')
|
|
.forEach((el) => el.remove())
|
|
})
|
|
}
|
|
|
|
async attachScreenshot(
|
|
testInfo: TestInfo,
|
|
name: string,
|
|
options?: DebugScreenshotOptions
|
|
): Promise<void> {
|
|
if (options?.markers) {
|
|
for (const marker of options.markers) {
|
|
await this.addMarker(marker.position, marker.id)
|
|
}
|
|
}
|
|
|
|
let screenshot: Buffer
|
|
const targetElement = options?.element || 'page'
|
|
|
|
if (targetElement === 'canvas') {
|
|
screenshot = await this.canvas.screenshot()
|
|
} else if (options?.fullPage) {
|
|
screenshot = await this.page.screenshot({ fullPage: true })
|
|
} else {
|
|
screenshot = await this.page.screenshot()
|
|
}
|
|
|
|
await testInfo.attach(name, {
|
|
body: screenshot,
|
|
contentType: 'image/png'
|
|
})
|
|
|
|
if (options?.markers) {
|
|
await this.removeMarkers()
|
|
}
|
|
}
|
|
|
|
async saveCanvasScreenshot(filename: string): Promise<void> {
|
|
await this.page.evaluate(async (filename) => {
|
|
const canvas = document.getElementById(
|
|
'graph-canvas'
|
|
) as HTMLCanvasElement
|
|
if (!canvas) {
|
|
throw new Error('Canvas not found')
|
|
}
|
|
|
|
return new Promise<void>((resolve) => {
|
|
canvas.toBlob(async (blob) => {
|
|
if (!blob) {
|
|
throw new Error('Failed to create blob from canvas')
|
|
}
|
|
|
|
const url = URL.createObjectURL(blob)
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = filename
|
|
document.body.appendChild(a)
|
|
a.click()
|
|
document.body.removeChild(a)
|
|
URL.revokeObjectURL(url)
|
|
resolve()
|
|
}, 'image/png')
|
|
})
|
|
}, filename)
|
|
}
|
|
|
|
async getCanvasDataURL(): Promise<string> {
|
|
return await this.page.evaluate(() => {
|
|
const canvas = document.getElementById(
|
|
'graph-canvas'
|
|
) as HTMLCanvasElement
|
|
if (!canvas) {
|
|
throw new Error('Canvas not found')
|
|
}
|
|
return canvas.toDataURL('image/png')
|
|
})
|
|
}
|
|
|
|
async showCanvasOverlay(): Promise<void> {
|
|
await this.page.evaluate(() => {
|
|
const canvas = document.getElementById(
|
|
'graph-canvas'
|
|
) as HTMLCanvasElement
|
|
if (!canvas) {
|
|
throw new Error('Canvas not found')
|
|
}
|
|
|
|
const existingOverlay = document.getElementById('debug-canvas-overlay')
|
|
if (existingOverlay) {
|
|
existingOverlay.remove()
|
|
}
|
|
|
|
const overlay = document.createElement('div')
|
|
overlay.id = 'debug-canvas-overlay'
|
|
overlay.style.position = 'fixed'
|
|
overlay.style.top = '0'
|
|
overlay.style.left = '0'
|
|
overlay.style.zIndex = '9999'
|
|
overlay.style.backgroundColor = 'white'
|
|
overlay.style.padding = '10px'
|
|
overlay.style.border = '2px solid red'
|
|
|
|
const img = document.createElement('img')
|
|
img.src = canvas.toDataURL('image/png')
|
|
img.style.maxWidth = '800px'
|
|
img.style.maxHeight = '600px'
|
|
overlay.appendChild(img)
|
|
|
|
document.body.appendChild(overlay)
|
|
})
|
|
}
|
|
|
|
async hideCanvasOverlay(): Promise<void> {
|
|
await this.page.evaluate(() => {
|
|
const overlay = document.getElementById('debug-canvas-overlay')
|
|
if (overlay) {
|
|
overlay.remove()
|
|
}
|
|
})
|
|
}
|
|
}
|