diff --git a/cypress/e2e/chat/chat-input.cy.ts b/cypress/e2e/chat/chat-input.cy.ts index d3d660fc..de7a66f5 100644 --- a/cypress/e2e/chat/chat-input.cy.ts +++ b/cypress/e2e/chat/chat-input.cy.ts @@ -1,6 +1,6 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { TestTool } from '../../support/page-utils'; -import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors } from '../../support/selectors'; +import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors, byTestId } from '../../support/selectors'; import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config'; describe('Chat Input Tests', () => { @@ -34,8 +34,8 @@ describe('Chat Input Tests', () => { authUtils.signInWithTestUrl(testEmail).then(() => { cy.url().should('include', '/app'); - SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 }); - PageSelectors.items().should('exist', { timeout: 30000 }); + SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible'); + PageSelectors.items({ timeout: 30000 }).should('exist'); cy.wait(2000); TestTool.expandSpace(); @@ -52,17 +52,19 @@ describe('Chat Input Tests', () => { AddPageSelectors.addAIChatButton().should('be.visible').click(); cy.wait(2000); + ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible'); // Test 1: Format toggle cy.log('Testing format toggle'); - ChatSelectors.formatGroup().then($group => { - if ($group.length > 0) { + cy.get('body').then(($body) => { + if ($body.find(byTestId('chat-format-group')).length > 0) { ChatSelectors.formatToggle().click(); ChatSelectors.formatGroup().should('not.exist'); } }); - ChatSelectors.formatToggle().should('be.visible').click(); + ChatSelectors.formatToggle({ timeout: 30000 }).should('be.visible'); + ChatSelectors.formatToggle().click(); ChatSelectors.formatGroup().should('exist'); ChatSelectors.formatGroup().find('button').should('have.length.at.least', 4); ChatSelectors.formatToggle().click(); @@ -111,8 +113,8 @@ describe('Chat Input Tests', () => { authUtils.signInWithTestUrl(testEmail).then(() => { cy.url().should('include', '/app'); - SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 }); - PageSelectors.items().should('exist', { timeout: 30000 }); + SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible'); + PageSelectors.items({ timeout: 30000 }).should('exist'); cy.wait(2000); TestTool.expandSpace(); @@ -129,6 +131,7 @@ describe('Chat Input Tests', () => { AddPageSelectors.addAIChatButton().should('be.visible').click(); cy.wait(3000); // Wait for chat to fully load + ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible'); // Mock API endpoints with more realistic responses cy.intercept('POST', '**/api/chat/**/message/question', (req) => { diff --git a/cypress/e2e/chat/create-ai-chat.cy.ts b/cypress/e2e/chat/create-ai-chat.cy.ts index a366f85d..35d355fa 100644 --- a/cypress/e2e/chat/create-ai-chat.cy.ts +++ b/cypress/e2e/chat/create-ai-chat.cy.ts @@ -53,18 +53,18 @@ describe('AI Chat Creation and Navigation Tests', () => { cy.get('body', { timeout: 30000 }).should('not.contain', 'Welcome!'); // Wait for the sidebar to be visible (indicates app is loaded) - SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 }); + SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible'); // Wait for at least one page to exist in the sidebar - PageSelectors.names().should('exist', { timeout: 30000 }); + PageSelectors.names({ timeout: 30000 }).should('exist'); // Additional wait for stability cy.wait(2000); // Now wait for the new page button to be available testLog.info( 'Looking for new page button...'); - PageSelectors.newPageButton() - .should('exist', { timeout: 20000 }) + PageSelectors.newPageButton({ timeout: 20000 }) + .should('exist') .then(() => { testLog.info( 'New page button found!'); }); @@ -121,33 +121,12 @@ describe('AI Chat Creation and Navigation Tests', () => { testLog.info( '=== Step 4: Verifying AI Chat page loaded ==='); // Check that the URL contains a view ID (indicating navigation to chat) - cy.url().should('match', /\/app\/[a-f0-9-]+\/[a-f0-9-]+/, { timeout: 10000 }); + cy.url({ timeout: 20000 }).should('match', /\/app\/[^/]+\/[^/?#]+/); testLog.info( '✓ Navigated to AI Chat page'); - // Check if the AI Chat container exists (but don't fail if it doesn't load immediately) - ChatSelectors.aiChatContainer().then($container => { - if ($container.length > 0) { - testLog.info( '✓ AI Chat container exists'); - } else { - testLog.info( 'AI Chat container not immediately visible, checking for navigation success...'); - } - }); - - // Wait a bit for the chat to fully load - cy.wait(2000); - - // Check for AI Chat specific elements (the chat interface) - // The AI chat library loads its own components - cy.get('body').then($body => { - ChatSelectors.aiChatContainer().then($container => { - const hasChatElements = $body.find('.ai-chat').length > 0 || $container.length > 0; - if (hasChatElements) { - testLog.info( '✓ AI Chat interface loaded'); - } else { - testLog.info( 'Warning: AI Chat elements not immediately visible, but container exists'); - } - }); - }); + // Verify AI Chat container renders (chat UI may load async after container is mounted) + ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible'); + testLog.info( '✓ AI Chat container exists'); // Verify no error messages are displayed cy.get('body').then($body => { diff --git a/cypress/e2e/database/database-container-add-linked-views.cy.ts b/cypress/e2e/database/database-container-add-linked-views.cy.ts index 108279ac..31bf8e1d 100644 --- a/cypress/e2e/database/database-container-add-linked-views.cy.ts +++ b/cypress/e2e/database/database-container-add-linked-views.cy.ts @@ -98,12 +98,12 @@ describe('Database Container - Add Linked Views via Tab Bar', () => { DatabaseGridSelectors.cells().should('have.length.greaterThan', 0); // Scenario 4 parity: tab bar "+" adds linked views to the same container - testLog.step(2, 'Verify initial tabs (Grid only)'); + testLog.step(2, 'Verify initial tabs (single tab)'); DatabaseViewSelectors.viewTab() .should('have.length', 1) .first() .should('have.attr', 'data-state', 'active') - .and('contain.text', 'Grid'); + .and('contain.text', dbName); testLog.step(3, 'Add Board view via tab bar "+"'); addViewViaPlus('Board'); @@ -113,15 +113,15 @@ describe('Database Container - Add Linked Views via Tab Bar', () => { addViewViaPlus('Calendar'); DatabaseViewSelectors.viewTab().should('have.length', 3); - testLog.step(5, 'Verify sidebar container children show Grid/Board/Calendar'); + testLog.step(5, 'Verify sidebar container children updated'); closeModalsIfOpen(); ensureSpaceExpanded(spaceName); ensurePageExpanded(dbName); PageSelectors.itemByName(dbName).within(() => { - cy.get('[data-testid="page-name"]').contains('Grid').should('be.visible'); - cy.get('[data-testid="page-name"]').contains('Board').should('be.visible'); - cy.get('[data-testid="page-name"]').contains('Calendar').should('be.visible'); + PageSelectors.nameContaining('Board').should('be.visible'); + PageSelectors.nameContaining('Calendar').should('be.visible'); + PageSelectors.items().should('have.length', 3); }); testLog.testEnd('Database container add linked views'); diff --git a/cypress/e2e/database/database-container-open.cy.ts b/cypress/e2e/database/database-container-open.cy.ts index 123bdb90..49172cd4 100644 --- a/cypress/e2e/database/database-container-open.cy.ts +++ b/cypress/e2e/database/database-container-open.cy.ts @@ -64,12 +64,12 @@ describe('Database Container Open Behavior', () => { DatabaseGridSelectors.grid().should('exist'); DatabaseGridSelectors.cells().should('have.length.greaterThan', 0); - // Scenario 1 parity: a newly created container has exactly 1 child view (Grid) + // Scenario 1 parity: a newly created container has exactly 1 child view DatabaseViewSelectors.viewTab() .should('have.length', 1) .first() .should('have.attr', 'data-state', 'active') - .and('contain.text', 'Grid'); + .and('contain.text', dbName); // Ensure sidebar is visible and space expanded SpaceSelectors.itemByName(spaceName).should('exist'); diff --git a/cypress/e2e/database/database-container-tab-operations.cy.ts b/cypress/e2e/database/database-container-tab-operations.cy.ts index 2a32544b..a0164bbb 100644 --- a/cypress/e2e/database/database-container-tab-operations.cy.ts +++ b/cypress/e2e/database/database-container-tab-operations.cy.ts @@ -91,9 +91,9 @@ describe('Database Container - Tab Operations', () => { DatabaseGridSelectors.grid().should('exist'); DatabaseGridSelectors.cells().should('have.length.greaterThan', 0); - // 2) Rename the first view (Grid -> A) - testLog.step(2, 'Rename Grid tab to A'); - openTabMenuByLabel('Grid'); + // 2) Rename the first view (New Database -> A) + testLog.step(2, 'Rename first tab to A'); + openTabMenuByLabel(dbName); DatabaseViewSelectors.tabActionRename().should('be.visible').click({ force: true }); ModalSelectors.renameInput().should('be.visible').clear().type('A'); ModalSelectors.renameSaveButton().click({ force: true }); diff --git a/cypress/e2e/embeded/database/database-container-embedded-create-delete.cy.ts b/cypress/e2e/embeded/database/database-container-embedded-create-delete.cy.ts index b1624b1c..9d3ca810 100644 --- a/cypress/e2e/embeded/database/database-container-embedded-create-delete.cy.ts +++ b/cypress/e2e/embeded/database/database-container-embedded-create-delete.cy.ts @@ -156,8 +156,8 @@ describe('Database Container - Embedded Create/Delete', () => { cy.get(`#editor-${docViewId}`).find(BlockSelectors.blockSelector('grid')).should('exist'); }); - // 3) Verify sidebar: document has a child database container, which has a Grid child view - testLog.step(3, 'Verify sidebar hierarchy: document -> container -> Grid view'); + // 3) Verify sidebar: document has a child database container with a child view + testLog.step(3, 'Verify sidebar hierarchy: document -> container -> child view'); ensureSpaceExpanded(spaceName); cy.get('@docViewId').then((docViewId) => { @@ -177,7 +177,7 @@ describe('Database Container - Embedded Create/Delete', () => { containerPageItem().should('exist'); - // Expand the container to reveal its first child view (Grid) + // Expand the container to reveal its first child view containerPageItem() .find('[data-testid="outline-toggle-collapse"]') .then(($collapse) => { @@ -194,7 +194,7 @@ describe('Database Container - Embedded Create/Delete', () => { containerPageItem().within(() => { // When the current page is open in a modal, the sidebar can be covered by the dialog backdrop. // We only need to assert the hierarchy exists. - cy.get('[data-testid="page-name"]').contains('Grid').should('exist'); + PageSelectors.items().should('have.length.at.least', 1); }); }); @@ -265,7 +265,7 @@ describe('Database Container - Embedded Create/Delete', () => { .first() .closest('[data-testid="page-item"]') .within(() => { - cy.get('[data-testid="page-name"]').contains(dbName).should('not.exist'); + PageSelectors.names().should('not.contain.text', dbName); }); }); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index d93d2e46..3b9c1c97 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -21,13 +21,18 @@ import './commands'; // Global hooks for console logging beforeEach(() => { - // Clear stale localStorage values that could cause issues between tests - // This prevents the app from trying to navigate to a non-existent view from previous tests - cy.clearLocalStorage('last_view_id'); + // Visit a blank page on the app origin so we can clear storage/IndexedDB + // before the app bundle initializes and opens its caches. + cy.visit('/cypress-blank.html', { + failOnStatusCode: false, + onBeforeLoad: (win) => { + // Clear all localStorage to remove stale view IDs from previous tests + win.localStorage.clear(); + win.sessionStorage.clear(); + }, + }); // Clear all IndexedDB databases to remove stale document caches - // Visit about:blank first to get a window context for IndexedDB operations - cy.visit('about:blank'); cy.clearAllIndexedDB(); // Start capturing console logs for each test @@ -69,4 +74,4 @@ Cypress.on('uncaught:exception', (err) => { return false; } return true; -}); \ No newline at end of file +}); diff --git a/cypress/support/selectors.ts b/cypress/support/selectors.ts index 309f37d5..cd769e83 100644 --- a/cypress/support/selectors.ts +++ b/cypress/support/selectors.ts @@ -21,6 +21,8 @@ export function byTestIdContains(fragment: string): string { return `[data-testid*="${fragment}"]`; } +type CypressGetOptions = Partial; + /** * Extracts a viewId from a sidebar page item test id (e.g. "page-"). */ @@ -37,13 +39,13 @@ export function viewIdFromPageTestId(testId: string | null | undefined): string */ export const PageSelectors = { // Get all page items - items: () => cy.get(byTestId('page-item')), + items: (options?: CypressGetOptions) => cy.get(byTestId('page-item'), options), // Get all page names - names: () => cy.get(byTestId('page-name')), + names: (options?: CypressGetOptions) => cy.get(byTestId('page-name'), options), // Get page name containing specific text - nameContaining: (text: string) => cy.get(byTestId('page-name')).contains(text), + nameContaining: (text: string, options?: CypressGetOptions) => cy.get(byTestId('page-name'), options).contains(text), // Get page item containing specific page name itemByName: (pageName: string) => { @@ -69,10 +71,10 @@ export const PageSelectors = { }, // Get new page button - newPageButton: () => cy.get(byTestId('new-page-button')), + newPageButton: (options?: CypressGetOptions) => cy.get(byTestId('new-page-button'), options), // Get page title input - titleInput: () => cy.get(byTestId('page-title-input')), + titleInput: (options?: CypressGetOptions) => cy.get(byTestId('page-title-input'), options), }; /** @@ -290,7 +292,7 @@ export const WorkspaceSelectors = { */ export const SidebarSelectors = { // Sidebar page header - pageHeader: () => cy.get(byTestId('sidebar-page-header')), + pageHeader: (options?: CypressGetOptions) => cy.get(byTestId('sidebar-page-header'), options), }; /** @@ -330,13 +332,13 @@ export const ModelSelectorSelectors = { * Chat UI selectors */ export const ChatSelectors = { - aiChatContainer: () => cy.get(byTestId('ai-chat-container')), - formatToggle: () => cy.get(byTestId('chat-input-format-toggle')), - formatGroup: () => cy.get(byTestId('chat-format-group')), - browsePromptsButton: () => cy.get(byTestId('chat-input-browse-prompts')), - relatedViewsButton: () => cy.get(byTestId('chat-input-related-views')), - relatedViewsPopover: () => cy.get(byTestId('chat-related-views-popover')), - sendButton: () => cy.get(byTestId('chat-input-send')), + aiChatContainer: (options?: CypressGetOptions) => cy.get(byTestId('ai-chat-container'), options), + formatToggle: (options?: CypressGetOptions) => cy.get(byTestId('chat-input-format-toggle'), options), + formatGroup: (options?: CypressGetOptions) => cy.get(byTestId('chat-format-group'), options), + browsePromptsButton: (options?: CypressGetOptions) => cy.get(byTestId('chat-input-browse-prompts'), options), + relatedViewsButton: (options?: CypressGetOptions) => cy.get(byTestId('chat-input-related-views'), options), + relatedViewsPopover: (options?: CypressGetOptions) => cy.get(byTestId('chat-related-views-popover'), options), + sendButton: (options?: CypressGetOptions) => cy.get(byTestId('chat-input-send'), options), }; /** diff --git a/public/cypress-blank.html b/public/cypress-blank.html new file mode 100644 index 00000000..dcc9fc45 --- /dev/null +++ b/public/cypress-blank.html @@ -0,0 +1,11 @@ + + + + + + Cypress Blank + + + + + diff --git a/src/components/database/components/tabs/DatabaseTabItem.tsx b/src/components/database/components/tabs/DatabaseTabItem.tsx index e0e4cc8d..0c7d6ce0 100644 --- a/src/components/database/components/tabs/DatabaseTabItem.tsx +++ b/src/components/database/components/tabs/DatabaseTabItem.tsx @@ -32,7 +32,6 @@ export const DatabaseTabItem = memo( viewId, view, databasePageId, - databaseName, menuViewId, readOnly, visibleViewIds, @@ -62,13 +61,7 @@ export const DatabaseTabItem = memo( // Get name from YDatabaseView (real-time, always correct) const rawName = view.get(YjsDatabaseKey.name); const defaultName = getDefaultNameByLayout(); - const shouldUseDefaultName = - rawName && - databaseName && - visibleViewIds.length === 1 && - viewId !== databasePageId && - rawName.trim() === databaseName.trim(); - const name = shouldUseDefaultName ? defaultName : rawName || defaultName; + const name = rawName?.trim() || defaultName; // Compute the layout for PageIcon (icon is based on layout type) const computedLayout =