diff --git a/cypress/e2e/account/avatar/avatar-api.cy.ts b/cypress/e2e/account/avatar/avatar-api.cy.ts index 96f68e20..87f5c528 100644 --- a/cypress/e2e/account/avatar/avatar-api.cy.ts +++ b/cypress/e2e/account/avatar/avatar-api.cy.ts @@ -1,5 +1,5 @@ import { avatarTestUtils } from './avatar-test-utils'; -import { byTestId } from '../../../support/selectors'; +import { AccountSelectors, AvatarUiSelectors } from '../../../support/selectors'; import { testLog } from '../../../support/test-helpers'; const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils; @@ -39,7 +39,7 @@ describe('Avatar API', () => { testLog.info( 'Step 5: Open Account Settings to verify avatar'); WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); testLog.info( 'Step 6: Verify avatar image is displayed in Account Settings'); @@ -50,7 +50,7 @@ describe('Avatar API', () => { // Wait for any avatar image to be present and loaded // The AvatarImage component loads asynchronously and sets opacity to 0 while loading - cy.get(byTestId('avatar-image'), { timeout: 10000 }) + AvatarUiSelectors.image() .should('exist') .should(($imgs) => { // Find the first visible avatar image (opacity not 0) @@ -68,7 +68,7 @@ describe('Avatar API', () => { }); // Verify that the avatar image has loaded (check for non-empty src and visible state) - cy.get(byTestId('avatar-image')).then(($imgs) => { + AvatarUiSelectors.image().then(($imgs) => { let foundLoaded = false; $imgs.each((index, img) => { const $img = Cypress.$(img); @@ -160,7 +160,7 @@ describe('Avatar API', () => { testLog.info( 'Step 5: Open Account Settings'); WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); testLog.info( 'Step 6: Verify emoji is displayed in fallback'); diff --git a/cypress/e2e/account/avatar/avatar-header.cy.ts b/cypress/e2e/account/avatar/avatar-header.cy.ts index 246bbcd6..484b0eda 100644 --- a/cypress/e2e/account/avatar/avatar-header.cy.ts +++ b/cypress/e2e/account/avatar/avatar-header.cy.ts @@ -1,5 +1,5 @@ import { avatarTestUtils } from './avatar-test-utils'; -import { byTestIdContains } from '../../../support/selectors'; +import { PageSelectors } from '../../../support/selectors'; import { testLog } from '../../../support/test-helpers'; const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils; @@ -39,12 +39,15 @@ describe('Avatar Header Display', () => { testLog.info( 'Step 4: Interact with editor to trigger collaborative user awareness'); // Click on a page to open editor - cy.get('body').then(($body) => { - // Try to find and click on a page in the sidebar - if ($body.find(byTestIdContains('page')).length > 0) { - cy.get(byTestIdContains('page')).first().click(); - } else if ($body.text().includes('Getting started')) { - cy.contains('Getting started').click(); + PageSelectors.items().then($pages => { + if ($pages.length > 0) { + cy.wrap($pages.first()).click(); + } else { + cy.get('body').then($body => { + if ($body.text().includes('Getting started')) { + cy.contains('Getting started').click(); + } + }); } }); @@ -118,11 +121,15 @@ describe('Avatar Header Display', () => { testLog.info( 'Step 4: Interact with editor to trigger collaborative user awareness'); // Click on a page to open editor - cy.get('body').then(($body) => { - if ($body.find(byTestIdContains('page')).length > 0) { - cy.get(byTestIdContains('page')).first().click(); - } else if ($body.text().includes('Getting started')) { - cy.contains('Getting started').click(); + PageSelectors.items().then($pages => { + if ($pages.length > 0) { + cy.wrap($pages.first()).click(); + } else { + cy.get('body').then($body => { + if ($body.text().includes('Getting started')) { + cy.contains('Getting started').click(); + } + }); } }); @@ -214,11 +221,15 @@ describe('Avatar Header Display', () => { testLog.info( 'Step 6: Interact with editor to trigger collaborative user awareness'); // Click on a page to open editor - cy.get('body').then(($body) => { - if ($body.find(byTestIdContains('page')).length > 0) { - cy.get(byTestIdContains('page')).first().click(); - } else if ($body.text().includes('Getting started')) { - cy.contains('Getting started').click(); + PageSelectors.items().then($pages => { + if ($pages.length > 0) { + cy.wrap($pages.first()).click(); + } else { + cy.get('body').then($body => { + if ($body.text().includes('Getting started')) { + cy.contains('Getting started').click(); + } + }); } }); diff --git a/cypress/e2e/account/avatar/avatar-notifications.cy.ts b/cypress/e2e/account/avatar/avatar-notifications.cy.ts index 9a9ee577..c98f3006 100644 --- a/cypress/e2e/account/avatar/avatar-notifications.cy.ts +++ b/cypress/e2e/account/avatar/avatar-notifications.cy.ts @@ -1,5 +1,5 @@ import { avatarTestUtils } from './avatar-test-utils'; -import { byTestId } from '../../../support/selectors'; +import { AccountSelectors } from '../../../support/selectors'; import { testLog } from '../../../support/test-helpers'; const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils; @@ -63,7 +63,7 @@ describe('Avatar Notifications', () => { testLog.info( 'Step 7: Open Account Settings to verify avatar'); WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); testLog.info( 'Step 8: Verify avatar image uses updated URL'); diff --git a/cypress/e2e/account/avatar/avatar-persistence.cy.ts b/cypress/e2e/account/avatar/avatar-persistence.cy.ts index a0f5090d..d446d1b9 100644 --- a/cypress/e2e/account/avatar/avatar-persistence.cy.ts +++ b/cypress/e2e/account/avatar/avatar-persistence.cy.ts @@ -1,5 +1,5 @@ import { avatarTestUtils } from './avatar-test-utils'; -import { byTestId } from '../../../support/selectors'; +import { AccountSelectors } from '../../../support/selectors'; import { testLog } from '../../../support/test-helpers'; const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils; @@ -41,7 +41,7 @@ describe('Avatar Persistence', () => { testLog.info( 'Step 5: Verify avatar persisted'); WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); AvatarSelectors.avatarImage().should('exist').and('have.attr', 'src', testAvatarUrl); @@ -52,7 +52,7 @@ describe('Avatar Persistence', () => { WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); AvatarSelectors.avatarImage().should('exist').and('have.attr', 'src', testAvatarUrl); diff --git a/cypress/e2e/account/avatar/avatar-priority.cy.ts b/cypress/e2e/account/avatar/avatar-priority.cy.ts index 2b0f264d..8da93f03 100644 --- a/cypress/e2e/account/avatar/avatar-priority.cy.ts +++ b/cypress/e2e/account/avatar/avatar-priority.cy.ts @@ -1,5 +1,5 @@ import { avatarTestUtils } from './avatar-test-utils'; -import { byTestId } from '../../../support/selectors'; +import { AccountSelectors } from '../../../support/selectors'; import { testLog } from '../../../support/test-helpers'; const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils; @@ -47,7 +47,7 @@ describe('Avatar Priority', () => { testLog.info( 'Step 5: Verify workspace avatar is displayed (priority)'); WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); // Workspace avatar should be displayed, not user metadata avatar diff --git a/cypress/e2e/account/avatar/avatar-types.cy.ts b/cypress/e2e/account/avatar/avatar-types.cy.ts index 299ce6ee..47eace8b 100644 --- a/cypress/e2e/account/avatar/avatar-types.cy.ts +++ b/cypress/e2e/account/avatar/avatar-types.cy.ts @@ -1,5 +1,5 @@ import { avatarTestUtils } from './avatar-test-utils'; -import { byTestId } from '../../../support/selectors'; +import { AccountSelectors } from '../../../support/selectors'; import { testLog } from '../../../support/test-helpers'; const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils; @@ -38,7 +38,7 @@ describe('Avatar Types', () => { WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); AvatarSelectors.avatarImage().should('exist').and('have.attr', 'src', httpsAvatar); @@ -75,7 +75,7 @@ describe('Avatar Types', () => { WorkspaceSelectors.dropdownTrigger().click(); cy.wait(1000); - cy.get(byTestId('account-settings-button')).click(); + AccountSelectors.settingsButton().click(); AvatarSelectors.accountSettingsDialog().should('be.visible'); // Emoji should be displayed in fallback, not as image diff --git a/cypress/e2e/account/update-user-profile.cy.ts b/cypress/e2e/account/update-user-profile.cy.ts index c96cb49c..5d1953b1 100644 --- a/cypress/e2e/account/update-user-profile.cy.ts +++ b/cypress/e2e/account/update-user-profile.cy.ts @@ -1,5 +1,5 @@ import { AuthTestUtils } from '../../support/auth-utils'; -import { WorkspaceSelectors, byTestId } from '../../support/selectors'; +import { WorkspaceSelectors, AccountSelectors } from '../../support/selectors'; import { generateRandomEmail } from '../../support/test-config'; describe('Update User Profile', () => { @@ -40,61 +40,61 @@ describe('Update User Profile', () => { // Click on Account Settings cy.log('Step 4: Opening Account Settings'); - cy.get(byTestId('account-settings-button')).should('be.visible').click(); + AccountSelectors.settingsButton().should('be.visible').click(); // Add a wait to ensure the dialog has time to open cy.wait(1000); // Wait for Account Settings dialog to open cy.log('Step 5: Verifying Account Settings dialog opened'); - cy.get(byTestId('account-settings-dialog'), { timeout: 10000 }).should('be.visible'); + AccountSelectors.settingsDialog().should('be.visible'); // Check initial date format (should be Month/Day/Year) cy.log('Step 6: Checking initial date format'); - cy.get(byTestId('date-format-dropdown')).should('be.visible'); + AccountSelectors.dateFormatDropdown().should('be.visible'); // Test Date Format change - select Year/Month/Day cy.log('Step 7: Testing Date Format change to Year/Month/Day'); - cy.get(byTestId('date-format-dropdown')).click(); + AccountSelectors.dateFormatDropdown().click(); cy.wait(500); // Select US format (value 1) which is Year/Month/Day - cy.get(byTestId('date-format-1')).should('be.visible').click(); + AccountSelectors.dateFormatOptionYearMonthDay().should('be.visible').click(); cy.wait(3000); // Wait for API call to complete // Verify the dropdown now shows Year/Month/Day - cy.get(byTestId('date-format-dropdown')).should('contain.text', 'Year/Month/Day'); + AccountSelectors.dateFormatDropdown().should('contain.text', 'Year/Month/Day'); // Test Time Format change cy.log('Step 8: Testing Time Format change'); - cy.get(byTestId('time-format-dropdown')).should('be.visible').click(); + AccountSelectors.timeFormatDropdown().should('be.visible').click(); cy.wait(500); // Select 24-hour format (value 1) - cy.get(byTestId('time-format-1')).should('be.visible').click(); + AccountSelectors.timeFormatOption24().should('be.visible').click(); cy.wait(3000); // Wait for API call to complete // Verify the dropdown now shows 24-hour format - cy.get(byTestId('time-format-dropdown')).should('contain.text', '24'); + AccountSelectors.timeFormatDropdown().should('contain.text', '24'); // Test Start Week On change cy.log('Step 9: Testing Start Week On change'); - cy.get(byTestId('start-week-on-dropdown')).should('be.visible').click(); + AccountSelectors.startWeekDropdown().should('be.visible').click(); cy.wait(500); // Select Monday (value 1) - cy.get(byTestId('start-week-1')).should('be.visible').click(); + AccountSelectors.startWeekMonday().should('be.visible').click(); cy.wait(3000); // Wait for API call to complete - cy.get(byTestId('start-week-on-dropdown')).should('contain.text', 'Monday'); + AccountSelectors.startWeekDropdown().should('contain.text', 'Monday'); // The settings should remain selected in the current session cy.log('Step 10: Verifying all settings are showing correctly'); // Verify all dropdowns still show the selected values - cy.get(byTestId('date-format-dropdown')).should('contain.text', 'Year/Month/Day'); - cy.get(byTestId('time-format-dropdown')).should('contain.text', '24'); - cy.get(byTestId('start-week-on-dropdown')).should('contain.text', 'Monday'); + AccountSelectors.dateFormatDropdown().should('contain.text', 'Year/Month/Day'); + AccountSelectors.timeFormatDropdown().should('contain.text', '24'); + AccountSelectors.startWeekDropdown().should('contain.text', 'Monday'); cy.log('Test completed: User profile settings updated successfully'); }); diff --git a/cypress/e2e/chat/chat-input.cy.ts b/cypress/e2e/chat/chat-input.cy.ts index 3fa5a89b..d3d660fc 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, byTestId } from '../../support/selectors'; +import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors } from '../../support/selectors'; import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config'; describe('Chat Input Tests', () => { @@ -55,18 +55,18 @@ describe('Chat Input Tests', () => { // Test 1: Format toggle cy.log('Testing format toggle'); - cy.get('body').then($body => { - if ($body.find(byTestId('chat-format-group')).length > 0) { - cy.get(byTestId('chat-input-format-toggle')).click(); - cy.get(byTestId('chat-format-group')).should('not.exist'); + ChatSelectors.formatGroup().then($group => { + if ($group.length > 0) { + ChatSelectors.formatToggle().click(); + ChatSelectors.formatGroup().should('not.exist'); } }); - cy.get(byTestId('chat-input-format-toggle')).should('be.visible').click(); - cy.get(byTestId('chat-format-group')).should('exist'); - cy.get(byTestId('chat-format-group')).find('button').should('have.length.at.least', 4); - cy.get(byTestId('chat-input-format-toggle')).click(); - cy.get(byTestId('chat-format-group')).should('not.exist'); + ChatSelectors.formatToggle().should('be.visible').click(); + ChatSelectors.formatGroup().should('exist'); + ChatSelectors.formatGroup().find('button').should('have.length.at.least', 4); + ChatSelectors.formatToggle().click(); + ChatSelectors.formatGroup().should('not.exist'); // Test 2: Model selector cy.log('Testing model selector'); @@ -76,7 +76,7 @@ describe('Chat Input Tests', () => { // Test 3: Browse prompts cy.log('Testing browse prompts'); - cy.get(byTestId('chat-input-browse-prompts')).click(); + ChatSelectors.browsePromptsButton().click(); cy.get('[role="dialog"]').should('exist'); cy.get('[role="dialog"]').contains('Browse prompts').should('be.visible'); cy.get('body').type('{esc}'); @@ -84,10 +84,10 @@ describe('Chat Input Tests', () => { // Test 4: Related views cy.log('Testing related views'); - cy.get(byTestId('chat-input-related-views')).click(); - cy.get(byTestId('chat-related-views-popover')).should('be.visible'); + ChatSelectors.relatedViewsButton().click(); + ChatSelectors.relatedViewsPopover().should('be.visible'); cy.get('body').type('{esc}'); - cy.get(byTestId('chat-related-views-popover')).should('not.exist'); + ChatSelectors.relatedViewsPopover().should('not.exist'); }); }); @@ -204,8 +204,8 @@ describe('Chat Input Tests', () => { cy.wait(500); // Check send button is disabled when empty - cy.get(byTestId('chat-input-send')).should('exist'); - cy.get(byTestId('chat-input-send')).then($button => { + ChatSelectors.sendButton().should('exist'); + ChatSelectors.sendButton().then($button => { // Button might be disabled via attribute or opacity const isDisabled = $button.prop('disabled') || $button.css('opacity') === '0.5'; expect(isDisabled).to.be.true; @@ -215,7 +215,7 @@ describe('Chat Input Tests', () => { getTextarea().type('Test message'); cy.wait(500); - cy.get(byTestId('chat-input-send')).then($button => { + ChatSelectors.sendButton().then($button => { const isDisabled = $button.prop('disabled') || $button.css('opacity') === '0.5'; expect(isDisabled).to.be.false; }); @@ -225,7 +225,7 @@ describe('Chat Input Tests', () => { getTextarea().clear().type('Hello world'); cy.wait(500); - cy.get(byTestId('chat-input-send')).click(); + ChatSelectors.sendButton().click(); cy.wait('@submitQuestion', { timeout: 10000 }); // Wait for textarea to be ready again diff --git a/cypress/e2e/chat/create-ai-chat.cy.ts b/cypress/e2e/chat/create-ai-chat.cy.ts index e7726897..a366f85d 100644 --- a/cypress/e2e/chat/create-ai-chat.cy.ts +++ b/cypress/e2e/chat/create-ai-chat.cy.ts @@ -1,6 +1,6 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { TestTool } from '../../support/page-utils'; -import { AddPageSelectors, PageSelectors, ModalSelectors, SidebarSelectors, byTestId, waitForReactUpdate } from '../../support/selectors'; +import { AddPageSelectors, PageSelectors, ModalSelectors, SidebarSelectors, ChatSelectors, waitForReactUpdate } from '../../support/selectors'; import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config'; import { testLog } from '../../support/test-helpers'; @@ -125,8 +125,8 @@ describe('AI Chat Creation and Navigation Tests', () => { testLog.info( '✓ Navigated to AI Chat page'); // Check if the AI Chat container exists (but don't fail if it doesn't load immediately) - cy.get('body').then($body => { - if ($body.find(byTestId('ai-chat-container')).length > 0) { + 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...'); @@ -139,15 +139,14 @@ describe('AI Chat Creation and Navigation Tests', () => { // Check for AI Chat specific elements (the chat interface) // The AI chat library loads its own components cy.get('body').then($body => { - // Check if chat interface elements exist - const hasChatElements = $body.find('.ai-chat').length > 0 || - $body.find(byTestId('ai-chat-container')).length > 0; - - if (hasChatElements) { - testLog.info( '✓ AI Chat interface loaded'); - } else { - testLog.info( 'Warning: AI Chat elements not immediately visible, but container exists'); - } + 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 no error messages are displayed diff --git a/cypress/e2e/database/checkbox-column.cy.ts b/cypress/e2e/database/checkbox-column.cy.ts index 41a8bbc6..5f3aaade 100644 --- a/cypress/e2e/database/checkbox-column.cy.ts +++ b/cypress/e2e/database/checkbox-column.cy.ts @@ -2,8 +2,6 @@ import { AddPageSelectors, DatabaseGridSelectors, CheckboxSelectors, - byTestId, - byTestIdPrefix, waitForReactUpdate } from '../../support/selectors'; import { AuthTestUtils } from '../../support/auth-utils'; @@ -59,18 +57,18 @@ describe('Checkbox Column Type', () => { cy.log('[STEP 9] Looking for checkbox elements'); cy.get('body').then($body => { // Check for checkbox cells with our data-testid - const checkboxCells = $body.find(byTestIdPrefix('checkbox-cell-')); - if (checkboxCells.length > 0) { - cy.log(`[STEP 10] Found ${checkboxCells.length} checkbox cells`); + CheckboxSelectors.allCheckboxCells().then($checkboxCells => { + if ($checkboxCells.length > 0) { + cy.log(`[STEP 10] Found ${$checkboxCells.length} checkbox cells`); - // Click first checkbox cell - CheckboxSelectors.allCheckboxCells().first().click(); - waitForReactUpdate(500); - cy.log('[STEP 11] Clicked checkbox cell'); - } else { - cy.log('[STEP 10] No checkbox cells found, cell interaction test completed'); - } - }); + // Click first checkbox cell + CheckboxSelectors.allCheckboxCells().first().click(); + waitForReactUpdate(500); + cy.log('[STEP 11] Clicked checkbox cell'); + } else { + cy.log('[STEP 10] No checkbox cells found, cell interaction test completed'); + } + }); cy.log('[STEP 12] Test completed successfully'); }); diff --git a/cypress/e2e/database/datetime-column.cy.ts b/cypress/e2e/database/datetime-column.cy.ts index cae2967b..b0ec0d8c 100644 --- a/cypress/e2e/database/datetime-column.cy.ts +++ b/cypress/e2e/database/datetime-column.cy.ts @@ -5,8 +5,6 @@ import { PropertyMenuSelectors, GridFieldSelectors, FieldType, - byTestId, - byTestIdPrefix, waitForReactUpdate } from '../../support/selectors'; import { AuthTestUtils } from '../../support/auth-utils'; @@ -66,28 +64,23 @@ describe('DateTime Column Type', () => { // The new column is created and the property menu should be open automatically // Let's wait for property trigger to be available cy.log('[STEP 8] Waiting for property menu to open'); - cy.get('body').then($body => { - // Check if property type trigger exists - if ($body.find(byTestId('property-type-trigger')).length > 0) { + PropertyMenuSelectors.propertyTypeTrigger().then($trigger => { + if ($trigger.length > 0) { cy.log('[STEP 9] Property type trigger found, changing to DateTime'); - PropertyMenuSelectors.propertyTypeTrigger().first().click({ force: true }); + cy.wrap($trigger.first()).click({ force: true }); waitForReactUpdate(1000); - // Select DateTime option cy.log('[STEP 10] Selecting DateTime option'); PropertyMenuSelectors.propertyTypeOption(FieldType.DateTime).click({ force: true }); waitForReactUpdate(2000); } else { cy.log('[STEP 9] Property type trigger not found, looking for field header'); - // Try clicking on the new field header first GridFieldSelectors.allFieldHeaders().last().scrollIntoView().click({ force: true }); waitForReactUpdate(1000); - // Now try to find the property type trigger PropertyMenuSelectors.propertyTypeTrigger().first().click({ force: true }); waitForReactUpdate(1000); - // Select DateTime option cy.log('[STEP 10] Selecting DateTime option'); PropertyMenuSelectors.propertyTypeOption(FieldType.DateTime).click({ force: true }); waitForReactUpdate(2000); @@ -101,21 +94,17 @@ describe('DateTime Column Type', () => { // Verify datetime cells exist cy.log('[STEP 12] Checking for datetime cells'); - cy.get('body').then($body => { - const datetimeCells = $body.find(byTestIdPrefix('datetime-cell-')); - if (datetimeCells.length > 0) { - cy.log(`[STEP 13] Found ${datetimeCells.length} datetime cells`); + DateTimeSelectors.allDateTimeCells().then($cells => { + if ($cells.length > 0) { + cy.log(`[STEP 13] Found ${$cells.length} datetime cells`); - // Try to interact with the first datetime cell DateTimeSelectors.allDateTimeCells().first().scrollIntoView().click({ force: true }); waitForReactUpdate(1000); - // Check if picker opens - cy.get('body').then($body => { - if ($body.find(byTestId('datetime-picker-popover')).length > 0) { + DateTimeSelectors.dateTimePickerPopover().then($popover => { + if ($popover.length > 0) { cy.log('[STEP 14] DateTime picker opened successfully'); - // Enter a date const today = new Date(); const dateStr = `${(today.getMonth() + 1).toString().padStart(2, '0')}/${today.getDate().toString().padStart(2, '0')}/${today.getFullYear()}`; diff --git a/cypress/e2e/database/single-select-column.cy.ts b/cypress/e2e/database/single-select-column.cy.ts index abde8a51..ea3b128c 100644 --- a/cypress/e2e/database/single-select-column.cy.ts +++ b/cypress/e2e/database/single-select-column.cy.ts @@ -6,8 +6,6 @@ import { SingleSelectSelectors, PageSelectors, FieldType, - byTestId, - byTestIdPrefix, waitForReactUpdate } from '../../support/selectors'; import { AuthTestUtils } from '../../support/auth-utils'; @@ -54,29 +52,24 @@ describe('Single Select Column Type', () => { cy.log('[STEP 4.1] Waiting for inline add button or new page button'); // Try to find either inline add button or new page button - cy.get('body').then($body => { - const inlineAddExists = $body.find(byTestId('inline-add-page')).length > 0; - const newPageExists = $body.find(byTestId('new-page-button')).length > 0; - - if (inlineAddExists) { - cy.log('[STEP 4.2] Using inline add button'); - return cy.wrap(null).then(() => { + AddPageSelectors.inlineAddButton().then($inlineAdd => { + const inlineAddExists = $inlineAdd.length > 0; + PageSelectors.newPageButton().then($newPage => { + const newPageExists = $newPage.length > 0; + + if (inlineAddExists) { + cy.log('[STEP 4.2] Using inline add button'); AddPageSelectors.inlineAddButton().first().click({ force: true }); - }); - } else if (newPageExists) { - cy.log('[STEP 4.2] Using new page button instead'); - return cy.wrap(null).then(() => { + } else if (newPageExists) { + cy.log('[STEP 4.2] Using new page button instead'); PageSelectors.newPageButton().first().click({ force: true }); - }); - } else { - // Wait a bit more and try inline add button - cy.log('[STEP 4.2] Waiting for UI to stabilize'); - return cy.wrap(null).then(() => { + } else { + cy.log('[STEP 4.2] Waiting for UI to stabilize'); cy.wait(3000); AddPageSelectors.inlineAddButton().should('exist', { timeout: 15000 }); AddPageSelectors.inlineAddButton().first().click({ force: true }); - }); - } + } + }); }); waitForReactUpdate(1000); @@ -166,14 +159,13 @@ describe('Single Select Column Type', () => { // Check if property menu is open and change to SingleSelect cy.log('[STEP 7] Changing column type to SingleSelect'); - cy.get('body').then($body => { - if ($body.find(byTestId('property-type-trigger')).length > 0) { - PropertyMenuSelectors.propertyTypeTrigger().first().click({ force: true }); + PropertyMenuSelectors.propertyTypeTrigger().then($trigger => { + if ($trigger.length > 0) { + cy.wrap($trigger.first()).click({ force: true }); waitForReactUpdate(1000); PropertyMenuSelectors.propertyTypeOption(FieldType.SingleSelect).click({ force: true }); waitForReactUpdate(2000); } else { - // Try clicking on the field header first GridFieldSelectors.allFieldHeaders().last().scrollIntoView().click({ force: true }); waitForReactUpdate(1000); PropertyMenuSelectors.propertyTypeTrigger().first().click({ force: true }); @@ -191,20 +183,16 @@ describe('Single Select Column Type', () => { cy.log('[STEP 8] Adding select options to cells'); // First try to find select cells - cy.get('body').then($body => { - const selectCells = $body.find(byTestIdPrefix('select-option-cell-')); - - if (selectCells.length > 0) { - cy.log(`[STEP 9] Found ${selectCells.length} select cells`); + SingleSelectSelectors.allSelectOptionCells().then($cells => { + if ($cells.length > 0) { + cy.log(`[STEP 9] Found ${$cells.length} select cells`); - // Click first cell with force and add option SingleSelectSelectors.allSelectOptionCells().first().click({ force: true }); waitForReactUpdate(500); cy.focused().type('Option A{enter}'); waitForReactUpdate(1000); - // Add second option if possible - if (selectCells.length > 1) { + if ($cells.length > 1) { SingleSelectSelectors.allSelectOptionCells().eq(1).click({ force: true }); waitForReactUpdate(500); cy.focused().type('Option B{enter}'); @@ -213,18 +201,14 @@ describe('Single Select Column Type', () => { } else { cy.log('[STEP 9] No select cells found, using regular cells'); - // Get all rows and find cells in the newly added column DatabaseGridSelectors.rows().first().within(() => { - // Click the last cell in this row (should be the new column) DatabaseGridSelectors.cells().last().click({ force: true }); waitForReactUpdate(500); }); - // Type option A cy.focused().type('Option A{enter}'); waitForReactUpdate(1000); - // Try second row DatabaseGridSelectors.rows().eq(1).within(() => { DatabaseGridSelectors.cells().last().click({ force: true }); waitForReactUpdate(500); @@ -241,9 +225,9 @@ describe('Single Select Column Type', () => { waitForReactUpdate(1000); // Click edit property if available - cy.get('body').then($body => { - if ($body.find(byTestId('grid-field-edit-property')).length > 0) { - PropertyMenuSelectors.editPropertyMenuItem().click(); + PropertyMenuSelectors.editPropertyMenuItem().then($edit => { + if ($edit.length > 0) { + cy.wrap($edit).click(); waitForReactUpdate(1000); } }); @@ -285,9 +269,9 @@ describe('Single Select Column Type', () => { waitForReactUpdate(1000); // Click edit property if available - cy.get('body').then($body => { - if ($body.find(byTestId('grid-field-edit-property')).length > 0) { - PropertyMenuSelectors.editPropertyMenuItem().click(); + PropertyMenuSelectors.editPropertyMenuItem().then($edit => { + if ($edit.length > 0) { + cy.wrap($edit).click(); waitForReactUpdate(1000); } }); @@ -305,18 +289,15 @@ describe('Single Select Column Type', () => { // Verify select options are displayed again cy.log('[STEP 16] Verifying select options are displayed again'); - cy.get('body').then($body => { - const selectCells = $body.find(byTestIdPrefix('select-option-cell-')); - if (selectCells.length > 0) { - cy.log(`[STEP 17] Success! Found ${selectCells.length} select option cells after conversion`); + SingleSelectSelectors.allSelectOptionCells().then($cells => { + if ($cells.length > 0) { + cy.log(`[STEP 17] Success! Found ${$cells.length} select option cells after conversion`); - // Click on a cell to verify options are still available SingleSelectSelectors.allSelectOptionCells().first().click(); waitForReactUpdate(500); - // Check if select menu appears - cy.get('body').then($body => { - if ($body.find(byTestId('select-option-menu')).length > 0) { + SingleSelectSelectors.selectOptionMenu().then($menu => { + if ($menu.length > 0) { cy.log('[STEP 18] Select option menu opened - options preserved!'); } else { cy.log('[STEP 18] Select cells exist but menu behavior may differ'); diff --git a/cypress/e2e/page/breadcrumb-navigation.cy.ts b/cypress/e2e/page/breadcrumb-navigation.cy.ts index acbf31ae..8fdd2132 100644 --- a/cypress/e2e/page/breadcrumb-navigation.cy.ts +++ b/cypress/e2e/page/breadcrumb-navigation.cy.ts @@ -1,12 +1,11 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { TestTool } from '../../support/page-utils'; -import { import { testLog } from '../../support/test-helpers'; +import { PageSelectors, SpaceSelectors, SidebarSelectors, - byTestId, - byTestIdContains, + BreadcrumbSelectors, waitForReactUpdate } from '../../support/selectors'; import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config'; @@ -73,12 +72,10 @@ describe('Breadcrumb Navigation Complete Tests', () => { // Step 4: Check for breadcrumb navigation testLog.info( '=== Step 4: Checking for breadcrumb navigation ==='); - cy.get('body').then($body => { - if ($body.find(byTestId('breadcrumb-navigation')).length > 0) { + BreadcrumbSelectors.navigation().then($nav => { + if ($nav.length > 0) { testLog.info( '✓ Breadcrumb navigation found on this page'); - - // Count breadcrumb items - cy.get(byTestIdContains('breadcrumb-item-')).then($items => { + BreadcrumbSelectors.items().then($items => { testLog.info( `✓ Found ${$items.length} breadcrumb items`); }); } else { @@ -135,19 +132,19 @@ describe('Breadcrumb Navigation Complete Tests', () => { // Check for breadcrumb navigation testLog.info( '=== Step 4: Testing breadcrumb navigation ==='); - cy.get('body', { timeout: 5000 }).then($body => { - if ($body.find(byTestId('breadcrumb-navigation')).length > 0) { + BreadcrumbSelectors.navigation().then($nav => { + if ($nav.length > 0) { testLog.info( '✓ Breadcrumb navigation is visible'); - - // Try to click breadcrumb to navigate back - if ($body.find(byTestIdContains('breadcrumb-item-')).length > 1) { - cy.get(byTestIdContains('breadcrumb-item-')).first().click({ force: true }); - testLog.info( '✓ Clicked breadcrumb item to navigate back'); - cy.wait(2000); - testLog.info( '✓ Successfully used breadcrumb navigation'); - } else { - testLog.info( 'Only one breadcrumb item found'); - } + BreadcrumbSelectors.items().then($items => { + if ($items.length > 1) { + cy.wrap($items).first().click({ force: true }); + testLog.info( '✓ Clicked breadcrumb item to navigate back'); + cy.wait(2000); + testLog.info( '✓ Successfully used breadcrumb navigation'); + } else { + testLog.info( 'Only one breadcrumb item found'); + } + }); } else { testLog.info( 'No breadcrumb navigation on nested page'); } @@ -253,22 +250,16 @@ describe('Breadcrumb Navigation Complete Tests', () => { // Step 4: Test breadcrumb navigation testLog.info( '=== Step 4: Testing breadcrumb navigation ==='); - cy.get('body').then($body => { - if ($body.find(byTestId('breadcrumb-navigation')).length > 0) { + BreadcrumbSelectors.navigation().then($nav => { + if ($nav.length > 0) { testLog.info( '✓ Breadcrumb navigation is visible'); - - // Check breadcrumb items with timeout - cy.get(byTestIdContains('breadcrumb-item-'), { timeout: 10000 }).then($items => { + BreadcrumbSelectors.items().then($items => { testLog.info( `Found ${$items.length} breadcrumb items`); - if ($items.length > 1) { - // Click second-to-last breadcrumb (parent page) const targetIndex = Math.max(0, $items.length - 2); cy.wrap($items[targetIndex]).click({ force: true }); testLog.info( `✓ Clicked breadcrumb at index ${targetIndex} to go back`); cy.wait(2000); - - // Verify navigation worked testLog.info( '✓ Successfully navigated back using breadcrumb'); } }); diff --git a/cypress/e2e/page/delete-page-verify-trash.cy.ts b/cypress/e2e/page/delete-page-verify-trash.cy.ts index 3959f04c..195d4bd5 100644 --- a/cypress/e2e/page/delete-page-verify-trash.cy.ts +++ b/cypress/e2e/page/delete-page-verify-trash.cy.ts @@ -1,6 +1,6 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { TestTool } from '../../support/page-utils'; -import { PageSelectors, ModalSelectors, SidebarSelectors, byTestId, waitForReactUpdate } from '../../support/selectors'; +import { PageSelectors, ModalSelectors, SidebarSelectors, TrashSelectors, waitForReactUpdate } from '../../support/selectors'; import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config'; import { testLog } from '../../support/test-helpers'; @@ -164,7 +164,7 @@ describe('Delete Page, Verify in Trash, and Restore Tests', () => { testLog.info( '=== Step 5: Navigating to trash page ==='); // Click on the trash button in the sidebar - cy.get(byTestId('sidebar-trash-button')).click(); + TrashSelectors.sidebarTrashButton().click(); // Wait for navigation cy.wait(2000); @@ -177,10 +177,10 @@ describe('Delete Page, Verify in Trash, and Restore Tests', () => { testLog.info( '=== Step 6: Verifying deleted page exists in trash ==='); // Wait for trash table to load - cy.get(byTestId('trash-table'), { timeout: 10000 }).should('be.visible'); + TrashSelectors.table().should('be.visible'); // Look for our deleted page in the trash table - cy.get(byTestId('trash-table-row')).then($rows => { + TrashSelectors.rows().then($rows => { let foundPage = false; // Check each row for our page name @@ -206,13 +206,13 @@ describe('Delete Page, Verify in Trash, and Restore Tests', () => { // Step 7: Verify restore and permanent delete buttons are present testLog.info( '=== Step 7: Verifying trash actions are available ==='); - cy.get(byTestId('trash-table-row')).first().within(() => { + TrashSelectors.rows().first().within(() => { // Check for restore button - cy.get(byTestId('trash-restore-button')).should('exist'); + TrashSelectors.restoreButton().should('exist'); testLog.info( '✓ Restore button found'); // Check for permanent delete button - cy.get(byTestId('trash-delete-button')).should('exist'); + TrashSelectors.deleteButton().should('exist'); testLog.info( '✓ Permanent delete button found'); }); @@ -223,7 +223,7 @@ describe('Delete Page, Verify in Trash, and Restore Tests', () => { let restoredPageName = 'Untitled'; // Default to Untitled since that's what usually gets created // Click the restore button on the first row (our deleted page) - cy.get(byTestId('trash-table-row')).first().within(() => { + TrashSelectors.rows().first().within(() => { // Get the page name before restoring cy.get('td').first().invoke('text').then((text) => { restoredPageName = text.trim() || 'Untitled'; @@ -231,7 +231,7 @@ describe('Delete Page, Verify in Trash, and Restore Tests', () => { }); // Click restore button - cy.get(byTestId('trash-restore-button')).click(); + TrashSelectors.restoreButton().click(); }); // Wait for restore to complete @@ -242,30 +242,26 @@ describe('Delete Page, Verify in Trash, and Restore Tests', () => { testLog.info( '=== Step 9: Verifying page is removed from trash ==='); // Check if trash is now empty or doesn't contain our page - cy.get('body').then(($body) => { - // Check if there are any rows left in trash - const rowsExist = $body.find(byTestId('trash-table-row')).length > 0; + TrashSelectors.rows().then($rows => { + const rowsExist = $rows.length > 0; if (!rowsExist) { testLog.info( '✓ Trash is now empty - page successfully removed from trash'); } else { - // If there are still rows, verify our page is not among them - cy.get(byTestId('trash-table-row')).then($rows => { - let pageStillInTrash = false; - - $rows.each((index, row) => { - const rowText = Cypress.$(row).text(); - if (rowText.includes(restoredPageName)) { - pageStillInTrash = true; - } - }); - - if (pageStillInTrash) { - throw new Error(`Page "${restoredPageName}" is still in trash after restore`); - } else { - testLog.info( `✓ Page "${restoredPageName}" successfully removed from trash`); + let pageStillInTrash = false; + + $rows.each((index, row) => { + const rowText = Cypress.$(row).text(); + if (rowText.includes(restoredPageName)) { + pageStillInTrash = true; } }); + + if (pageStillInTrash) { + throw new Error(`Page "${restoredPageName}" is still in trash after restore`); + } else { + testLog.info( `✓ Page "${restoredPageName}" successfully removed from trash`); + } } }); diff --git a/cypress/e2e/page/more-page-action.cy.ts b/cypress/e2e/page/more-page-action.cy.ts index c55cb29f..f11b22c7 100644 --- a/cypress/e2e/page/more-page-action.cy.ts +++ b/cypress/e2e/page/more-page-action.cy.ts @@ -1,6 +1,6 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { TestTool } from '../../support/page-utils'; -import { PageSelectors, byTestId, waitForReactUpdate } from '../../support/selectors'; +import { PageSelectors, ModalSelectors, waitForReactUpdate } from '../../support/selectors'; import { generateRandomEmail } from '../../support/test-config'; import { testLog } from '../../support/test-helpers'; @@ -189,15 +189,15 @@ describe('More Page Actions', () => { testLog.info( 'Clicked Rename option'); // Wait for the rename modal to appear - cy.get(byTestId('rename-modal-input'), { timeout: 5000 }) - .should('be.visible') + ModalSelectors.renameInput() + .should('be.visible', { timeout: 5000 }) .clear() .type(renamedPageName); testLog.info( `Entered new page name: ${renamedPageName}`); // Click the save button - cy.get(byTestId('rename-modal-save')).click(); + ModalSelectors.renameSaveButton().click(); testLog.info( 'Clicked save button'); diff --git a/cypress/e2e/page/publish-page.cy.ts b/cypress/e2e/page/publish-page.cy.ts index 7a06abc5..05eeb57e 100644 --- a/cypress/e2e/page/publish-page.cy.ts +++ b/cypress/e2e/page/publish-page.cy.ts @@ -1,6 +1,6 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { TestTool } from '../../support/page-utils'; -import { PageSelectors, ShareSelectors, SidebarSelectors, byTestId } from '../../support/selectors'; +import { PageSelectors, ShareSelectors, SidebarSelectors } from '../../support/selectors'; import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config'; import { testLog } from '../../support/test-helpers'; @@ -71,7 +71,7 @@ describe('Publish Page Test', () => { cy.wait(5000); // Verify that the page is now published by checking for published UI elements - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); testLog.info( 'Page published successfully, URL elements visible'); // 6. Get the published URL by constructing it from UI elements @@ -79,8 +79,8 @@ describe('Publish Page Test', () => { const origin = win.location.origin; // Get namespace and publish name from the UI - cy.get(byTestId('publish-namespace')).should('be.visible').invoke('text').then((namespace) => { - cy.get(byTestId('publish-name-input')).should('be.visible').invoke('val').then((publishName) => { + cy.get(ShareSelectors.publishNamespace()).should('be.visible').invoke('text').then((namespace) => { + cy.get(ShareSelectors.publishNameInput()).should('be.visible').invoke('val').then((publishName) => { const namespaceText = namespace.trim(); const publishNameText = String(publishName).trim(); const publishedUrl = `${origin}/${namespaceText}/${publishNameText}`; @@ -91,7 +91,7 @@ describe('Publish Page Test', () => { // Located in a div with class "p-1 text-text-primary" next to the URL container ShareSelectors.sharePopover().within(() => { // Find the parent container that holds both URL inputs and copy button - cy.get(byTestId('publish-name-input')) + cy.get(ShareSelectors.publishNameInput()) .closest('div.flex.w-full.items-center.overflow-hidden') .find('div.p-1.text-text-primary') .should('be.visible') @@ -263,13 +263,13 @@ describe('Publish Page Test', () => { cy.wait(5000); // Verify published - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); // Get the published URL cy.window().then((win) => { const origin = win.location.origin; - cy.get(byTestId('publish-namespace')).should('be.visible').invoke('text').then((namespace) => { - cy.get(byTestId('publish-name-input')).should('be.visible').invoke('val').then((publishName) => { + cy.get(ShareSelectors.publishNamespace()).should('be.visible').invoke('text').then((namespace) => { + cy.get(ShareSelectors.publishNameInput()).should('be.visible').invoke('val').then((publishName) => { const publishedUrl = `${origin}/${namespace.trim()}/${String(publishName).trim()}`; testLog.info( `Published URL: ${publishedUrl}`); @@ -315,20 +315,20 @@ describe('Publish Page Test', () => { ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true }); cy.wait(5000); - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); // Get original URL cy.window().then((win) => { const origin = win.location.origin; - cy.get(byTestId('publish-namespace')).invoke('text').then((namespace) => { - cy.get(byTestId('publish-name-input')).invoke('val').then((originalName) => { + cy.get(ShareSelectors.publishNamespace()).invoke('text').then((namespace) => { + cy.get(ShareSelectors.publishNameInput()).invoke('val').then((originalName) => { const namespaceText = namespace.trim(); const originalNameText = String(originalName).trim(); testLog.info( `Original publish name: ${originalNameText}`); // Edit the publish name directly in the input const newPublishName = `custom-name-${Date.now()}`; - cy.get(byTestId('publish-name-input')) + cy.get(ShareSelectors.publishNameInput()) .clear() .type(newPublishName) .blur(); @@ -397,14 +397,14 @@ describe('Publish Page Test', () => { ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true }); cy.wait(5000); - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); testLog.info( '✓ First publish successful'); // Get published URL cy.window().then((win) => { const origin = win.location.origin; - cy.get(byTestId('publish-namespace')).invoke('text').then((namespace) => { - cy.get(byTestId('publish-name-input')).invoke('val').then((publishName) => { + cy.get(ShareSelectors.publishNamespace()).invoke('text').then((namespace) => { + cy.get(ShareSelectors.publishNameInput()).invoke('val').then((publishName) => { const publishedUrl = `${origin}/${namespace.trim()}/${String(publishName).trim()}`; testLog.info( `Published URL: ${publishedUrl}`); @@ -458,7 +458,7 @@ describe('Publish Page Test', () => { // Republish with updated content ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true }); cy.wait(5000); - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); testLog.info( '✓ Republished successfully'); // Verify updated content is published @@ -501,14 +501,14 @@ describe('Publish Page Test', () => { ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true }); cy.wait(5000); - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); // Try to set invalid publish name with spaces - cy.get(byTestId('publish-name-input')).invoke('val').then((originalName) => { + cy.get(ShareSelectors.publishNameInput()).invoke('val').then((originalName) => { testLog.info( `Original name: ${originalName}`); // Try to set name with space (should be rejected) - cy.get(byTestId('publish-name-input')) + cy.get(ShareSelectors.publishNameInput()) .clear() .type('invalid name with spaces') .blur(); @@ -519,7 +519,7 @@ describe('Publish Page Test', () => { cy.get('body').then(($body) => { const bodyText = $body.text(); // The name should either revert or show an error - cy.get(byTestId('publish-name-input')).invoke('val').then((currentName) => { + cy.get(ShareSelectors.publishNameInput()).invoke('val').then((currentName) => { // Name should not contain spaces (validation should prevent it) if (String(currentName).includes(' ')) { testLog.info( '⚠ Warning: Invalid characters were not rejected'); @@ -558,7 +558,7 @@ describe('Publish Page Test', () => { ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true }); cy.wait(5000); - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); // Test comments switch - find by looking for Switch components in the published panel ShareSelectors.sharePopover().within(() => { @@ -632,13 +632,13 @@ describe('Publish Page Test', () => { ShareSelectors.publishConfirmButton().should('be.visible').click({ force: true }); cy.wait(5000); - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); // Get first URL cy.window().then((win) => { const origin = win.location.origin; - cy.get(byTestId('publish-namespace')).invoke('text').then((namespace) => { - cy.get(byTestId('publish-name-input')).invoke('val').then((publishName) => { + cy.get(ShareSelectors.publishNamespace()).invoke('text').then((namespace) => { + cy.get(ShareSelectors.publishNameInput()).invoke('val').then((publishName) => { firstPublishedUrl = `${origin}/${namespace.trim()}/${String(publishName).trim()}`; testLog.info( `First published URL: ${firstPublishedUrl}`); @@ -651,9 +651,9 @@ describe('Publish Page Test', () => { cy.contains('Publish').should('exist').click({ force: true }); cy.wait(1000); - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); - cy.get(byTestId('publish-namespace')).invoke('text').then((namespace2) => { - cy.get(byTestId('publish-name-input')).invoke('val').then((publishName2) => { + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).invoke('text').then((namespace2) => { + cy.get(ShareSelectors.publishNameInput()).invoke('val').then((publishName2) => { const secondPublishedUrl = `${origin}/${namespace2.trim()}/${String(publishName2).trim()}`; testLog.info( `Second check URL: ${secondPublishedUrl}`); @@ -750,7 +750,7 @@ describe('Publish Page Test', () => { cy.wait(5000); // Verify that the database is now published by checking for published UI elements - cy.get(byTestId('publish-namespace')).should('be.visible', { timeout: 10000 }); + cy.get(ShareSelectors.publishNamespace()).should('be.visible', { timeout: 10000 }); testLog.info( 'Database published successfully, URL elements visible'); // Get the published URL @@ -758,8 +758,8 @@ describe('Publish Page Test', () => { const origin = win.location.origin; // Get namespace and publish name from the UI - cy.get(byTestId('publish-namespace')).should('be.visible').invoke('text').then((namespace) => { - cy.get(byTestId('publish-name-input')).should('be.visible').invoke('val').then((publishName) => { + cy.get(ShareSelectors.publishNamespace()).should('be.visible').invoke('text').then((namespace) => { + cy.get(ShareSelectors.publishNameInput()).should('be.visible').invoke('val').then((publishName) => { const namespaceText = namespace.trim(); const publishNameText = String(publishName).trim(); const publishedUrl = `${origin}/${namespaceText}/${publishNameText}`; diff --git a/cypress/e2e/space/create-space.cy.ts b/cypress/e2e/space/create-space.cy.ts index b46ffc17..185c3544 100644 --- a/cypress/e2e/space/create-space.cy.ts +++ b/cypress/e2e/space/create-space.cy.ts @@ -1,6 +1,6 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { TestTool } from '../../support/page-utils'; -import { PageSelectors, SpaceSelectors, SidebarSelectors, byTestId, waitForReactUpdate } from '../../support/selectors'; +import { PageSelectors, SpaceSelectors, SidebarSelectors, ModalSelectors, waitForReactUpdate } from '../../support/selectors'; import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config'; import { testLog } from '../../support/test-helpers'; @@ -83,7 +83,7 @@ describe('Space Creation Tests', () => { // Step 3: Click on "Create New Space" option testLog.info( '=== Step 3: Clicking Create New Space option ==='); - cy.get(byTestId('create-new-space-button')) + SpaceSelectors.createNewSpaceButton() .should('be.visible') .click(); @@ -96,13 +96,13 @@ describe('Space Creation Tests', () => { testLog.info( '=== Step 4: Filling space creation form ==='); // Verify the modal is visible - cy.get(byTestId('create-space-modal')) + SpaceSelectors.createSpaceModal() .should('be.visible'); testLog.info( 'Create Space modal is visible'); // Enter space name - cy.get(byTestId('space-name-input')) + SpaceSelectors.spaceNameInput() .should('be.visible') .clear() .type(spaceName); @@ -116,7 +116,7 @@ describe('Space Creation Tests', () => { testLog.info( '=== Step 5: Saving new space ==='); // Click the Save button - cy.get(byTestId('modal-ok-button')) + ModalSelectors.okButton() .should('be.visible') .click(); diff --git a/cypress/support/selectors.ts b/cypress/support/selectors.ts index 2606492e..d2ff579c 100644 --- a/cypress/support/selectors.ts +++ b/cypress/support/selectors.ts @@ -81,6 +81,19 @@ export const SpaceSelectors = { // Get more actions button for spaces moreActionsButton: () => cy.get(byTestId('inline-more-actions')), + + // New space creation controls + createNewSpaceButton: () => cy.get(byTestId('create-new-space-button')), + createSpaceModal: () => cy.get(byTestId('create-space-modal')), + spaceNameInput: () => cy.get(byTestId('space-name-input')), +}; + +/** + * Breadcrumb selectors + */ +export const BreadcrumbSelectors = { + navigation: () => cy.get(byTestId('breadcrumb-navigation')), + items: () => cy.get(byTestIdContains('breadcrumb-item-')), }; /** @@ -124,6 +137,13 @@ export const ModalSelectors = { // Get space item in modal spaceItemInModal: () => cy.get(byTestId('space-item')), + + // Generic modal accept/ok button + okButton: () => cy.get(byTestId('modal-ok-button')), + + // Rename modal inputs + renameInput: () => cy.get(byTestId('rename-modal-input')), + renameSaveButton: () => cy.get(byTestId('rename-modal-save')), }; /** @@ -154,6 +174,10 @@ export const ShareSelectors = { // Publish URL input publishUrlInput: () => cy.get(byTestId('publish-url-input')), + // Publish namespace and name inputs + publishNamespace: () => cy.get(byTestId('publish-namespace')), + publishNameInput: () => cy.get(byTestId('publish-name-input')), + // Page settings button pageSettingsButton: () => cy.get(byTestId('page-settings-button')), @@ -201,6 +225,17 @@ export const SidebarSelectors = { pageHeader: () => cy.get(byTestId('sidebar-page-header')), }; +/** + * Trash view selectors + */ +export const TrashSelectors = { + sidebarTrashButton: () => cy.get(byTestId('sidebar-trash-button')), + table: () => cy.get(byTestId('trash-table')), + rows: () => cy.get(byTestId('trash-table-row')), + restoreButton: () => cy.get(byTestId('trash-restore-button')), + deleteButton: () => cy.get(byTestId('trash-delete-button')), +}; + /** * Chat Model Selector-related selectors * Used for testing AI model selection in chat interface @@ -222,6 +257,19 @@ export const ModelSelectorSelectors = { selectedOption: () => cy.get('[data-testid^="model-option-"]').filter('.bg-fill-content-select'), }; +/** + * 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')), +}; + /** * Database Grid-related selectors */ @@ -445,6 +493,27 @@ export const AuthSelectors = { logoutConfirmButton: () => cy.get(byTestId('logout-confirm-button')), }; +/** + * Account settings selectors + */ +export const AccountSelectors = { + settingsButton: () => cy.get(byTestId('account-settings-button')), + settingsDialog: () => cy.get(byTestId('account-settings-dialog')), + dateFormatDropdown: () => cy.get(byTestId('date-format-dropdown')), + dateFormatOptionYearMonthDay: () => cy.get(byTestId('date-format-1')), + timeFormatDropdown: () => cy.get(byTestId('time-format-dropdown')), + timeFormatOption24: () => cy.get(byTestId('time-format-1')), + startWeekDropdown: () => cy.get(byTestId('start-week-on-dropdown')), + startWeekMonday: () => cy.get(byTestId('start-week-1')), +}; + +/** + * Avatar display selectors + */ +export const AvatarUiSelectors = { + image: () => cy.get(byTestId('avatar-image')), +}; + export function waitForReactUpdate(ms: number = 500) { return cy.wait(ms); }