mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Complete the @e2e/ path alias migration started in #10735 by converting all 354 remaining relative imports and adding a lint rule to prevent backsliding. ## Changes - **What**: Migrate all relative imports in browser_tests/ to use `@e2e/` (intra-directory) and `@/` (src/ imports) path aliases. Add `no-restricted-imports` ESLint rule banning `./` and `../` imports in `browser_tests/**/*.ts`. Suppress pre-existing oxlint `no-eval` and `no-console` warnings exposed by touching those files. ## Review Focus - ESLint flat-config merging: the `@playwright/test` ban and relative-import ban are in two separate blocks to avoid last-match-wins collision with the `useI18n`/`useVirtualList` blocks higher in the config. - The `['./**', '../**']` glob patterns (not `['./*', '../*']`) are needed to catch multi-level relative paths like `../../../src/foo`. Follows up on #10735 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10958-test-migrate-browser_tests-to-e2e-path-alias-and-add-lint-rule-33c6d73d365081649d1be771eac986fd) by [Unito](https://www.unito.io) Co-authored-by: Amp <amp@ampcode.com>
138 lines
4.6 KiB
TypeScript
138 lines
4.6 KiB
TypeScript
import type { Locator, Page } from '@playwright/test'
|
|
|
|
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
|
import { TestIds } from '@e2e/fixtures/selectors'
|
|
|
|
import { AppModeWidgetHelper } from '@e2e/fixtures/helpers/AppModeWidgetHelper'
|
|
import { BuilderFooterHelper } from '@e2e/fixtures/helpers/BuilderFooterHelper'
|
|
import { BuilderSaveAsHelper } from '@e2e/fixtures/helpers/BuilderSaveAsHelper'
|
|
import { BuilderSelectHelper } from '@e2e/fixtures/helpers/BuilderSelectHelper'
|
|
import { BuilderStepsHelper } from '@e2e/fixtures/helpers/BuilderStepsHelper'
|
|
|
|
export class AppModeHelper {
|
|
readonly steps: BuilderStepsHelper
|
|
readonly footer: BuilderFooterHelper
|
|
readonly saveAs: BuilderSaveAsHelper
|
|
readonly select: BuilderSelectHelper
|
|
readonly widgets: AppModeWidgetHelper
|
|
|
|
constructor(private readonly comfyPage: ComfyPage) {
|
|
this.steps = new BuilderStepsHelper(comfyPage)
|
|
this.footer = new BuilderFooterHelper(comfyPage)
|
|
this.saveAs = new BuilderSaveAsHelper(comfyPage)
|
|
this.select = new BuilderSelectHelper(comfyPage)
|
|
this.widgets = new AppModeWidgetHelper(comfyPage)
|
|
}
|
|
|
|
private get page(): Page {
|
|
return this.comfyPage.page
|
|
}
|
|
|
|
/** Enable the linear mode feature flag and top menu. */
|
|
async enableLinearMode() {
|
|
await this.page.evaluate(() => {
|
|
window.app!.api.serverFeatureFlags.value = {
|
|
...window.app!.api.serverFeatureFlags.value,
|
|
linear_toggle_enabled: true
|
|
}
|
|
})
|
|
await this.comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
|
|
}
|
|
|
|
/** Enter builder mode via the "Workflow actions" dropdown. */
|
|
async enterBuilder() {
|
|
await this.page
|
|
.getByRole('button', { name: 'Workflow actions' })
|
|
.first()
|
|
.click()
|
|
await this.page
|
|
.getByRole('menuitem', { name: /Build app|Edit app/ })
|
|
.click()
|
|
await this.comfyPage.nextFrame()
|
|
}
|
|
|
|
/** Toggle app mode (linear view) on/off. */
|
|
async toggleAppMode() {
|
|
await this.page.evaluate(() => {
|
|
window.app!.extensionManager.command.execute('Comfy.ToggleLinear')
|
|
})
|
|
await this.comfyPage.nextFrame()
|
|
}
|
|
|
|
/**
|
|
* Inject linearData into the current graph and enter app mode.
|
|
*
|
|
* Serializes the graph, injects linearData with the given inputs and
|
|
* auto-detected output node IDs, then reloads so the appModeStore
|
|
* picks up the data via its activeWorkflow watcher.
|
|
*
|
|
* @param inputs - Widget selections as [nodeId, widgetName] tuples
|
|
*/
|
|
async enterAppModeWithInputs(inputs: [string, string][]) {
|
|
await this.page.evaluate(async (inputTuples) => {
|
|
const graph = window.app!.graph
|
|
if (!graph) return
|
|
|
|
const outputNodeIds = graph.nodes
|
|
.filter(
|
|
(n: { type?: string }) =>
|
|
n.type === 'SaveImage' || n.type === 'PreviewImage'
|
|
)
|
|
.map((n: { id: number | string }) => String(n.id))
|
|
|
|
const workflow = graph.serialize() as unknown as Record<string, unknown>
|
|
const extra = (workflow.extra ?? {}) as Record<string, unknown>
|
|
extra.linearData = { inputs: inputTuples, outputs: outputNodeIds }
|
|
workflow.extra = extra
|
|
await window.app!.loadGraphData(
|
|
workflow as unknown as Parameters<
|
|
NonNullable<typeof window.app>['loadGraphData']
|
|
>[0]
|
|
)
|
|
}, inputs)
|
|
await this.comfyPage.nextFrame()
|
|
await this.toggleAppMode()
|
|
}
|
|
|
|
/** The "Connect an output" popover shown when saving without outputs. */
|
|
get connectOutputPopover(): Locator {
|
|
return this.page.getByTestId(TestIds.builder.connectOutputPopover)
|
|
}
|
|
|
|
/** The empty-state placeholder shown when no outputs are selected. */
|
|
get outputPlaceholder(): Locator {
|
|
return this.page.getByTestId(TestIds.builder.outputPlaceholder)
|
|
}
|
|
|
|
/** The linear-mode widget list container (visible in app mode). */
|
|
get linearWidgets(): Locator {
|
|
return this.page.locator('[data-testid="linear-widgets"]')
|
|
}
|
|
|
|
/** The PrimeVue Popover for the image picker (renders with role="dialog"). */
|
|
get imagePickerPopover(): Locator {
|
|
return this.page
|
|
.getByRole('dialog')
|
|
.filter({ has: this.page.getByRole('button', { name: 'All' }) })
|
|
.first()
|
|
}
|
|
|
|
/** The Run button in the app mode footer. */
|
|
get runButton(): Locator {
|
|
return this.page
|
|
.getByTestId('linear-run-button')
|
|
.getByRole('button', { name: /run/i })
|
|
}
|
|
|
|
/**
|
|
* Get the actions menu trigger for a widget in the app mode widget list.
|
|
* @param widgetName Text shown in the widget label (e.g. "seed").
|
|
*/
|
|
getAppModeWidgetMenu(widgetName: string): Locator {
|
|
return this.linearWidgets
|
|
.locator(`div:has(> div > span:text-is("${widgetName}"))`)
|
|
.getByTestId(TestIds.builder.widgetActionsMenu)
|
|
.first()
|
|
}
|
|
}
|