Browse Source

test: sync

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/6278/head
Raju Udava 1 year ago
parent
commit
b2134c0ff9
  1. 3
      tests/playwright/pages/Account/AppStore.ts
  2. 2
      tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts
  3. 17
      tests/playwright/pages/Dashboard/BulkUpdate/index.ts
  4. 107
      tests/playwright/pages/Dashboard/Details/ErdPage.ts
  5. 3
      tests/playwright/pages/Dashboard/Details/index.ts
  6. 46
      tests/playwright/pages/Dashboard/Grid/columnHeader.ts
  7. 25
      tests/playwright/pages/Dashboard/Grid/index.ts
  8. 21
      tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts
  9. 22
      tests/playwright/pages/Dashboard/ProjectView/Metadata.ts
  10. 2
      tests/playwright/pages/Dashboard/QrCodeOverlay/index.ts
  11. 2
      tests/playwright/pages/Dashboard/Settings/DataSources.ts
  12. 43
      tests/playwright/pages/Dashboard/TreeView.ts
  13. 4
      tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
  14. 2
      tests/playwright/pages/Dashboard/common/Cell/DateCell.ts
  15. 2
      tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts
  16. 2
      tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts
  17. 2
      tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts
  18. 4
      tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts
  19. 2
      tests/playwright/pages/Dashboard/common/Cell/YearCell.ts
  20. 39
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  21. 10
      tests/playwright/pages/Dashboard/common/Footbar/index.ts
  22. 10
      tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts
  23. 2
      tests/playwright/pages/Dashboard/common/ProjectMenu/index.ts
  24. 39
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  25. 2
      tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts
  26. 2
      tests/playwright/pages/Dashboard/common/Toolbar/SearchData.ts
  27. 4
      tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts
  28. 47
      tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts
  29. 32
      tests/playwright/pages/Dashboard/common/Toolbar/index.ts
  30. 16
      tests/playwright/pages/Dashboard/common/Topbar/Share.ts
  31. 7
      tests/playwright/pages/Dashboard/common/Topbar/index.ts
  32. 14
      tests/playwright/pages/Dashboard/commonBase/Erd.ts
  33. 7
      tests/playwright/setup/db.ts
  34. 27
      tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts
  35. 26
      tests/playwright/tests/db/features/baseShare.spec.ts
  36. 167
      tests/playwright/tests/db/features/erd.spec.ts
  37. 3
      tests/playwright/tests/db/features/filters.spec.ts
  38. 54
      tests/playwright/tests/db/features/keyboardShortcuts.spec.ts
  39. 2
      tests/playwright/tests/db/features/language.spec.ts
  40. 95
      tests/playwright/tests/db/features/metaSync.spec.ts
  41. 4
      tests/playwright/tests/db/features/swagger.spec.ts
  42. 8
      tests/playwright/tests/db/features/timezone.spec.ts
  43. 18
      tests/playwright/tests/db/features/undo-redo.spec.ts
  44. 95
      tests/playwright/tests/db/general/projectOperations.spec.ts
  45. 4
      tests/playwright/tests/db/general/tableOperations.spec.ts
  46. 10
      tests/playwright/tests/db/general/viewMenu.spec.ts
  47. 71
      tests/playwright/tests/db/views/viewForm.spec.ts

3
tests/playwright/pages/Account/AppStore.ts

@ -1,4 +1,3 @@
import { expect } from '@playwright/test';
import BasePage from '../Base'; import BasePage from '../Base';
import { AccountPage } from './index'; import { AccountPage } from './index';
@ -23,7 +22,7 @@ export class AccountAppStorePage extends BasePage {
} }
async install({ name }: { name: string }) { async install({ name }: { name: string }) {
const card = await this.accountPage.get().locator(`.nc-app-store-card-${name}`); const card = this.accountPage.get().locator(`.nc-app-store-card-${name}`);
await card.click(); await card.click();
// todo: Hack to solve the issue when if the test installing a plugin fails, the next test will fail because the plugin is already installed // todo: Hack to solve the issue when if the test installing a plugin fails, the next test will fail because the plugin is already installed

2
tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts

@ -16,7 +16,7 @@ export class BarcodeOverlay extends BasePage {
async verifyBarcodeSvgValue(expectedValue: string) { async verifyBarcodeSvgValue(expectedValue: string) {
const foundBarcodeSvg = await this.get().getByTestId('barcode').innerHTML(); const foundBarcodeSvg = await this.get().getByTestId('barcode').innerHTML();
await expect(foundBarcodeSvg).toContain(expectedValue); expect(foundBarcodeSvg).toContain(expectedValue);
} }
async clickCloseButton() { async clickCloseButton() {

17
tests/playwright/pages/Dashboard/BulkUpdate/index.ts

@ -1,7 +1,6 @@
import { expect, Locator } from '@playwright/test'; import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base'; import BasePage from '../../Base';
import { DashboardPage } from '..'; import { DashboardPage } from '..';
import { DateTimeCellPageObject } from '../common/Cell/DateTimeCell';
import { getTextExcludeIconText } from '../../../tests/utils/general'; import { getTextExcludeIconText } from '../../../tests/utils/general';
export class BulkUpdatePage extends BasePage { export class BulkUpdatePage extends BasePage {
@ -29,17 +28,17 @@ export class BulkUpdatePage extends BasePage {
} }
async getInactiveColumn(index: number) { async getInactiveColumn(index: number) {
const inactiveColumns = await this.columnsDrawer.locator('.ant-card'); const inactiveColumns = this.columnsDrawer.locator('.ant-card');
return inactiveColumns.nth(index); return inactiveColumns.nth(index);
} }
async getActiveColumn(index: number) { async getActiveColumn(index: number) {
const activeColumns = await this.form.locator('[data-testid="nc-bulk-update-fields"]'); const activeColumns = this.form.locator('[data-testid="nc-bulk-update-fields"]');
return activeColumns.nth(index); return activeColumns.nth(index);
} }
async getInactiveColumns() { async getInactiveColumns() {
const inactiveColumns = await this.columnsDrawer.locator('.ant-card'); const inactiveColumns = this.columnsDrawer.locator('.ant-card');
const inactiveColumnsCount = await inactiveColumns.count(); const inactiveColumnsCount = await inactiveColumns.count();
const inactiveColumnsTitles = []; const inactiveColumnsTitles = [];
// get title for each inactive column // get title for each inactive column
@ -52,7 +51,7 @@ export class BulkUpdatePage extends BasePage {
} }
async getActiveColumns() { async getActiveColumns() {
const activeColumns = await this.form.locator('[data-testid="nc-bulk-update-fields"]'); const activeColumns = this.form.locator('[data-testid="nc-bulk-update-fields"]');
const activeColumnsCount = await activeColumns.count(); const activeColumnsCount = await activeColumns.count();
const activeColumnsTitles = []; const activeColumnsTitles = [];
// get title for each active column // get title for each active column
@ -67,7 +66,7 @@ export class BulkUpdatePage extends BasePage {
} }
async removeField(index: number) { async removeField(index: number) {
const removeFieldButton = await this.form.locator('[data-testid="nc-bulk-update-fields"]'); const removeFieldButton = this.form.locator('[data-testid="nc-bulk-update-fields"]');
const removeFieldButtonCount = await removeFieldButton.count(); const removeFieldButtonCount = await removeFieldButton.count();
await removeFieldButton.nth(index).locator('[data-testid="nc-bulk-update-fields-remove-icon"]').click(); await removeFieldButton.nth(index).locator('[data-testid="nc-bulk-update-fields-remove-icon"]').click();
const newRemoveFieldButtonCount = await removeFieldButton.count(); const newRemoveFieldButtonCount = await removeFieldButton.count();
@ -75,7 +74,7 @@ export class BulkUpdatePage extends BasePage {
} }
async addField(index: number) { async addField(index: number) {
const addFieldButton = await this.columnsDrawer.locator('.ant-card'); const addFieldButton = this.columnsDrawer.locator('.ant-card');
const addFieldButtonCount = await addFieldButton.count(); const addFieldButtonCount = await addFieldButton.count();
await addFieldButton.nth(index).click(); await addFieldButton.nth(index).click();
const newAddFieldButtonCount = await addFieldButton.count(); const newAddFieldButtonCount = await addFieldButton.count();
@ -143,7 +142,7 @@ export class BulkUpdatePage extends BasePage {
case 'attachment': case 'attachment':
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const attachFileAction = field.locator('[data-testid="attachment-cell-file-picker-button"]').click(); const attachFileAction = field.locator('[data-testid="attachment-cell-file-picker-button"]').click();
await this.attachFile({ filePickUIAction: attachFileAction, filePath: value }); await this.attachFile({ filePickUIAction: attachFileAction, filePath: [value] });
break; break;
case 'date': case 'date':
{ {
@ -174,7 +173,7 @@ export class BulkUpdatePage extends BasePage {
awaitResponse?: boolean; awaitResponse?: boolean;
} = {}) { } = {}) {
await this.bulkUpdateButton.click(); await this.bulkUpdateButton.click();
const confirmModal = await this.rootPage.locator('.ant-modal-confirm'); const confirmModal = this.rootPage.locator('.ant-modal-confirm');
const saveRowAction = () => confirmModal.locator('.ant-btn-primary').click(); const saveRowAction = () => confirmModal.locator('.ant-btn-primary').click();
if (!awaitResponse) { if (!awaitResponse) {

107
tests/playwright/pages/Dashboard/Details/ErdPage.ts

@ -0,0 +1,107 @@
import BasePage from '../../Base';
import { expect, Locator } from '@playwright/test';
import { DetailsPage } from './index';
export class ErdPage extends BasePage {
readonly detailsPage: DetailsPage;
readonly contextMenuBase: Locator;
readonly contextMenu = {};
readonly btn_fullScreen: Locator;
readonly btn_zoomIn: Locator;
readonly btn_zoomOut: Locator;
constructor(details: DetailsPage) {
super(details.rootPage);
this.detailsPage = details;
this.btn_fullScreen = this.get().locator('.nc-erd-histogram > .nc-icon');
this.btn_zoomIn = this.get().locator('.nc-erd-zoom-btn').nth(0);
this.btn_zoomOut = this.get().locator('.nc-erd-zoom-btn').nth(1);
this.contextMenuBase = this.get().locator('.nc-erd-context-menu');
this.contextMenu['Show Columns'] = this.contextMenuBase.locator('.ant-checkbox-wrapper').nth(0);
this.contextMenu['Show Primary and Foreign Keys'] = this.contextMenuBase.locator('.ant-checkbox-wrapper').nth(1);
this.contextMenu['Show SQL Views'] = this.contextMenuBase.locator('.ant-checkbox-wrapper').nth(2);
}
get() {
// pop up when triggered from data sources page
return this.rootPage.locator('.vue-flow');
}
async verifyNode({
tableName,
columnName,
columnNameShouldNotExist,
}: {
tableName: string;
columnName?: string;
columnNameShouldNotExist?: string;
}) {
await this.get().locator(`.nc-erd-table-node-${tableName}`).waitFor({ state: 'visible' });
if (columnName) {
await this.get().locator(`.nc-erd-table-node-${tableName}-column-${columnName}`).waitFor({ state: 'visible' });
}
if (columnNameShouldNotExist) {
await this.get()
.locator(`.nc-erd-table-node-${tableName}-column-${columnNameShouldNotExist}`)
.waitFor({ state: 'hidden' });
}
}
async verifyNodeDoesNotExist({ tableName }: { tableName: string }) {
await this.get().locator(`.nc-erd-table-node-${tableName}`).waitFor({ state: 'hidden' });
}
async verifyColumns({ tableName, columns }: { tableName: string; columns: string[] }) {
for (const column of columns) {
await this.verifyNode({ tableName, columnName: column });
}
}
async verifyNodesCount(count: number) {
await expect(this.get().locator('.nc-erd-table-node')).toHaveCount(count);
}
async verifyEdgesCount({
count,
circleCount,
rectangleCount,
}: {
count: number;
circleCount: number;
rectangleCount: number;
}) {
await expect(this.get().locator('.vue-flow__edge')).toHaveCount(count);
await expect(this.get().locator('.nc-erd-edge-circle')).toHaveCount(circleCount);
await expect(this.get().locator('.nc-erd-edge-rect')).toHaveCount(rectangleCount);
}
async verifyJunctionTableLabel({ tableTitle, tableName }: { tableName: string; tableTitle: string }) {
await this.get().locator(`.nc-erd-table-label-${tableTitle}-${tableName}`).waitFor({
state: 'visible',
});
}
async clickShowColumnNames() {
await this.contextMenu['Show Columns'].click();
await (await this.get().elementHandle())?.waitForElementState('stable');
}
async clickShowPkAndFk() {
await this.contextMenu['Show Primary and Foreign Keys'].click();
await (await this.get().elementHandle())?.waitForElementState('stable');
}
async clickShowSqlViews() {
await this.contextMenu['Show SQL Views'].click();
await (await this.get().elementHandle())?.waitForElementState('stable');
}
async close() {
await this.get().click();
await this.rootPage.keyboard.press('Escape');
await this.get().waitFor({ state: 'hidden' });
}
}

3
tests/playwright/pages/Dashboard/Details/index.ts

@ -3,11 +3,13 @@ import BasePage from '../../Base';
import { TopbarPage } from '../common/Topbar'; import { TopbarPage } from '../common/Topbar';
import { Locator } from '@playwright/test'; import { Locator } from '@playwright/test';
import { WebhookPage } from './WebhookPage'; import { WebhookPage } from './WebhookPage';
import { ErdPage } from './ErdPage';
export class DetailsPage extends BasePage { export class DetailsPage extends BasePage {
readonly dashboard: DashboardPage; readonly dashboard: DashboardPage;
readonly topbar: TopbarPage; readonly topbar: TopbarPage;
readonly webhook: WebhookPage; readonly webhook: WebhookPage;
readonly relations: ErdPage;
readonly tab_webhooks: Locator; readonly tab_webhooks: Locator;
readonly tab_apiSnippet: Locator; readonly tab_apiSnippet: Locator;
@ -21,6 +23,7 @@ export class DetailsPage extends BasePage {
this.dashboard = dashboard; this.dashboard = dashboard;
this.topbar = dashboard.grid.topbar; this.topbar = dashboard.grid.topbar;
this.webhook = new WebhookPage(this); this.webhook = new WebhookPage(this);
this.relations = new ErdPage(this);
this.tab_webhooks = this.get().locator(`[data-testid="nc-webhooks-tab"]`); this.tab_webhooks = this.get().locator(`[data-testid="nc-webhooks-tab"]`);
this.tab_apiSnippet = this.get().locator(`[data-testid="nc-apis-tab"]`); this.tab_apiSnippet = this.get().locator(`[data-testid="nc-apis-tab"]`);

46
tests/playwright/pages/Dashboard/Grid/columnHeader.ts

@ -0,0 +1,46 @@
import { GridPage } from '../Grid';
import BasePage from '../../Base';
import { expect, Locator } from '@playwright/test';
export class ColumnHeaderPageObject extends BasePage {
readonly grid: GridPage;
readonly btn_addColumn: Locator;
readonly btn_selectAll: Locator;
constructor(grid: GridPage) {
super(grid.rootPage);
this.grid = grid;
this.btn_addColumn = this.get().locator(`.nc-grid-add-edit-column`);
this.btn_selectAll = this.get().locator(`[data-testid="nc-check-all"]`);
}
get() {
return this.rootPage.locator('.nc-grid-header');
}
async getColumnHeader({ title }: { title: string }) {
return this.get().locator(`th[data-title="${title}"]`);
}
async getColumnHeaderContextMenu({ title }: { title: string }) {
return (await this.getColumnHeader({ title })).locator(`.nc-ui-dt-dropdown`);
}
async verifyLockMode() {
// add column button
await expect(this.btn_addColumn).toBeVisible({ visible: false });
// column header context menu
expect(await this.get().locator('.nc-ui-dt-dropdown').count()).toBe(0);
}
async verifyCollaborativeMode() {
// add column button
await expect(this.btn_addColumn).toBeVisible({ visible: true });
// column header context menu
expect(await this.get().locator('.nc-ui-dt-dropdown').count()).toBeGreaterThan(1);
}
}

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

@ -12,6 +12,7 @@ import { BarcodeOverlay } from '../BarcodeOverlay';
import { RowPageObject } from './Row'; import { RowPageObject } from './Row';
import { WorkspaceMenuObject } from '../common/WorkspaceMenu'; import { WorkspaceMenuObject } from '../common/WorkspaceMenu';
import { GroupPageObject } from './Group'; import { GroupPageObject } from './Group';
import { ColumnHeaderPageObject } from './columnHeader';
export class GridPage extends BasePage { export class GridPage extends BasePage {
readonly dashboard: DashboardPage; readonly dashboard: DashboardPage;
@ -19,6 +20,7 @@ export class GridPage extends BasePage {
readonly dashboardPage: DashboardPage; readonly dashboardPage: DashboardPage;
readonly qrCodeOverlay: QrCodeOverlay; readonly qrCodeOverlay: QrCodeOverlay;
readonly barcodeOverlay: BarcodeOverlay; readonly barcodeOverlay: BarcodeOverlay;
readonly columnHeader: ColumnHeaderPageObject;
readonly column: ColumnPageObject; readonly column: ColumnPageObject;
readonly cell: CellPageObject; readonly cell: CellPageObject;
readonly topbar: TopbarPage; readonly topbar: TopbarPage;
@ -29,6 +31,8 @@ export class GridPage extends BasePage {
readonly rowPage: RowPageObject; readonly rowPage: RowPageObject;
readonly groupPage: GroupPageObject; readonly groupPage: GroupPageObject;
readonly btn_addNewRow: Locator;
constructor(dashboardPage: DashboardPage) { constructor(dashboardPage: DashboardPage) {
super(dashboardPage.rootPage); super(dashboardPage.rootPage);
this.dashboard = dashboardPage; this.dashboard = dashboardPage;
@ -36,6 +40,7 @@ export class GridPage extends BasePage {
this.qrCodeOverlay = new QrCodeOverlay(this); this.qrCodeOverlay = new QrCodeOverlay(this);
this.barcodeOverlay = new BarcodeOverlay(this); this.barcodeOverlay = new BarcodeOverlay(this);
this.column = new ColumnPageObject(this); this.column = new ColumnPageObject(this);
this.columnHeader = new ColumnHeaderPageObject(this);
this.cell = new CellPageObject(this); this.cell = new CellPageObject(this);
this.topbar = new TopbarPage(this); this.topbar = new TopbarPage(this);
this.toolbar = new ToolbarPage(this); this.toolbar = new ToolbarPage(this);
@ -44,6 +49,26 @@ export class GridPage extends BasePage {
this.workspaceMenu = new WorkspaceMenuObject(this); this.workspaceMenu = new WorkspaceMenuObject(this);
this.rowPage = new RowPageObject(this); this.rowPage = new RowPageObject(this);
this.groupPage = new GroupPageObject(this); this.groupPage = new GroupPageObject(this);
this.btn_addNewRow = this.get().locator('.nc-grid-add-new-cell');
}
async verifyLockMode() {
// add new row button
expect(await this.btn_addNewRow.count()).toBe(0);
await this.toolbar.verifyLockMode();
await this.footbar.verifyLockMode();
await this.columnHeader.verifyLockMode();
}
async verifyCollaborativeMode() {
// add new row button
expect(await this.btn_addNewRow.count()).toBe(1);
await this.toolbar.verifyCollaborativeMode();
await this.footbar.verifyCollaborativeMode();
await this.columnHeader.verifyCollaborativeMode();
} }
get() { get() {

21
tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts

@ -1,15 +1,18 @@
import BasePage from '../../Base'; import BasePage from '../../Base';
import { ProjectViewPage } from './index'; import { ProjectViewPage } from './index';
import { Locator } from '@playwright/test'; import { Locator } from '@playwright/test';
import { MetaDataPage } from './Metadata';
export class DataSourcePage extends BasePage { export class DataSourcePage extends BasePage {
readonly projectView: ProjectViewPage; readonly projectView: ProjectViewPage;
readonly databaseType: Locator; readonly databaseType: Locator;
readonly metaData: MetaDataPage;
constructor(projectView: ProjectViewPage) { constructor(projectView: ProjectViewPage) {
super(projectView.rootPage); super(projectView.rootPage);
this.projectView = projectView; this.projectView = projectView;
this.databaseType = this.get().locator('.nc-extdb-db-type'); this.databaseType = this.get().locator('.nc-extdb-db-type');
this.metaData = new MetaDataPage(this);
} }
get() { get() {
@ -28,12 +31,22 @@ export class DataSourcePage extends BasePage {
return list; return list;
} }
async openMetaSync({ rowIndex }: { rowIndex: number }) {
// 0th offset for header
const row = this.get()
.locator('.ds-table-row')
.nth(rowIndex + 1);
await row.locator('button.nc-action-btn:has-text("Sync Metadata")').click();
}
async openERD({ rowIndex }: { rowIndex: number }) { async openERD({ rowIndex }: { rowIndex: number }) {
// hardwired // hardwired
await this.rootPage.locator('button.nc-action-btn').nth(1).click(); // await this.rootPage.locator('button.nc-action-btn:has-text("Relations")').click();
// const row = this.get().locator('.ds-table-row').nth(rowIndex); // 0th offset for header
// await row.locator('.ds-table-actions').locator('button.nc-action-btn').waitFor(); const row = this.get()
// await row.locator('.ds-table-actions').locator('button.nc-action-btn').nth(1).click(); .locator('.ds-table-row')
.nth(rowIndex + 1);
await row.locator('button.nc-action-btn:has-text("Relations")').click();
} }
} }

22
tests/playwright/pages/Dashboard/Settings/Metadata.ts → tests/playwright/pages/Dashboard/ProjectView/Metadata.ts

@ -1,18 +1,15 @@
import { expect } from '@playwright/test'; import { expect } from '@playwright/test';
import BasePage from '../../Base'; import BasePage from '../../Base';
import { DataSourcesPage } from './DataSources';
import { getTextExcludeIconText } from '../../../tests/utils/general'; import { getTextExcludeIconText } from '../../../tests/utils/general';
import { DataSourcePage } from './DataSourcePage';
export class MetaDataPage extends BasePage { export class MetaDataPage extends BasePage {
private readonly dataSources: DataSourcesPage; constructor(dataSource: DataSourcePage) {
super(dataSource.rootPage);
constructor(dataSources: DataSourcesPage) {
super(dataSources.rootPage);
this.dataSources = dataSources;
} }
get() { get() {
return this.dataSources.get(); return this.rootPage.locator('div.ant-modal-content');
} }
async clickReload() { async clickReload() {
@ -24,6 +21,13 @@ export class MetaDataPage extends BasePage {
await this.get().locator(`.animate-spin`).waitFor({ state: 'detached' }); await this.get().locator(`.animate-spin`).waitFor({ state: 'detached' });
} }
async close() {
await this.get().click();
await this.rootPage.keyboard.press('Escape');
await this.rootPage.keyboard.press('Escape');
await this.get().waitFor({ state: 'detached' });
}
async sync() { async sync() {
await this.get().locator(`button:has-text("Sync Now")`).click(); await this.get().locator(`button:has-text("Sync Now")`).click();
await this.verifyToast({ message: 'Table metadata recreated successfully' }); await this.verifyToast({ message: 'Table metadata recreated successfully' });
@ -32,9 +36,9 @@ export class MetaDataPage extends BasePage {
} }
async verifyRow({ index, model, state }: { index: number; model: string; state: string }) { async verifyRow({ index, model, state }: { index: number; model: string; state: string }) {
const fieldLocator = await this.get().locator(`tr.ant-table-row`).nth(index).locator(`td.ant-table-cell`).nth(0); const fieldLocator = this.get().locator(`tr.ant-table-row`).nth(index).locator(`td.ant-table-cell`).nth(0);
const fieldText = await getTextExcludeIconText(fieldLocator); const fieldText = await getTextExcludeIconText(fieldLocator);
await expect(fieldText).toBe(model); expect(fieldText).toBe(model);
await expect(this.get().locator(`tr.ant-table-row`).nth(index).locator(`td.ant-table-cell`).nth(1)).toHaveText( await expect(this.get().locator(`tr.ant-table-row`).nth(index).locator(`td.ant-table-cell`).nth(1)).toHaveText(
state, state,

2
tests/playwright/pages/Dashboard/QrCodeOverlay/index.ts

@ -18,7 +18,7 @@ export class QrCodeOverlay extends BasePage {
const foundQrValueLabelText = await this.get() const foundQrValueLabelText = await this.get()
.locator('[data-testid="nc-qr-code-large-value-label"]') .locator('[data-testid="nc-qr-code-large-value-label"]')
.textContent(); .textContent();
await expect(foundQrValueLabelText).toContain(expectedValue); expect(foundQrValueLabelText).toContain(expectedValue);
} }
async clickCloseButton() { async clickCloseButton() {

2
tests/playwright/pages/Dashboard/Settings/DataSources.ts

@ -3,7 +3,7 @@ import { defaultBaseName } from '../../../constants';
import BasePage from '../../Base'; import BasePage from '../../Base';
import { AclPage } from './Acl'; import { AclPage } from './Acl';
import { SettingsErdPage } from './Erd'; import { SettingsErdPage } from './Erd';
import { MetaDataPage } from './Metadata'; import { MetaDataPage } from '../ProjectView/Metadata';
export class DataSourcesPage extends BasePage { export class DataSourcesPage extends BasePage {
private readonly settings: SettingsPage; private readonly settings: SettingsPage;

43
tests/playwright/pages/Dashboard/TreeView.ts

@ -63,7 +63,7 @@ export class TreeViewPage extends BasePage {
} }
async openBase({ title }: { title: string }) { async openBase({ title }: { title: string }) {
const nodes = await this.get().locator(`[data-testid="nc-sidebar-project-${title.toLowerCase()}"]`); const nodes = this.get().locator(`[data-testid="nc-sidebar-project-${title.toLowerCase()}"]`);
await nodes.click(); await nodes.click();
return; return;
} }
@ -164,11 +164,12 @@ export class TreeViewPage extends BasePage {
await this.dashboard.get().locator('div.nc-project-menu-item:has-text("Delete"):visible').click(); await this.dashboard.get().locator('div.nc-project-menu-item:has-text("Delete"):visible').click();
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => { uiAction: async () => {
// Create a promise that resolves after 1 second // Create a promise that resolves after 1 second
const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
// Returning a promise that resolves with the result after the 1-second delay // Returning a promise that resolves with the result after the 1-second delay
return delay(100).then(() => this.dashboard.get().locator('button:has-text("Delete Table")').click()); await delay(100);
return await this.dashboard.get().locator('button:has-text("Delete Table")').click();
}, },
httpMethodsToMatch: ['DELETE'], httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: `/api/v1/db/meta/tables/`, requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
@ -210,7 +211,7 @@ export class TreeViewPage extends BasePage {
await settingsMenu.locator(`[data-menu-id="teamAndSettings"]`).click(); await settingsMenu.locator(`[data-menu-id="teamAndSettings"]`).click();
} }
async quickImport({ title, projectTitle }: { title: string; projectTitle }) { async quickImport({ title, projectTitle }: { title: string; projectTitle: string }) {
await this.getProjectContextMenu({ projectTitle }).hover(); await this.getProjectContextMenu({ projectTitle }).hover();
await this.getProjectContextMenu({ projectTitle }).click(); await this.getProjectContextMenu({ projectTitle }).click();
const importMenu = this.dashboard.get().locator('.ant-dropdown-menu.nc-scrollbar-md'); const importMenu = this.dashboard.get().locator('.ant-dropdown-menu.nc-scrollbar-md');
@ -223,7 +224,7 @@ export class TreeViewPage extends BasePage {
await this.get().locator(`.nc-project-tree-tbl-${title} .nc-table-icon`).click(); await this.get().locator(`.nc-project-tree-tbl-${title} .nc-table-icon`).click();
await this.rootPage.locator('.emoji-mart-search').type(icon); await this.rootPage.locator('.emoji-mart-search').type(icon);
const emojiList = await this.rootPage.locator('[id="emoji-mart-list"]'); const emojiList = this.rootPage.locator('[id="emoji-mart-list"]');
await emojiList.locator('button').first().click(); await emojiList.locator('button').first().click();
await expect( await expect(
this.get().locator(`.nc-project-tree-tbl-${title}`).locator(`.nc-table-icon:has-text("${iconDisplay}")`) this.get().locator(`.nc-project-tree-tbl-${title}`).locator(`.nc-table-icon:has-text("${iconDisplay}")`)
@ -235,14 +236,14 @@ export class TreeViewPage extends BasePage {
await this.dashboard.get().locator('div.nc-project-menu-item:has-text("Duplicate")').click(); await this.dashboard.get().locator('div.nc-project-menu-item:has-text("Duplicate")').click();
// Find the checkbox element with the label "Include data" // Find the checkbox element with the label "Include data"
const includeDataCheckbox = await this.dashboard.get().getByText('Include data', { exact: true }); const includeDataCheckbox = this.dashboard.get().getByText('Include data', { exact: true });
// Check the checkbox if it is not already checked // Check the checkbox if it is not already checked
if ((await includeDataCheckbox.isChecked()) && !includeData) { if ((await includeDataCheckbox.isChecked()) && !includeData) {
await includeDataCheckbox.click(); // click the checkbox to check it await includeDataCheckbox.click(); // click the checkbox to check it
} }
// Find the checkbox element with the label "Include data" // Find the checkbox element with the label "Include data"
const includeViewsCheckbox = await this.dashboard.get().getByText('Include views', { exact: true }); const includeViewsCheckbox = this.dashboard.get().getByText('Include views', { exact: true });
// Check the checkbox if it is not already checked // Check the checkbox if it is not already checked
if ((await includeViewsCheckbox.isChecked()) && !includeViews) { if ((await includeViewsCheckbox.isChecked()) && !includeViews) {
await includeViewsCheckbox.click(); // click the checkbox to check it await includeViewsCheckbox.click(); // click the checkbox to check it
@ -319,12 +320,36 @@ export class TreeViewPage extends BasePage {
return this.get().locator(`.project-title-node`).nth(param.index); return this.get().locator(`.project-title-node`).nth(param.index);
} }
async renameProject(param: { newTitle: string; title: string }) {
await this.getProjectContextMenu({ projectTitle: param.title }).hover();
await this.getProjectContextMenu({ projectTitle: param.title }).click();
const contextMenu = this.dashboard.get().locator('.ant-dropdown-menu.nc-scrollbar-md:visible').last();
await contextMenu.waitFor();
await contextMenu.locator(`.ant-dropdown-menu-item:has-text("Edit")`).click();
const projectNodeInput = (await this.getProject({ index: 0, title: param.title })).locator('input');
await projectNodeInput.clear();
await projectNodeInput.fill(param.newTitle);
await projectNodeInput.press('Enter');
}
async deleteProject(param: { title: string }) { async deleteProject(param: { title: string }) {
await this.getProjectContextMenu({ projectTitle: param.title }).hover(); await this.getProjectContextMenu({ projectTitle: param.title }).hover();
await this.getProjectContextMenu({ projectTitle: param.title }).click(); await this.getProjectContextMenu({ projectTitle: param.title }).click();
const contextMenu = this.dashboard.get().locator('.ant-dropdown-menu.nc-scrollbar-md'); const contextMenu = this.dashboard.get().locator('.ant-dropdown-menu.nc-scrollbar-md:visible').last();
await contextMenu.waitFor();
await contextMenu.locator(`.ant-dropdown-menu-item:has-text("Delete")`).click(); await contextMenu.locator(`.ant-dropdown-menu-item:has-text("Delete")`).click();
await this.rootPage.locator('div.ant-modal-content').locator(`button.ant-btn:has-text("Delete Project")`).click(); await this.rootPage.locator('div.ant-modal-content').locator(`button.ant-btn:has-text("Delete Project")`).click();
} }
async duplicateProject(param: { title: string }) {
await this.getProjectContextMenu({ projectTitle: param.title }).hover();
await this.getProjectContextMenu({ projectTitle: param.title }).click();
const contextMenu = this.dashboard.get().locator('.ant-dropdown-menu.nc-scrollbar-md:visible');
await contextMenu.waitFor();
await contextMenu.locator(`.ant-dropdown-menu-item:has-text("Duplicate Project")`).click();
await this.rootPage.locator('div.ant-modal-content').locator(`button.ant-btn:has-text("Confirm")`).click();
}
} }

4
tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts

@ -46,14 +46,14 @@ export class AttachmentCellPageObject extends BasePage {
} }
async verifyFile({ index, columnHeader }: { index: number; columnHeader: string }) { async verifyFile({ index, columnHeader }: { index: number; columnHeader: string }) {
await expect(await this.get({ index, columnHeader }).locator('.nc-attachment')).toBeVisible(); await expect(this.get({ index, columnHeader }).locator('.nc-attachment')).toBeVisible();
} }
async verifyFileCount({ index, columnHeader, count }: { index: number; columnHeader: string; count: number }) { async verifyFileCount({ index, columnHeader, count }: { index: number; columnHeader: string; count: number }) {
// retry below logic for 5 times, with 1 second delay // retry below logic for 5 times, with 1 second delay
let retryCount = 0; let retryCount = 0;
while (retryCount < 5) { while (retryCount < 5) {
const attachments = await this.get({ index, columnHeader }).locator('.nc-attachment'); const attachments = this.get({ index, columnHeader }).locator('.nc-attachment');
// console.log(await attachments.count()); // console.log(await attachments.count());
if ((await attachments.count()) === count) { if ((await attachments.count()) === count) {
break; break;

2
tests/playwright/pages/Dashboard/common/Cell/DateCell.ts

@ -22,7 +22,7 @@ export class DateCellPageObject extends BasePage {
} }
async verify({ index, columnHeader, date }: { index: number; columnHeader: string; date: string }) { async verify({ index, columnHeader, date }: { index: number; columnHeader: string; date: string }) {
const cell = await this.get({ index, columnHeader }); const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded(); await cell.scrollIntoViewIfNeeded();
await expect(cell.locator(`[title="${date}"]`)).toBeVisible(); await expect(cell.locator(`[title="${date}"]`)).toBeVisible();
} }

2
tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts

@ -85,7 +85,7 @@ export class DateTimeCellPageObject extends BasePage {
async setDateTime({ index, columnHeader, dateTime }: { index: number; columnHeader: string; dateTime: string }) { async setDateTime({ index, columnHeader, dateTime }: { index: number; columnHeader: string; dateTime: string }) {
const [date, time] = dateTime.split(' '); const [date, time] = dateTime.split(' ');
const [hour, minute, second] = time.split(':'); const [hour, minute, _second] = time.split(':');
await this.open({ index, columnHeader }); await this.open({ index, columnHeader });
await this.selectDate({ date }); await this.selectDate({ date });
await this.selectTime({ hour: +hour, minute: +minute }); await this.selectTime({ hour: +hour, minute: +minute });

2
tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts

@ -24,7 +24,7 @@ export class RatingCellPageObject extends BasePage {
} }
async verify({ index, columnHeader, rating }: { index: number; columnHeader: string; rating: number }) { async verify({ index, columnHeader, rating }: { index: number; columnHeader: string; rating: number }) {
const cell = await this.get({ index, columnHeader }); const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded(); await cell.scrollIntoViewIfNeeded();
await expect(cell.locator(`li.ant-rate-star.ant-rate-star-full`)).toHaveCount(rating); await expect(cell.locator(`li.ant-rate-star.ant-rate-star-full`)).toHaveCount(rating);
} }

2
tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts

@ -91,7 +91,7 @@ export class SelectOptionCellPageObject extends BasePage {
return await expect(this.cell.get({ index, columnHeader })).toContainText(option, { useInnerText: true }); return await expect(this.cell.get({ index, columnHeader })).toContainText(option, { useInnerText: true });
} }
const locator = await this.cell.get({ index, columnHeader }).locator('.ant-tag'); const locator = this.cell.get({ index, columnHeader }).locator('.ant-tag');
await locator.waitFor({ state: 'visible' }); await locator.waitFor({ state: 'visible' });
const text = await locator.allInnerTexts(); const text = await locator.allInnerTexts();
return expect(text).toContain(option); return expect(text).toContain(option);

4
tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts

@ -15,7 +15,7 @@ export class TimeCellPageObject extends BasePage {
} }
async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) { async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) {
const cell = await this.get({ index, columnHeader }); const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded(); await cell.scrollIntoViewIfNeeded();
await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' }); await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' });
await expect(cell.locator(`[title="${value}"]`)).toBeVisible(); await expect(cell.locator(`[title="${value}"]`)).toBeVisible();
@ -31,7 +31,7 @@ export class TimeCellPageObject extends BasePage {
hour: number; hour: number;
minute: number; minute: number;
}) { }) {
const timePanel = await this.rootPage.locator('.ant-picker-time-panel-column'); const timePanel = this.rootPage.locator('.ant-picker-time-panel-column');
await timePanel.nth(0).locator('.ant-picker-time-panel-cell').nth(hour).click(); await timePanel.nth(0).locator('.ant-picker-time-panel-cell').nth(hour).click();
await timePanel.nth(1).locator('.ant-picker-time-panel-cell').nth(minute).click(); await timePanel.nth(1).locator('.ant-picker-time-panel-cell').nth(minute).click();
if (hour < 12) { if (hour < 12) {

2
tests/playwright/pages/Dashboard/common/Cell/YearCell.ts

@ -15,7 +15,7 @@ export class YearCellPageObject extends BasePage {
} }
async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: number }) { async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: number }) {
const cell = await this.get({ index, columnHeader }); const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded(); await cell.scrollIntoViewIfNeeded();
await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' }); await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' });
await expect(cell.locator(`[title="${value}"]`)).toBeVisible(); await expect(cell.locator(`[title="${value}"]`)).toBeVisible();

39
tests/playwright/pages/Dashboard/common/Cell/index.ts

@ -135,10 +135,7 @@ export class CellPageObject extends BasePage {
columnHeader, columnHeader,
}).scrollIntoViewIfNeeded(); }).scrollIntoViewIfNeeded();
while (count < 5) { while (count < 5) {
const innerTexts = await this.get({ const innerTexts = await getTextExcludeIconText(this.get({ index, columnHeader }));
index,
columnHeader,
}).allInnerTexts();
const cellText = typeof innerTexts === 'string' ? [innerTexts] : innerTexts; const cellText = typeof innerTexts === 'string' ? [innerTexts] : innerTexts;
if (cellText) { if (cellText) {
@ -148,7 +145,12 @@ export class CellPageObject extends BasePage {
} }
await this.rootPage.waitForTimeout(1000); await this.rootPage.waitForTimeout(1000);
count++; count++;
if (count === 5) throw new Error(`Cell text ${text} not found`); if (count === 5) {
console.log('cellText', cellText);
console.log('text', text);
throw new Error(`Cell text "${text}" not found`);
}
} }
}; };
@ -254,14 +256,11 @@ export class CellPageObject extends BasePage {
columnHeader: string; columnHeader: string;
expectedSvgValue: string; expectedSvgValue: string;
}) { }) {
const _verify = async expectedBarcodeSvg => { const _verify = async (expectedBarcodeSvg: unknown) => {
await expect await expect
.poll(async () => { .poll(async () => {
const barcodeCell = await this.get({ const barcodeCell = this.get({ index, columnHeader });
index, const barcodeSvg = barcodeCell.getByTestId('barcode');
columnHeader,
});
const barcodeSvg = await barcodeCell.getByTestId('barcode');
return await barcodeSvg.innerHTML(); return await barcodeSvg.innerHTML();
}) })
.toEqual(expectedBarcodeSvg); .toEqual(expectedBarcodeSvg);
@ -290,12 +289,12 @@ export class CellPageObject extends BasePage {
verifyChildList?: boolean; verifyChildList?: boolean;
options?: { singular?: string; plural?: string }; options?: { singular?: string; plural?: string };
}) { }) {
const cell = await this.get({ index, columnHeader }); const cell = this.get({ index, columnHeader });
const linkText = await cell.locator('.nc-datatype-link'); const linkText = cell.locator('.nc-datatype-link');
await cell.scrollIntoViewIfNeeded(); await cell.scrollIntoViewIfNeeded();
// lazy load- give enough time for cell to load // lazy load - give enough time for cell to load
await this.rootPage.waitForTimeout(1000); await this.rootPage.waitForTimeout(1000);
if (type === 'bt') { if (type === 'bt') {
@ -342,7 +341,7 @@ export class CellPageObject extends BasePage {
await this.rootPage.waitForSelector('.nc-modal-child-list:visible'); await this.rootPage.waitForSelector('.nc-modal-child-list:visible');
// verify child list count & contents // verify child list count & contents
const childList = await this.rootPage.locator('.ant-card:visible'); const childList = this.rootPage.locator('.ant-card:visible');
expect(await childList.count()).toBe(count); expect(await childList.count()).toBe(count);
// close child list // close child list
@ -377,23 +376,23 @@ export class CellPageObject extends BasePage {
const role = param.role.toLowerCase(); const role = param.role.toLowerCase();
const count = role === 'creator' || role === 'editor' || role === 'owner' ? 1 : 0; const count = role === 'creator' || role === 'editor' || role === 'owner' ? 1 : 0;
// normal text cell // normal text cell
const cell = await this.get({ index: 0, columnHeader: 'Country' }); const cell = this.get({ index: 0, columnHeader: 'Country' });
// editable cell // editable cell
await cell.dblclick(); await cell.dblclick();
await expect(await cell.locator(`input`)).toHaveCount(count); await expect(cell.locator(`input`)).toHaveCount(count);
// press escape to close the input // press escape to close the input
await cell.press('Escape'); await cell.press('Escape');
await cell.press('Escape'); await cell.press('Escape');
await cell.click({ button: 'right', clickCount: 1 }); await cell.click({ button: 'right', clickCount: 1 });
await expect(await this.rootPage.locator(`.nc-dropdown-grid-context-menu:visible`)).toHaveCount(count); await expect(this.rootPage.locator(`.nc-dropdown-grid-context-menu:visible`)).toHaveCount(count);
// virtual cell // virtual cell
const vCell = await this.get({ index: 0, columnHeader: 'Cities' }); const vCell = this.get({ index: 0, columnHeader: 'Cities' });
await vCell.hover(); await vCell.hover();
// in-cell add // in-cell add
await expect(await vCell.locator('.nc-action-icon.nc-plus:visible')).toHaveCount(count); await expect(vCell.locator('.nc-action-icon.nc-plus:visible')).toHaveCount(count);
// virtual cell link text // virtual cell link text
const linkText = await getTextExcludeIconText(vCell); const linkText = await getTextExcludeIconText(vCell);

10
tests/playwright/pages/Dashboard/common/Footbar/index.ts

@ -42,4 +42,14 @@ export class FootbarPage extends BasePage {
await this.rootPage.locator('.ant-dropdown-content:visible').waitFor(); await this.rootPage.locator('.ant-dropdown-content:visible').waitFor();
await this.rootPage.locator('.ant-dropdown-content:visible').locator('.nc-new-record-with-form').click(); await this.rootPage.locator('.ant-dropdown-content:visible').locator('.nc-new-record-with-form').click();
} }
async verifyLockMode() {
// add record button
await expect(this.btn_addNewRow).toBeVisible({ visible: false });
}
async verifyCollaborativeMode() {
// add record button
await expect(this.btn_addNewRow).toBeVisible({ visible: true });
}
} }

10
tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts

@ -30,6 +30,16 @@ export class LeftSidebarPage extends BasePage {
return this.dashboard.get().locator('.nc-sidebar'); return this.dashboard.get().locator('.nc-sidebar');
} }
async createProject({ title }: { title: string }) {
await this.btn_newProject.click();
await this.rootPage.locator('.ant-modal-content:has-text(" Create Database")').waitFor();
await this.rootPage.locator('.ant-modal-content:has-text(" Create Database")').locator('input').fill(title);
await this.rootPage
.locator('.ant-modal-content:has-text(" Create Database")')
.locator('button.ant-btn-primary')
.click();
}
async clickTeamAndSettings() { async clickTeamAndSettings() {
await this.btn_teamAndSettings.click(); await this.btn_teamAndSettings.click();
} }

2
tests/playwright/pages/Dashboard/common/ProjectMenu/index.ts

@ -18,7 +18,7 @@ export class ProjectMenuObject extends BasePage {
} }
async click({ menu, subMenu }: { menu: string; subMenu: string }) { async click({ menu, subMenu }: { menu: string; subMenu: string }) {
const pMenu = await this.rootPage.locator(`.nc-dropdown-project-menu:visible`); const pMenu = this.rootPage.locator(`.nc-dropdown-project-menu:visible`);
await pMenu.locator(`div.nc-project-menu-item:has-text("${menu}"):visible`).click(); await pMenu.locator(`div.nc-project-menu-item:has-text("${menu}"):visible`).click();
await this.rootPage.waitForTimeout(2000); await this.rootPage.waitForTimeout(2000);

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

@ -17,9 +17,9 @@ export class ToolbarFilterPage extends BasePage {
} }
async verify({ index, column, operator, value }: { index: number; column: string; operator: string; value: string }) { async verify({ index, column, operator, value }: { index: number; column: string; operator: string; value: string }) {
const fieldLocator = await this.get().locator('.nc-filter-field-select').nth(index); const fieldLocator = this.get().locator('.nc-filter-field-select').nth(index);
const fieldText = await getTextExcludeIconText(fieldLocator); const fieldText = await getTextExcludeIconText(fieldLocator);
await expect(fieldText).toBe(column); expect(fieldText).toBe(column);
await expect(this.get().locator('.nc-filter-operation-select').nth(index)).toHaveText(operator); await expect(this.get().locator('.nc-filter-operation-select').nth(index)).toHaveText(operator);
await expect await expect
@ -63,16 +63,16 @@ export class ToolbarFilterPage extends BasePage {
filterLogicalOperator?: string; filterLogicalOperator?: string;
}) { }) {
await this.get().locator(`button:has-text("Add Filter Group")`).last().click(); await this.get().locator(`button:has-text("Add Filter Group")`).last().click();
const filterDropdown = await this.get().locator('.menu-filter-dropdown').nth(filterGroupIndex); const filterDropdown = this.get().locator('.menu-filter-dropdown').nth(filterGroupIndex);
await filterDropdown.waitFor({ state: 'visible' }); await filterDropdown.waitFor({ state: 'visible' });
await filterDropdown.locator(`button:has-text("Add Filter")`).first().click(); await filterDropdown.locator(`button:has-text("Add Filter")`).first().click();
const selectField = await filterDropdown.locator('.nc-filter-field-select').last(); const selectField = filterDropdown.locator('.nc-filter-field-select').last();
const selectOperation = await filterDropdown.locator('.nc-filter-operation-select').last(); const selectOperation = filterDropdown.locator('.nc-filter-operation-select').last();
const selectValue = await filterDropdown.locator('.nc-filter-value-select > input').last(); const selectValue = filterDropdown.locator('.nc-filter-value-select > input').last();
await selectField.waitFor({ state: 'visible' }); await selectField.waitFor({ state: 'visible' });
await selectField.click(); await selectField.click();
const fieldDropdown = await this.rootPage const fieldDropdown = this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list') .locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.last() .last()
.locator(`div[label="${title}"]:visible`); .locator(`div[label="${title}"]:visible`);
@ -81,7 +81,7 @@ export class ToolbarFilterPage extends BasePage {
await selectOperation.waitFor({ state: 'visible' }); await selectOperation.waitFor({ state: 'visible' });
await selectOperation.click(); await selectOperation.click();
const operationDropdown = await this.rootPage const operationDropdown = this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-filter-comp-op') .locator('div.ant-select-dropdown.nc-dropdown-filter-comp-op')
.last() .last()
.locator(`.ant-select-item:has-text("${operation}")`); .locator(`.ant-select-item:has-text("${operation}")`);
@ -93,13 +93,11 @@ export class ToolbarFilterPage extends BasePage {
if (filterGroupIndex) { if (filterGroupIndex) {
if (filterLogicalOperator === 'OR') { if (filterLogicalOperator === 'OR') {
const logicalButton = await this.rootPage.locator('div.flex.nc-filter-logical-op').nth(filterGroupIndex - 1); const logicalButton = this.rootPage.locator('div.flex.nc-filter-logical-op').nth(filterGroupIndex - 1);
await logicalButton.waitFor({ state: 'visible' }); await logicalButton.waitFor({ state: 'visible' });
await logicalButton.click(); await logicalButton.click();
const logicalDropdown = await this.rootPage.locator( const logicalDropdown = this.rootPage.locator('div.ant-select-dropdown.nc-dropdown-filter-logical-op-group');
'div.ant-select-dropdown.nc-dropdown-filter-logical-op-group'
);
await logicalDropdown.waitFor({ state: 'visible' }); await logicalDropdown.waitFor({ state: 'visible' });
await logicalDropdown.locator(`.ant-select-item:has-text("${filterLogicalOperator}")`).click(); await logicalDropdown.locator(`.ant-select-item:has-text("${filterLogicalOperator}")`).click();
} }
@ -133,7 +131,7 @@ export class ToolbarFilterPage extends BasePage {
skipWaitingResponse = true; skipWaitingResponse = true;
const selectedField = await getTextExcludeIconText( const selectedField = await getTextExcludeIconText(
await this.rootPage.locator('.nc-filter-field-select .ant-select-selection-item') this.rootPage.locator('.nc-filter-field-select .ant-select-selection-item')
); );
if (selectedField !== title) { if (selectedField !== title) {
await this.rootPage.locator('.nc-filter-field-select').last().click(); await this.rootPage.locator('.nc-filter-field-select').last().click();
@ -158,7 +156,7 @@ export class ToolbarFilterPage extends BasePage {
} }
} }
const selectedOpType = await getTextExcludeIconText(await this.rootPage.locator('.nc-filter-operation-select')); const selectedOpType = await getTextExcludeIconText(this.rootPage.locator('.nc-filter-operation-select'));
if (selectedOpType !== operation) { if (selectedOpType !== operation) {
await this.rootPage.locator('.nc-filter-operation-select').click(); await this.rootPage.locator('.nc-filter-operation-select').click();
// first() : filter list has >, >= // first() : filter list has >, >=
@ -187,9 +185,7 @@ export class ToolbarFilterPage extends BasePage {
// subtype for date // subtype for date
if (dataType === UITypes.Date && subOperation) { if (dataType === UITypes.Date && subOperation) {
const selectedSubType = await getTextExcludeIconText( const selectedSubType = await getTextExcludeIconText(this.rootPage.locator('.nc-filter-sub_operation-select'));
await this.rootPage.locator('.nc-filter-sub_operation-select')
);
if (selectedSubType !== subOperation) { if (selectedSubType !== subOperation) {
await this.rootPage.locator('.nc-filter-sub_operation-select').click(); await this.rootPage.locator('.nc-filter-sub_operation-select').click();
// first() : filter list has >, >= // first() : filter list has >, >=
@ -222,14 +218,14 @@ export class ToolbarFilterPage extends BasePage {
switch (dataType) { switch (dataType) {
case UITypes.Year: case UITypes.Year:
await this.get().locator('.nc-filter-value-select').click(); await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`); await this.rootPage.locator(`.ant-picker-dropdown:visible`).waitFor();
await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(); await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
break; break;
case UITypes.Time: case UITypes.Time:
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const time = value.split(':'); const time = value.split(':');
await this.get().locator('.nc-filter-value-select').click(); await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`); await this.rootPage.locator(`.ant-picker-dropdown:visible`).waitFor();
await this.rootPage await this.rootPage
.locator(`.ant-picker-time-panel-column:nth-child(1)`) .locator(`.ant-picker-time-panel-column:nth-child(1)`)
.locator(`.ant-picker-time-panel-cell:has-text("${time[0]}")`) .locator(`.ant-picker-time-panel-cell:has-text("${time[0]}")`)
@ -243,7 +239,7 @@ export class ToolbarFilterPage extends BasePage {
case UITypes.Date: case UITypes.Date:
if (subOperation === 'exact date') { if (subOperation === 'exact date') {
await this.get().locator('.nc-filter-value-select').click(); await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`); await this.rootPage.locator(`.ant-picker-dropdown:visible`).waitFor();
if (skipWaitingResponse) { if (skipWaitingResponse) {
await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(); await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
@ -274,6 +270,7 @@ export class ToolbarFilterPage extends BasePage {
case UITypes.Duration: case UITypes.Duration:
if (skipWaitingResponse) { if (skipWaitingResponse) {
await this.get().locator('.nc-filter-value-select').locator('input').fill(value); 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); await this.rootPage.waitForTimeout(350);
} else { } else {
await this.waitForResponse({ await this.waitForResponse({
@ -386,7 +383,7 @@ export class ToolbarFilterPage extends BasePage {
} }
await this.rootPage.locator('.nc-filter-operation-select').click(); await this.rootPage.locator('.nc-filter-operation-select').click();
const opList = await this.rootPage const opList = this.rootPage
.locator('.nc-dropdown-filter-comp-op') .locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item > .ant-select-item-option-content`); .locator(`.ant-select-item > .ant-select-item-option-content`);

2
tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts

@ -16,7 +16,7 @@ export class ToolbarGroupByPage extends BasePage {
} }
async verify({ index, column, direction }: { index: number; column: string; direction: string }) { async verify({ index, column, direction }: { index: number; column: string; direction: string }) {
const fieldLocator = await this.get().locator('.nc-sort-field-select').nth(index); const fieldLocator = this.get().locator('.nc-sort-field-select').nth(index);
const fieldText = await getTextExcludeIconText(fieldLocator); const fieldText = await getTextExcludeIconText(fieldLocator);
expect(fieldText).toBe(column); expect(fieldText).toBe(column);

2
tests/playwright/pages/Dashboard/common/Toolbar/SearchData.ts

@ -15,6 +15,6 @@ export class ToolbarSearchDataPage extends BasePage {
} }
async verify(query: string) { async verify(query: string) {
await expect(await this.get().inputValue()).toBe(query); expect(await this.get().inputValue()).toBe(query);
} }
} }

4
tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts

@ -83,13 +83,13 @@ export class ToolbarSortPage extends BasePage {
// Check if create sort modal is open or sort list is open // Check if create sort modal is open or sort list is open
let isSortListOpen = false; let isSortListOpen = false;
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const sortList = await this.rootPage.locator('.nc-filter-list'); const sortList = this.rootPage.locator('.nc-filter-list');
if (await sortList.isVisible()) { if (await sortList.isVisible()) {
isSortListOpen = true; isSortListOpen = true;
break; break;
} }
const searchInput = await this.rootPage.locator('.nc-sort-create-modal'); const searchInput = this.rootPage.locator('.nc-sort-create-modal');
if (await searchInput.isVisible()) { if (await searchInput.isVisible()) {
isSortListOpen = false; isSortListOpen = false;
break; break;

47
tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts

@ -1,6 +1,5 @@
import { expect, Locator } from '@playwright/test'; import { expect, Locator } from '@playwright/test';
import BasePage from '../../../Base'; import BasePage from '../../../Base';
import { GridPage } from '../../Grid';
import { ToolbarPage } from './index'; import { ToolbarPage } from './index';
// @ts-ignore // @ts-ignore
import fs from 'fs'; import fs from 'fs';
@ -44,7 +43,7 @@ export class ToolbarViewMenuPage extends BasePage {
// verify downloaded content against expected content // verify downloaded content against expected content
const expectedData = fs.readFileSync(expectedDataFile, 'utf8').replace(/\r/g, '').split('\n'); const expectedData = fs.readFileSync(expectedDataFile, 'utf8').replace(/\r/g, '').split('\n');
const file = fs.readFileSync('./output/test.txt', 'utf8').replace(/\r/g, '').split('\n'); const file = fs.readFileSync('./output/test.txt', 'utf8').replace(/\r/g, '').split('\n');
await expect(file).toEqual(expectedData); expect(file).toEqual(expectedData);
} }
async verifyDownloadAsXLSX({ async verifyDownloadAsXLSX({
@ -72,7 +71,7 @@ export class ToolbarViewMenuPage extends BasePage {
const expectedData = fs.readFileSync(expectedDataFile, 'utf8'); const expectedData = fs.readFileSync(expectedDataFile, 'utf8');
const file = fs.readFileSync('./output/test.txt', 'utf8'); const file = fs.readFileSync('./output/test.txt', 'utf8');
// XLSX writes file with UTF-8 BOM, adds '\ufeff' to cater it // XLSX writes file with UTF-8 BOM, adds '\ufeff' to cater it
await expect(file).toEqual('\ufeff' + expectedData); expect(file).toEqual('\ufeff' + expectedData);
} }
// menu items // menu items
@ -92,16 +91,12 @@ export class ToolbarViewMenuPage extends BasePage {
// for CSV download, pass locator instead of clicking it here // for CSV download, pass locator instead of clicking it here
if (subMenu === 'Download as CSV') { if (subMenu === 'Download as CSV') {
await this.verifyDownloadAsCSV({ await this.verifyDownloadAsCSV({
downloadLocator: await this.rootPage downloadLocator: this.rootPage.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`).last(),
.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`)
.last(),
expectedDataFile: verificationInfo?.verificationFile ?? './fixtures/expectedBaseDownloadData.txt', expectedDataFile: verificationInfo?.verificationFile ?? './fixtures/expectedBaseDownloadData.txt',
}); });
} else if (subMenu === 'Download as XLSX') { } else if (subMenu === 'Download as XLSX') {
await this.verifyDownloadAsXLSX({ await this.verifyDownloadAsXLSX({
downloadLocator: await this.rootPage downloadLocator: this.rootPage.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`).last(),
.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`)
.last(),
expectedDataFile: verificationInfo?.verificationFile ?? './fixtures/expectedBaseDownloadData.txt', expectedDataFile: verificationInfo?.verificationFile ?? './fixtures/expectedBaseDownloadData.txt',
}); });
} else { } else {
@ -135,38 +130,4 @@ export class ToolbarViewMenuPage extends BasePage {
} }
await this.toolbar.parent.waitLoading(); await this.toolbar.parent.waitLoading();
} }
async verifyLockMode() {
await expect(await this.toolbar.get().locator(`.nc-fields-menu-btn.nc-toolbar-btn`)).toBeDisabled();
await expect(await this.toolbar.get().locator(`.nc-filter-menu-btn.nc-toolbar-btn`)).toBeDisabled();
await expect(await this.toolbar.get().locator(`.nc-sort-menu-btn.nc-toolbar-btn`)).toBeDisabled();
// await expect(
// await this.toolbar.get().locator(`.nc-add-new-row-btn.nc-toolbar-btn > .material-symbols.disabled`)
// ).toBeVisible();
await expect(
await this.rootPage.locator('.nc-pagination-wrapper').locator('button.ant-btn:has-text(" New Record ")')
).not.toBeVisible();
await (this.toolbar.parent as GridPage).verifyEditDisabled({
columnHeader: 'Country',
});
}
async verifyCollaborativeMode() {
await expect(await this.toolbar.get().locator(`.nc-fields-menu-btn.nc-toolbar-btn`)).toBeEnabled();
await expect(await this.toolbar.get().locator(`.nc-filter-menu-btn.nc-toolbar-btn`)).toBeEnabled();
await expect(await this.toolbar.get().locator(`.nc-sort-menu-btn.nc-toolbar-btn`)).toBeEnabled();
// Add button not in toolbar now
// await expect(
// await this.toolbar.get().locator(`.nc-add-new-row-btn.nc-toolbar-btn > .material-symbols`)
// ).toBeVisible();
await expect(
await this.rootPage.locator('.nc-pagination-wrapper').locator('button.ant-btn:has-text(" New Record ")')
).toBeVisible();
await (this.toolbar.parent as GridPage).verifyEditEnabled({
columnHeader: 'Country',
});
}
} }

32
tests/playwright/pages/Dashboard/common/Toolbar/index.ts

@ -35,6 +35,7 @@ export class ToolbarPage extends BasePage {
readonly btn_sort: Locator; readonly btn_sort: Locator;
readonly btn_filter: Locator; readonly btn_filter: Locator;
readonly btn_rowHeight: Locator; readonly btn_rowHeight: Locator;
readonly btn_groupBy: Locator;
constructor(parent: GridPage | GalleryPage | FormPage | KanbanPage | MapPage) { constructor(parent: GridPage | GalleryPage | FormPage | KanbanPage | MapPage) {
super(parent.rootPage); super(parent.rootPage);
@ -54,6 +55,7 @@ export class ToolbarPage extends BasePage {
this.btn_sort = this.get().locator(`button.nc-sort-menu-btn`); this.btn_sort = this.get().locator(`button.nc-sort-menu-btn`);
this.btn_filter = this.get().locator(`button.nc-filter-menu-btn`); this.btn_filter = this.get().locator(`button.nc-filter-menu-btn`);
this.btn_rowHeight = this.get().locator(`button.nc-height-menu-btn`); this.btn_rowHeight = this.get().locator(`button.nc-height-menu-btn`);
this.btn_groupBy = this.get().locator(`button.nc-group-by-menu-btn`);
} }
get() { get() {
@ -96,9 +98,9 @@ export class ToolbarPage extends BasePage {
await expect(this.get().locator(`button.nc-fields-menu-btn`)).toBeVisible(); await expect(this.get().locator(`button.nc-fields-menu-btn`)).toBeVisible();
// menu text // menu text
const fieldLocator = await this.get().locator(`button.nc-fields-menu-btn`); const fieldLocator = this.get().locator(`button.nc-fields-menu-btn`);
const fieldText = await getTextExcludeIconText(fieldLocator); const fieldText = await getTextExcludeIconText(fieldLocator);
await expect(fieldText).toBe('Fields'); expect(fieldText).toBe('Fields');
// icons count within fields menu button // icons count within fields menu button
expect(await this.get().locator(`button.nc-fields-menu-btn`).locator(`.material-symbols`).count()).toBe(2); expect(await this.get().locator(`button.nc-fields-menu-btn`).locator(`.material-symbols`).count()).toBe(2);
@ -108,9 +110,9 @@ export class ToolbarPage extends BasePage {
await expect(this.get().locator(`button.nc-fields-menu-btn`)).toBeVisible(); await expect(this.get().locator(`button.nc-fields-menu-btn`)).toBeVisible();
// menu text // menu text
const fieldLocator = await this.get().locator(`button.nc-fields-menu-btn`); const fieldLocator = this.get().locator(`button.nc-fields-menu-btn`);
const fieldText = await getTextExcludeIconText(fieldLocator); const fieldText = await getTextExcludeIconText(fieldLocator);
await expect(fieldText).not.toBe('Fields'); expect(fieldText).not.toBe('Fields');
// icons count within fields menu button // icons count within fields menu button
expect(await this.get().locator(`button.nc-fields-menu-btn`).locator(`.material-symbols`).count()).toBe(2); expect(await this.get().locator(`button.nc-fields-menu-btn`).locator(`.material-symbols`).count()).toBe(2);
@ -177,7 +179,7 @@ export class ToolbarPage extends BasePage {
// verify downloaded content against expected content // verify downloaded content against expected content
const expectedData = fs.readFileSync(`./fixtures/${verificationFile}`, 'utf8').replace(/\r/g, '').split('\n'); const expectedData = fs.readFileSync(`./fixtures/${verificationFile}`, 'utf8').replace(/\r/g, '').split('\n');
const file = fs.readFileSync('./output/at.txt', 'utf8').replace(/\r/g, '').split('\n'); const file = fs.readFileSync('./output/at.txt', 'utf8').replace(/\r/g, '').split('\n');
await expect(file).toEqual(expectedData); expect(file).toEqual(expectedData);
} }
async clickRowHeight() { async clickRowHeight() {
@ -188,7 +190,7 @@ export class ToolbarPage extends BasePage {
async verifyStackByButton({ title }: { title: string }) { async verifyStackByButton({ title }: { title: string }) {
await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`).waitFor({ state: 'visible' }); await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`).waitFor({ state: 'visible' });
await expect( await expect(
await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn:has-text("${title}")`) this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn:has-text("${title}")`)
).toBeVisible(); ).toBeVisible();
} }
@ -207,7 +209,7 @@ export class ToolbarPage extends BasePage {
commenter: ['Download as CSV', 'Download as XLSX'], commenter: ['Download as CSV', 'Download as XLSX'],
viewer: ['Download as CSV', 'Download as XLSX'], viewer: ['Download as CSV', 'Download as XLSX'],
}; };
const vMenu = await this.rootPage.locator('.nc-dropdown-actions-menu:visible'); const vMenu = this.rootPage.locator('.nc-dropdown-actions-menu:visible');
for (const item of menuItems[param.role.toLowerCase()]) { for (const item of menuItems[param.role.toLowerCase()]) {
await expect(vMenu).toContainText(item); await expect(vMenu).toContainText(item);
} }
@ -228,4 +230,20 @@ export class ToolbarPage extends BasePage {
expect(await this.btn_sort.count()).toBe(1); expect(await this.btn_sort.count()).toBe(1);
expect(await this.btn_rowHeight.count()).toBe(1); expect(await this.btn_rowHeight.count()).toBe(1);
} }
async verifyLockMode() {
await expect(this.btn_fields).toBeDisabled();
await expect(this.btn_filter).toBeDisabled();
await expect(this.btn_sort).toBeDisabled();
await expect(this.btn_groupBy).toBeDisabled();
await expect(this.btn_rowHeight).toBeDisabled();
}
async verifyCollaborativeMode() {
await expect(this.btn_fields).toBeEnabled();
await expect(this.btn_filter).toBeEnabled();
await expect(this.btn_sort).toBeEnabled();
await expect(this.btn_groupBy).toBeEnabled();
await expect(this.btn_rowHeight).toBeEnabled();
}
} }

16
tests/playwright/pages/Dashboard/common/Topbar/Share.ts

@ -54,10 +54,26 @@ export class TopbarSharePage extends BasePage {
await this.get().locator(`[data-testid="nc-share-base-sub-modal"]`).locator('.ant-switch').nth(0).click(); await this.get().locator(`[data-testid="nc-share-base-sub-modal"]`).locator('.ant-switch').nth(0).click();
} }
async isSharedBasePublicAccessEnabled() {
return await this.get()
.locator(`[data-testid="nc-share-base-sub-modal"]`)
.locator('.ant-switch')
.nth(0)
.isChecked();
}
async clickShareBaseEditorAccess() { async clickShareBaseEditorAccess() {
await this.get().locator(`[data-testid="nc-share-base-sub-modal"]`).locator('.ant-switch').nth(1).click(); await this.get().locator(`[data-testid="nc-share-base-sub-modal"]`).locator('.ant-switch').nth(1).click();
} }
async isSharedBaseEditorAccessEnabled() {
return await this.get()
.locator(`[data-testid="nc-share-base-sub-modal"]`)
.locator('.ant-switch')
.nth(1)
.isChecked();
}
async clickShareViewSurveyMode() { async clickShareViewSurveyMode() {
await this.get().locator(`[data-testid="nc-modal-share-view__surveyMode"]`).click(); await this.get().locator(`[data-testid="nc-modal-share-view__surveyMode"]`).click();
} }

7
tests/playwright/pages/Dashboard/common/Topbar/index.ts

@ -56,9 +56,10 @@ export class TopbarPage extends BasePage {
async getSharedBaseUrl({ role }: { role: string }) { async getSharedBaseUrl({ role }: { role: string }) {
await this.clickShare(); await this.clickShare();
await this.share.clickShareBase(); if (!(await this.share.isSharedBasePublicAccessEnabled())) await this.share.clickShareBasePublicAccess();
await this.share.clickShareBasePublicAccess(); if (role === 'editor' && !(await this.share.isSharedBaseEditorAccessEnabled())) {
if (role === 'editor') { await this.share.clickShareBaseEditorAccess();
} else if (role === 'viewer' && (await this.share.isSharedBaseEditorAccessEnabled())) {
await this.share.clickShareBaseEditorAccess(); await this.share.clickShareBaseEditorAccess();
} }
await this.share.clickCopyLink(); await this.share.clickCopyLink();

14
tests/playwright/pages/Dashboard/commonBase/Erd.ts

@ -8,36 +8,36 @@ export abstract class ErdBasePage extends BasePage {
async clickShowColumnNames() { async clickShowColumnNames() {
await this.get().locator(`.nc-erd-showColumns-checkbox`).click(); await this.get().locator(`.nc-erd-showColumns-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable'); await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
} }
async dbClickShowColumnNames() { async dbClickShowColumnNames() {
await this.get().locator(`.nc-erd-showColumns-label`).dblclick(); await this.get().locator(`.nc-erd-showColumns-label`).dblclick();
(await this.vueFlow().elementHandle())?.waitForElementState('stable'); await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
} }
async clickShowPkAndFk() { async clickShowPkAndFk() {
await this.get().locator(`.nc-erd-showPkAndFk-checkbox`).click(); await this.get().locator(`.nc-erd-showPkAndFk-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable'); await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
} }
async clickShowSqlViews() { async clickShowSqlViews() {
await this.get().locator(`.nc-erd-showViews-checkbox`).click(); await this.get().locator(`.nc-erd-showViews-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable'); await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
} }
async clickShowMMTables() { async clickShowMMTables() {
await this.get().locator(`.nc-erd-showMMTables-checkbox`).click(); await this.get().locator(`.nc-erd-showMMTables-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable'); await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
} }
async clickShowJunctionTableNames() { async clickShowJunctionTableNames() {
await this.get().locator(`.nc-erd-showJunctionTableNames-checkbox`).click(); await this.get().locator(`.nc-erd-showJunctionTableNames-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable'); await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
} }
async verifyEasterEggNotShown() { async verifyEasterEggNotShown() {
await expect(await this.get().locator('.nc-erd-showMMTables-checkbox')).not.toBeVisible(); await expect(this.get().locator('.nc-erd-showMMTables-checkbox')).not.toBeVisible();
} }
async verifyNode({ async verifyNode({

7
tests/playwright/setup/db.ts

@ -11,16 +11,13 @@ const isSqlite = (context: NcContext) => context.dbType === 'sqlite';
const isPg = (context: NcContext) => context.dbType === 'pg'; const isPg = (context: NcContext) => context.dbType === 'pg';
// hardwired for hub; this has to be configured to false in nocodb
// consider reading this from environment variable
const isHub = () => true;
const isEE = () => process.env.EE === 'true'; const isEE = () => process.env.EE === 'true';
const pg_credentials = (context: NcContext) => ({ const pg_credentials = (context: NcContext) => ({
user: 'postgres', user: 'postgres',
host: 'localhost', host: 'localhost',
// todo: Hack to resolve issue with pg resetting // todo: Hack to resolve issue with pg resetting
database: `sakila_${context.workerId}`, database: `sakila${context.workerId}`,
password: 'password', password: 'password',
port: 5432, port: 5432,
}); });
@ -66,4 +63,4 @@ async function sqliteExec(query) {
await sqliteDb.close(); await sqliteDb.close();
} }
export { sqliteExec, mysqlExec, isMysql, isSqlite, isPg, pgExec, isHub, isEE }; export { sqliteExec, mysqlExec, isMysql, isSqlite, isPg, pgExec, isEE };

27
tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts

@ -19,9 +19,6 @@ test.describe('LTAR create & update', () => {
}); });
test('LTAR', async () => { test('LTAR', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.createTable({ title: 'Sheet1', projectTitle: context.project.title }); await dashboard.treeView.createTable({ title: 'Sheet1', projectTitle: context.project.title });
// subsequent table creation fails; hence delay // subsequent table creation fails; hence delay
await dashboard.rootPage.waitForTimeout(1000); await dashboard.rootPage.waitForTimeout(1000);
@ -189,6 +186,20 @@ test.describe('LTAR create & update', () => {
await dashboard.treeView.deleteTable({ title: 'Sheet1' }); await dashboard.treeView.deleteTable({ title: 'Sheet1' });
await dashboard.treeView.deleteTable({ title: 'Sheet2' }); await dashboard.treeView.deleteTable({ title: 'Sheet2' });
}); });
});
test.describe('Links after edit record', () => {
let dashboard: DashboardPage;
let context: any;
test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: false });
dashboard = new DashboardPage(page, context.project);
});
test.afterEach(async () => {
await unsetup(context);
});
async function verifyRow(param: { async function verifyRow(param: {
index: number; index: number;
@ -214,9 +225,10 @@ test.describe('LTAR create & update', () => {
await dashboard.grid.cell.verifyVirtualCell({ await dashboard.grid.cell.verifyVirtualCell({
index: param.index, index: param.index,
columnHeader: 'Cities', columnHeader: 'Cities',
count: param.value['Cities'].length, count: param.value.Cities.length,
value: param.value['Cities'], options: { singular: 'City', plural: 'Cities' },
}); });
if (param.value.SLT) { if (param.value.SLT) {
await dashboard.grid.cell.verify({ await dashboard.grid.cell.verify({
index: param.index, index: param.index,
@ -235,10 +247,7 @@ test.describe('LTAR create & update', () => {
* https://github.com/nocodb/nocodb/issues/4220 * https://github.com/nocodb/nocodb/issues/4220
* *
*/ */
test.skip('Existing LTAR table verification', async () => { test('Existing LTAR table verification', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
// open table // open table
await dashboard.treeView.openTable({ title: 'Country' }); await dashboard.treeView.openTable({ title: 'Country' });
await verifyRow({ await verifyRow({

26
tests/playwright/tests/db/features/baseShare.spec.ts

@ -7,7 +7,7 @@ import { ProjectsPage } from '../../../pages/ProjectsPage';
import { getDefaultPwd } from '../../../tests/utils/general'; import { getDefaultPwd } from '../../../tests/utils/general';
// To be enabled after shared base is implemented // To be enabled after shared base is implemented
test.describe.skip('Shared base', () => { test.describe('Shared base', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;
let toolbar: ToolbarPage; let toolbar: ToolbarPage;
let context: any; let context: any;
@ -18,10 +18,11 @@ test.describe.skip('Shared base', () => {
// todo: Wait till the page is loaded // todo: Wait till the page is loaded
await dashboard.rootPage.waitForTimeout(2000); await dashboard.rootPage.waitForTimeout(2000);
await dashboard.validateProjectMenu({ // fix me! this is currently disabled
role: role.toLowerCase(), // await dashboard.validateProjectMenu({
mode: 'shareBase', // role: role.toLowerCase(),
}); // mode: 'shareBase',
// });
await dashboard.treeView.openTable({ title: 'Country', mode: 'shareBase' }); await dashboard.treeView.openTable({ title: 'Country', mode: 'shareBase' });
@ -67,7 +68,7 @@ test.describe.skip('Shared base', () => {
let url = ''; let url = '';
// share button visible only if a table is opened // share button visible only if a table is opened
await dashboard.treeView.openTable({ title: 'Country' }); await dashboard.treeView.openTable({ title: 'Country' });
url = await dashboard.grid.toolbar.getSharedBaseUrl({ role: 'editor' }); url = await dashboard.grid.topbar.getSharedBaseUrl({ role: 'editor' });
await dashboard.rootPage.waitForTimeout(2000); await dashboard.rootPage.waitForTimeout(2000);
// access shared base link // access shared base link
@ -84,16 +85,9 @@ test.describe.skip('Shared base', () => {
withoutPrefix: true, withoutPrefix: true,
}); });
await projectPage.openProject({ title: context.project.title, withoutPrefix: true }); // await dashboard.treeView.openProject({ title: context.project.title });
await dashboard.closeTab({ title: 'Team & Auth' }); await dashboard.treeView.openTable({ title: 'Country' });
url = await dashboard.grid.topbar.getSharedBaseUrl({ role: 'viewer' });
await dashboard.gotoSettings();
await dashboard.settings.teams.clickInviteTeamBtn();
await dashboard.settings.teams.toggleSharedBase({ toggle: true });
await dashboard.settings.teams.sharedBaseRole({ role: 'viewer' });
url = await dashboard.settings.teams.getSharedBaseUrl();
await dashboard.settings.teams.closeInvite();
await dashboard.settings.close();
await dashboard.rootPage.waitForTimeout(2000); await dashboard.rootPage.waitForTimeout(2000);
// access shared base link // access shared base link

167
tests/playwright/tests/db/features/erd.spec.ts

@ -7,14 +7,11 @@ import {
sqliteSakilaSqlViews, sqliteSakilaSqlViews,
} from '../../../tests/utils/sakila'; } from '../../../tests/utils/sakila';
import { DashboardPage } from '../../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { SettingsSubTab, SettingTab } from '../../../pages/Dashboard/Settings';
import setup, { unsetup } from '../../../setup'; import setup, { unsetup } from '../../../setup';
import { isMysql, isPg, isSqlite } from '../../../setup/db'; import { isMysql, isPg, isSqlite } from '../../../setup/db';
import { SettingsErdPage } from '../../../pages/Dashboard/Settings/Erd';
import { defaultBaseName } from '../../../constants';
// Global ERD to be enabled after project-menu landing page is implemented // Global ERD to be enabled after project-menu landing page is implemented
test.describe.skip('Erd', () => { test.describe('Erd', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;
let context: any; let context: any;
let sakilaTables, sakilaSqlViews; let sakilaTables, sakilaSqlViews;
@ -42,16 +39,9 @@ test.describe.skip('Erd', () => {
}); });
const toggleMM = async () => { const toggleMM = async () => {
await dashboard.treeView.projectSettings({}); await dashboard.treeView.projectSettings({ title: context.project.title });
await dashboard.settings.miscellaneous.clickShowM2MTables(); await dashboard.settings.miscellaneous.clickShowM2MTables();
await dashboard.settings.close(); await dashboard.settings.close();
// await dashboard.settings.selectTab({ tab: SettingTab.ProjectSettings, subTab: SettingsSubTab.Miscellaneous });
// await dashboard.settings.miscellaneous.clickShowM2MTables();
// await dashboard.settings.selectTab({ tab: SettingTab.DataSources });
// await dashboard.settings.dataSources.openErd({
// dataSourceName: defaultBaseName,
// });
}; };
const openProjectErd = async () => { const openProjectErd = async () => {
@ -69,9 +59,7 @@ test.describe.skip('Erd', () => {
await toggleMM(); await toggleMM();
await openProjectErd(); await openProjectErd();
const erd: SettingsErdPage = dashboard.settings.dataSources.erd; const erd = dashboard.details.relations;
await erd.dbClickShowColumnNames();
if (isPg(context)) { if (isPg(context)) {
await erd.verifyNodesCount(sakilaTables.length); await erd.verifyNodesCount(sakilaTables.length);
@ -105,22 +93,23 @@ test.describe.skip('Erd', () => {
}); });
// Disable show column names and pk/fk // Disable show column names and pk/fk
// todo: rerender edges, otherwise some edges wont be rendered
await erd.clickShowColumnNames();
await erd.clickShowJunctionTableNames();
await erd.clickShowJunctionTableNames();
await erd.verifyColumns({ // Fix me! Disabled currently
tableName: `actor`, // await erd.clickShowColumnNames();
columns: actorLTARColumns, // await erd.clickShowJunctionTableNames();
}); // await erd.clickShowJunctionTableNames();
await erd.verifyColumns({ //
tableName: `payment`, // await erd.verifyColumns({
columns: paymentLTARColumns, // tableName: `actor`,
}); // columns: actorLTARColumns,
// });
// await erd.verifyColumns({
// tableName: `payment`,
// columns: paymentLTARColumns,
// });
// Enable show column names and disable pk/fk // Enable show column names and disable pk/fk
await erd.clickShowColumnNames(); // await erd.clickShowColumnNames();
await erd.clickShowPkAndFk(); await erd.clickShowPkAndFk();
await erd.verifyColumns({ await erd.verifyColumns({
@ -165,38 +154,39 @@ test.describe.skip('Erd', () => {
await erd.verifyNodeDoesNotExist({ tableName: `film_actor` }); await erd.verifyNodeDoesNotExist({ tableName: `film_actor` });
// Fix me! Easter egg menu is disabled currently
// // Verify MM tables // // Verify MM tables
await erd.clickShowMMTables(); // await erd.clickShowMMTables();
await erd.clickShowJunctionTableNames(); // await erd.clickShowJunctionTableNames();
await erd.clickShowJunctionTableNames(); // await erd.clickShowJunctionTableNames();
//
await erd.verifyNodesCount(isPg(context) ? 21 : 16); // await erd.verifyNodesCount(isPg(context) ? 21 : 16);
await erd.verifyEdgesCount({ // await erd.verifyEdgesCount({
count: isPg(context) ? 42 : 24, // count: isPg(context) ? 42 : 24,
circleCount: isPg(context) ? 40 : 22, // circleCount: isPg(context) ? 40 : 22,
rectangleCount: isPg(context) ? 44 : 26, // rectangleCount: isPg(context) ? 44 : 26,
}); // });
//
await erd.verifyNode({ tableName: `film_actor` }); // await erd.verifyNode({ tableName: `film_actor` });
//
// Verify show junction table names // // Verify show junction table names
await erd.clickShowJunctionTableNames(); // await erd.clickShowJunctionTableNames();
await erd.verifyJunctionTableLabel({ // await erd.verifyJunctionTableLabel({
tableName: `film_actor`, // tableName: `film_actor`,
tableTitle: 'filmactor', // tableTitle: 'filmactor',
}); // });
}); });
test('Verify ERD Table view, and verify column operations are reflected to the ERD view', async () => { test('Verify ERD Table view, and verify column operations are reflected to the ERD view', async () => {
await openErdOfATable('Country'); await openErdOfATable('Country');
const erd = dashboard.grid.toolbar.actions.erd; const erd = dashboard.details.relations;
await erd.clickShowColumnNames();
// Verify tables with default config // Verify tables with default config
await erd.verifyColumns({ await erd.verifyColumns({
tableName: `country`, tableName: `country`,
columns: ['country_id', 'country', 'last_update', 'cities'], columns: ['country_id', 'country', 'last_update', 'cities'],
}); });
await erd.verifyColumns({ await erd.verifyColumns({
tableName: `city`, tableName: `city`,
columns: ['city_id', 'city', 'country_id', 'last_update', 'country', 'addresses'], columns: ['city_id', 'city', 'country_id', 'last_update', 'country', 'addresses'],
@ -208,7 +198,6 @@ test.describe.skip('Erd', () => {
tableName: `country`, tableName: `country`,
columns: ['country', 'last_update', 'cities'], columns: ['country', 'last_update', 'cities'],
}); });
await erd.verifyColumns({ await erd.verifyColumns({
tableName: `city`, tableName: `city`,
columns: ['city', 'last_update', 'country', 'addresses'], columns: ['city', 'last_update', 'country', 'addresses'],
@ -217,7 +206,6 @@ test.describe.skip('Erd', () => {
// Verify with all columns disabled // Verify with all columns disabled
await erd.clickShowColumnNames(); await erd.clickShowColumnNames();
await erd.verifyColumns({ tableName: `country`, columns: ['cities'] }); await erd.verifyColumns({ tableName: `country`, columns: ['cities'] });
await erd.verifyColumns({ await erd.verifyColumns({
tableName: `city`, tableName: `city`,
columns: ['country', 'addresses'], columns: ['country', 'addresses'],
@ -226,25 +214,26 @@ test.describe.skip('Erd', () => {
// Enable All columns // Enable All columns
await erd.clickShowColumnNames(); await erd.clickShowColumnNames();
await erd.close(); // switch to data tab, add column, switch back to ERD tab, verify column added
//
await dashboard.grid.topbar.btn_data.click();
// Add column // Add column
await dashboard.grid.column.create({ title: 'test_column' }); await dashboard.grid.column.create({ title: 'test_column' });
// Verify in Settings ERD and table ERD
await openProjectErd();
await dashboard.settings.dataSources.erd.verifyNode({
tableName: `country`,
columnName: 'test_column',
});
await dashboard.settings.close();
await dashboard.viewSidebar.openDeveloperTab({ option: 'ERD' }); // Verify
await dashboard.grid.topbar.btn_details.click();
await openErdOfATable('Country');
await erd.clickShowColumnNames();
await dashboard.grid.toolbar.actions.erd.verifyNode({ await erd.verifyNode({
tableName: `country`, tableName: `country`,
columnName: 'test_column', columnName: 'test_column',
}); });
await dashboard.grid.toolbar.actions.erd.close();
/////////////////////////////////////////////////////////////////
await dashboard.grid.topbar.btn_data.click();
// Update column // Update column
await dashboard.grid.column.openEdit({ title: 'test_column' }); await dashboard.grid.column.openEdit({ title: 'test_column' });
@ -252,66 +241,58 @@ test.describe.skip('Erd', () => {
await dashboard.grid.column.save({ await dashboard.grid.column.save({
isUpdated: true, isUpdated: true,
}); });
// Verify in Settings ERD and table ERD
await openProjectErd();
await dashboard.settings.dataSources.erd.verifyNode({
tableName: `country`,
columnName: 'new_test_column',
});
await dashboard.settings.close();
await dashboard.viewSidebar.openDeveloperTab({ option: 'ERD' }); // Verify
await dashboard.grid.topbar.btn_details.click();
await openErdOfATable('Country');
await erd.clickShowColumnNames();
await dashboard.grid.toolbar.actions.erd.verifyNode({ await erd.verifyNode({
tableName: `country`, tableName: `country`,
columnName: 'new_test_column', columnName: 'new_test_column',
}); });
await dashboard.grid.toolbar.actions.erd.close();
/////////////////////////////////////////////////////////////////
await dashboard.grid.topbar.btn_data.click();
// Delete column // Delete column
await dashboard.grid.column.delete({ title: 'new_test_column' }); await dashboard.grid.column.delete({ title: 'new_test_column' });
// Verify in Settings ERD and table ERD await dashboard.grid.topbar.btn_details.click();
await openProjectErd(); await openErdOfATable('Country');
await dashboard.settings.dataSources.erd.verifyNode({ await erd.clickShowColumnNames();
await erd.verifyNode({
tableName: `country`, tableName: `country`,
columnNameShouldNotExist: 'new_test_column', columnNameShouldNotExist: 'new_test_column',
}); });
await dashboard.settings.close();
}); });
test('Verify table operations sync with ERD', async () => { test('Verify table operations sync with ERD', async () => {
await openProjectErd(); await openProjectErd();
await dashboard.settings.close(); await dashboard.details.relations.verifyNode({
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.viewSidebar.openDeveloperTab({ option: 'ERD' });
await dashboard.grid.toolbar.actions.erd.verifyNode({
tableName: `country`, tableName: `country`,
columnNameShouldNotExist: 'new_test_column', columnNameShouldNotExist: 'new_test_column',
}); });
await dashboard.grid.toolbar.actions.erd.close();
await dashboard.details.relations.close();
// Create table and verify ERD // Create table and verify ERD
await dashboard.treeView.createTable({ title: 'Test', projectTitle: context.project.title }); await dashboard.treeView.createTable({ title: 'Test', projectTitle: context.project.title });
// Verify in Settings ERD and table ERD // Verify in Settings ERD and table ERD
await dashboard.treeView.openProject({ title: context.project.title });
await openProjectErd(); await openProjectErd();
await dashboard.settings.dataSources.erd.verifyNode({ await dashboard.details.relations.verifyNode({
tableName: `Test`, tableName: `Test`,
}); });
await dashboard.settings.close(); await dashboard.details.relations.close();
// Delete table and verify ERD // Delete table and verify ERD
await dashboard.treeView.deleteTable({ title: 'Test' }); await dashboard.treeView.deleteTable({ title: 'Test' });
await openProjectErd(); await openProjectErd();
await dashboard.settings.dataSources.erd.verifyNodeDoesNotExist({ await dashboard.details.relations.verifyNodeDoesNotExist({
tableName: `Test`, tableName: `Test`,
}); });
await dashboard.details.relations.close();
// Verify that `show mm table` option disabled will not trigger easter in ERD options
await dashboard.settings.dataSources.erd.dbClickShowColumnNames();
await dashboard.settings.dataSources.erd.verifyEasterEggNotShown();
await dashboard.settings.close();
}); });
}); });
@ -343,12 +324,8 @@ const pgPaymentTableColumns = [
]; ];
const actorLTARColumns = ['filmactors', 'films']; const actorLTARColumns = ['filmactors', 'films'];
const actorNonPkFkColumns = ['first_name', 'last_name', 'last_update', 'films', 'filmactors']; const actorNonPkFkColumns = ['first_name', 'last_name', 'last_update', 'films', 'filmactors'];
const paymentLTARColumns = ['customer', 'rental', 'staff']; const paymentLTARColumns = ['customer', 'rental', 'staff'];
const pgPaymentNonPkFkColumns = ['amount', 'payment_date', 'customer', 'rental', 'staff']; const pgPaymentNonPkFkColumns = ['amount', 'payment_date', 'customer', 'rental', 'staff'];
const paymentNonPkFkColumns = [...pgPaymentNonPkFkColumns, 'last_update']; const paymentNonPkFkColumns = [...pgPaymentNonPkFkColumns, 'last_update'];
const salesByStoreColumns = ['store', 'manager', 'total_sales']; const salesByStoreColumns = ['store', 'manager', 'total_sales'];

3
tests/playwright/tests/db/features/filters.spec.ts

@ -98,6 +98,7 @@ async function verifyFilter(param: {
locallySaved: false, locallySaved: false,
dataType: param?.dataType, dataType: param?.dataType,
openModal: true, openModal: true,
skipWaitingResponse: true,
}); });
// verify filtered rows // verify filtered rows
@ -257,7 +258,7 @@ test.describe('Filter Tests: Numerical', () => {
await numBasedFilterTest('Rating', '3', '2'); await numBasedFilterTest('Rating', '3', '2');
}); });
test.skip('Filter: Duration', async () => { test('Filter: Duration', async () => {
await numBasedFilterTest('Duration', '00:01', '01:03'); await numBasedFilterTest('Duration', '00:01', '01:03');
}); });

54
tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

@ -3,7 +3,7 @@ import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup, { unsetup } from '../../../setup'; import setup, { unsetup } from '../../../setup';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
import { isEE, isHub } from '../../../setup/db'; import { isEE } from '../../../setup/db';
import { getDefaultPwd } from '../../utils/general'; import { getDefaultPwd } from '../../utils/general';
import config from '../../../playwright.config'; import config from '../../../playwright.config';
@ -76,43 +76,27 @@ test.describe('Verify shortcuts', () => {
// fullscreen // fullscreen
// to be implemented for hub // to be implemented for hub
if (!isHub()) { // await page.keyboard.press('Alt+f');
await page.keyboard.press('Alt+f'); // await dashboard.treeView.verifyVisibility({
await dashboard.treeView.verifyVisibility({ // isVisible: false,
isVisible: false, // });
}); // await dashboard.viewSidebar.verifyVisibility({
await dashboard.viewSidebar.verifyVisibility({ // isVisible: false,
isVisible: false, // });
}); // await page.keyboard.press('Alt+f');
await page.keyboard.press('Alt+f'); // await dashboard.treeView.verifyVisibility({
await dashboard.treeView.verifyVisibility({ // isVisible: true,
isVisible: true, // });
}); // await dashboard.viewSidebar.verifyVisibility({
await dashboard.viewSidebar.verifyVisibility({ // isVisible: true,
isVisible: true, // });
});
}
// disabled temporarily for hub. Clipboard access to be fixed. // disabled temporarily for hub. Clipboard access to be fixed.
if (!isHub()) {
// invite team member // invite team member
await page.keyboard.press('Alt+i'); // await page.keyboard.press('Alt+i');
if (isHub()) { // await dashboard.grid.toolbar.share.invite({ email: 'new@example.com', role: 'editor' });
await dashboard.grid.toolbar.share.invite({ email: 'new@example.com', role: 'editor' }); // const url = await dashboard.grid.toolbar.share.getInvitationUrl();
const url = await dashboard.grid.toolbar.share.getInvitationUrl(); // expect(url).toContain('signup');
expect(url).toContain('signup');
} else {
await dashboard.settings.teams.invite({
email: 'new@example.com',
role: 'editor',
skipOpeningModal: true,
});
const url = await dashboard.settings.teams.getInvitationUrl();
expect(url).toContain('signup');
await page.waitForTimeout(1000);
await dashboard.settings.teams.closeInvite();
}
}
// Cmd + Right arrow // Cmd + Right arrow
await dashboard.treeView.openTable({ title: 'Country' }); await dashboard.treeView.openTable({ title: 'Country' });

2
tests/playwright/tests/db/features/language.spec.ts

@ -41,6 +41,8 @@ const langMenu = [
'zh-Hant.json', 'zh-Hant.json',
]; ];
// i18n menu not enabled for EE
//
test.describe.skip('Common', () => { test.describe.skip('Common', () => {
let context: any; let context: any;
let dashboard: DashboardPage; let dashboard: DashboardPage;

95
tests/playwright/tests/db/features/metaSync.spec.ts

@ -3,17 +3,18 @@ import { DashboardPage } from '../../../pages/Dashboard';
import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings'; import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
import setup, { NcContext, unsetup } from '../../../setup'; import setup, { NcContext, unsetup } from '../../../setup';
import { isMysql, isPg, isSqlite, mysqlExec, pgExec, sqliteExec } from '../../../setup/db'; import { isMysql, isPg, isSqlite, mysqlExec, pgExec, sqliteExec } from '../../../setup/db';
import { MetaDataPage } from '../../../pages/Dashboard/ProjectView/Metadata';
test.describe.skip('Meta sync', () => { test.describe('Meta sync', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;
let settings: SettingsPage;
let context: NcContext; let context: NcContext;
let dbExec; let dbExec;
let metaData: MetaDataPage;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: false }); context = await setup({ page, isEmptyProject: false });
dashboard = new DashboardPage(page, context.project); dashboard = new DashboardPage(page, context.project);
settings = dashboard.settings; metaData = dashboard.projectView.dataSources.metaData;
switch (context.dbType) { switch (context.dbType) {
case 'sqlite': case 'sqlite':
@ -35,34 +36,33 @@ test.describe.skip('Meta sync', () => {
test('Meta sync', async () => { test('Meta sync', async () => {
test.setTimeout(process.env.CI ? 100000 : 70000); test.setTimeout(process.env.CI ? 100000 : 70000);
await dashboard.gotoSettings(); await dashboard.projectView.tab_dataSources.click();
await settings.selectTab({ tab: SettingTab.DataSources }); await dashboard.projectView.dataSources.openMetaSync({ rowIndex: 0 });
await settings.dataSources.openMetaSync();
await dbExec(`CREATE TABLE table1 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`); await dbExec(`CREATE TABLE table1 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`);
await dbExec(`CREATE TABLE table2 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`); await dbExec(`CREATE TABLE table2 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`);
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await dashboard.rootPage.waitForTimeout(1000); await dashboard.rootPage.waitForTimeout(1000);
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: `table1`, model: `table1`,
state: 'New table', state: 'New table',
}); });
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 22 : 17, index: isPg(context) ? 22 : 17,
model: `table2`, model: `table2`,
state: 'New table', state: 'New table',
}); });
await settings.dataSources.metaData.sync(); await metaData.sync();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'No change identified', state: 'No change identified',
}); });
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 22 : 17, index: isPg(context) ? 22 : 17,
model: 'Table2', model: 'Table2',
state: 'No change identified', state: 'No change identified',
@ -78,16 +78,16 @@ test.describe.skip('Meta sync', () => {
`ALTER TABLE table1 ADD CONSTRAINT fk1 FOREIGN KEY (col1) REFERENCES table2 (id) ON DELETE NO ACTION ON UPDATE NO ACTION` `ALTER TABLE table1 ADD CONSTRAINT fk1 FOREIGN KEY (col1) REFERENCES table2 (id) ON DELETE NO ACTION ON UPDATE NO ACTION`
); );
} }
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'New relation added', state: 'New relation added',
}); });
//verify after sync //verify after sync
await settings.dataSources.metaData.sync(); await metaData.sync();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'No change identified', state: 'No change identified',
@ -100,16 +100,16 @@ test.describe.skip('Meta sync', () => {
await dbExec(`ALTER TABLE table1 DROP FOREIGN KEY fk1`); await dbExec(`ALTER TABLE table1 DROP FOREIGN KEY fk1`);
await dbExec(`ALTER TABLE table1 DROP INDEX fk1_idx`); await dbExec(`ALTER TABLE table1 DROP INDEX fk1_idx`);
} }
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'Relation removed', state: 'Relation removed',
}); });
//verify after sync //verify after sync
await settings.dataSources.metaData.sync(); await metaData.sync();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'No change identified', state: 'No change identified',
@ -125,16 +125,16 @@ test.describe.skip('Meta sync', () => {
await dbExec(`ALTER TABLE table1 ADD COLUMN newCol INT`); await dbExec(`ALTER TABLE table1 ADD COLUMN newCol INT`);
} }
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: `Table1`, model: `Table1`,
state: 'New column(newCol)', state: 'New column(newCol)',
}); });
//verify after sync //verify after sync
await settings.dataSources.metaData.sync(); await metaData.sync();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'No change identified', state: 'No change identified',
@ -149,16 +149,16 @@ test.describe.skip('Meta sync', () => {
await dbExec(`ALTER TABLE table1 RENAME COLUMN newCol TO newColName`); await dbExec(`ALTER TABLE table1 RENAME COLUMN newCol TO newColName`);
} }
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: `Table1`, model: `Table1`,
state: 'New column(newColName), Column removed(newCol)', state: 'New column(newColName), Column removed(newCol)',
}); });
//verify after sync //verify after sync
await settings.dataSources.metaData.sync(); await metaData.sync();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'No change identified', state: 'No change identified',
@ -168,16 +168,16 @@ test.describe.skip('Meta sync', () => {
// todo: Add for sqlite // todo: Add for sqlite
if (!isSqlite(context)) { if (!isSqlite(context)) {
await dbExec(`ALTER TABLE table1 DROP COLUMN newColName`); await dbExec(`ALTER TABLE table1 DROP COLUMN newColName`);
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: `Table1`, model: `Table1`,
state: 'Column removed(newColName)', state: 'Column removed(newColName)',
}); });
//verify after sync //verify after sync
await settings.dataSources.metaData.sync(); await metaData.sync();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: 'Table1', model: 'Table1',
state: 'No change identified', state: 'No change identified',
@ -187,51 +187,51 @@ test.describe.skip('Meta sync', () => {
// Delete table // Delete table
await dbExec(`DROP TABLE table1`); await dbExec(`DROP TABLE table1`);
await dbExec(`DROP TABLE table2`); await dbExec(`DROP TABLE table2`);
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 21 : 16, index: isPg(context) ? 21 : 16,
model: `table1`, model: `table1`,
state: 'Table removed', state: 'Table removed',
}); });
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: isPg(context) ? 22 : 17, index: isPg(context) ? 22 : 17,
model: `table2`, model: `table2`,
state: 'Table removed', state: 'Table removed',
}); });
//verify after sync //verify after sync
await settings.dataSources.metaData.sync(); await metaData.sync();
if (isSqlite(context)) { if (isSqlite(context)) {
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: 16, index: 16,
model: 'CustomerList', model: 'CustomerList',
state: 'No change identified', state: 'No change identified',
}); });
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: 17, index: 17,
model: 'FilmList', model: 'FilmList',
state: 'No change identified', state: 'No change identified',
}); });
} }
if (isPg(context)) { if (isPg(context)) {
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: 21, index: 21,
model: 'ActorInfo', model: 'ActorInfo',
state: 'No change identified', state: 'No change identified',
}); });
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: 22, index: 22,
model: 'CustomerList', model: 'CustomerList',
state: 'No change identified', state: 'No change identified',
}); });
} else if (isMysql(context)) { } else if (isMysql(context)) {
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: 16, index: 16,
model: 'ActorInfo', model: 'ActorInfo',
state: 'No change identified', state: 'No change identified',
}); });
await settings.dataSources.metaData.verifyRow({ await metaData.verifyRow({
index: 17, index: 17,
model: 'CustomerList', model: 'CustomerList',
state: 'No change identified', state: 'No change identified',
@ -247,13 +247,12 @@ test.describe.skip('Meta sync', () => {
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (1,1,1,1,1), (2,2,2,2,2), (3,3,3,3,3), (4,4,4,4,4), (5,5,5,5,5), (6,6,6,6,6), (7,7,7,7,7), (8,8,8,8,8), (9,9,9,9,9);` `INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (1,1,1,1,1), (2,2,2,2,2), (3,3,3,3,3), (4,4,4,4,4), (5,5,5,5,5), (6,6,6,6,6), (7,7,7,7,7), (8,8,8,8,8), (9,9,9,9,9);`
); );
await dashboard.gotoSettings(); await dashboard.projectView.tab_dataSources.click();
await settings.selectTab({ tab: SettingTab.DataSources }); await dashboard.projectView.dataSources.openMetaSync({ rowIndex: 0 });
await settings.dataSources.openMetaSync();
await settings.dataSources.metaData.clickReload(); await metaData.clickReload();
await settings.dataSources.metaData.sync(); await metaData.sync();
await settings.close(); await metaData.close();
await dashboard.treeView.openTable({ title: 'Table1' }); await dashboard.treeView.openTable({ title: 'Table1' });

4
tests/playwright/tests/db/features/swagger.spec.ts

@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup, { unsetup } from '../../../setup'; import setup, { unsetup } from '../../../setup';
test.describe.skip('Table Column Operations', () => { test.describe('Swagger', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;
let context: any; let context: any;
@ -20,7 +20,7 @@ test.describe.skip('Table Column Operations', () => {
const link = `http://localhost:8080/api/v1/db/meta/projects/${context.project.id}/swagger`; const link = `http://localhost:8080/api/v1/db/meta/projects/${context.project.id}/swagger`;
await dashboard.rootPage.goto(link, { waitUntil: 'networkidle' }); await dashboard.rootPage.goto(link, { waitUntil: 'networkidle' });
const swagger = await dashboard.rootPage; const swagger = dashboard.rootPage;
// authorize with token information // authorize with token information
await swagger.locator('.btn.authorize').click(); await swagger.locator('.btn.authorize').click();

8
tests/playwright/tests/db/features/timezone.spec.ts

@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup, { NcContext, unsetup } from '../../../setup'; import setup, { NcContext, unsetup } from '../../../setup';
import { Api, ProjectListType, UITypes } from 'nocodb-sdk'; import { Api, ProjectListType, UITypes } from 'nocodb-sdk';
import { isEE, isHub, isMysql, isPg, isSqlite } from '../../../setup/db'; import { isEE, isMysql, isPg, isSqlite } from '../../../setup/db';
import { getKnexConfig } from '../../utils/config'; import { getKnexConfig } from '../../utils/config';
import { getBrowserTimezoneOffset } from '../../utils/general'; import { getBrowserTimezoneOffset } from '../../utils/general';
import config from '../../../playwright.config'; import config from '../../../playwright.config';
@ -116,7 +116,7 @@ test.describe.serial('Timezone-XCDB : Japan/Tokyo', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: true }); context = await setup({ page, isEmptyProject: true });
dashboard = new DashboardPage(page, context.project); dashboard = new DashboardPage(page, context.project);
if (!isSqlite(context) && !isHub()) return; if (!isSqlite(context)) return;
try { try {
const { project, table, api } = await timezoneSuite(`xcdb${context.workerId}`, context); const { project, table, api } = await timezoneSuite(`xcdb${context.workerId}`, context);
@ -153,7 +153,7 @@ test.describe.serial('Timezone-XCDB : Japan/Tokyo', () => {
* Display value is converted to Asia/Tokyo * Display value is converted to Asia/Tokyo
*/ */
test('API insert, verify display value', async () => { test('API insert, verify display value', async () => {
if (!isSqlite(context) && !isHub()) return; if (!isSqlite(context)) return;
await dashboard.treeView.openBase({ title: `xcdb${context.workerId}` }); await dashboard.treeView.openBase({ title: `xcdb${context.workerId}` });
await dashboard.treeView.openTable({ title: 'dateTimeTable' }); await dashboard.treeView.openTable({ title: 'dateTimeTable' });
@ -189,7 +189,7 @@ test.describe.serial('Timezone-XCDB : Japan/Tokyo', () => {
*/ */
test('API Insert, verify API read response', async () => { test('API Insert, verify API read response', async () => {
if (!isSqlite(context) && !isHub()) return; if (!isSqlite(context)) return;
const dateInserted = new Date(`2021-01-01 00:00:00${getBrowserTimezoneOffset()}`); const dateInserted = new Date(`2021-01-01 00:00:00${getBrowserTimezoneOffset()}`);
// translate dateInserted to UTC in YYYY-MM-DD HH:mm format // translate dateInserted to UTC in YYYY-MM-DD HH:mm format

18
tests/playwright/tests/db/features/undo-redo.spec.ts

@ -327,18 +327,24 @@ test.describe('Undo Redo', () => {
await verifyRowHeight({ height: '1.8rem' }); await verifyRowHeight({ height: '1.8rem' });
}); });
// fix me! is flaky, and need to be validated test('Column width', async ({ page }) => {
test.skip('Column width', async ({ page }) => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'numberBased' }); await dashboard.treeView.openTable({ title: 'numberBased' });
const originalWidth = await dashboard.grid.column.getWidth({ title: 'Number' }); const originalWidth = await dashboard.grid.column.getWidth({ title: 'Number' });
await dashboard.grid.column.resize({ src: 'Number', dst: 'Decimal' }); await dashboard.grid.column.resize({ src: 'Number', dst: 'Decimal' });
await dashboard.rootPage.waitForTimeout(100); let modifiedWidth = await dashboard.grid.column.getWidth({ title: 'Number' });
let retryCounter = 0;
while (modifiedWidth === originalWidth) {
retryCounter++;
await dashboard.rootPage.waitForTimeout(500 * retryCounter);
if (retryCounter > 5) {
break;
}
modifiedWidth = await dashboard.grid.column.getWidth({ title: 'Number' });
}
const modifiedWidth = await dashboard.grid.column.getWidth({ title: 'Number' });
expect(modifiedWidth).toBeGreaterThan(originalWidth); expect(modifiedWidth).toBeGreaterThan(originalWidth);
await undo({ page, dashboard }); await undo({ page, dashboard });

95
tests/playwright/tests/db/general/projectOperations.spec.ts

@ -2,26 +2,30 @@ import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { airtableApiBase, airtableApiKey } from '../../../constants'; import { airtableApiBase, airtableApiKey } from '../../../constants';
import setup, { unsetup } from '../../../setup'; import setup, { unsetup } from '../../../setup';
import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { ProjectsPage } from '../../../pages/ProjectsPage';
import { Api, ProjectListType } from 'nocodb-sdk'; import { Api, ProjectListType } from 'nocodb-sdk';
import { ProjectInfo, ProjectInfoApiUtil } from '../../../tests/utils/projectInfoApiUtil'; import { ProjectInfo, ProjectInfoApiUtil } from '../../../tests/utils/projectInfoApiUtil';
import { deepCompare } from '../../../tests/utils/objectCompareUtil'; import { deepCompare } from '../../../tests/utils/objectCompareUtil';
import { isHub } from '../../../setup/db'; import { isEE } from '../../../setup/db';
// tests covered under tests/playwright/tests/nocohub/hubDashboard.spec.ts test.describe('Project operations', () => {
test.describe.skip('Project operations', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any; let context: any;
let api: Api<any>; let api: Api<any>;
let projectPage: ProjectsPage;
test.setTimeout(100000); test.setTimeout(100000);
async function getProjectList() {
let projectList: ProjectListType;
if (isEE() && api['workspaceProject']) {
const ws = await api['workspace'].list();
projectList = await api['workspaceProject'].list(ws.list[1].id);
} else {
projectList = await api.project.list();
}
return projectList;
}
async function deleteIfExists(name: string) { async function deleteIfExists(name: string) {
try { try {
const ws = await api.workspace.list(); const projectList = await getProjectList();
const projectList = await api.workspaceProject.list(ws.list[0].id);
const project = projectList.list.find((p: any) => p.title === name); const project = projectList.list.find((p: any) => p.title === name);
if (project) { if (project) {
@ -34,29 +38,25 @@ test.describe.skip('Project operations', () => {
} }
async function createTestProjectWithData(testProjectName: string) { async function createTestProjectWithData(testProjectName: string) {
await dashboard.clickHome(); await dashboard.leftSidebar.createProject({ title: testProjectName });
await projectPage.createProject({ name: testProjectName, withoutPrefix: true }); await dashboard.treeView.openProject({ title: testProjectName });
await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: context.project.title }); await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: testProjectName });
await dashboard.importAirtable.import({ await dashboard.importAirtable.import({
key: airtableApiKey, key: airtableApiKey,
baseId: airtableApiBase, baseId: airtableApiBase,
}); });
await dashboard.rootPage.waitForTimeout(1000); await dashboard.rootPage.waitForTimeout(1000);
// await quickVerify({ dashboard, airtableImport: true, context });
} }
async function cleanupTestData(dupeProjectName: string, testProjectName: string) { async function cleanupTestData(dupeProjectName: string, testProjectName: string) {
await dashboard.clickHome(); await dashboard.treeView.deleteProject({ title: dupeProjectName });
await projectPage.deleteProject({ title: dupeProjectName, withoutPrefix: true }); await dashboard.treeView.deleteProject({ title: testProjectName });
await projectPage.deleteProject({ title: testProjectName, withoutPrefix: true });
} }
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
page.setDefaultTimeout(70000); page.setDefaultTimeout(70000);
context = await setup({ page }); context = await setup({ page });
dashboard = new DashboardPage(page, context.project); dashboard = new DashboardPage(page, context.project);
projectPage = new ProjectsPage(page);
toolbar = dashboard.grid.toolbar;
api = new Api({ api = new Api({
baseURL: `http://localhost:8080/`, baseURL: `http://localhost:8080/`,
@ -70,57 +70,35 @@ test.describe.skip('Project operations', () => {
await unsetup(context); await unsetup(context);
}); });
test.skip('rename, delete', async () => { test('rename, delete', async () => {
// Already verified as part of workspace tests
// if project already exists, delete it // if project already exists, delete it
await deleteIfExists('project-firstName'); await deleteIfExists('project-firstName');
await dashboard.clickHome(); await dashboard.leftSidebar.createProject({ title: 'project-firstName' });
await projectPage.createProject({ name: 'project-firstName', withoutPrefix: true }); await dashboard.treeView.renameProject({ title: 'project-firstName', newTitle: 'project-rename' });
await dashboard.clickHome(); await dashboard.treeView.openProject({ title: 'project-rename' });
await projectPage.renameProject({ await dashboard.treeView.deleteProject({ title: 'project-rename' });
title: 'project-firstName',
newTitle: 'project-rename',
withoutPrefix: true,
});
await dashboard.clickHome();
await projectPage.openProject({ title: 'project-rename', withoutPrefix: true });
await dashboard.clickHome();
await projectPage.deleteProject({ title: 'project-rename', withoutPrefix: true });
}); });
test('project_duplicate', async () => { test('project_duplicate', async () => {
// if project already exists, delete it to avoid test failures due to residual data // if project already exists, delete it to avoid test failures due to residual data
const testProjectName = 'project-to-imexp'; const testProjectName = 'Project-To-Import-Export';
const dupeProjectName: string = testProjectName + ' copy'; const dupeProjectName: string = testProjectName + ' copy';
await deleteIfExists(testProjectName); await deleteIfExists(testProjectName);
await deleteIfExists(dupeProjectName); await deleteIfExists(dupeProjectName);
// // data creation for orginial test project // // data creation for original test project
await createTestProjectWithData(testProjectName); await createTestProjectWithData(testProjectName);
// create duplicate // duplicate duplicate
await dashboard.clickHome(); await dashboard.treeView.duplicateProject({ title: testProjectName });
await projectPage.duplicateProject({ await dashboard.treeView.openProject({ title: testProjectName });
name: testProjectName,
withoutPrefix: true,
includeData: true,
includeViews: true,
});
await projectPage.openProject({ title: dupeProjectName, withoutPrefix: true });
// await quickVerify({ dashboard, airtableImport: true, context });
// compare // compare
let projectList: ProjectListType; const projectList = await getProjectList();
if (isHub()) {
const ws = await api.workspace.list(); const testProjectId = projectList.list.find((p: any) => p.title === testProjectName);
projectList = await api.workspaceProject.list(ws.list[0].id); const dupeProjectId = projectList.list.find((p: any) => p.title.startsWith(testProjectName + ' copy'));
} else {
projectList = await api.project.list();
}
const testProjectId = await projectList.list.find((p: any) => p.title === testProjectName);
const dupeProjectId = await projectList.list.find((p: any) => p.title === dupeProjectName);
const projectInfoOp: ProjectInfoApiUtil = new ProjectInfoApiUtil(context.token); const projectInfoOp: ProjectInfoApiUtil = new ProjectInfoApiUtil(context.token);
const original: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(testProjectId.id); const original: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(testProjectId.id);
const duplicate: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(dupeProjectId.id); const duplicate: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(dupeProjectId.id);
@ -163,12 +141,15 @@ test.describe.skip('Project operations', () => {
'.users.1.roles', '.users.1.roles',
'.users.2.roles', '.users.2.roles',
]); ]);
const orginalProjectInfo: ProjectInfo = arr[0]; const originalProjectInfo: ProjectInfo = arr[0];
const duplicateProjectInfo: ProjectInfo = arr[1]; const duplicateProjectInfo: ProjectInfo = arr[1];
expect(deepCompare(orginalProjectInfo, duplicateProjectInfo, ignoredFields, ignoredKeys, '', false)).toBeTruthy(); expect(
deepCompare(originalProjectInfo, duplicateProjectInfo, ignoredFields, ignoredKeys, '', false)
).toBeTruthy();
}); });
// cleanup test-data // cleanup test-data
await cleanupTestData(dupeProjectName, testProjectName); // fix me! skip project cleanup
// await cleanupTestData(dupeProjectId.title, testProjectId.title);
}); });
}); });

4
tests/playwright/tests/db/general/tableOperations.spec.ts

@ -5,7 +5,7 @@ import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
import { deepCompare } from '../../../tests/utils/objectCompareUtil'; import { deepCompare } from '../../../tests/utils/objectCompareUtil';
import setup, { unsetup } from '../../../setup'; import setup, { unsetup } from '../../../setup';
import { ProjectInfoApiUtil, TableInfo } from '../../../tests/utils/projectInfoApiUtil'; import { ProjectInfoApiUtil, TableInfo } from '../../../tests/utils/projectInfoApiUtil';
import { isHub } from '../../../setup/db'; import { isEE } from '../../../setup/db';
test.describe('Table Operations', () => { test.describe('Table Operations', () => {
let dashboard: DashboardPage, settings: SettingsPage; let dashboard: DashboardPage, settings: SettingsPage;
@ -28,7 +28,7 @@ test.describe('Table Operations', () => {
await dashboard.treeView.deleteTable({ title: 'tablex' }); await dashboard.treeView.deleteTable({ title: 'tablex' });
await dashboard.treeView.verifyTable({ title: 'tablex', exists: false }); await dashboard.treeView.verifyTable({ title: 'tablex', exists: false });
if (!isHub()) { if (!isEE()) {
// Audit logs in clickhouse; locally wont be accessible // Audit logs in clickhouse; locally wont be accessible
await dashboard.gotoSettings(); await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.Audit }); await settings.selectTab({ tab: SettingTab.Audit });

10
tests/playwright/tests/db/general/viewMenu.spec.ts

@ -16,12 +16,10 @@ test.describe('Grid view locked', () => {
await unsetup(context); await unsetup(context);
}); });
test.skip('ReadOnly lock & collaboration mode', async () => { test('ReadOnly lock & collaboration mode', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country' }); await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.toolbar.viewsMenu.verifyCollaborativeMode(); await dashboard.grid.verifyCollaborativeMode();
// enable view lock // enable view lock
await dashboard.grid.toolbar.viewsMenu.click({ await dashboard.grid.toolbar.viewsMenu.click({
@ -30,7 +28,7 @@ test.describe('Grid view locked', () => {
}); });
// verify view lock // verify view lock
await dashboard.grid.toolbar.viewsMenu.verifyLockMode(); await dashboard.grid.verifyLockMode();
// enable collaborative view // enable collaborative view
await dashboard.grid.toolbar.viewsMenu.click({ await dashboard.grid.toolbar.viewsMenu.click({
@ -38,7 +36,7 @@ test.describe('Grid view locked', () => {
subMenu: 'Collaborative View', subMenu: 'Collaborative View',
}); });
await dashboard.grid.toolbar.viewsMenu.verifyCollaborativeMode(); await dashboard.grid.verifyCollaborativeMode();
}); });
test('Download CSV', async () => { test('Download CSV', async () => {

71
tests/playwright/tests/db/views/viewForm.spec.ts

@ -9,7 +9,7 @@ import { Api, UITypes } from 'nocodb-sdk';
import { LoginPage } from '../../../pages/LoginPage'; import { LoginPage } from '../../../pages/LoginPage';
import { getDefaultPwd } from '../../../tests/utils/general'; import { getDefaultPwd } from '../../../tests/utils/general';
import { WorkspacePage } from '../../../pages/WorkspacePage'; import { WorkspacePage } from '../../../pages/WorkspacePage';
import { isEE, isHub } from '../../../setup/db'; import { isEE } from '../../../setup/db';
// todo: Move most of the ui actions to page object and await on the api response // todo: Move most of the ui actions to page object and await on the api response
test.describe('Form view', () => { test.describe('Form view', () => {
@ -188,44 +188,39 @@ test.describe('Form view', () => {
}); });
const url = dashboard.rootPage.url(); const url = dashboard.rootPage.url();
// fix me! for app store, need super admin login.
if (isHub()) {
return;
}
// activate SMTP plugin // activate SMTP plugin
await accountAppStorePage.goto(); // await accountAppStorePage.goto();
//
// install SMTP // // install SMTP
await accountAppStorePage.install({ name: 'SMTP' }); // await accountAppStorePage.install({ name: 'SMTP' });
await accountAppStorePage.configureSMTP({ // await accountAppStorePage.configureSMTP({
email: 'a@b.com', // email: 'a@b.com',
host: 'smtp.gmail.com', // host: 'smtp.gmail.com',
port: '587', // port: '587',
}); // });
await dashboard.verifyToast({ // await dashboard.verifyToast({
message: 'Successfully installed and email notification will use SMTP configuration', // message: 'Successfully installed and email notification will use SMTP configuration',
}); // });
//
// revisit form view // // revisit form view
await page.goto(url); // await page.goto(url);
//
// enable 'email-me' option // // enable 'email-me' option
await dashboard.viewSidebar.openView({ title: 'CountryForm' }); // await dashboard.viewSidebar.openView({ title: 'CountryForm' });
await form.emailMeRadioButton.click(); // await form.emailMeRadioButton.click();
await form.verifyAfterSubmitMenuState({ // await form.verifyAfterSubmitMenuState({
emailMe: true, // emailMe: true,
submitAnotherForm: false, // submitAnotherForm: false,
showBlankForm: false, // showBlankForm: false,
}); // });
//
// Uninstall SMTP // // Uninstall SMTP
await accountAppStorePage.goto(); // await accountAppStorePage.goto();
await accountAppStorePage.uninstall({ name: 'SMTP' }); // await accountAppStorePage.uninstall({ name: 'SMTP' });
//
await dashboard.verifyToast({ // await dashboard.verifyToast({
message: 'Plugin uninstalled successfully', // message: 'Plugin uninstalled successfully',
}); // });
}); });
test('Form share, verify attachment file', async () => { test('Form share, verify attachment file', async () => {

Loading…
Cancel
Save