多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

411 lines
16 KiB

import { expect } from '@playwright/test';
import BasePage from '../../../Base';
import { ToolbarPage } from './index';
import { UITypes } from 'nocodb-sdk';
import { getTextExcludeIconText } from '../../../../tests/utils/general';
export class ToolbarFilterPage extends BasePage {
readonly toolbar: ToolbarPage;
constructor(toolbar: ToolbarPage) {
super(toolbar.rootPage);
this.toolbar = toolbar;
}
get() {
return this.rootPage.locator(`[data-testid="nc-filter-menu"]`);
}
async verify({ index, column, operator, value }: { index: number; column: string; operator: string; value: string }) {
const fieldLocator = this.get().locator('.nc-filter-field-select').nth(index);
const fieldText = await getTextExcludeIconText(fieldLocator);
expect(fieldText).toBe(column);
await expect(this.get().locator('.nc-filter-operation-select').nth(index)).toHaveText(operator);
await expect
.poll(async () => await this.get().locator('.nc-filter-value-select > input').nth(index).inputValue())
.toBe(value);
}
async verifyFilter({ title }: { title: string }) {
await expect(
this.get().locator(`[data-testid="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]')
).toBeChecked();
}
async clickAddFilter() {
await this.get().locator(`button:has-text("Add Filter")`).first().click();
}
// can reuse code for addFilterGroup and addFilter
// support for subOperation & datatype specific filter operations not supported yet
async addFilterGroup({
title,
operation,
_subOperation: _subOperation,
value,
_locallySaved: _locallySaved = false,
_dataType: _dataType,
_openModal: _openModal = false,
_skipWaitingResponse: _skipWaitingResponse = false, // used for undo (single request, less stable)
filterGroupIndex = 0,
filterLogicalOperator = 'AND',
}: {
title: string;
operation: string;
_subOperation?: string; // for date datatype
value?: string;
_locallySaved?: boolean;
_dataType?: string;
_openModal?: boolean;
_skipWaitingResponse?: boolean;
filterGroupIndex?: number;
filterLogicalOperator?: string;
}) {
await this.get().locator(`button:has-text("Add Filter Group")`).last().click();
const filterDropdown = this.get().locator('.menu-filter-dropdown').nth(filterGroupIndex);
await filterDropdown.waitFor({ state: 'visible' });
await filterDropdown.locator(`button:has-text("Add Filter")`).first().click();
const selectField = filterDropdown.locator('.nc-filter-field-select').last();
const selectOperation = filterDropdown.locator('.nc-filter-operation-select').last();
const selectValue = filterDropdown.locator('.nc-filter-value-select > input').last();
await selectField.waitFor({ state: 'visible' });
await selectField.click();
const fieldDropdown = this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.last()
.locator(`div[label="${title}"]:visible`);
await fieldDropdown.waitFor({ state: 'visible' });
await fieldDropdown.click();
await selectOperation.waitFor({ state: 'visible' });
await selectOperation.click();
const operationDropdown = this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-filter-comp-op')
.last()
.locator(`.ant-select-item:has-text("${operation}")`);
await operationDropdown.waitFor({ state: 'visible' });
await operationDropdown.click();
await selectValue.waitFor({ state: 'visible' });
await selectValue.fill(value);
if (filterGroupIndex) {
if (filterLogicalOperator === 'OR') {
const logicalButton = this.rootPage.locator('div.flex.nc-filter-logical-op').nth(filterGroupIndex - 1);
await logicalButton.waitFor({ state: 'visible' });
await logicalButton.click();
const logicalDropdown = this.rootPage.locator('div.ant-select-dropdown.nc-dropdown-filter-logical-op-group');
await logicalDropdown.waitFor({ state: 'visible' });
await logicalDropdown.locator(`.ant-select-item:has-text("${filterLogicalOperator}")`).click();
}
}
}
async add({
title,
operation,
subOperation,
value,
locallySaved = false,
dataType,
openModal = false,
// TODO: we do not wait for api response as there are cases where new filter will not be saved
skipWaitingResponse = false, // used for undo (single request, less stable)
}: {
title: string;
operation: string;
subOperation?: string; // for date datatype
value?: string;
locallySaved?: boolean;
dataType?: string;
openModal?: boolean;
skipWaitingResponse?: boolean;
}) {
if (!openModal) await this.get().locator(`button:has-text("Add Filter")`).first().click();
// TODO: Integrated the draft filter logic here as well, since when we add a filter its not saved till all
// its values are filled
skipWaitingResponse = true;
const selectedField = await getTextExcludeIconText(
this.rootPage.locator('.nc-filter-field-select .ant-select-selection-item')
);
if (selectedField !== title) {
await this.rootPage.locator('.nc-filter-field-select').last().click();
if (skipWaitingResponse) {
await this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${title}"]:visible`)
.click()
.then(() => {});
await this.rootPage.waitForTimeout(350);
} else {
await this.waitForResponse({
uiAction: async () =>
await this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${title}"]:visible`)
.click(),
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
}
}
const selectedOpType = await getTextExcludeIconText(this.rootPage.locator('.nc-filter-operation-select'));
if (selectedOpType !== operation) {
await this.rootPage.locator('.nc-filter-operation-select').click();
// first() : filter list has >, >=
if (skipWaitingResponse) {
await this.rootPage
.locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item:has-text("${operation}")`)
.first()
.click()
.then(() => {});
await this.rootPage.waitForTimeout(350);
} else {
await this.waitForResponse({
uiAction: async () =>
await this.rootPage
.locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item:has-text("${operation}")`)
.first()
.click(),
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
}
}
// subtype for date
if (dataType === UITypes.Date && subOperation) {
const selectedSubType = await getTextExcludeIconText(this.rootPage.locator('.nc-filter-sub_operation-select'));
if (selectedSubType !== subOperation) {
await this.rootPage.locator('.nc-filter-sub_operation-select').click();
// first() : filter list has >, >=
if (skipWaitingResponse) {
await this.rootPage
.locator('.nc-dropdown-filter-comp-sub-op')
.locator(`.ant-select-item:has-text("${subOperation}")`)
.first()
.click();
await this.rootPage.waitForTimeout(350);
} else {
await this.waitForResponse({
uiAction: async () =>
await this.rootPage
.locator('.nc-dropdown-filter-comp-sub-op')
.locator(`.ant-select-item:has-text("${subOperation}")`)
.first()
.click(),
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
}
}
}
// if value field was provided, fill it
if (value) {
let fillFilter: any = null;
switch (dataType) {
case UITypes.Year:
await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`).waitFor();
await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
break;
case UITypes.Time:
// eslint-disable-next-line no-case-declarations
const time = value.split(':');
await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`).waitFor();
await this.rootPage
.locator(`.ant-picker-time-panel-column:nth-child(1)`)
.locator(`.ant-picker-time-panel-cell:has-text("${time[0]}")`)
.click();
await this.rootPage
.locator(`.ant-picker-time-panel-column:nth-child(2)`)
.locator(`.ant-picker-time-panel-cell:has-text("${time[1]}")`)
.click();
await this.rootPage.locator(`.ant-btn-primary:has-text("Ok")`).click();
break;
case UITypes.Date:
if (subOperation === 'exact date') {
await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`).waitFor();
if (skipWaitingResponse) {
await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
await this.rootPage.waitForTimeout(350);
} else {
await this.waitForResponse({
uiAction: async () =>
await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(),
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
}
} else {
fillFilter = () => this.rootPage.locator('.nc-filter-value-select > input').last().fill(value);
if (skipWaitingResponse) {
await fillFilter();
await this.rootPage.waitForTimeout(350);
} else {
await this.waitForResponse({
uiAction: fillFilter,
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
}
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
await this.toolbar.parent.waitLoading();
}
break;
case UITypes.Duration:
if (skipWaitingResponse) {
await this.get().locator('.nc-filter-value-select').locator('input').fill(value);
await this.get().locator('.nc-filter-value-select').locator('input').press('Enter');
await this.rootPage.waitForTimeout(350);
} else {
await this.waitForResponse({
uiAction: async () => await this.get().locator('.nc-filter-value-select').locator('input').fill(value),
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
}
break;
case UITypes.Rating:
await this.get()
.locator('.ant-rate-star > div')
.nth(parseInt(value) - 1)
.click();
break;
case UITypes.MultiSelect:
await this.get().locator('.nc-filter-value-select').locator('.ant-select-arrow').click({ force: true });
// eslint-disable-next-line no-case-declarations
const v = value.split(',');
for (let i = 0; i < v.length; i++) {
await this.rootPage
.locator(`.nc-dropdown-multi-select-cell`)
.locator(`[data-testid="select-option-MultiSelect-filter"].nc-select-option-MultiSelect-${v[i]}`)
.click();
}
break;
case UITypes.SingleSelect:
// for single select field, the drop select arrow is visible only for some operations
if ((await this.get().locator('.nc-filter-value-select').locator('.ant-select-arrow').count()) > 0) {
await this.get().locator('.nc-filter-value-select').locator('.ant-select-arrow').click({ force: true });
} else {
await this.get().locator('.nc-filter-value-select').click({ force: true });
}
// check if value was an array
// eslint-disable-next-line no-case-declarations
const val = value.split(',');
if (val.length > 1) {
for (let i = 0; i < val.length; i++) {
await this.rootPage
.locator(`.nc-dropdown-multi-select-cell`)
.locator(`.nc-select-option-SingleSelect-${val[i]}`)
.click();
}
} else {
await this.rootPage
.locator(`.nc-dropdown-single-select-cell`)
.locator(`.nc-select-option-${title}-${value}`)
.click();
}
break;
case UITypes.User:
if (!['is blank', 'is not blank'].includes(operation)) {
await this.get().locator('.nc-filter-value-select').locator('.ant-select-arrow').click({ force: true });
const v = value.split(',');
for (let i = 0; i < v.length; i++) {
await this.rootPage
.locator(`.nc-dropdown-user-select-cell`)
.getByTestId('select-option-User-filter')
.getByText(v[i])
.click();
}
}
break;
default:
fillFilter = async () => {
await this.rootPage.locator('.nc-filter-value-select > input').last().clear({ force: true });
return this.rootPage.locator('.nc-filter-value-select > input').last().fill(value);
};
await this.waitForResponse({
uiAction: fillFilter,
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
await this.toolbar.parent.waitLoading();
break;
}
}
}
async reset({ networkValidation = true }: { networkValidation?: boolean } = {}) {
await this.toolbar.clickFilter();
if (networkValidation) {
await this.waitForResponse({
uiAction: async () => await this.get().locator('.nc-filter-item-remove-btn').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/filters/',
});
} else {
await this.get().locator('.nc-filter-item-remove-btn').click();
}
// TODO: Filter reset await not working all the time
await this.rootPage.waitForTimeout(650);
await this.toolbar.clickFilter();
}
async remove({ networkValidation = true }: { networkValidation?: boolean } = {}) {
if (networkValidation) {
await this.waitForResponse({
uiAction: async () => await this.get().locator('.nc-filter-item-remove-btn').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/filters/',
});
} else {
await this.get().locator('.nc-filter-item-remove-btn').click();
}
}
async columnOperatorList(param: { columnTitle: string }) {
await this.get().locator(`button:has-text("Add Filter")`).first().click();
const selectedField = await this.rootPage.locator('.nc-filter-field-select').textContent();
if (selectedField !== param.columnTitle) {
await this.rootPage.locator('.nc-filter-field-select').last().click();
await this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${param.columnTitle}"]:visible`)
.click();
}
await this.rootPage.locator('.nc-filter-operation-select').click();
const opList = this.rootPage
.locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item > .ant-select-item-option-content`);
// extract text from each element & put them in an array
const opListText = [];
for (let i = 0; i < (await opList.count()); i++) {
opListText.push(await opList.nth(i).textContent());
}
return opListText;
}
}