From 73120c3452e281ec90b2a9b35d2455d800a2ed1f Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 22 Sep 2025 22:54:46 +0800 Subject: [PATCH 1/4] chore: duplicate row --- cypress/e2e/database/row-duplication.cy.ts | 163 ++++++++++++++++++ .../grid/controls/HoverControls.tsx | 1 + .../components/grid/controls/RowMenu.tsx | 1 + 3 files changed, 165 insertions(+) create mode 100644 cypress/e2e/database/row-duplication.cy.ts diff --git a/cypress/e2e/database/row-duplication.cy.ts b/cypress/e2e/database/row-duplication.cy.ts new file mode 100644 index 00000000..c170d294 --- /dev/null +++ b/cypress/e2e/database/row-duplication.cy.ts @@ -0,0 +1,163 @@ +import { v4 as uuidv4 } from 'uuid'; +import { AuthTestUtils } from '../../support/auth-utils'; +import { + AddPageSelectors, + DatabaseGridSelectors, + waitForReactUpdate +} from '../../support/selectors'; + +describe('Database Row Duplication', () => { + const generateRandomEmail = () => `${uuidv4()}@appflowy.io`; + + beforeEach(() => { + cy.on('uncaught:exception', (err) => { + if (err.message.includes('Minified React error') || + err.message.includes('View not found') || + err.message.includes('No workspace or service found')) { + return false; + } + return true; + }); + + cy.viewport(1280, 720); + }); + + it('should create a new grid, add content to first row, and duplicate it', () => { + const testEmail = generateRandomEmail(); + const testContent = `Test Content ${Date.now()}`; + + cy.log(`[TEST START] Testing row duplication - Test email: ${testEmail}`); + + // Login + cy.log('[STEP 1] Visiting login page'); + cy.visit('/login', { failOnStatusCode: false }); + cy.wait(2000); + + const authUtils = new AuthTestUtils(); + cy.log('[STEP 2] Starting authentication'); + authUtils.signInWithTestUrl(testEmail).then(() => { + cy.log('[STEP 3] Authentication successful'); + cy.url({ timeout: 30000 }).should('include', '/app'); + cy.wait(3000); + + // Create a new grid + cy.log('[STEP 4] Creating new grid'); + AddPageSelectors.inlineAddButton().first().should('be.visible').click(); + waitForReactUpdate(1000); + AddPageSelectors.addGridButton().should('be.visible').click(); + cy.wait(8000); + + // Verify grid exists + cy.log('[STEP 5] Verifying grid exists'); + DatabaseGridSelectors.grid().should('exist'); + + // Get cells and edit the first one + cy.log('[STEP 6] Getting cells and editing first cell'); + DatabaseGridSelectors.cells().should('exist'); + + DatabaseGridSelectors.cells().then($cells => { + cy.log(`[STEP 7] Found ${$cells.length} cells`); + + // Click first cell and type content + cy.wrap($cells.first()).click(); + waitForReactUpdate(500); + cy.focused().type(testContent); + cy.focused().type('{enter}'); + waitForReactUpdate(1000); + + // Verify data was entered + cy.log('[STEP 8] Verifying content was added'); + cy.wrap($cells.first()).should('contain.text', testContent); + }); + + // Now hover over the first row to show controls + cy.log('[STEP 9] Hovering over first row to show controls'); + + // Get the first row and hover over it + DatabaseGridSelectors.firstRow() + .parent() + .parent() + .trigger('mouseenter', { force: true }) + .trigger('mouseover', { force: true }); + + cy.wait(1000); + + // Click on the drag icon/row accessory button + cy.log('[STEP 10] Looking for and clicking row accessory button'); + + // The drag icon should be visible after hovering + // Try to find it by the data-testid we added or by its class + cy.get('[data-testid="row-accessory-button"]').then($button => { + if ($button.length > 0) { + cy.log('[STEP 10.1] Found row accessory button with data-testid'); + cy.wrap($button.first()).click({ force: true }); + } else { + cy.log('[STEP 10.1] Looking for drag icon by class'); + // Try to find the drag icon button + cy.get('div[class*="cursor-pointer"]').first().click({ force: true }); + } + }); + + cy.wait(1000); + + // Now the dropdown menu should be open + cy.log('[STEP 11] Looking for duplicate option in dropdown menu'); + + // Wait for the dropdown menu to be visible + cy.get('[role="menu"], [data-slot="dropdown-menu-content"]').should('be.visible'); + + // Click on the duplicate option + cy.get('[role="menuitem"]').then($items => { + let found = false; + $items.each((index, item) => { + const text = Cypress.$(item).text(); + if (text.includes('Duplicate') || text.includes('duplicate')) { + cy.log('[STEP 11.1] Found duplicate menu item by text'); + cy.wrap(item).click(); + found = true; + return false; + } + }); + + if (!found) { + // Try clicking by the data-testid we added + cy.get('[data-testid="row-menu-duplicate"]').then($duplicate => { + if ($duplicate.length > 0) { + cy.log('[STEP 11.2] Found duplicate menu item by data-testid'); + cy.wrap($duplicate).click(); + } else { + // Fallback: click the third menu item (typically duplicate is after insert above/below) + cy.log('[STEP 11.3] Clicking third menu item as fallback'); + cy.get('[role="menuitem"]').eq(2).click(); + } + }); + } + }); + + cy.wait(2000); + + // Verify the row was duplicated + cy.log('[STEP 12] Verifying row was duplicated'); + DatabaseGridSelectors.rows().should('have.length.at.least', 2); + + // Get all cells again and verify the duplicated content + cy.log('[STEP 13] Verifying duplicated content'); + DatabaseGridSelectors.cells().then($allCells => { + // The duplicated row should have the same content + // Find cells that contain our test content + let contentCount = 0; + $allCells.each((index, cell) => { + if (Cypress.$(cell).text().includes(testContent)) { + contentCount++; + } + }); + + cy.log(`[STEP 14] Found ${contentCount} cells with test content`); + // We should have at least 2 cells with the same content (original and duplicate) + expect(contentCount).to.be.at.least(2); + }); + + cy.log('[STEP 15] Row duplication test completed successfully'); + }); + }); +}); \ No newline at end of file diff --git a/src/components/database/components/grid/controls/HoverControls.tsx b/src/components/database/components/grid/controls/HoverControls.tsx index e6c7c234..72a084ae 100644 --- a/src/components/database/components/grid/controls/HoverControls.tsx +++ b/src/components/database/components/grid/controls/HoverControls.tsx @@ -98,6 +98,7 @@ export function HoverControls ({ rowId, dragHandleRef }: {
{ setMenuOpen(true); }} diff --git a/src/components/database/components/grid/controls/RowMenu.tsx b/src/components/database/components/grid/controls/RowMenu.tsx index dc682cc4..f1c4236c 100644 --- a/src/components/database/components/grid/controls/RowMenu.tsx +++ b/src/components/database/components/grid/controls/RowMenu.tsx @@ -77,6 +77,7 @@ function RowMenu({ rowId, onClose }: { rowId: string; onClose: () => void }) { {actions.map((item) => ( { e.preventDefault(); item.onSelect(); From 2be8ed0b03c2b5146557d2d5094cf49186faf14e Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 23 Sep 2025 13:13:25 +0800 Subject: [PATCH 2/4] test: add insert row --- cypress/e2e/database/row-insertion.cy.ts | 237 ++++++++++++++++++ .../components/grid/controls/RowMenu.tsx | 8 +- 2 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 cypress/e2e/database/row-insertion.cy.ts diff --git a/cypress/e2e/database/row-insertion.cy.ts b/cypress/e2e/database/row-insertion.cy.ts new file mode 100644 index 00000000..0211aa0e --- /dev/null +++ b/cypress/e2e/database/row-insertion.cy.ts @@ -0,0 +1,237 @@ +import { v4 as uuidv4 } from 'uuid'; +import { AuthTestUtils } from '../../support/auth-utils'; +import { + AddPageSelectors, + DatabaseGridSelectors, + waitForReactUpdate +} from '../../support/selectors'; + +describe('Database Row Insertion', () => { + const generateRandomEmail = () => `${uuidv4()}@appflowy.io`; + + beforeEach(() => { + cy.on('uncaught:exception', (err) => { + if (err.message.includes('Minified React error') || + err.message.includes('View not found') || + err.message.includes('No workspace or service found')) { + return false; + } + return true; + }); + + cy.viewport(1280, 720); + }); + + it('should insert rows above and below existing row', () => { + const testEmail = generateRandomEmail(); + const originalContent = `Original Row ${Date.now()}`; + const aboveContent = `Above Row ${Date.now()}`; + const belowContent = `Below Row ${Date.now()}`; + + cy.log(`[TEST START] Testing row insertion above and below - Test email: ${testEmail}`); + + // Login + cy.log('[STEP 1] Visiting login page'); + cy.visit('/login', { failOnStatusCode: false }); + cy.wait(2000); + + const authUtils = new AuthTestUtils(); + cy.log('[STEP 2] Starting authentication'); + authUtils.signInWithTestUrl(testEmail).then(() => { + cy.log('[STEP 3] Authentication successful'); + cy.url({ timeout: 30000 }).should('include', '/app'); + cy.wait(3000); + + // Create a new grid + cy.log('[STEP 4] Creating new grid'); + AddPageSelectors.inlineAddButton().first().should('be.visible').click(); + waitForReactUpdate(1000); + AddPageSelectors.addGridButton().should('be.visible').click(); + cy.wait(8000); + + // Verify grid exists + cy.log('[STEP 5] Verifying grid exists'); + DatabaseGridSelectors.grid().should('exist'); + + // Get cells and edit the first one + cy.log('[STEP 6] Getting cells and editing first cell'); + DatabaseGridSelectors.cells().should('exist'); + + DatabaseGridSelectors.cells().then($cells => { + cy.log(`[STEP 7] Found ${$cells.length} cells`); + + // Click first cell and type content + cy.wrap($cells.first()).click(); + waitForReactUpdate(500); + cy.focused().type(originalContent); + cy.focused().type('{enter}'); + waitForReactUpdate(1000); + + // Verify data was entered + cy.log('[STEP 8] Verifying content was added'); + cy.wrap($cells.first()).should('contain.text', originalContent); + }); + + // Get initial row count + cy.log('[STEP 9] Getting initial row count'); + DatabaseGridSelectors.rows().then($rows => { + const initialRowCount = $rows.length; + cy.log(`[STEP 9.1] Initial row count: ${initialRowCount}`); + + // Insert a row above + cy.log('[STEP 10] Inserting row above'); + + // Get the first row and hover over it + DatabaseGridSelectors.firstRow() + .parent() + .parent() + .trigger('mouseenter', { force: true }) + .trigger('mouseover', { force: true }); + + cy.wait(1000); + + // Click on the drag icon/row accessory button + cy.log('[STEP 11] Looking for and clicking row accessory button'); + + cy.get('[data-testid="row-accessory-button"]').then($button => { + if ($button.length > 0) { + cy.log('[STEP 11.1] Found row accessory button with data-testid'); + cy.wrap($button.first()).click({ force: true }); + } else { + cy.log('[STEP 11.1] Looking for drag icon by class'); + cy.get('div[class*="cursor-pointer"]').first().click({ force: true }); + } + }); + + cy.wait(1000); + + // Now the dropdown menu should be open + cy.log('[STEP 12] Looking for insert above option in dropdown menu'); + + // Wait for the dropdown menu to be visible + cy.get('[role="menu"], [data-slot="dropdown-menu-content"]').should('be.visible'); + + // Click on the insert above option - using data-testid first + cy.get('[data-testid="row-menu-insert-above"]').then($insertAbove => { + if ($insertAbove.length > 0) { + cy.log('[STEP 12.1] Found insert above menu item by data-testid'); + cy.wrap($insertAbove).click(); + } else { + cy.log('[STEP 12.1] Looking for insert above by position'); + // First menu item is typically insert above + cy.get('[role="menuitem"]').first().click(); + } + }); + + cy.wait(2000); + + // Verify a new row was added + cy.log('[STEP 13] Verifying row was inserted above'); + DatabaseGridSelectors.rows().should('have.length', initialRowCount + 1); + + // Add content to the newly inserted row (which should be the first row now) + cy.log('[STEP 14] Adding content to newly inserted row above'); + DatabaseGridSelectors.cells().first().click(); + waitForReactUpdate(500); + cy.focused().type(aboveContent); + cy.focused().type('{enter}'); + waitForReactUpdate(1000); + + // Now insert a row below the original row (which is now the second row) + cy.log('[STEP 15] Inserting row below original row'); + + // Get the second row and hover over it + DatabaseGridSelectors.rows().eq(1) + .parent() + .parent() + .trigger('mouseenter', { force: true }) + .trigger('mouseover', { force: true }); + + cy.wait(1000); + + // Click on the drag icon/row accessory button again + cy.log('[STEP 16] Looking for and clicking row accessory button for second row'); + + // The row controls should appear for the second row after hovering + cy.get('[data-testid="row-accessory-button"]').then($buttons => { + if ($buttons.length > 0) { + cy.log('[STEP 16.1] Found row accessory button'); + // Click the last visible button (should be for the hovered row) + cy.wrap($buttons.last()).click({ force: true }); + } else { + cy.log('[STEP 16.1] Looking for drag icon by class'); + cy.get('div[class*="cursor-pointer"]').last().click({ force: true }); + } + }); + + cy.wait(1000); + + // Now the dropdown menu should be open + cy.log('[STEP 17] Looking for insert below option in dropdown menu'); + + // Wait for the dropdown menu to be visible + cy.get('[role="menu"], [data-slot="dropdown-menu-content"]').should('be.visible'); + + // Click on the insert below option - using data-testid first + cy.get('[data-testid="row-menu-insert-below"]').then($insertBelow => { + if ($insertBelow.length > 0) { + cy.log('[STEP 17.1] Found insert below menu item by data-testid'); + cy.wrap($insertBelow).click(); + } else { + cy.log('[STEP 17.1] Looking for insert below by position'); + // Second menu item is typically insert below + cy.get('[role="menuitem"]').eq(1).click(); + } + }); + + cy.wait(2000); + + // Verify another new row was added + cy.log('[STEP 18] Verifying row was inserted below'); + DatabaseGridSelectors.rows().should('have.length', initialRowCount + 2); + + // Add content to the newly inserted row below (should be the third row) + cy.log('[STEP 19] Adding content to newly inserted row below'); + DatabaseGridSelectors.rows().eq(2).within(() => { + DatabaseGridSelectors.cells().first().click(); + }); + waitForReactUpdate(500); + cy.focused().type(belowContent); + cy.focused().type('{enter}'); + waitForReactUpdate(1000); + + // Final verification - check all cells have the correct content + cy.log('[STEP 20] Final verification of content'); + DatabaseGridSelectors.cells().then($allCells => { + // Find cells that contain our test content + let foundAbove = false; + let foundOriginal = false; + let foundBelow = false; + + $allCells.each((index, cell) => { + const text = Cypress.$(cell).text(); + if (text.includes(aboveContent)) { + foundAbove = true; + cy.log(`[STEP 20.1] Found above content at index ${index}`); + } + if (text.includes(originalContent)) { + foundOriginal = true; + cy.log(`[STEP 20.2] Found original content at index ${index}`); + } + if (text.includes(belowContent)) { + foundBelow = true; + cy.log(`[STEP 20.3] Found below content at index ${index}`); + } + }); + + // Verify all content was found + expect(foundAbove).to.be.true; + expect(foundOriginal).to.be.true; + expect(foundBelow).to.be.true; + }); + + cy.log('[STEP 21] Row insertion test completed successfully'); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/components/database/components/grid/controls/RowMenu.tsx b/src/components/database/components/grid/controls/RowMenu.tsx index f1c4236c..fee21e82 100644 --- a/src/components/database/components/grid/controls/RowMenu.tsx +++ b/src/components/database/components/grid/controls/RowMenu.tsx @@ -77,7 +77,13 @@ function RowMenu({ rowId, onClose }: { rowId: string; onClose: () => void }) { {actions.map((item) => ( { e.preventDefault(); item.onSelect(); From 9441f4adc6e09ecb11956120cdfc0a497be15e2f Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 23 Sep 2025 14:09:48 +0800 Subject: [PATCH 3/4] test: deelte row --- cypress/e2e/database/row-deletion.cy.ts | 170 ++++++++++++++++++ .../database-row/DeleteRowConfirm.tsx | 2 +- 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 cypress/e2e/database/row-deletion.cy.ts diff --git a/cypress/e2e/database/row-deletion.cy.ts b/cypress/e2e/database/row-deletion.cy.ts new file mode 100644 index 00000000..75e144f3 --- /dev/null +++ b/cypress/e2e/database/row-deletion.cy.ts @@ -0,0 +1,170 @@ +import { v4 as uuidv4 } from 'uuid'; +import { AuthTestUtils } from '../../support/auth-utils'; +import { + AddPageSelectors, + DatabaseGridSelectors, + waitForReactUpdate +} from '../../support/selectors'; + +describe('Database Row Deletion', () => { + const generateRandomEmail = () => `${uuidv4()}@appflowy.io`; + + beforeEach(() => { + cy.on('uncaught:exception', (err) => { + if (err.message.includes('Minified React error') || + err.message.includes('View not found') || + err.message.includes('No workspace or service found')) { + return false; + } + return true; + }); + + cy.viewport(1280, 720); + }); + + it('should delete a row from the grid', () => { + const testEmail = generateRandomEmail(); + const testContent = `Test Row ${Date.now()}`; + + cy.log(`[TEST START] Testing row deletion - Test email: ${testEmail}`); + + // Login + cy.log('[STEP 1] Visiting login page'); + cy.visit('/login', { failOnStatusCode: false }); + cy.wait(2000); + + const authUtils = new AuthTestUtils(); + cy.log('[STEP 2] Starting authentication'); + authUtils.signInWithTestUrl(testEmail).then(() => { + cy.log('[STEP 3] Authentication successful'); + cy.url({ timeout: 30000 }).should('include', '/app'); + cy.wait(3000); + + // Create a new grid + cy.log('[STEP 4] Creating new grid'); + AddPageSelectors.inlineAddButton().first().should('be.visible').click(); + waitForReactUpdate(1000); + AddPageSelectors.addGridButton().should('be.visible').click(); + cy.wait(8000); + + // Verify grid exists + cy.log('[STEP 5] Verifying grid exists'); + DatabaseGridSelectors.grid().should('exist'); + + // Get cells and edit the first one + cy.log('[STEP 6] Getting cells and editing first cell'); + DatabaseGridSelectors.cells().should('exist'); + + DatabaseGridSelectors.cells().then($cells => { + cy.log(`[STEP 7] Found ${$cells.length} cells`); + + // Click first cell and type content + cy.wrap($cells.first()).click(); + waitForReactUpdate(500); + cy.focused().type(testContent); + cy.focused().type('{enter}'); + waitForReactUpdate(1000); + + // Verify data was entered + cy.log('[STEP 8] Verifying content was added'); + cy.wrap($cells.first()).should('contain.text', testContent); + }); + + // Get initial row count + cy.log('[STEP 9] Getting initial row count'); + DatabaseGridSelectors.rows().then($rows => { + const initialRowCount = $rows.length; + cy.log(`[STEP 9.1] Initial row count: ${initialRowCount}`); + + // Now hover over the first row to show controls + cy.log('[STEP 10] Hovering over first row to show controls'); + + // Get the first row and hover over it + DatabaseGridSelectors.firstRow() + .parent() + .parent() + .trigger('mouseenter', { force: true }) + .trigger('mouseover', { force: true }); + + cy.wait(1000); + + // Click on the drag icon/row accessory button + cy.log('[STEP 11] Looking for and clicking row accessory button'); + + cy.get('[data-testid="row-accessory-button"]').then($button => { + if ($button.length > 0) { + cy.log('[STEP 11.1] Found row accessory button with data-testid'); + cy.wrap($button.first()).click({ force: true }); + } else { + cy.log('[STEP 11.1] Looking for drag icon by class'); + cy.get('div[class*="cursor-pointer"]').first().click({ force: true }); + } + }); + + cy.wait(1000); + + // Now the dropdown menu should be open + cy.log('[STEP 12] Looking for delete option in dropdown menu'); + + // Wait for the dropdown menu to be visible + cy.get('[role="menu"], [data-slot="dropdown-menu-content"]').should('be.visible'); + + // Click on the delete option + cy.get('[data-testid="row-menu-delete"]').then($delete => { + if ($delete.length > 0) { + cy.log('[STEP 12.1] Found delete menu item by data-testid'); + cy.wrap($delete).click(); + } else { + cy.log('[STEP 12.1] Looking for delete by text'); + // Look for delete option by text + cy.get('[role="menuitem"]').contains(/delete/i).click(); + } + }); + + cy.wait(1000); + + // Handle the confirmation dialog + cy.log('[STEP 13] Handling deletion confirmation dialog'); + + // Click the confirm button + cy.get('[data-testid="delete-row-confirm-button"]').then($confirm => { + if ($confirm.length > 0) { + cy.log('[STEP 13.1] Found delete confirmation button by data-testid'); + cy.wrap($confirm).click(); + } else { + cy.log('[STEP 13.1] Looking for delete confirmation by button text'); + // Look for a button with text "Delete" or similar + cy.get('button').contains(/delete/i).click(); + } + }); + + cy.wait(2000); + + // Verify the row was deleted + cy.log('[STEP 14] Verifying row was deleted'); + + // Check that we now have fewer rows + DatabaseGridSelectors.rows().should('have.length', initialRowCount - 1); + + // Verify the content is gone + cy.log('[STEP 15] Verifying deleted content is gone'); + DatabaseGridSelectors.cells().then($allCells => { + let foundContent = false; + + $allCells.each((index, cell) => { + const text = Cypress.$(cell).text(); + if (text.includes(testContent)) { + foundContent = true; + cy.log(`[STEP 15.1] ERROR: Found deleted content at index ${index}`); + } + }); + + // The content should be gone + expect(foundContent).to.be.false; + }); + + cy.log('[STEP 16] Row deletion test completed successfully'); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/components/database/components/database-row/DeleteRowConfirm.tsx b/src/components/database/components/database-row/DeleteRowConfirm.tsx index 2bf687d2..ffc474ed 100644 --- a/src/components/database/components/database-row/DeleteRowConfirm.tsx +++ b/src/components/database/components/database-row/DeleteRowConfirm.tsx @@ -68,7 +68,7 @@ export function DeleteRowConfirm({ - From e0f4c2201457b57f2bd5a7ad432b5f0ec907a05d Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 23 Sep 2025 14:26:52 +0800 Subject: [PATCH 4/4] chore: selector id --- cypress/e2e/database/row-deletion.cy.ts | 7 ++++--- cypress/e2e/database/row-duplication.cy.ts | 5 +++-- cypress/e2e/database/row-insertion.cy.ts | 9 +++++---- cypress/support/selectors.ts | 17 +++++++++++++++++ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/cypress/e2e/database/row-deletion.cy.ts b/cypress/e2e/database/row-deletion.cy.ts index 75e144f3..0cc099d0 100644 --- a/cypress/e2e/database/row-deletion.cy.ts +++ b/cypress/e2e/database/row-deletion.cy.ts @@ -3,6 +3,7 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { AddPageSelectors, DatabaseGridSelectors, + RowControlsSelectors, waitForReactUpdate } from '../../support/selectors'; @@ -91,7 +92,7 @@ describe('Database Row Deletion', () => { // Click on the drag icon/row accessory button cy.log('[STEP 11] Looking for and clicking row accessory button'); - cy.get('[data-testid="row-accessory-button"]').then($button => { + RowControlsSelectors.rowAccessoryButton().then($button => { if ($button.length > 0) { cy.log('[STEP 11.1] Found row accessory button with data-testid'); cy.wrap($button.first()).click({ force: true }); @@ -110,7 +111,7 @@ describe('Database Row Deletion', () => { cy.get('[role="menu"], [data-slot="dropdown-menu-content"]').should('be.visible'); // Click on the delete option - cy.get('[data-testid="row-menu-delete"]').then($delete => { + RowControlsSelectors.rowMenuDelete().then($delete => { if ($delete.length > 0) { cy.log('[STEP 12.1] Found delete menu item by data-testid'); cy.wrap($delete).click(); @@ -127,7 +128,7 @@ describe('Database Row Deletion', () => { cy.log('[STEP 13] Handling deletion confirmation dialog'); // Click the confirm button - cy.get('[data-testid="delete-row-confirm-button"]').then($confirm => { + RowControlsSelectors.deleteRowConfirmButton().then($confirm => { if ($confirm.length > 0) { cy.log('[STEP 13.1] Found delete confirmation button by data-testid'); cy.wrap($confirm).click(); diff --git a/cypress/e2e/database/row-duplication.cy.ts b/cypress/e2e/database/row-duplication.cy.ts index c170d294..56129e5d 100644 --- a/cypress/e2e/database/row-duplication.cy.ts +++ b/cypress/e2e/database/row-duplication.cy.ts @@ -3,6 +3,7 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { AddPageSelectors, DatabaseGridSelectors, + RowControlsSelectors, waitForReactUpdate } from '../../support/selectors'; @@ -87,7 +88,7 @@ describe('Database Row Duplication', () => { // The drag icon should be visible after hovering // Try to find it by the data-testid we added or by its class - cy.get('[data-testid="row-accessory-button"]').then($button => { + RowControlsSelectors.rowAccessoryButton().then($button => { if ($button.length > 0) { cy.log('[STEP 10.1] Found row accessory button with data-testid'); cy.wrap($button.first()).click({ force: true }); @@ -121,7 +122,7 @@ describe('Database Row Duplication', () => { if (!found) { // Try clicking by the data-testid we added - cy.get('[data-testid="row-menu-duplicate"]').then($duplicate => { + RowControlsSelectors.rowMenuDuplicate().then($duplicate => { if ($duplicate.length > 0) { cy.log('[STEP 11.2] Found duplicate menu item by data-testid'); cy.wrap($duplicate).click(); diff --git a/cypress/e2e/database/row-insertion.cy.ts b/cypress/e2e/database/row-insertion.cy.ts index 0211aa0e..a682acc7 100644 --- a/cypress/e2e/database/row-insertion.cy.ts +++ b/cypress/e2e/database/row-insertion.cy.ts @@ -3,6 +3,7 @@ import { AuthTestUtils } from '../../support/auth-utils'; import { AddPageSelectors, DatabaseGridSelectors, + RowControlsSelectors, waitForReactUpdate } from '../../support/selectors'; @@ -93,7 +94,7 @@ describe('Database Row Insertion', () => { // Click on the drag icon/row accessory button cy.log('[STEP 11] Looking for and clicking row accessory button'); - cy.get('[data-testid="row-accessory-button"]').then($button => { + RowControlsSelectors.rowAccessoryButton().then($button => { if ($button.length > 0) { cy.log('[STEP 11.1] Found row accessory button with data-testid'); cy.wrap($button.first()).click({ force: true }); @@ -112,7 +113,7 @@ describe('Database Row Insertion', () => { cy.get('[role="menu"], [data-slot="dropdown-menu-content"]').should('be.visible'); // Click on the insert above option - using data-testid first - cy.get('[data-testid="row-menu-insert-above"]').then($insertAbove => { + RowControlsSelectors.rowMenuInsertAbove().then($insertAbove => { if ($insertAbove.length > 0) { cy.log('[STEP 12.1] Found insert above menu item by data-testid'); cy.wrap($insertAbove).click(); @@ -153,7 +154,7 @@ describe('Database Row Insertion', () => { cy.log('[STEP 16] Looking for and clicking row accessory button for second row'); // The row controls should appear for the second row after hovering - cy.get('[data-testid="row-accessory-button"]').then($buttons => { + RowControlsSelectors.rowAccessoryButton().then($buttons => { if ($buttons.length > 0) { cy.log('[STEP 16.1] Found row accessory button'); // Click the last visible button (should be for the hovered row) @@ -173,7 +174,7 @@ describe('Database Row Insertion', () => { cy.get('[role="menu"], [data-slot="dropdown-menu-content"]').should('be.visible'); // Click on the insert below option - using data-testid first - cy.get('[data-testid="row-menu-insert-below"]').then($insertBelow => { + RowControlsSelectors.rowMenuInsertBelow().then($insertBelow => { if ($insertBelow.length > 0) { cy.log('[STEP 17.1] Found insert below menu item by data-testid'); cy.wrap($insertBelow).click(); diff --git a/cypress/support/selectors.ts b/cypress/support/selectors.ts index a5d5e089..b03aefb3 100644 --- a/cypress/support/selectors.ts +++ b/cypress/support/selectors.ts @@ -363,6 +363,23 @@ export const FieldType = { FileMedia: 14 }; +/** + * Database Row Controls selectors + */ +export const RowControlsSelectors = { + // Row accessory button (appears on hover) + rowAccessoryButton: () => cy.get(byTestId('row-accessory-button')), + + // Row menu items + rowMenuDuplicate: () => cy.get(byTestId('row-menu-duplicate')), + rowMenuInsertAbove: () => cy.get(byTestId('row-menu-insert-above')), + rowMenuInsertBelow: () => cy.get(byTestId('row-menu-insert-below')), + rowMenuDelete: () => cy.get(byTestId('row-menu-delete')), + + // Delete confirmation + deleteRowConfirmButton: () => cy.get(byTestId('delete-row-confirm-button')), +}; + export function waitForReactUpdate(ms: number = 500) { return cy.wait(ms); } \ No newline at end of file