import { expect } from '@playwright/test'; import type { Locator } from '@playwright/test'; import { BasePage } from './BasePage'; export class DataTableDetails extends BasePage { getPageWrapper() { return this.page.getByTestId('data-table-details-view'); } getDataTableProjectBreadcrumb() { return this.page.getByTestId('home-project'); } getDataTableBreadcrumb() { return this.page.getByTestId('data-table-header-name-input'); } async renameDataTable(newName: string) { const nameInput = this.getDataTableBreadcrumb(); await nameInput.click(); const input = nameInput.locator('input'); await input.fill(''); await input.fill(newName); await input.press('Enter'); } getVisibleColumns() { return this.page.getByTestId('data-table-column-header'); } getNoRowsMessage() { return this.page.getByTestId('data-table-no-rows-overlay'); } getAddColumnHeaderButton() { return this.page.getByTestId('data-table-add-column-trigger-button').first(); } getAddColumnTableButton() { return this.page.getByTestId('data-table-add-column-trigger-button').last(); } getAddColumnPopoverContent() { return this.page.getByTestId('add-column-popover-content'); } getAddColumnSubmitButton() { return this.page.getByTestId('data-table-add-column-submit-button'); } getColumnHeaderByName(name: string) { return this.page .getByTestId('data-table-column-header') .filter({ has: this.page.getByTestId('data-table-column-header-text').getByText(name) }); } async getColumnIdByName(name: string): Promise { const columnHeader = this.page .getByRole('columnheader') .filter({ has: this.page.getByTestId('data-table-column-header-text').getByText(name) }); const colId = await columnHeader.getAttribute('col-id'); if (!colId) { throw new Error(`Could not find col-id for column with name: ${name}`); } return colId; } async addColumn( name: string, type: 'string' | 'number' | 'boolean' | 'date', source: 'header' | 'table', ) { if (source === 'header') { await this.getAddColumnHeaderButton().click(); } else { await this.getAddColumnTableButton().click(); } await this.getAddColumnPopoverContent().waitFor({ state: 'visible' }); const nameInput = this.page .getByTestId('add-column-popover-content') .locator('input[type="text"]') .first(); await nameInput.fill(name); const typeSelect = this.page .getByTestId('add-column-popover-content') .locator('.n8n-select') .first(); await typeSelect.click(); const typeLabel = type === 'date' ? 'datetime' : type; await this.page.getByRole('option', { name: typeLabel, exact: true }).click(); await this.getAddColumnSubmitButton().click(); await this.getAddColumnPopoverContent().waitFor({ state: 'hidden' }); } getAddRowHeaderButton() { return this.page.getByTestId('data-table-header-add-row-button'); } getAddRowTableButton() { return this.page.locator('[data-test-id="data-table-grid"] .add-row-cell button'); } async addRow() { await this.getAddRowHeaderButton().click(); } async addRowFromTable() { await this.getAddRowTableButton().click(); } getDataRows() { return this.page.locator( '[data-test-id="data-table-grid"] .ag-center-cols-container [row-index]', ); } getRowSelectionCheckbox(rowIndex: number) { return this.page.locator( `[data-test-id="data-table-grid"] .ag-center-cols-container [row-index="${rowIndex}"] [col-id="ag-Grid-SelectionColumn"] input[type="checkbox"]`, ); } async selectRow(rowIndex: number) { const checkbox = this.getRowSelectionCheckbox(rowIndex); await checkbox.check(); } getSelectedItemsInfo() { return this.page.getByTestId('selected-items-info'); } getDeleteSelectedButton() { return this.page.getByTestId('delete-selected-button'); } getClearSelectionButton() { return this.page.getByTestId('clear-selection-button'); } async deleteSelectedRows() { await this.getDeleteSelectedButton().click(); const confirmButton = this.page.locator('.btn--confirm'); await confirmButton.click(); } async clearSelection() { await this.getClearSelectionButton().click(); } getColumnHeaderActions(columnName: string) { const columnHeader = this.page.getByRole('columnheader').filter({ has: this.page.getByTestId('data-table-column-header-text').getByText(columnName), }); return columnHeader.getByTestId('data-table-column-header-actions'); } async deleteColumn(columnName: string) { const columnHeader = this.page.getByTestId('data-table-column-header').filter({ has: this.page.getByTestId('data-table-column-header-text').getByText(columnName), }); await columnHeader.hover(); const actionsButton = this.getColumnHeaderActions(columnName); await actionsButton.click(); await this.page .getByTestId('data-table-column-header-actions-item-delete') .filter({ visible: true }) .click(); const confirmButton = this.page.locator('.btn--confirm'); await confirmButton.click(); } async openColumnFilter(columnName: string) { const columnHeader = this.page.getByRole('columnheader').filter({ has: this.page.getByTestId('data-table-column-header-text').getByText(columnName), }); await columnHeader.hover(); const filterButton = columnHeader.getByTestId('data-table-column-header-filter-button'); await filterButton.click(); } private async openFilterPanelAndWait(columnName: string) { await this.openColumnFilter(columnName); const filterPanel = this.page.locator('.ag-filter'); await filterPanel.waitFor({ state: 'visible' }); return filterPanel; } private async selectPickerOption(label: string) { await this.page.locator('.ag-picker-field-icon').click(); await this.page.locator('.ag-select-list-item').filter({ hasText: label }).first().click(); } private async selectFilterOperator(condition: 'equals' | 'greaterThan' | 'lessThan') { if (condition === 'equals') return; const label = condition === 'greaterThan' ? 'Greater than' : 'Less than'; await this.selectPickerOption(label); } private async fillFilterValue( filterPanel: Locator, value: string, type: 'text' | 'number' | 'date', ) { let input: Locator; if (type === 'number') { input = filterPanel.locator('input[type="number"]').first(); } else if (type === 'text') { input = filterPanel.locator('input[type="text"]').first(); } else { input = filterPanel.locator('input').first(); } await input.fill(value); return input; } async clearColumnFilter(columnName: string) { await this.openColumnFilter(columnName); const filterPanel = this.page.locator('.ag-filter'); await filterPanel.locator('[data-ref="resetFilterButton"]').click(); } async setTextFilter(columnName: string, value: string) { await this.openColumnFilter(columnName); const filterPanel = this.page.locator('.ag-filter'); await filterPanel.waitFor({ state: 'visible' }); const filterInput = filterPanel.locator('input[type="text"]').first(); await filterInput.fill(value); await this.page.keyboard.press('Enter'); } async setNumberFilter( columnName: string, value: string, condition: 'equals' | 'greaterThan' | 'lessThan' = 'equals', ) { const filterPanel = await this.openFilterPanelAndWait(columnName); await this.selectFilterOperator(condition); const filterInput = await this.fillFilterValue(filterPanel, value, 'number'); // Wait for the value to be set before pressing Enter await expect(filterInput).toHaveValue(value); await this.page.keyboard.press('Enter'); } async setBooleanFilter(columnName: string, value: boolean) { await this.openFilterPanelAndWait(columnName); await this.selectPickerOption(value ? 'True' : 'False'); } async setDateFilter( columnName: string, value: string, condition: 'equals' | 'greaterThan' | 'lessThan' = 'equals', ) { const filterPanel = await this.openFilterPanelAndWait(columnName); await this.selectFilterOperator(condition); await this.fillFilterValue(filterPanel, value, 'date'); await this.page.keyboard.press('Enter'); } getPagination() { return this.page.getByTestId('data-table-content-pagination'); } async setPageSize(size: '10' | '20' | '50') { const pagination = this.getPagination(); const selectTrigger = pagination.locator('.el-pagination__sizes .el-select'); await selectTrigger.click(); await this.page.getByRole('option').getByText(`${size}/page`).click(); } getCell(rowIndex: number, columnId: string) { const cell = this.page.locator( `[data-test-id="data-table-grid"] .ag-center-cols-container [row-index="${rowIndex}"] [col-id="${columnId}"]`, ); return cell; } async getCellValue( rowIndex: number, columnId: string, type: 'string' | 'number' | 'boolean' | 'date', ): Promise { const cell = this.getCell(rowIndex, columnId); if (type === 'boolean') { const checkbox = cell.locator('input[type="checkbox"]'); const isChecked = await checkbox.isChecked(); return isChecked ? 'true' : 'false'; } else { const text = await cell.textContent(); return (text ?? '').trim(); } } async setCellValue( rowIndex: number, columnId: string, value: string, type: 'string' | 'number' | 'boolean' | 'date', options?: { skipDoubleClick?: boolean }, ) { const cell = this.getCell(rowIndex, columnId); if (!options?.skipDoubleClick) { await cell.dblclick(); } if (type === 'string') { const input = this.page.locator('.ag-text-area-input'); await input.fill(value); await input.press('Enter'); } else if (type === 'date') { const input = this.page.locator('#data-table-datepicker'); await input.fill(value); await input.press('Enter'); } else if (type === 'boolean') { const checkbox = cell.locator('input[type="checkbox"]'); const shouldBeChecked = value === 'true' || value === '1'; if (shouldBeChecked) { await checkbox.check(); } else { await checkbox.uncheck(); } await this.page.keyboard.press('Enter'); } else if (type === 'number') { const input = cell.locator('input'); await input.fill(value); await input.press('Enter'); } } async getColumnOrder(): Promise { const headers = this.page.getByRole('columnheader'); const count = await headers.count(); const columnData: Array<{ index: number; name: string }> = []; for (let i = 0; i < count; i++) { const header = headers.nth(i); const ariaColIndex = await header.getAttribute('aria-colindex'); const textElement = header.getByTestId('data-table-column-header-text'); const textCount = await textElement.count(); if (textCount > 0 && ariaColIndex) { // Use innerText instead of textContent to avoid getting hidden input values const text = await textElement.innerText(); if (text) { columnData.push({ index: parseInt(ariaColIndex, 10), name: text.trim(), }); } } } columnData.sort((a, b) => a.index - b.index); return columnData.map((col) => col.name); } async dragColumnToPosition(sourceColumnName: string, targetColumnName: string) { const sourceColumn = this.getColumnHeaderByName(sourceColumnName); const targetColumn = this.getColumnHeaderByName(targetColumnName); await sourceColumn.dragTo(targetColumn); } getSearchInput() { return this.page.getByTestId('data-table-search-input'); } async search(query: string) { const searchInput = this.getSearchInput(); await searchInput.fill(query); // Wait for debounce await this.page.waitForTimeout(300); await this.page.getByText('Loading...').waitFor({ state: 'hidden' }); } async clearSearch() { await this.search(''); } }