n8n/packages/testing/playwright/pages/DataTableDetails.ts
Declan Carroll f96cdb17db
Some checks are pending
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.14.1) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (25.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
test: Resolve 20 janitor scope-lockdown and dead-code violations (#27948)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 17:32:27 +00:00

404 lines
12 KiB
TypeScript

import { expect } from '@playwright/test';
import type { Locator } from '@playwright/test';
import { BasePage } from './BasePage';
export class DataTableDetails extends BasePage {
async goto(datatableId: string) {
await this.page.goto(`/datatables/${datatableId}`);
}
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<string> {
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<string> {
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<string[]> {
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('');
}
}