mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 14:45:36 +00:00
## Summary Use `group_by_job_time` for exports spanning multiple jobs while keeping single-job exports on `preserve`, and add regression coverage for the new naming-strategy behavior. ## Changes - **What**: updated the asset export payload and request typing for the new naming-strategy values, added unit coverage for single-job vs multi-job export requests, added `@cloud` sidebar browser coverage for export payloads, and adjusted the cloud Playwright setup helpers so setup API calls can hit the backend directly and Firebase auth is seeded on the app origin - **Breaking**: none - **Dependencies**: none ## Review Focus Please sanity-check the cloud Playwright harness changes in `ComfyPage` and `CloudAuthHelper`, plus the single-job vs multi-job export naming-strategy assertions in the new browser tests. ## Screenshots (if applicable) N/A ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11610-Fix-naming-strategy-for-multi-job-asset-exports-34c6d73d365081a68a88ea38d897578f) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
176 lines
5.8 KiB
TypeScript
176 lines
5.8 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
|
|
/**
|
|
* Mocks Firebase authentication for cloud E2E tests.
|
|
*
|
|
* The cloud build's router guard waits for Firebase `onAuthStateChanged`
|
|
* to fire, then checks `getAuthHeader()`. In CI no Firebase project is
|
|
* configured, so the user is never authenticated and the app redirects
|
|
* to `/cloud/login`.
|
|
*
|
|
* This helper seeds Firebase's IndexedDB persistence layer with a mock
|
|
* user and intercepts the Firebase REST APIs (securetoken, identitytoolkit)
|
|
* so the SDK believes a user is signed in. Must be called before navigation.
|
|
*/
|
|
export class CloudAuthHelper {
|
|
private readonly appUrl: string
|
|
|
|
constructor(private readonly page: Page) {
|
|
this.appUrl = process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188'
|
|
}
|
|
|
|
/**
|
|
* Set up all auth mocks. Must be called before `comfyPage.setup()`.
|
|
*/
|
|
async mockAuth(): Promise<void> {
|
|
await this.seedFirebaseIndexedDB()
|
|
await this.mockFirebaseEndpoints()
|
|
}
|
|
|
|
/**
|
|
* Navigate to a lightweight same-origin page to seed Firebase's
|
|
* IndexedDB persistence with a mock user. This ensures the data
|
|
* is written before the app loads and Firebase reads it.
|
|
*
|
|
* Firebase auth uses `browserLocalPersistence` which stores data in
|
|
* IndexedDB database `firebaseLocalStorageDb`, object store
|
|
* `firebaseLocalStorage`, keyed by `firebase:authUser:<apiKey>:<appName>`.
|
|
*/
|
|
private async seedFirebaseIndexedDB(): Promise<void> {
|
|
// Navigate to a lightweight endpoint to get a same-origin context
|
|
await this.page.goto(`${this.appUrl}/api/users`)
|
|
|
|
await this.page.evaluate(() => {
|
|
const MOCK_USER_DATA = {
|
|
uid: 'test-user-e2e',
|
|
email: 'e2e@test.comfy.org',
|
|
displayName: 'E2E Test User',
|
|
emailVerified: true,
|
|
isAnonymous: false,
|
|
providerData: [
|
|
{
|
|
providerId: 'google.com',
|
|
uid: 'test-user-e2e',
|
|
displayName: 'E2E Test User',
|
|
email: 'e2e@test.comfy.org',
|
|
phoneNumber: null,
|
|
photoURL: null
|
|
}
|
|
],
|
|
stsTokenManager: {
|
|
refreshToken: 'mock-refresh-token',
|
|
accessToken: 'mock-firebase-id-token',
|
|
expirationTime: Date.now() + 60 * 60 * 1000
|
|
},
|
|
apiKey: 'AIzaSyDa_YMeyzV0SkVe92vBZ1tVikWBmOU5KVE',
|
|
appName: '[DEFAULT]'
|
|
}
|
|
|
|
const DB_NAME = 'firebaseLocalStorageDb'
|
|
const STORE_NAME = 'firebaseLocalStorage'
|
|
const KEY = `firebase:authUser:${MOCK_USER_DATA.apiKey}:${MOCK_USER_DATA.appName}`
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
const request = indexedDB.open(DB_NAME)
|
|
request.onerror = () => reject(request.error)
|
|
request.onupgradeneeded = () => {
|
|
const db = request.result
|
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
db.createObjectStore(STORE_NAME)
|
|
}
|
|
}
|
|
request.onsuccess = () => {
|
|
const db = request.result
|
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
db.close()
|
|
const upgradeReq = indexedDB.open(DB_NAME, db.version + 1)
|
|
upgradeReq.onerror = () => reject(upgradeReq.error)
|
|
upgradeReq.onupgradeneeded = () => {
|
|
const upgradedDb = upgradeReq.result
|
|
if (!upgradedDb.objectStoreNames.contains(STORE_NAME)) {
|
|
upgradedDb.createObjectStore(STORE_NAME)
|
|
}
|
|
}
|
|
upgradeReq.onsuccess = () => {
|
|
const upgradedDb = upgradeReq.result
|
|
const tx = upgradedDb.transaction(STORE_NAME, 'readwrite')
|
|
tx.objectStore(STORE_NAME).put(
|
|
{ fpiVersion: '1', value: MOCK_USER_DATA },
|
|
KEY
|
|
)
|
|
tx.oncomplete = () => {
|
|
upgradedDb.close()
|
|
resolve()
|
|
}
|
|
tx.onerror = () => reject(tx.error)
|
|
}
|
|
return
|
|
}
|
|
const tx = db.transaction(STORE_NAME, 'readwrite')
|
|
tx.objectStore(STORE_NAME).put(
|
|
{ fpiVersion: '1', value: MOCK_USER_DATA },
|
|
KEY
|
|
)
|
|
tx.oncomplete = () => {
|
|
db.close()
|
|
resolve()
|
|
}
|
|
tx.onerror = () => reject(tx.error)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Intercept Firebase Auth REST API endpoints so the SDK can
|
|
* "refresh" the mock user's token without real credentials.
|
|
*/
|
|
private async mockFirebaseEndpoints(): Promise<void> {
|
|
await this.page.route('**/securetoken.googleapis.com/**', (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
access_token: 'mock-access-token',
|
|
expires_in: '3600',
|
|
token_type: 'Bearer',
|
|
refresh_token: 'mock-refresh-token',
|
|
id_token: 'mock-firebase-id-token',
|
|
user_id: 'test-user-e2e',
|
|
project_id: 'dreamboothy-dev'
|
|
})
|
|
})
|
|
)
|
|
|
|
await this.page.route('**/identitytoolkit.googleapis.com/**', (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
kind: 'identitytoolkit#GetAccountInfoResponse',
|
|
users: [
|
|
{
|
|
localId: 'test-user-e2e',
|
|
email: 'e2e@test.comfy.org',
|
|
displayName: 'E2E Test User',
|
|
emailVerified: true,
|
|
validSince: '0',
|
|
lastLoginAt: String(Date.now()),
|
|
createdAt: String(Date.now()),
|
|
lastRefreshAt: new Date().toISOString()
|
|
}
|
|
]
|
|
})
|
|
})
|
|
)
|
|
|
|
await this.page.route('**/__/auth/**', (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'text/html',
|
|
body: '<html><body></body></html>'
|
|
})
|
|
)
|
|
}
|
|
}
|