chore: fix test

This commit is contained in:
Nathan
2025-12-15 10:29:40 +08:00
parent 79408fc6ef
commit 0026dee844
10 changed files with 73 additions and 80 deletions

View File

@@ -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) => {

View File

@@ -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 => {

View File

@@ -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');

View File

@@ -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');

View File

@@ -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 });

View File

@@ -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<string>('@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);
});
});

View File

@@ -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;
});
});

View File

@@ -21,6 +21,8 @@ export function byTestIdContains(fragment: string): string {
return `[data-testid*="${fragment}"]`;
}
type CypressGetOptions = Partial<Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow>;
/**
* Extracts a viewId from a sidebar page item test id (e.g. "page-<viewId>").
*/
@@ -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),
};
/**

11
public/cypress-blank.html Normal file
View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cypress Blank</title>
</head>
<body>
<!-- Intentionally blank page used by Cypress to clear storage/IndexedDB before app loads -->
</body>
</html>

View File

@@ -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 =