Compare commits

...

2 Commits

Author SHA1 Message Date
dante01yoon
1be1ac71f4 fix(test): fix broken selectors and menu label in publish E2E tests
- Use 'New' instead of 'New Workflow' for topbar command (matches menubarLabel)
- Replace non-existent [data-tag-item] selector with getByText for tag suggestions
- Rename tagSuggestions() to tagSuggestion(name) using semantic locator pattern
2026-03-31 20:21:15 +09:00
dante01yoon
00dda88a40 test: add E2E tests for publish flow wizard
Add Playwright E2E test coverage for the ComfyHub publish workflow
dialog, covering wizard navigation, form interactions, profile gate,
save prompt, and publish submission scenarios.

- Add showPublishDialog() to dialogService for production-safe dialog
  opening via Vite-bundled lazy imports
- Add PublishDialog page object fixture with publish-specific root
  testid to avoid matching other PrimeVue dialogs
- Add PublishApiHelper for mocking all publish flow API endpoints
- Add data-testid attributes to 6 publish flow components
- Add 17 test scenarios across 7 describe blocks

Fixes #9079
2026-03-31 19:30:14 +09:00
11 changed files with 736 additions and 5 deletions

View File

@@ -0,0 +1,106 @@
import type { Locator, Page } from '@playwright/test'
import { TestIds } from '@e2e/fixtures/selectors'
import { BaseDialog } from './BaseDialog'
export class PublishDialog extends BaseDialog {
readonly nav: Locator
readonly footer: Locator
readonly savePrompt: Locator
constructor(page: Page) {
super(page, TestIds.publish.dialog)
this.nav = this.root.getByTestId(TestIds.publish.nav)
this.footer = this.root.getByTestId(TestIds.publish.footer)
this.savePrompt = this.root.getByTestId(TestIds.publish.savePrompt)
}
/**
* Opens the publish dialog via the dialog service's showPublishDialog(),
* which uses Vite-bundled lazy imports that work in both dev and production.
*/
async open(): Promise<void> {
await this.page.evaluate(async () => {
const store = window.app!.extensionManager as {
dialog: { showPublishDialog: () => Promise<void> }
}
await store.dialog.showPublishDialog()
})
await this.waitForVisible()
}
// Step content locators
get describeStep(): Locator {
return this.root.getByTestId(TestIds.publish.describeStep)
}
get finishStep(): Locator {
return this.root.getByTestId(TestIds.publish.finishStep)
}
get profilePrompt(): Locator {
return this.root.getByTestId(TestIds.publish.profilePrompt)
}
get gateFlow(): Locator {
return this.root.getByTestId(TestIds.publish.gateFlow)
}
// Describe step locators
get nameInput(): Locator {
return this.describeStep.getByRole('textbox').first()
}
get descriptionTextarea(): Locator {
return this.describeStep.locator('textarea')
}
get tagsInput(): Locator {
return this.describeStep.locator('[role="list"]').first()
}
tagSuggestion(name: string): Locator {
return this.describeStep.getByText(name, { exact: true })
}
// Footer button locators
get backButton(): Locator {
return this.footer.getByRole('button', { name: 'Back' })
}
get nextButton(): Locator {
return this.footer.getByRole('button', { name: 'Next' })
}
get publishButton(): Locator {
return this.footer.getByRole('button', { name: 'Publish to ComfyHub' })
}
// Nav locators
navStep(label: string): Locator {
return this.nav.getByRole('button', { name: label })
}
currentNavStep(): Locator {
return this.nav.locator('[aria-current="step"]')
}
// Navigation helpers
async goNext(): Promise<void> {
await this.nextButton.click()
}
async goBack(): Promise<void> {
await this.backButton.click()
}
async goToStep(label: string): Promise<void> {
await this.navStep(label).click()
}
}

View File

@@ -0,0 +1,200 @@
import type { Page, Route } from '@playwright/test'
import type {
AssetInfo,
HubAssetUploadUrlResponse,
HubLabelInfo,
HubLabelListResponse,
HubProfile,
WorkflowPublishInfo
} from '@comfyorg/ingest-types'
import type { ShareableAssetsResponse } from '@/schemas/apiSchema'
const DEFAULT_PROFILE: HubProfile = {
username: 'testuser',
display_name: 'Test User',
description: 'A test creator',
avatar_url: undefined
}
const DEFAULT_TAG_LABELS: HubLabelInfo[] = [
{ name: 'anime', display_name: 'anime', type: 'tag' },
{ name: 'upscale', display_name: 'upscale', type: 'tag' },
{ name: 'faceswap', display_name: 'faceswap', type: 'tag' },
{ name: 'img2img', display_name: 'img2img', type: 'tag' },
{ name: 'controlnet', display_name: 'controlnet', type: 'tag' }
]
const DEFAULT_PUBLISH_RESPONSE: WorkflowPublishInfo = {
workflow_id: 'test-workflow-id-456',
share_id: 'test-share-id-123',
publish_time: new Date().toISOString(),
listed: true,
assets: []
}
const DEFAULT_UPLOAD_URL_RESPONSE: HubAssetUploadUrlResponse = {
upload_url: 'https://mock-s3.example.com/upload',
public_url: 'https://mock-s3.example.com/asset.png',
token: 'mock-upload-token'
}
export class PublishApiHelper {
private routeHandlers: Array<{
pattern: string
handler: (route: Route) => Promise<void>
}> = []
constructor(private readonly page: Page) {}
async mockProfile(profile: HubProfile | null): Promise<void> {
await this.addRoute('**/hub/profiles/me', async (route) => {
if (route.request().method() !== 'GET') {
await route.continue()
return
}
if (profile === null) {
await route.fulfill({ status: 404, body: 'Not found' })
} else {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(profile)
})
}
})
}
async mockTagLabels(
labels: HubLabelInfo[] = DEFAULT_TAG_LABELS
): Promise<void> {
const response: HubLabelListResponse = { labels }
await this.addRoute('**/hub/labels**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response)
})
})
}
async mockPublishStatus(
status: 'unpublished' | WorkflowPublishInfo
): Promise<void> {
await this.addRoute('**/userdata/*/publish', async (route) => {
if (route.request().method() !== 'GET') {
await route.continue()
return
}
if (status === 'unpublished') {
await route.fulfill({ status: 404, body: 'Not found' })
} else {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(status)
})
}
})
}
async mockShareableAssets(assets: AssetInfo[] = []): Promise<void> {
const response: ShareableAssetsResponse = { assets }
await this.addRoute('**/assets/from-workflow', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response)
})
})
}
async mockPublishWorkflow(
response: WorkflowPublishInfo = DEFAULT_PUBLISH_RESPONSE
): Promise<void> {
await this.addRoute('**/hub/workflows', async (route) => {
if (route.request().method() !== 'POST') {
await route.continue()
return
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response)
})
})
}
async mockPublishWorkflowError(
statusCode = 500,
message = 'Failed to publish workflow'
): Promise<void> {
await this.addRoute('**/hub/workflows', async (route) => {
if (route.request().method() !== 'POST') {
await route.continue()
return
}
await route.fulfill({
status: statusCode,
contentType: 'application/json',
body: JSON.stringify({ message })
})
})
}
async mockUploadUrl(
response: HubAssetUploadUrlResponse = DEFAULT_UPLOAD_URL_RESPONSE
): Promise<void> {
await this.addRoute('**/hub/assets/upload-url', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(response)
})
})
}
async setupDefaultMocks(options?: {
hasProfile?: boolean
hasPrivateAssets?: boolean
}): Promise<void> {
const { hasProfile = true, hasPrivateAssets = false } = options ?? {}
await this.mockProfile(hasProfile ? DEFAULT_PROFILE : null)
await this.mockTagLabels()
await this.mockPublishStatus('unpublished')
await this.mockShareableAssets(
hasPrivateAssets
? [
{
id: 'asset-1',
name: 'my_model.safetensors',
preview_url: '',
storage_url: '',
model: true,
public: false,
in_library: true
}
]
: []
)
await this.mockPublishWorkflow()
await this.mockUploadUrl()
}
async cleanup(): Promise<void> {
for (const { pattern, handler } of this.routeHandlers) {
await this.page.unroute(pattern, handler)
}
this.routeHandlers = []
}
private async addRoute(
pattern: string,
handler: (route: Route) => Promise<void>
): Promise<void> {
this.routeHandlers.push({ pattern, handler })
await this.page.route(pattern, handler)
}
}

View File

@@ -106,6 +106,16 @@ export const TestIds = {
errors: {
imageLoadError: 'error-loading-image',
videoLoadError: 'error-loading-video'
},
publish: {
dialog: 'publish-dialog',
savePrompt: 'publish-save-prompt',
describeStep: 'publish-describe-step',
finishStep: 'publish-finish-step',
footer: 'publish-footer',
profilePrompt: 'publish-profile-prompt',
nav: 'publish-nav',
gateFlow: 'publish-gate-flow'
}
} as const
@@ -133,3 +143,4 @@ export type TestIdValue =
| (typeof TestIds.user)[keyof typeof TestIds.user]
| (typeof TestIds.queue)[keyof typeof TestIds.queue]
| (typeof TestIds.errors)[keyof typeof TestIds.errors]
| (typeof TestIds.publish)[keyof typeof TestIds.publish]

View File

@@ -0,0 +1,380 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import { PublishDialog } from '@e2e/fixtures/components/PublishDialog'
import { PublishApiHelper } from '@e2e/fixtures/helpers/PublishApiHelper'
test.describe('Publish dialog - wizard navigation', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
await publishApi.setupDefaultMocks()
await comfyPage.menu.topbar.saveWorkflow('test-publish-wf')
// Handle overwrite confirmation if the file already exists
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
})
test('opens on the Describe step by default', async () => {
await expect(dialog.describeStep).toBeVisible()
await expect(dialog.nameInput).toBeVisible()
await expect(dialog.descriptionTextarea).toBeVisible()
})
test('pre-fills workflow name from active workflow', async () => {
await expect(dialog.nameInput).toHaveValue(/test-publish-wf/)
})
test('Next button navigates to Examples step', async () => {
await dialog.goNext()
await expect(dialog.describeStep).toBeHidden()
// Examples step should show thumbnail toggle and upload area
await expect(dialog.root.getByText('Select a thumbnail')).toBeVisible()
})
test('Back button returns to Describe step from Examples', async () => {
await dialog.goNext()
await expect(dialog.describeStep).toBeHidden()
await dialog.goBack()
await expect(dialog.describeStep).toBeVisible()
})
test('navigates through all steps to Finish', async () => {
await dialog.goNext() // → Examples
await dialog.goNext() // → Finish
await expect(dialog.finishStep).toBeVisible()
await expect(dialog.publishButton).toBeVisible()
})
test('clicking nav step navigates directly', async () => {
await dialog.goToStep('Finish publishing')
await expect(dialog.finishStep).toBeVisible()
await dialog.goToStep('Describe your workflow')
await expect(dialog.describeStep).toBeVisible()
})
test('closes dialog via Escape key', async ({ comfyPage }) => {
await comfyPage.page.keyboard.press('Escape')
await expect(dialog.root).toBeHidden()
})
})
test.describe('Publish dialog - Describe step', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
await publishApi.setupDefaultMocks()
await comfyPage.menu.topbar.saveWorkflow('test-describe-wf')
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
})
test('allows editing the workflow name', async () => {
await dialog.nameInput.clear()
await dialog.nameInput.fill('My Custom Workflow')
await expect(dialog.nameInput).toHaveValue('My Custom Workflow')
})
test('allows editing the description', async () => {
await dialog.descriptionTextarea.fill('A great workflow for anime art')
await expect(dialog.descriptionTextarea).toHaveValue(
'A great workflow for anime art'
)
})
test('displays tag suggestions from mocked API', async () => {
await expect(dialog.root.getByText('anime')).toBeVisible()
await expect(dialog.root.getByText('upscale')).toBeVisible()
})
// TODO: Tag click emits update:tags but the tag does not appear in the
// active list during E2E. Needs investigation of the parent state binding.
test.fixme('clicking a tag suggestion adds it', async () => {
await dialog.root.getByText('anime').click()
const activeTags = dialog.describeStep.locator('[role="list"]').first()
await expect(activeTags.getByText('anime')).toBeVisible()
})
})
test.describe('Publish dialog - Examples step', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
await publishApi.setupDefaultMocks()
await comfyPage.menu.topbar.saveWorkflow('test-examples-wf')
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
await dialog.goNext() // Navigate to Examples step
})
test('shows thumbnail type toggle options', async () => {
await expect(dialog.root.getByText('Image', { exact: true })).toBeVisible()
await expect(dialog.root.getByText('Video', { exact: true })).toBeVisible()
await expect(
dialog.root.getByText('Image comparison', { exact: true })
).toBeVisible()
})
test('shows example image upload tile', async () => {
await expect(
dialog.root.getByRole('button', { name: 'Upload example image' })
).toBeVisible()
})
})
test.describe('Publish dialog - Finish step with profile', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
await publishApi.setupDefaultMocks({ hasProfile: true })
await comfyPage.menu.topbar.saveWorkflow('test-finish-wf')
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
await dialog.goToStep('Finish publishing')
})
test('shows profile card with username', async () => {
await expect(dialog.finishStep).toBeVisible()
await expect(dialog.root.getByText('@testuser')).toBeVisible()
await expect(dialog.root.getByText('Test User')).toBeVisible()
})
test('publish button is enabled when no private assets', async () => {
await expect(dialog.publishButton).toBeEnabled()
})
})
test.describe('Publish dialog - Finish step with private assets', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
await publishApi.setupDefaultMocks({
hasProfile: true,
hasPrivateAssets: true
})
await comfyPage.menu.topbar.saveWorkflow('test-assets-wf')
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
await dialog.goToStep('Finish publishing')
})
test('publish button is disabled until assets acknowledged', async () => {
await expect(dialog.finishStep).toBeVisible()
await expect(dialog.publishButton).toBeDisabled()
// Check the acknowledge checkbox
const checkbox = dialog.finishStep.getByRole('checkbox')
await checkbox.check()
await expect(dialog.publishButton).toBeEnabled()
})
})
test.describe('Publish dialog - no profile', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
await publishApi.setupDefaultMocks({ hasProfile: false })
await comfyPage.menu.topbar.saveWorkflow('test-noprofile-wf')
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
await dialog.goToStep('Finish publishing')
})
test('shows profile creation prompt when user has no profile', async () => {
await expect(dialog.profilePrompt).toBeVisible()
await expect(
dialog.root.getByText('Create a profile to publish to ComfyHub')
).toBeVisible()
})
test('clicking create profile CTA shows profile creation form', async () => {
await dialog.root.getByRole('button', { name: 'Create a profile' }).click()
await expect(dialog.gateFlow).toBeVisible()
})
})
test.describe('Publish dialog - unsaved workflow', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
await publishApi.setupDefaultMocks()
// Don't save workflow — open dialog on the default temporary workflow
})
test('shows save prompt for temporary workflow', async ({ comfyPage }) => {
// Create a new workflow to ensure it's temporary
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
await dialog.open()
await expect(dialog.savePrompt).toBeVisible()
await expect(
dialog.root.getByText('You must save your workflow before publishing')
).toBeVisible()
// Nav should be hidden when save is required
await expect(dialog.nav).toBeHidden()
})
})
test.describe('Publish dialog - submission', () => {
let dialog: PublishDialog
let publishApi: PublishApiHelper
test.beforeEach(async ({ comfyPage }) => {
dialog = new PublishDialog(comfyPage.page)
publishApi = new PublishApiHelper(comfyPage.page)
await comfyPage.featureFlags.setFlags({
comfyhub_upload_enabled: true,
comfyhub_profile_gate_enabled: true
})
})
test('successful publish closes dialog', async ({ comfyPage }) => {
await publishApi.setupDefaultMocks({ hasProfile: true })
await comfyPage.menu.topbar.saveWorkflow('test-submit-wf')
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
await dialog.goToStep('Finish publishing')
await expect(dialog.finishStep).toBeVisible()
await dialog.publishButton.click()
await expect(dialog.root).toBeHidden({ timeout: 10_000 })
})
test('failed publish shows error toast', async ({ comfyPage }) => {
await publishApi.setupDefaultMocks({ hasProfile: true })
// Override publish mock with error response
await publishApi.mockPublishWorkflowError(500, 'Internal error')
await comfyPage.menu.topbar.saveWorkflow('test-submit-fail-wf')
const overwriteDialog = comfyPage.page.locator(
'.p-dialog:has-text("Overwrite")'
)
if (await overwriteDialog.isVisible()) {
await comfyPage.confirmDialog.click('overwrite')
}
await dialog.open()
await dialog.goToStep('Finish publishing')
await expect(dialog.finishStep).toBeVisible()
await dialog.publishButton.click()
// Error toast should appear
await expect(comfyPage.page.locator('.p-toast-message-error')).toBeVisible({
timeout: 10_000
})
// Dialog should remain open
await expect(dialog.root).toBeVisible()
})
})

View File

@@ -1,5 +1,8 @@
<template>
<div class="flex min-h-0 flex-1 flex-col gap-6 px-6 py-4">
<div
data-testid="publish-describe-step"
class="flex min-h-0 flex-1 flex-col gap-6 px-6 py-4"
>
<label class="flex flex-col gap-2">
<span class="text-sm text-base-foreground">
{{ $t('comfyHubPublish.workflowName') }}

View File

@@ -1,5 +1,8 @@
<template>
<div class="flex min-h-0 flex-1 flex-col gap-8 px-6 py-4">
<div
data-testid="publish-finish-step"
class="flex min-h-0 flex-1 flex-col gap-8 px-6 py-4"
>
<section class="flex flex-col gap-4">
<span class="text-sm text-base-foreground">
{{ $t('comfyHubPublish.shareAs') }}

View File

@@ -1,5 +1,8 @@
<template>
<div class="flex min-h-0 flex-1 flex-col gap-4 px-6 py-4">
<div
data-testid="publish-profile-prompt"
class="flex min-h-0 flex-1 flex-col gap-4 px-6 py-4"
>
<p class="text-sm text-base-foreground">
{{ $t('comfyHubPublish.createProfileToPublish') }}
</p>

View File

@@ -21,7 +21,11 @@
<template #header />
<template #content>
<div v-if="needsSave" class="flex flex-col gap-4 p-6">
<div
v-if="needsSave"
data-testid="publish-save-prompt"
class="flex flex-col gap-4 p-6"
>
<p class="m-0 text-sm text-muted-foreground">
{{ $t('comfyHubPublish.unsavedDescription') }}
</p>

View File

@@ -1,5 +1,6 @@
<template>
<footer
data-testid="publish-footer"
class="flex shrink items-center justify-end gap-4 border-t border-border-default px-6 py-4"
>
<Button v-if="!isFirstStep" size="lg" @click="$emit('back')">

View File

@@ -1,5 +1,5 @@
<template>
<nav class="flex flex-col gap-6 px-3 py-4">
<nav data-testid="publish-nav" class="flex flex-col gap-6 px-3 py-4">
<ol class="flex list-none flex-col p-0">
<li
v-for="step in steps"

View File

@@ -30,6 +30,8 @@ const lazyComfyOrgHeader = () =>
import('@/components/dialog/header/ComfyOrgHeader.vue')
const lazyCloudNotificationContent = () =>
import('@/platform/cloud/notification/components/CloudNotificationContent.vue')
const lazyPublishDialog = () =>
import('@/platform/workflow/sharing/components/publish/ComfyHubPublishDialog.vue')
export type ConfirmationDialogType =
| 'default'
@@ -591,10 +593,28 @@ export const useDialogService = () => {
})
}
async function showPublishDialog(): Promise<void> {
const { default: ComfyHubPublishDialog } = await lazyPublishDialog()
const key = 'global-comfyhub-publish'
showLayoutDialog({
key,
component: ComfyHubPublishDialog,
props: {
onClose: () => dialogStore.closeDialog({ key })
},
dialogComponentProps: {
pt: {
root: { 'data-testid': 'publish-dialog' }
}
}
})
}
return {
showExecutionErrorDialog,
showApiNodesSignInDialog,
showSignInDialog,
showPublishDialog,
showSubscriptionRequiredDialog,
showTopUpCreditsDialog,
showUpdatePasswordDialog,