Browse Source

test: filters for Number, Decimal, Percent, Currency

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/5106/head
Raju Udava 2 years ago
parent
commit
448e047d25
  1. 54
      tests/playwright/package-lock.json
  2. 1
      tests/playwright/package.json
  3. 19
      tests/playwright/pages/Dashboard/Grid/index.ts
  4. 49
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  5. 131
      tests/playwright/setup/xcdb-records.ts
  6. 417
      tests/playwright/tests/filters.spec.ts

54
tests/playwright/package-lock.json generated

@ -11,6 +11,7 @@
"dependencies": {
"body-parser": "^1.20.1",
"express": "^4.18.2",
"nocodb-sdk": "^0.104.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
@ -2032,7 +2033,6 @@
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"dev": true,
"funding": [
{
"type": "individual",
@ -2713,6 +2713,14 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsep": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.8.tgz",
"integrity": "sha512-qofGylTGgYj9gZFsHuyWAN4jr35eJ66qJCK4eKDnldohuUoQFbU3iZn2zjvEbd9wOAhP9Wx5DsAAduTyE1PSWQ==",
"engines": {
"node": ">= 10.16.0"
}
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -3165,6 +3173,23 @@
"node": ">= 0.6"
}
},
"node_modules/nocodb-sdk": {
"version": "0.104.3",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.104.3.tgz",
"integrity": "sha512-C6uIeexVz2aMWmabpaut3Y/sI8Myp2cRDyKZIjRXmMjSdV1F8BIRrPWVTLGSgHxe6YECFtl+eLR5ma70eJsqCw==",
"dependencies": {
"axios": "^0.21.1",
"jsep": "^1.3.6"
}
},
"node_modules/nocodb-sdk/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/node-pre-gyp": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
@ -6431,8 +6456,7 @@
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"dev": true
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"forwarded": {
"version": "0.2.0",
@ -6898,6 +6922,11 @@
"esprima": "^4.0.0"
}
},
"jsep": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.8.tgz",
"integrity": "sha512-qofGylTGgYj9gZFsHuyWAN4jr35eJ66qJCK4eKDnldohuUoQFbU3iZn2zjvEbd9wOAhP9Wx5DsAAduTyE1PSWQ=="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -7248,6 +7277,25 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"nocodb-sdk": {
"version": "0.104.3",
"resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.104.3.tgz",
"integrity": "sha512-C6uIeexVz2aMWmabpaut3Y/sI8Myp2cRDyKZIjRXmMjSdV1F8BIRrPWVTLGSgHxe6YECFtl+eLR5ma70eJsqCw==",
"requires": {
"axios": "^0.21.1",
"jsep": "^1.3.6"
},
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
}
}
}
},
"node-pre-gyp": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",

1
tests/playwright/package.json

@ -45,6 +45,7 @@
"dependencies": {
"body-parser": "^1.20.1",
"express": "^4.18.2",
"nocodb-sdk": "^0.104.3",
"xlsx": "^0.18.5"
}
}

19
tests/playwright/pages/Dashboard/Grid/index.ts

@ -201,6 +201,25 @@ export class GridPage extends BasePage {
await this.dashboard.waitForLoaderToDisappear();
}
async verifyTotalRowCount({ count }: { count: number }) {
// wait for 100 ms and try again : 5 times
let i = 0;
await this.get().locator(`.nc-pagination`).waitFor();
let records = await this.get().locator(`[data-testid="grid-pagination"]`).allInnerTexts();
let recordCnt = records[0].split(' ')[0];
while (parseInt(recordCnt) !== count && i < 5) {
await this.get().locator(`.nc-pagination`).waitFor();
records = await this.get().locator(`[data-testid="grid-pagination"]`).allInnerTexts();
recordCnt = records[0].split(' ')[0];
// to ensure page loading is complete
await this.rootPage.waitForTimeout(500);
i++;
}
expect(parseInt(recordCnt)).toEqual(count);
}
private async pagination({ page }: { page: string }) {
await this.get().locator(`.nc-pagination`).waitFor();

49
tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -42,32 +42,47 @@ export class ToolbarFilterPage extends BasePage {
await this.get().locator(`button:has-text("Add Filter")`).first().click();
await this.rootPage.locator('.nc-filter-field-select').last().click();
const selectColumn = this.rootPage
await this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${columnTitle}"][aria-selected="false"]:visible`)
.locator(`div[label="${columnTitle}"]`)
.click();
await this.waitForResponse({
uiAction: selectColumn,
httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
// const selectColumn = this.rootPage
// .locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
// .locator(`div[label="${columnTitle}"]`)
// .click();
// await this.waitForResponse({
// uiAction: selectColumn,
// httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'],
// requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`,
// });
// await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
const selectedOpType = await this.rootPage.locator('.nc-filter-operation-select').textContent();
if (selectedOpType !== opType) {
await this.rootPage.locator('.nc-filter-operation-select').last().click();
const selectOpType = this.rootPage
await this.rootPage.locator('.nc-filter-operation-select').click();
// first() : filter list has >, >=
await this.rootPage
.locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item:has-text("${opType}")`)
.first()
.click();
await this.waitForResponse({
uiAction: selectOpType,
httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'],
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
}
// if (selectedOpType !== opType) {
// await this.rootPage.locator('.nc-filter-operation-select').last().click();
// // first() : filter list has >, >=
// const selectOpType = this.rootPage
// .locator('.nc-dropdown-filter-comp-op')
// .locator(`.ant-select-item:has-text("${opType}")`)
// .first()
// .click();
//
// await this.waitForResponse({
// uiAction: selectOpType,
// httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'],
// requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`,
// });
// await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
// }
// if value field was provided, fill it
if (value) {

131
tests/playwright/setup/xcdb-records.ts

@ -0,0 +1,131 @@
import { ColumnType, UITypes } from 'nocodb-sdk';
const rowMixedValue = (column: ColumnType, index: number) => {
// Array of country names
const countries = [
'Afghanistan',
'Albania',
'',
'Andorra',
'Angola',
'Antigua and Barbuda',
'Argentina',
null,
'Armenia',
'Australia',
'Austria',
'',
null,
];
// Array of sample random paragraphs (comma separated list of cities and countries). Not more than 200 characters
const longText = [
'Aberdeen, United Kingdom',
'Abidjan, Côte d’Ivoire',
'Abuja, Nigeria',
'',
'Addis Ababa, Ethiopia',
'Adelaide, Australia',
'Ahmedabad, India',
'Albuquerque, United States',
null,
'Alexandria, Egypt',
'Algiers, Algeria',
'Allahabad, India',
'',
null,
];
// Array of random integers, not more than 10000
const numbers = [33, null, 456, 333, 267, 34, 8754, 3234, 44, 33, null];
const decimals = [33.3, 456.34, 333.3, null, 267.5674, 34.0, 8754.0, 3234.547, 44.2647, 33.98, null];
const duration = [10, 20, 30, 40, 50, 60, null, 70, 80, 90, null];
const rating = [0, 1, 2, 3, null, 0, 4, 5, 0, 1, null];
// Array of random sample email strings (not more than 100 characters)
const emails = [
'jbutt@gmail.com',
'josephine_darakjy@darakjy.org',
'art@venere.org',
'',
null,
'donette.foller@cox.net',
'simona@morasca.com',
'mitsue_tollner@yahoo.com',
'leota@hotmail.com',
'sage_wieser@cox.net',
'',
null,
];
// Array of random sample phone numbers
const phoneNumbers = [
'1-541-754-3010',
'504-621-8927',
'810-292-9388',
'856-636-8749',
'907-385-4412',
'513-570-1893',
'419-503-2484',
'773-573-6914',
'',
null,
];
// Array of random sample URLs
const urls = [
'https://www.google.com',
'https://www.facebook.com',
'https://www.youtube.com',
'https://www.amazon.com',
'https://www.wikipedia.org',
'https://www.twitter.com',
'https://www.instagram.com',
'https://www.linkedin.com',
'https://www.reddit.com',
'https://www.tiktok.com',
'https://www.pinterest.com',
'https://www.netflix.com',
'https://www.microsoft.com',
'https://www.apple.com',
'',
null,
];
const singleSelect = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', null];
const multiSelect = ['jan,feb,mar', 'apr,may,jun', 'jul,aug,sep', 'oct,nov,dec', 'jan,feb,mar', null];
switch (column.uidt) {
case UITypes.Number:
case UITypes.Percent:
return numbers[index % numbers.length];
case UITypes.Decimal:
case UITypes.Currency:
return decimals[index % decimals.length];
case UITypes.Duration:
return duration[index % duration.length];
case UITypes.Rating:
return rating[index % rating.length];
case UITypes.SingleLineText:
return countries[index % countries.length];
case UITypes.Email:
return emails[index % emails.length];
case UITypes.PhoneNumber:
return phoneNumbers[index % phoneNumbers.length];
case UITypes.LongText:
return longText[index % longText.length];
case UITypes.Date:
return '2020-01-01';
case UITypes.URL:
return urls[index % urls.length];
case UITypes.SingleSelect:
return singleSelect[index % singleSelect.length];
case UITypes.MultiSelect:
return multiSelect[index % multiSelect.length];
default:
return `test-${index}`;
}
};
export { rowMixedValue };

417
tests/playwright/tests/filters.spec.ts

@ -0,0 +1,417 @@
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import setup from '../setup';
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar';
import { UITypes } from 'nocodb-sdk';
import { Api } from 'nocodb-sdk';
import { rowMixedValue } from '../setup/xcdb-records';
let dashboard: DashboardPage, toolbar: ToolbarPage;
let context: any;
let api: Api<any>;
let records = [];
const skipList = {
Number: ['is null', 'is not null', 'is blank', 'is not blank'],
Decimal: ['is null', 'is not null', 'is blank', 'is not blank'],
Percent: ['is null', 'is not null', 'is blank', 'is not blank'],
Currency: ['is null', 'is not null', 'is blank', 'is not blank'],
Rating: ['is null', 'is not null', 'is blank', 'is not blank'],
};
// define validateRowArray function
async function validateRowArray(param) {
const { rowCount } = param;
await dashboard.grid.verifyTotalRowCount({ count: rowCount });
// const { sequence, length, totalRowCount, column } = param;
//
// await dashboard.grid.verifyTotalRowCount({ count: totalRowCount });
//
// for (let j = 0; j < length; j++) {
// await dashboard.grid.cell.verify({
// index: j,
// columnHeader: column,
// value: sequence,
// });
// }
}
async function verifyFilter(param: { column: string; opType: string; value?: string; result: { rowCount: number } }) {
// if opType was included in skip list, skip it
if (skipList[param.column]?.includes(param.opType)) {
return;
}
await toolbar.clickFilter();
await toolbar.filter.add({
columnTitle: param.column,
opType: param.opType,
value: param.value,
isLocallySaved: false,
});
await toolbar.clickFilter();
// verify filtered rows
await validateRowArray({
rowCount: param.result.rowCount,
});
// Reset filter
await toolbar.filter.reset();
}
test.describe('Filter Tests: Numerical', () => {
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
toolbar = dashboard.grid.toolbar;
api = new Api({
baseURL: `http://localhost:8080/`,
headers: {
'xc-auth': context.token,
},
});
const columns = [
{
column_name: 'Id',
title: 'Id',
uidt: UITypes.ID,
},
{
column_name: 'Number',
title: 'Number',
uidt: UITypes.Number,
},
{
column_name: 'Decimal',
title: 'Decimal',
uidt: UITypes.Decimal,
},
{
column_name: 'Currency',
title: 'Currency',
uidt: UITypes.Currency,
},
{
column_name: 'Percent',
title: 'Percent',
uidt: UITypes.Percent,
},
{
column_name: 'Duration',
title: 'Duration',
uidt: UITypes.Duration,
},
{
column_name: 'Rating',
title: 'Rating',
uidt: UITypes.Rating,
},
];
try {
const project = await api.project.read(context.project.id);
const table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, {
table_name: 'numberBased',
title: 'numberBased',
columns: columns,
});
const rowAttributes = [];
for (let i = 0; i < 400; i++) {
const row = {
Number: rowMixedValue(columns[1], i),
Decimal: rowMixedValue(columns[2], i),
Currency: rowMixedValue(columns[3], i),
Percent: rowMixedValue(columns[4], i),
Duration: rowMixedValue(columns[5], i),
Rating: rowMixedValue(columns[6], i),
};
rowAttributes.push(row);
}
await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes);
records = await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 400 });
} catch (e) {
console.error(e);
}
});
test('Filter: Number', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'numberBased' });
const dataType = 'Number';
const filterList = [
{
op: '=',
value: '33',
rowCount: records.list.filter(r => r[dataType] === 33).length,
},
{
op: '!=',
value: '33',
rowCount: records.list.filter(r => r[dataType] !== 33).length,
},
{
op: 'is null',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not null',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: 'is blank',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not blank',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: '>',
value: '44',
rowCount: records.list.filter(r => r[dataType] > 44 && r[dataType] != null).length,
},
{
op: '>=',
value: '44',
rowCount: records.list.filter(r => r[dataType] >= 44 && r[dataType] != null).length,
},
{
op: '<',
value: '44',
rowCount: records.list.filter(r => r[dataType] < 44 && r[dataType] != null).length,
},
{
op: '<=',
value: '44',
rowCount: records.list.filter(r => r[dataType] <= 44 && r[dataType] != null).length,
},
];
for (let i = 0; i < filterList.length; i++) {
await verifyFilter({
column: dataType,
opType: filterList[i].op,
value: filterList[i].value,
result: { rowCount: filterList[i].rowCount },
});
}
});
test('Filter: Decimal', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'numberBased' });
const dataType = 'Decimal';
const filterList = [
{
op: '=',
value: '33.3',
rowCount: records.list.filter(r => r[dataType] === 33.3).length,
},
{
op: '!=',
value: '33.3',
rowCount: records.list.filter(r => r[dataType] !== 33.3).length,
},
{
op: 'is null',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not null',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: 'is blank',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not blank',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: '>',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] > 44.26 && r[dataType] != null).length,
},
{
op: '>=',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] >= 44.26 && r[dataType] != null).length,
},
{
op: '<',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] < 44.26 && r[dataType] != null).length,
},
{
op: '<=',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] <= 44.26 && r[dataType] != null).length,
},
];
for (let i = 0; i < filterList.length; i++) {
await verifyFilter({
column: dataType,
opType: filterList[i].op,
value: filterList[i].value,
result: { rowCount: filterList[i].rowCount },
});
}
});
test('Filter: Percent', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'numberBased' });
const dataType = 'Percent';
const filterList = [
{
op: '=',
value: '33',
rowCount: records.list.filter(r => r[dataType] === 33).length,
},
{
op: '!=',
value: '33',
rowCount: records.list.filter(r => r[dataType] !== 33).length,
},
{
op: 'is null',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not null',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: 'is blank',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not blank',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: '>',
value: '44',
rowCount: records.list.filter(r => r[dataType] > 44 && r[dataType] != null).length,
},
{
op: '>=',
value: '44',
rowCount: records.list.filter(r => r[dataType] >= 44 && r[dataType] != null).length,
},
{
op: '<',
value: '44',
rowCount: records.list.filter(r => r[dataType] < 44 && r[dataType] != null).length,
},
{
op: '<=',
value: '44',
rowCount: records.list.filter(r => r[dataType] <= 44 && r[dataType] != null).length,
},
];
for (let i = 0; i < filterList.length; i++) {
await verifyFilter({
column: dataType,
opType: filterList[i].op,
value: filterList[i].value,
result: { rowCount: filterList[i].rowCount },
});
}
});
test('Filter: Currency', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'numberBased' });
const dataType = 'Currency';
const filterList = [
{
op: '=',
value: '33.3',
rowCount: records.list.filter(r => r[dataType] === 33.3).length,
},
{
op: '!=',
value: '33.3',
rowCount: records.list.filter(r => r[dataType] !== 33.3).length,
},
{
op: 'is null',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not null',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: 'is blank',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
},
{
op: 'is not blank',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
},
{
op: '>',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] > 44.26 && r[dataType] != null).length,
},
{
op: '>=',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] >= 44.26 && r[dataType] != null).length,
},
{
op: '<',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] < 44.26 && r[dataType] != null).length,
},
{
op: '<=',
value: '44.26',
rowCount: records.list.filter(r => r[dataType] <= 44.26 && r[dataType] != null).length,
},
];
for (let i = 0; i < filterList.length; i++) {
await verifyFilter({
column: dataType,
opType: filterList[i].op,
value: filterList[i].value,
result: { rowCount: filterList[i].rowCount },
});
}
});
});
Loading…
Cancel
Save