mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 22:59:14 +00:00
feat: Add Happy DOM location mocking for Playwright tests
- Enhanced setup-browser-globals.js with configurable URL and full location mock - Created LocationMock helper class for dynamic location mocking in tests - Integrated LocationMock into ComfyPage fixture with optional setup - Added example test file demonstrating location mock usage - Support for mocking location.assign, location.replace, and location.reload methods
This commit is contained in:
@@ -11,6 +11,7 @@ import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
|
||||
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
||||
import { ComfyActionbar } from '../helpers/actionbar'
|
||||
import { ComfyTemplates } from '../helpers/templates'
|
||||
import { LocationMock } from '../helpers/locationMock'
|
||||
import { ComfyMouse } from './ComfyMouse'
|
||||
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||
import { SettingDialog } from './components/SettingDialog'
|
||||
@@ -144,6 +145,7 @@ export class ComfyPage {
|
||||
public readonly templates: ComfyTemplates
|
||||
public readonly settingDialog: SettingDialog
|
||||
public readonly confirmDialog: ConfirmDialog
|
||||
public readonly locationMock: LocationMock
|
||||
|
||||
/** Worker index to test user ID */
|
||||
public readonly userIds: string[] = []
|
||||
@@ -172,6 +174,7 @@ export class ComfyPage {
|
||||
this.templates = new ComfyTemplates(page)
|
||||
this.settingDialog = new SettingDialog(page, this)
|
||||
this.confirmDialog = new ConfirmDialog(page)
|
||||
this.locationMock = new LocationMock(page)
|
||||
}
|
||||
|
||||
convertLeafToContent(structure: FolderStructure): FolderStructure {
|
||||
@@ -272,11 +275,19 @@ export class ComfyPage {
|
||||
|
||||
async setup({
|
||||
clearStorage = true,
|
||||
mockReleases = true
|
||||
mockReleases = true,
|
||||
mockLocation = false
|
||||
}: {
|
||||
clearStorage?: boolean
|
||||
mockReleases?: boolean
|
||||
mockLocation?: boolean | Parameters<LocationMock['setupLocationMock']>[0]
|
||||
} = {}) {
|
||||
// Setup location mock if requested
|
||||
if (mockLocation) {
|
||||
const config = typeof mockLocation === 'boolean' ? undefined : mockLocation
|
||||
await this.locationMock.setupLocationMock(config)
|
||||
}
|
||||
|
||||
await this.goto()
|
||||
|
||||
// Mock release endpoint to prevent changelog popups
|
||||
|
||||
142
browser_tests/helpers/locationMock.ts
Normal file
142
browser_tests/helpers/locationMock.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* Mock location object for testing navigation and URL manipulation
|
||||
*/
|
||||
export class LocationMock {
|
||||
constructor(private page: Page) {}
|
||||
|
||||
/**
|
||||
* Mock the location object in the page context
|
||||
* @param mockConfig Configuration for the mock location
|
||||
*/
|
||||
async setupLocationMock(mockConfig?: {
|
||||
href?: string
|
||||
origin?: string
|
||||
pathname?: string
|
||||
search?: string
|
||||
hash?: string
|
||||
hostname?: string
|
||||
port?: string
|
||||
protocol?: string
|
||||
}) {
|
||||
await this.page.addInitScript((config) => {
|
||||
const defaultUrl = config?.href || window.location.href
|
||||
const url = new URL(defaultUrl)
|
||||
|
||||
// Create a mock location object
|
||||
const mockLocation = {
|
||||
href: config?.href || url.href,
|
||||
origin: config?.origin || url.origin,
|
||||
protocol: config?.protocol || url.protocol,
|
||||
host: url.host,
|
||||
hostname: config?.hostname || url.hostname,
|
||||
port: config?.port || url.port,
|
||||
pathname: config?.pathname || url.pathname,
|
||||
search: config?.search || url.search,
|
||||
hash: config?.hash || url.hash,
|
||||
assign: (newUrl: string) => {
|
||||
console.log(`[Mock] location.assign called with: ${newUrl}`)
|
||||
mockLocation.href = newUrl
|
||||
// Trigger navigation event if needed
|
||||
window.dispatchEvent(new Event('popstate'))
|
||||
},
|
||||
replace: (newUrl: string) => {
|
||||
console.log(`[Mock] location.replace called with: ${newUrl}`)
|
||||
mockLocation.href = newUrl
|
||||
// Trigger navigation event if needed
|
||||
window.dispatchEvent(new Event('popstate'))
|
||||
},
|
||||
reload: () => {
|
||||
console.log('[Mock] location.reload called')
|
||||
// Trigger reload event if needed
|
||||
window.dispatchEvent(new Event('beforeunload'))
|
||||
},
|
||||
toString: () => mockLocation.href
|
||||
}
|
||||
|
||||
// Override window.location
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: mockLocation,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
|
||||
// Also override document.location
|
||||
Object.defineProperty(document, 'location', {
|
||||
value: mockLocation,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
}, mockConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the mock location during test execution
|
||||
*/
|
||||
async updateLocation(updates: Partial<{
|
||||
href: string
|
||||
pathname: string
|
||||
search: string
|
||||
hash: string
|
||||
}>) {
|
||||
await this.page.evaluate((updates) => {
|
||||
const location = window.location as any
|
||||
Object.keys(updates).forEach((key) => {
|
||||
if (location[key] !== undefined) {
|
||||
location[key] = updates[key as keyof typeof updates]
|
||||
}
|
||||
})
|
||||
}, updates)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mock location values
|
||||
*/
|
||||
async getLocation() {
|
||||
return await this.page.evaluate(() => {
|
||||
const loc = window.location
|
||||
return {
|
||||
href: loc.href,
|
||||
origin: loc.origin,
|
||||
protocol: loc.protocol,
|
||||
host: loc.host,
|
||||
hostname: loc.hostname,
|
||||
port: loc.port,
|
||||
pathname: loc.pathname,
|
||||
search: loc.search,
|
||||
hash: loc.hash
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate navigation to a new URL
|
||||
*/
|
||||
async navigateTo(url: string) {
|
||||
await this.page.evaluate((url) => {
|
||||
const location = window.location as any
|
||||
location.assign(url)
|
||||
}, url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate location.replace
|
||||
*/
|
||||
async replaceTo(url: string) {
|
||||
await this.page.evaluate((url) => {
|
||||
const location = window.location as any
|
||||
location.replace(url)
|
||||
}, url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate location.reload
|
||||
*/
|
||||
async reload() {
|
||||
await this.page.evaluate(() => {
|
||||
const location = window.location as any
|
||||
location.reload()
|
||||
})
|
||||
}
|
||||
}
|
||||
96
browser_tests/locationMock.example.test.ts
Normal file
96
browser_tests/locationMock.example.test.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
import { LocationMock } from './helpers/locationMock'
|
||||
|
||||
test.describe('Location Mock Example', () => {
|
||||
test('should mock location object', async ({ page, comfyPage }) => {
|
||||
const locationMock = new LocationMock(page)
|
||||
|
||||
// Setup location mock before navigating to the page
|
||||
await locationMock.setupLocationMock({
|
||||
href: 'http://example.com/test',
|
||||
pathname: '/test',
|
||||
search: '?query=value',
|
||||
hash: '#section'
|
||||
})
|
||||
|
||||
// Navigate to your app
|
||||
await comfyPage.goto()
|
||||
|
||||
// Verify the mock is working
|
||||
const location = await locationMock.getLocation()
|
||||
expect(location.pathname).toBe('/test')
|
||||
expect(location.search).toBe('?query=value')
|
||||
expect(location.hash).toBe('#section')
|
||||
|
||||
// Test navigation
|
||||
await locationMock.navigateTo('http://example.com/new-page')
|
||||
const newLocation = await locationMock.getLocation()
|
||||
expect(newLocation.href).toBe('http://example.com/new-page')
|
||||
|
||||
// Test updating specific properties
|
||||
await locationMock.updateLocation({
|
||||
pathname: '/updated-path',
|
||||
search: '?new=param'
|
||||
})
|
||||
|
||||
const updatedLocation = await locationMock.getLocation()
|
||||
expect(updatedLocation.pathname).toBe('/updated-path')
|
||||
expect(updatedLocation.search).toBe('?new=param')
|
||||
})
|
||||
|
||||
test('should handle location methods', async ({ page, comfyPage }) => {
|
||||
const locationMock = new LocationMock(page)
|
||||
|
||||
await locationMock.setupLocationMock({
|
||||
href: 'http://localhost:5173/'
|
||||
})
|
||||
|
||||
await comfyPage.goto()
|
||||
|
||||
// Test location.assign
|
||||
await page.evaluate(() => {
|
||||
window.location.assign('/new-route')
|
||||
})
|
||||
|
||||
// Check console for mock output
|
||||
const consoleMessages: string[] = []
|
||||
page.on('console', (msg) => {
|
||||
if (msg.text().includes('[Mock]')) {
|
||||
consoleMessages.push(msg.text())
|
||||
}
|
||||
})
|
||||
|
||||
await locationMock.navigateTo('/another-route')
|
||||
await locationMock.replaceTo('/replaced-route')
|
||||
await locationMock.reload()
|
||||
|
||||
// Verify mock methods were called
|
||||
expect(consoleMessages.some((msg) => msg.includes('location.assign'))).toBeTruthy()
|
||||
expect(consoleMessages.some((msg) => msg.includes('location.replace'))).toBeTruthy()
|
||||
expect(consoleMessages.some((msg) => msg.includes('location.reload'))).toBeTruthy()
|
||||
})
|
||||
|
||||
test('should work with Happy DOM globals', async ({ page, comfyPage }) => {
|
||||
// Set environment variable for Happy DOM URL
|
||||
process.env.HAPPY_DOM_URL = 'http://custom-domain.com/'
|
||||
|
||||
const locationMock = new LocationMock(page)
|
||||
await locationMock.setupLocationMock()
|
||||
|
||||
await comfyPage.goto()
|
||||
|
||||
// Verify location is mocked correctly
|
||||
const location = await page.evaluate(() => ({
|
||||
href: window.location.href,
|
||||
origin: window.location.origin,
|
||||
canAssign: typeof window.location.assign === 'function',
|
||||
canReplace: typeof window.location.replace === 'function',
|
||||
canReload: typeof window.location.reload === 'function'
|
||||
}))
|
||||
|
||||
expect(location.canAssign).toBeTruthy()
|
||||
expect(location.canReplace).toBeTruthy()
|
||||
expect(location.canReload).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -6,17 +6,43 @@ if (typeof globalThis.__USE_PROD_CONFIG__ === 'undefined') {
|
||||
globalThis.__USE_PROD_CONFIG__ = false;
|
||||
}
|
||||
|
||||
// Create a happy-dom window instance
|
||||
// Create a happy-dom window instance with configurable URL
|
||||
const defaultUrl = (typeof globalThis.process !== 'undefined' && globalThis.process.env?.HAPPY_DOM_URL) || 'http://localhost:5173/';
|
||||
const window = new Window({
|
||||
url: 'http://localhost:5173/',
|
||||
url: defaultUrl,
|
||||
width: 1024,
|
||||
height: 768
|
||||
});
|
||||
|
||||
// Mock location with additional properties for testing
|
||||
const mockLocation = {
|
||||
...window.location,
|
||||
href: defaultUrl,
|
||||
origin: new URL(defaultUrl).origin,
|
||||
protocol: new URL(defaultUrl).protocol,
|
||||
host: new URL(defaultUrl).host,
|
||||
hostname: new URL(defaultUrl).hostname,
|
||||
port: new URL(defaultUrl).port,
|
||||
pathname: new URL(defaultUrl).pathname,
|
||||
search: new URL(defaultUrl).search,
|
||||
hash: new URL(defaultUrl).hash,
|
||||
assign: (url) => {
|
||||
console.log(`[Mock] location.assign called with: ${url}`);
|
||||
mockLocation.href = url;
|
||||
},
|
||||
replace: (url) => {
|
||||
console.log(`[Mock] location.replace called with: ${url}`);
|
||||
mockLocation.href = url;
|
||||
},
|
||||
reload: () => {
|
||||
console.log('[Mock] location.reload called');
|
||||
}
|
||||
};
|
||||
|
||||
// Expose DOM globals (only set if not already defined)
|
||||
if (!globalThis.window) globalThis.window = window;
|
||||
if (!globalThis.document) globalThis.document = window.document;
|
||||
if (!globalThis.location) globalThis.location = window.location;
|
||||
if (!globalThis.location) globalThis.location = mockLocation;
|
||||
if (!globalThis.navigator) {
|
||||
try {
|
||||
globalThis.navigator = window.navigator;
|
||||
|
||||
Reference in New Issue
Block a user