diff --git a/browser_tests/fixtures/helpers/HelpCenterHelper.ts b/browser_tests/fixtures/helpers/HelpCenterHelper.ts new file mode 100644 index 0000000000..41279b587a --- /dev/null +++ b/browser_tests/fixtures/helpers/HelpCenterHelper.ts @@ -0,0 +1,184 @@ +import type { Locator, Page, Route } from '@playwright/test' + +import type { components } from '@comfyorg/registry-types' + +import { comfyPageFixture } from '@e2e/fixtures/ComfyPage' +import { TestIds } from '@e2e/fixtures/selectors' + +type ReleaseNote = components['schemas']['ReleaseNote'] + +export type HelpMenuItemKey = + | 'feedback' + | 'help' + | 'docs' + | 'discord' + | 'github' + | 'manager' + | 'update-comfyui' + | 'more' + +export class HelpCenterHelper { + public readonly button: Locator + public readonly popup: Locator + public readonly backdrop: Locator + public readonly whatsNewSection: Locator + + constructor(public readonly page: Page) { + this.button = page.getByTestId(TestIds.helpCenter.button) + this.popup = page.getByTestId(TestIds.helpCenter.popup) + this.backdrop = page.getByTestId(TestIds.helpCenter.backdrop) + this.whatsNewSection = page.getByTestId(TestIds.dialogs.whatsNewSection) + } + + menuItem(key: HelpMenuItemKey): Locator { + return this.page.getByTestId(TestIds.helpCenter.menuItem(key)) + } + + releaseItem(version: string): Locator { + return this.page.getByTestId(TestIds.helpCenter.releaseItem(version)) + } + + get releaseItems(): Locator { + return this.whatsNewSection.locator('[data-testid^="help-release-item-"]') + } + + async open(): Promise { + await this.button.waitFor({ state: 'visible' }) + await this.button.click() + await this.popup.waitFor({ state: 'visible' }) + } + + async closeViaBackdrop(): Promise { + await this.backdrop.click() + await this.popup.waitFor({ state: 'hidden' }) + } + + async toggle(): Promise { + await this.button.click() + } + + /** + * Mock the Comfy release API so the help center gets a deterministic + * list of releases. Empty array is used when `releases` is omitted. + */ + async mockReleases(releases: ReleaseNote[] = []): Promise { + await this.page.route('**/releases**', async (route: Route) => { + const url = route.request().url() + if ( + url.includes('api.comfy.org') || + url.includes('stagingapi.comfy.org') + ) { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(releases) + }) + } else { + await route.continue() + } + }) + } + + /** + * Intercept the Zendesk support URL so it never actually loads in the + * new tab opened by the Contact Support command. + */ + async stubSupportPage(): Promise { + await this.page + .context() + .route('https://support.comfy.org/**', (route: Route) => + route.fulfill({ + status: 200, + contentType: 'text/html', + body: '' + }) + ) + } + + /** + * Intercept the docs.comfy.org changelog / guide pages so new tabs opened + * by help center actions don't hit the real site during tests. + */ + async stubDocsPage(): Promise { + await this.page + .context() + .route('https://docs.comfy.org/**', (route: Route) => + route.fulfill({ + status: 200, + contentType: 'text/html', + body: '' + }) + ) + } + + /** + * Intercept outbound static URLs (discord, github, ...) so new tabs + * opened by help center actions don't navigate to the real sites. + */ + async stubExternalPages(): Promise { + for (const pattern of [ + 'https://www.comfy.org/**', + 'https://github.com/**' + ]) { + await this.page.context().route(pattern, (route: Route) => + route.fulfill({ + status: 200, + contentType: 'text/html', + body: '' + }) + ) + } + } +} + +/** + * Arms the `popup` listener, runs the action that triggers `window.open`, + * then waits for the popup's initial navigation to commit so `popup.url()` + * doesn't race and return `about:blank`. Returns a parsed `URL` and closes + * the popup. + * + * @example + * ```ts + * const url = await waitForPopup(page, () => button.click()) + * expect(url.hostname).toBe('example.com') + * ``` + */ +export async function waitForPopup( + page: Page, + action: () => Promise +): Promise { + const popupPromise = page.waitForEvent('popup') + await action() + const popup = await popupPromise + await popup.waitForLoadState('domcontentloaded') + const url = new URL(popup.url()) + await popup.close() + return url +} + +export function createMockRelease( + overrides: Partial = {} +): ReleaseNote { + return { + id: 1, + project: 'comfyui', + version: '0.3.44', + attention: 'medium', + content: '## New Features\n\n- Added awesome feature', + published_at: new Date().toISOString(), + ...overrides + } +} + +/** + * Extends the main comfyPageFixture so that depending on `helpCenter` + * automatically boots the full Comfy app (via the underlying comfyPage + * fixture's setup). Tests only need to destructure `helpCenter`. + */ +export const helpCenterFixture = comfyPageFixture.extend<{ + helpCenter: HelpCenterHelper +}>({ + helpCenter: async ({ comfyPage }, use) => { + await use(new HelpCenterHelper(comfyPage.page)) + } +}) diff --git a/browser_tests/fixtures/selectors.ts b/browser_tests/fixtures/selectors.ts index bdaaffc6cc..2304e248f1 100644 --- a/browser_tests/fixtures/selectors.ts +++ b/browser_tests/fixtures/selectors.ts @@ -115,6 +115,13 @@ export const TestIds = { menu: { moreMenuContent: 'more-menu-content' }, + helpCenter: { + button: 'help-center-button', + popup: 'help-center-popup', + backdrop: 'help-center-backdrop', + menuItem: (key: string) => `help-menu-item-${key}`, + releaseItem: (version: string) => `help-release-item-${version}` + }, widgets: { container: 'node-widgets', widget: 'node-widget', diff --git a/browser_tests/tests/helpCenter.spec.ts b/browser_tests/tests/helpCenter.spec.ts new file mode 100644 index 0000000000..5658a8753d --- /dev/null +++ b/browser_tests/tests/helpCenter.spec.ts @@ -0,0 +1,174 @@ +import { expect } from '@playwright/test' + +import { + createMockRelease, + helpCenterFixture as test, + waitForPopup +} from '@e2e/fixtures/helpers/HelpCenterHelper' + +test.describe('Help Center', () => { + test.describe('popup visibility', () => { + test('opens the popup and shows the backdrop when the sidebar button is clicked', async ({ + helpCenter + }) => { + await helpCenter.toggle() + await expect(helpCenter.popup).toBeVisible() + await expect(helpCenter.backdrop).toBeVisible() + }) + + test('closes when the backdrop is clicked', async ({ helpCenter }) => { + await helpCenter.open() + await helpCenter.closeViaBackdrop() + await expect(helpCenter.popup).toBeHidden() + }) + + test('closes after clicking a menu item that opens an external tab', async ({ + helpCenter + }) => { + await helpCenter.stubDocsPage() + await helpCenter.open() + + await waitForPopup(helpCenter.page, () => + helpCenter.menuItem('docs').click() + ) + + await expect(helpCenter.popup).toBeHidden() + }) + }) + + test.describe('popup positioning', () => { + test('anchors to the left when sidebar location is left', async ({ + comfyPage, + helpCenter + }) => { + await comfyPage.settings.setSetting('Comfy.Sidebar.Location', 'left') + await helpCenter.open() + await expect(helpCenter.popup).toHaveClass(/sidebar-left/) + await expect(helpCenter.popup).not.toHaveClass(/sidebar-right/) + }) + + test('anchors to the right when sidebar location is right', async ({ + comfyPage, + helpCenter + }) => { + await comfyPage.settings.setSetting('Comfy.Sidebar.Location', 'right') + await helpCenter.open() + await expect(helpCenter.popup).toHaveClass(/sidebar-right/) + await expect(helpCenter.popup).not.toHaveClass(/sidebar-left/) + }) + }) + + test.describe('menu item actions', () => { + test.beforeEach(async ({ helpCenter }) => { + await helpCenter.stubDocsPage() + await helpCenter.stubExternalPages() + await helpCenter.stubSupportPage() + await helpCenter.open() + }) + + test('Docs item opens docs.comfy.org/ in a new tab', async ({ + helpCenter + }) => { + const url = await waitForPopup(helpCenter.page, () => + helpCenter.menuItem('docs').click() + ) + + expect(url.hostname).toBe('docs.comfy.org') + expect(url.pathname).toBe('/') + }) + + test('Discord item opens comfy.org/discord in a new tab', async ({ + helpCenter + }) => { + const url = await waitForPopup(helpCenter.page, () => + helpCenter.menuItem('discord').click() + ) + + expect(url.hostname).toBe('www.comfy.org') + expect(url.pathname).toBe('/discord') + }) + + test('Github item opens the ComfyUI repo in a new tab', async ({ + helpCenter + }) => { + const url = await waitForPopup(helpCenter.page, () => + helpCenter.menuItem('github').click() + ) + + expect(url.hostname).toBe('github.com') + expect(url.pathname).toBe('/Comfy-Org/ComfyUI') + }) + + test('Help & Support item opens the Zendesk support form with OSS tag', async ({ + helpCenter + }) => { + const url = await waitForPopup(helpCenter.page, () => + helpCenter.menuItem('help').click() + ) + + expect(url.hostname).toBe('support.comfy.org') + expect(url.searchParams.get('tf_42243568391700')).toBe('oss') + }) + + test('Give Feedback item opens Contact Support in OSS mode', async ({ + helpCenter + }) => { + const url = await waitForPopup(helpCenter.page, () => + helpCenter.menuItem('feedback').click() + ) + + expect(url.hostname).toBe('support.comfy.org') + expect(url.searchParams.get('tf_42243568391700')).toBe('oss') + }) + }) + + test.describe("What's New releases", () => { + test('renders only the three most recent releases', async ({ + comfyPage, + helpCenter + }) => { + const versions = ['0.4.10', '0.4.9', '0.4.8', '0.4.7', '0.4.6'] + const now = Date.now() + const releases = versions.map((version, idx) => + createMockRelease({ + id: idx + 1, + version, + published_at: new Date(now - idx * 60_000).toISOString() + }) + ) + + await helpCenter.mockReleases(releases) + await comfyPage.setup({ mockReleases: false }) + await helpCenter.open() + + await expect(helpCenter.whatsNewSection).toBeVisible() + await expect(helpCenter.releaseItems).toHaveCount(3) + await expect(helpCenter.releaseItem('0.4.10')).toBeVisible() + await expect(helpCenter.releaseItem('0.4.9')).toBeVisible() + await expect(helpCenter.releaseItem('0.4.8')).toBeVisible() + await expect(helpCenter.releaseItem('0.4.7')).toHaveCount(0) + }) + + test('clicking a release opens the changelog with a version anchor', async ({ + comfyPage, + helpCenter + }) => { + const release = createMockRelease({ version: '0.3.50' }) + + await helpCenter.mockReleases([release]) + await helpCenter.stubDocsPage() + await comfyPage.setup({ mockReleases: false }) + await helpCenter.open() + + const url = await waitForPopup(helpCenter.page, () => + helpCenter.releaseItem('0.3.50').click() + ) + + expect(url.hostname).toBe('docs.comfy.org') + expect(url.pathname).toBe('/changelog') + expect(url.hash).toBe('#v0-3-50') + + await expect(helpCenter.popup).toBeHidden() + }) + }) +}) diff --git a/src/components/helpcenter/HelpCenterMenuContent.vue b/src/components/helpcenter/HelpCenterMenuContent.vue index c205615f4a..5773083340 100644 --- a/src/components/helpcenter/HelpCenterMenuContent.vue +++ b/src/components/helpcenter/HelpCenterMenuContent.vue @@ -14,6 +14,7 @@ type="button" class="help-menu-item" :class="{ 'more-item': menuItem.key === 'more' }" + :data-testid="`help-menu-item-${menuItem.key}`" role="menuitem" @click="menuItem.action" @mouseenter="onMenuItemHover(menuItem.key, $event)" @@ -103,6 +104,7 @@ v-for="release in releaseStore.recentReleases" :key="release.id || release.version" class="release-menu-item flex h-12 min-h-6 cursor-pointer items-center gap-2 self-stretch rounded-sm p-2 transition-colors hover:bg-interface-menu-component-surface-hovered" + :data-testid="`help-release-item-${release.version}`" role="button" tabindex="0" @click="onReleaseClick(release)" diff --git a/src/components/helpcenter/HelpCenterPopups.vue b/src/components/helpcenter/HelpCenterPopups.vue index 71ef88bccf..d7904b6a3b 100644 --- a/src/components/helpcenter/HelpCenterPopups.vue +++ b/src/components/helpcenter/HelpCenterPopups.vue @@ -4,6 +4,7 @@
diff --git a/src/components/rightSidePanel/errors/ErrorNodeCard.test.ts b/src/components/rightSidePanel/errors/ErrorNodeCard.test.ts index 6b603f6a95..d85ddbc99a 100644 --- a/src/components/rightSidePanel/errors/ErrorNodeCard.test.ts +++ b/src/components/rightSidePanel/errors/ErrorNodeCard.test.ts @@ -50,7 +50,7 @@ vi.mock('@/stores/commandStore', () => ({ vi.mock('@/composables/useExternalLink', () => ({ useExternalLink: vi.fn(() => ({ staticUrls: { - githubIssues: 'https://github.com/comfyanonymous/ComfyUI/issues' + githubIssues: 'https://github.com/Comfy-Org/ComfyUI/issues' } })) })) @@ -284,7 +284,7 @@ describe('ErrorNodeCard.vue', () => { await user.click(screen.getByRole('button', { name: /Find on GitHub/ })) expect(openSpy).toHaveBeenCalledWith( - expect.stringContaining('github.com/comfyanonymous/ComfyUI/issues?q='), + expect.stringContaining('github.com/Comfy-Org/ComfyUI/issues?q='), '_blank', 'noopener,noreferrer' ) diff --git a/src/components/rightSidePanel/errors/useErrorActions.test.ts b/src/components/rightSidePanel/errors/useErrorActions.test.ts index 2b913f6e7d..b51ae2fdf1 100644 --- a/src/components/rightSidePanel/errors/useErrorActions.test.ts +++ b/src/components/rightSidePanel/errors/useErrorActions.test.ts @@ -11,7 +11,7 @@ const mocks = vi.hoisted(() => ({ trackHelpResourceClicked: ReturnType } | null, staticUrls: { - githubIssues: 'https://github.com/comfyanonymous/ComfyUI/issues' + githubIssues: 'https://github.com/Comfy-Org/ComfyUI/issues' } })) diff --git a/src/components/sidebar/SidebarHelpCenterIcon.vue b/src/components/sidebar/SidebarHelpCenterIcon.vue index 1035f73757..486278076d 100644 --- a/src/components/sidebar/SidebarHelpCenterIcon.vue +++ b/src/components/sidebar/SidebarHelpCenterIcon.vue @@ -2,6 +2,7 @@ { // Static URLs expect(staticUrls.discord).toBe('https://www.comfy.org/discord') - expect(staticUrls.github).toBe( - 'https://github.com/comfyanonymous/ComfyUI' - ) + expect(staticUrls.github).toBe('https://github.com/Comfy-Org/ComfyUI') expect(staticUrls.githubIssues).toBe( - 'https://github.com/comfyanonymous/ComfyUI/issues' + 'https://github.com/Comfy-Org/ComfyUI/issues' ) expect(staticUrls.githubFrontend).toBe( 'https://github.com/Comfy-Org/ComfyUI_frontend' diff --git a/src/composables/useExternalLink.ts b/src/composables/useExternalLink.ts index 8caf621b64..15ac96b88c 100644 --- a/src/composables/useExternalLink.ts +++ b/src/composables/useExternalLink.ts @@ -85,8 +85,8 @@ export function useExternalLink() { const staticUrls = { // Static external URLs discord: 'https://www.comfy.org/discord', - github: 'https://github.com/comfyanonymous/ComfyUI', - githubIssues: 'https://github.com/comfyanonymous/ComfyUI/issues', + github: 'https://github.com/Comfy-Org/ComfyUI', + githubIssues: 'https://github.com/Comfy-Org/ComfyUI/issues', githubFrontend: 'https://github.com/Comfy-Org/ComfyUI_frontend', githubElectron: 'https://github.com/Comfy-Org/electron', forum: 'https://forum.comfy.org/',