mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:02:51 +08:00
chore: fix test
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
11
public/cypress-blank.html
Normal 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>
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user