Browse Source

Merge pull request #6278 from nocodb/test/0831-sync

test: sync
pull/6283/head
Raju Udava 1 year ago committed by GitHub
parent
commit
ee23544bb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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. 76
      tests/playwright/pages/Dashboard/ProjectView/Audit.ts
  9. 31
      tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts
  10. 22
      tests/playwright/pages/Dashboard/ProjectView/Metadata.ts
  11. 2
      tests/playwright/pages/Dashboard/QrCodeOverlay/index.ts
  12. 2
      tests/playwright/pages/Dashboard/Settings/DataSources.ts
  13. 43
      tests/playwright/pages/Dashboard/TreeView.ts
  14. 4
      tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
  15. 2
      tests/playwright/pages/Dashboard/common/Cell/DateCell.ts
  16. 2
      tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts
  17. 2
      tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts
  18. 2
      tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts
  19. 4
      tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts
  20. 2
      tests/playwright/pages/Dashboard/common/Cell/YearCell.ts
  21. 39
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  22. 10
      tests/playwright/pages/Dashboard/common/Footbar/index.ts
  23. 10
      tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts
  24. 2
      tests/playwright/pages/Dashboard/common/ProjectMenu/index.ts
  25. 39
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  26. 2
      tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts
  27. 2
      tests/playwright/pages/Dashboard/common/Toolbar/SearchData.ts
  28. 4
      tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts
  29. 47
      tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts
  30. 32
      tests/playwright/pages/Dashboard/common/Toolbar/index.ts
  31. 16
      tests/playwright/pages/Dashboard/common/Topbar/Share.ts
  32. 7
      tests/playwright/pages/Dashboard/common/Topbar/index.ts
  33. 14
      tests/playwright/pages/Dashboard/commonBase/Erd.ts
  34. 7
      tests/playwright/setup/db.ts
  35. 27
      tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts
  36. 26
      tests/playwright/tests/db/features/baseShare.spec.ts
  37. 167
      tests/playwright/tests/db/features/erd.spec.ts
  38. 3
      tests/playwright/tests/db/features/filters.spec.ts
  39. 56
      tests/playwright/tests/db/features/keyboardShortcuts.spec.ts
  40. 2
      tests/playwright/tests/db/features/language.spec.ts
  41. 95
      tests/playwright/tests/db/features/metaSync.spec.ts
  42. 4
      tests/playwright/tests/db/features/swagger.spec.ts
  43. 8
      tests/playwright/tests/db/features/timezone.spec.ts
  44. 18
      tests/playwright/tests/db/features/undo-redo.spec.ts
  45. 95
      tests/playwright/tests/db/general/projectOperations.spec.ts
  46. 27
      tests/playwright/tests/db/general/tableOperations.spec.ts
  47. 10
      tests/playwright/tests/db/general/viewMenu.spec.ts
  48. 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 { AccountPage } from './index';
@ -23,7 +22,7 @@ export class AccountAppStorePage extends BasePage {
}
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();
// 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) {
const foundBarcodeSvg = await this.get().getByTestId('barcode').innerHTML();
await expect(foundBarcodeSvg).toContain(expectedValue);
expect(foundBarcodeSvg).toContain(expectedValue);
}
async clickCloseButton() {

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

@ -1,7 +1,6 @@
import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base';
import { DashboardPage } from '..';
import { DateTimeCellPageObject } from '../common/Cell/DateTimeCell';
import { getTextExcludeIconText } from '../../../tests/utils/general';
export class BulkUpdatePage extends BasePage {
@ -29,17 +28,17 @@ export class BulkUpdatePage extends BasePage {
}
async getInactiveColumn(index: number) {
const inactiveColumns = await this.columnsDrawer.locator('.ant-card');
const inactiveColumns = this.columnsDrawer.locator('.ant-card');
return inactiveColumns.nth(index);
}
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);
}
async getInactiveColumns() {
const inactiveColumns = await this.columnsDrawer.locator('.ant-card');
const inactiveColumns = this.columnsDrawer.locator('.ant-card');
const inactiveColumnsCount = await inactiveColumns.count();
const inactiveColumnsTitles = [];
// get title for each inactive column
@ -52,7 +51,7 @@ export class BulkUpdatePage extends BasePage {
}
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 activeColumnsTitles = [];
// get title for each active column
@ -67,7 +66,7 @@ export class BulkUpdatePage extends BasePage {
}
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();
await removeFieldButton.nth(index).locator('[data-testid="nc-bulk-update-fields-remove-icon"]').click();
const newRemoveFieldButtonCount = await removeFieldButton.count();
@ -75,7 +74,7 @@ export class BulkUpdatePage extends BasePage {
}
async addField(index: number) {
const addFieldButton = await this.columnsDrawer.locator('.ant-card');
const addFieldButton = this.columnsDrawer.locator('.ant-card');
const addFieldButtonCount = await addFieldButton.count();
await addFieldButton.nth(index).click();
const newAddFieldButtonCount = await addFieldButton.count();
@ -143,7 +142,7 @@ export class BulkUpdatePage extends BasePage {
case 'attachment':
// eslint-disable-next-line no-case-declarations
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;
case 'date':
{
@ -174,7 +173,7 @@ export class BulkUpdatePage extends BasePage {
awaitResponse?: boolean;
} = {}) {
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();
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 { Locator } from '@playwright/test';
import { WebhookPage } from './WebhookPage';
import { ErdPage } from './ErdPage';
export class DetailsPage extends BasePage {
readonly dashboard: DashboardPage;
readonly topbar: TopbarPage;
readonly webhook: WebhookPage;
readonly relations: ErdPage;
readonly tab_webhooks: Locator;
readonly tab_apiSnippet: Locator;
@ -21,6 +23,7 @@ export class DetailsPage extends BasePage {
this.dashboard = dashboard;
this.topbar = dashboard.grid.topbar;
this.webhook = new WebhookPage(this);
this.relations = new ErdPage(this);
this.tab_webhooks = this.get().locator(`[data-testid="nc-webhooks-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 { WorkspaceMenuObject } from '../common/WorkspaceMenu';
import { GroupPageObject } from './Group';
import { ColumnHeaderPageObject } from './columnHeader';
export class GridPage extends BasePage {
readonly dashboard: DashboardPage;
@ -19,6 +20,7 @@ export class GridPage extends BasePage {
readonly dashboardPage: DashboardPage;
readonly qrCodeOverlay: QrCodeOverlay;
readonly barcodeOverlay: BarcodeOverlay;
readonly columnHeader: ColumnHeaderPageObject;
readonly column: ColumnPageObject;
readonly cell: CellPageObject;
readonly topbar: TopbarPage;
@ -29,6 +31,8 @@ export class GridPage extends BasePage {
readonly rowPage: RowPageObject;
readonly groupPage: GroupPageObject;
readonly btn_addNewRow: Locator;
constructor(dashboardPage: DashboardPage) {
super(dashboardPage.rootPage);
this.dashboard = dashboardPage;
@ -36,6 +40,7 @@ export class GridPage extends BasePage {
this.qrCodeOverlay = new QrCodeOverlay(this);
this.barcodeOverlay = new BarcodeOverlay(this);
this.column = new ColumnPageObject(this);
this.columnHeader = new ColumnHeaderPageObject(this);
this.cell = new CellPageObject(this);
this.topbar = new TopbarPage(this);
this.toolbar = new ToolbarPage(this);
@ -44,6 +49,26 @@ export class GridPage extends BasePage {
this.workspaceMenu = new WorkspaceMenuObject(this);
this.rowPage = new RowPageObject(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() {

76
tests/playwright/pages/Dashboard/ProjectView/Audit.ts

@ -0,0 +1,76 @@
import { expect } from '@playwright/test';
import BasePage from '../../Base';
import { DataSourcePage } from './DataSourcePage';
export class AuditPage extends BasePage {
constructor(dataSource: DataSourcePage) {
super(dataSource.rootPage);
}
get() {
return this.rootPage.locator('div.ant-modal-content');
}
async verifyRow({
index,
opType,
opSubtype,
description,
user,
created,
}: {
index: number;
opType?: string;
opSubtype?: string;
description?: string;
user?: string;
created?: string;
}) {
const table = this.get().locator('[data-testid="audit-tab-table"]');
const row = table.locator(`tr.ant-table-row`).nth(index);
if (opType) {
await row
.locator(`td.ant-table-cell`)
.nth(0)
.textContent()
.then(async text => expect(text).toContain(opType));
}
if (opSubtype) {
await row
.locator(`td.ant-table-cell`)
.nth(1)
.textContent()
.then(async text => expect(text).toContain(opSubtype));
}
if (description) {
await row
.locator(`td.ant-table-cell`)
.nth(2)
.textContent()
.then(async text => expect(text).toContain(description));
}
if (user) {
await row
.locator(`td.ant-table-cell`)
.nth(3)
.textContent()
.then(async text => expect(text).toContain(user));
}
if (created) {
await row
.locator(`td.ant-table-cell`)
.nth(4)
.textContent()
.then(async text => expect(text).toContain(created));
}
}
async close() {
await this.get().click();
await this.rootPage.keyboard.press('Escape');
}
}

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

@ -1,15 +1,21 @@
import BasePage from '../../Base';
import { ProjectViewPage } from './index';
import { Locator } from '@playwright/test';
import { MetaDataPage } from './Metadata';
import { AuditPage } from './Audit';
export class DataSourcePage extends BasePage {
readonly projectView: ProjectViewPage;
readonly databaseType: Locator;
readonly metaData: MetaDataPage;
readonly audit: AuditPage;
constructor(projectView: ProjectViewPage) {
super(projectView.rootPage);
this.projectView = projectView;
this.databaseType = this.get().locator('.nc-extdb-db-type');
this.metaData = new MetaDataPage(this);
this.audit = new AuditPage(this);
}
get() {
@ -28,12 +34,27 @@ export class DataSourcePage extends BasePage {
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 }) {
// hardwired
await this.rootPage.locator('button.nc-action-btn').nth(1).click();
// 0th offset for header
const row = this.get()
.locator('.ds-table-row')
.nth(rowIndex + 1);
await row.locator('button.nc-action-btn:has-text("Relations")').click();
}
// const row = this.get().locator('.ds-table-row').nth(rowIndex);
// await row.locator('.ds-table-actions').locator('button.nc-action-btn').waitFor();
// await row.locator('.ds-table-actions').locator('button.nc-action-btn').nth(1).click();
async openAudit({ 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("Audit")').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 BasePage from '../../Base';
import { DataSourcesPage } from './DataSources';
import { getTextExcludeIconText } from '../../../tests/utils/general';
import { DataSourcePage } from './DataSourcePage';
export class MetaDataPage extends BasePage {
private readonly dataSources: DataSourcesPage;
constructor(dataSources: DataSourcesPage) {
super(dataSources.rootPage);
this.dataSources = dataSources;
constructor(dataSource: DataSourcePage) {
super(dataSource.rootPage);
}
get() {
return this.dataSources.get();
return this.rootPage.locator('div.ant-modal-content');
}
async clickReload() {
@ -24,6 +21,13 @@ export class MetaDataPage extends BasePage {
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() {
await this.get().locator(`button:has-text("Sync Now")`).click();
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 }) {
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);
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(
state,

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

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

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

@ -3,7 +3,7 @@ import { defaultBaseName } from '../../../constants';
import BasePage from '../../Base';
import { AclPage } from './Acl';
import { SettingsErdPage } from './Erd';
import { MetaDataPage } from './Metadata';
import { MetaDataPage } from '../ProjectView/Metadata';
export class DataSourcesPage extends BasePage {
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 }) {
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();
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.waitForResponse({
uiAction: () => {
uiAction: async () => {
// 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
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'],
requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
@ -210,7 +211,7 @@ export class TreeViewPage extends BasePage {
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 }).click();
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.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 expect(
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();
// 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
if ((await includeDataCheckbox.isChecked()) && !includeData) {
await includeDataCheckbox.click(); // click the checkbox to check it
}
// 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
if ((await includeViewsCheckbox.isChecked()) && !includeViews) {
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);
}
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 }) {
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');
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 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 }) {
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 }) {
// retry below logic for 5 times, with 1 second delay
let retryCount = 0;
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());
if ((await attachments.count()) === count) {
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 }) {
const cell = await this.get({ index, columnHeader });
const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded();
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 }) {
const [date, time] = dateTime.split(' ');
const [hour, minute, second] = time.split(':');
const [hour, minute, _second] = time.split(':');
await this.open({ index, columnHeader });
await this.selectDate({ date });
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 }) {
const cell = await this.get({ index, columnHeader });
const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded();
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 });
}
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' });
const text = await locator.allInnerTexts();
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 }) {
const cell = await this.get({ index, columnHeader });
const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded();
await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' });
await expect(cell.locator(`[title="${value}"]`)).toBeVisible();
@ -31,7 +31,7 @@ export class TimeCellPageObject extends BasePage {
hour: 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(1).locator('.ant-picker-time-panel-cell').nth(minute).click();
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 }) {
const cell = await this.get({ index, columnHeader });
const cell = this.get({ index, columnHeader });
await cell.scrollIntoViewIfNeeded();
await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' });
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,
}).scrollIntoViewIfNeeded();
while (count < 5) {
const innerTexts = await this.get({
index,
columnHeader,
}).allInnerTexts();
const innerTexts = await getTextExcludeIconText(this.get({ index, columnHeader }));
const cellText = typeof innerTexts === 'string' ? [innerTexts] : innerTexts;
if (cellText) {
@ -148,7 +145,12 @@ export class CellPageObject extends BasePage {
}
await this.rootPage.waitForTimeout(1000);
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;
expectedSvgValue: string;
}) {
const _verify = async expectedBarcodeSvg => {
const _verify = async (expectedBarcodeSvg: unknown) => {
await expect
.poll(async () => {
const barcodeCell = await this.get({
index,
columnHeader,
});
const barcodeSvg = await barcodeCell.getByTestId('barcode');
const barcodeCell = this.get({ index, columnHeader });
const barcodeSvg = barcodeCell.getByTestId('barcode');
return await barcodeSvg.innerHTML();
})
.toEqual(expectedBarcodeSvg);
@ -290,12 +289,12 @@ export class CellPageObject extends BasePage {
verifyChildList?: boolean;
options?: { singular?: string; plural?: string };
}) {
const cell = await this.get({ index, columnHeader });
const linkText = await cell.locator('.nc-datatype-link');
const cell = this.get({ index, columnHeader });
const linkText = cell.locator('.nc-datatype-link');
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);
if (type === 'bt') {
@ -342,7 +341,7 @@ export class CellPageObject extends BasePage {
await this.rootPage.waitForSelector('.nc-modal-child-list:visible');
// 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);
// close child list
@ -377,23 +376,23 @@ export class CellPageObject extends BasePage {
const role = param.role.toLowerCase();
const count = role === 'creator' || role === 'editor' || role === 'owner' ? 1 : 0;
// normal text cell
const cell = await this.get({ index: 0, columnHeader: 'Country' });
const cell = this.get({ index: 0, columnHeader: 'Country' });
// editable cell
await cell.dblclick();
await expect(await cell.locator(`input`)).toHaveCount(count);
await expect(cell.locator(`input`)).toHaveCount(count);
// press escape to close the input
await cell.press('Escape');
await cell.press('Escape');
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
const vCell = await this.get({ index: 0, columnHeader: 'Cities' });
const vCell = this.get({ index: 0, columnHeader: 'Cities' });
await vCell.hover();
// 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
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').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');
}
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() {
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 }) {
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 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 }) {
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);
await expect(fieldText).toBe(column);
expect(fieldText).toBe(column);
await expect(this.get().locator('.nc-filter-operation-select').nth(index)).toHaveText(operator);
await expect
@ -63,16 +63,16 @@ export class ToolbarFilterPage extends BasePage {
filterLogicalOperator?: string;
}) {
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.locator(`button:has-text("Add Filter")`).first().click();
const selectField = await filterDropdown.locator('.nc-filter-field-select').last();
const selectOperation = await filterDropdown.locator('.nc-filter-operation-select').last();
const selectValue = await filterDropdown.locator('.nc-filter-value-select > input').last();
const selectField = filterDropdown.locator('.nc-filter-field-select').last();
const selectOperation = filterDropdown.locator('.nc-filter-operation-select').last();
const selectValue = filterDropdown.locator('.nc-filter-value-select > input').last();
await selectField.waitFor({ state: 'visible' });
await selectField.click();
const fieldDropdown = await this.rootPage
const fieldDropdown = this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.last()
.locator(`div[label="${title}"]:visible`);
@ -81,7 +81,7 @@ export class ToolbarFilterPage extends BasePage {
await selectOperation.waitFor({ state: 'visible' });
await selectOperation.click();
const operationDropdown = await this.rootPage
const operationDropdown = this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-filter-comp-op')
.last()
.locator(`.ant-select-item:has-text("${operation}")`);
@ -93,13 +93,11 @@ export class ToolbarFilterPage extends BasePage {
if (filterGroupIndex) {
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.click();
const logicalDropdown = await this.rootPage.locator(
'div.ant-select-dropdown.nc-dropdown-filter-logical-op-group'
);
const logicalDropdown = this.rootPage.locator('div.ant-select-dropdown.nc-dropdown-filter-logical-op-group');
await logicalDropdown.waitFor({ state: 'visible' });
await logicalDropdown.locator(`.ant-select-item:has-text("${filterLogicalOperator}")`).click();
}
@ -133,7 +131,7 @@ export class ToolbarFilterPage extends BasePage {
skipWaitingResponse = true;
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) {
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) {
await this.rootPage.locator('.nc-filter-operation-select').click();
// first() : filter list has >, >=
@ -187,9 +185,7 @@ export class ToolbarFilterPage extends BasePage {
// subtype for date
if (dataType === UITypes.Date && subOperation) {
const selectedSubType = await getTextExcludeIconText(
await this.rootPage.locator('.nc-filter-sub_operation-select')
);
const selectedSubType = await getTextExcludeIconText(this.rootPage.locator('.nc-filter-sub_operation-select'));
if (selectedSubType !== subOperation) {
await this.rootPage.locator('.nc-filter-sub_operation-select').click();
// first() : filter list has >, >=
@ -222,14 +218,14 @@ export class ToolbarFilterPage extends BasePage {
switch (dataType) {
case UITypes.Year:
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();
break;
case UITypes.Time:
// eslint-disable-next-line no-case-declarations
const time = value.split(':');
await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`);
await this.rootPage.locator(`.ant-picker-dropdown:visible`).waitFor();
await this.rootPage
.locator(`.ant-picker-time-panel-column:nth-child(1)`)
.locator(`.ant-picker-time-panel-cell:has-text("${time[0]}")`)
@ -243,7 +239,7 @@ export class ToolbarFilterPage extends BasePage {
case UITypes.Date:
if (subOperation === 'exact date') {
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) {
await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
@ -274,6 +270,7 @@ export class ToolbarFilterPage extends BasePage {
case UITypes.Duration:
if (skipWaitingResponse) {
await this.get().locator('.nc-filter-value-select').locator('input').fill(value);
await this.get().locator('.nc-filter-value-select').locator('input').press('Enter');
await this.rootPage.waitForTimeout(350);
} else {
await this.waitForResponse({
@ -386,7 +383,7 @@ export class ToolbarFilterPage extends BasePage {
}
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(`.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 }) {
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);
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) {
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
let isSortListOpen = false;
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()) {
isSortListOpen = true;
break;
}
const searchInput = await this.rootPage.locator('.nc-sort-create-modal');
const searchInput = this.rootPage.locator('.nc-sort-create-modal');
if (await searchInput.isVisible()) {
isSortListOpen = false;
break;

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

@ -1,6 +1,5 @@
import { expect, Locator } from '@playwright/test';
import BasePage from '../../../Base';
import { GridPage } from '../../Grid';
import { ToolbarPage } from './index';
// @ts-ignore
import fs from 'fs';
@ -44,7 +43,7 @@ export class ToolbarViewMenuPage extends BasePage {
// verify downloaded content against expected content
const expectedData = fs.readFileSync(expectedDataFile, '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({
@ -72,7 +71,7 @@ export class ToolbarViewMenuPage extends BasePage {
const expectedData = fs.readFileSync(expectedDataFile, 'utf8');
const file = fs.readFileSync('./output/test.txt', 'utf8');
// 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
@ -92,16 +91,12 @@ export class ToolbarViewMenuPage extends BasePage {
// for CSV download, pass locator instead of clicking it here
if (subMenu === 'Download as CSV') {
await this.verifyDownloadAsCSV({
downloadLocator: await this.rootPage
.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`)
.last(),
downloadLocator: this.rootPage.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`).last(),
expectedDataFile: verificationInfo?.verificationFile ?? './fixtures/expectedBaseDownloadData.txt',
});
} else if (subMenu === 'Download as XLSX') {
await this.verifyDownloadAsXLSX({
downloadLocator: await this.rootPage
.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`)
.last(),
downloadLocator: this.rootPage.locator(`.ant-dropdown-menu-title-content:has-text("${subMenu}")`).last(),
expectedDataFile: verificationInfo?.verificationFile ?? './fixtures/expectedBaseDownloadData.txt',
});
} else {
@ -135,38 +130,4 @@ export class ToolbarViewMenuPage extends BasePage {
}
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_filter: Locator;
readonly btn_rowHeight: Locator;
readonly btn_groupBy: Locator;
constructor(parent: GridPage | GalleryPage | FormPage | KanbanPage | MapPage) {
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_filter = this.get().locator(`button.nc-filter-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() {
@ -96,9 +98,9 @@ export class ToolbarPage extends BasePage {
await expect(this.get().locator(`button.nc-fields-menu-btn`)).toBeVisible();
// 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);
await expect(fieldText).toBe('Fields');
expect(fieldText).toBe('Fields');
// icons count within fields menu button
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();
// 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);
await expect(fieldText).not.toBe('Fields');
expect(fieldText).not.toBe('Fields');
// icons count within fields menu button
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
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');
await expect(file).toEqual(expectedData);
expect(file).toEqual(expectedData);
}
async clickRowHeight() {
@ -188,7 +190,7 @@ export class ToolbarPage extends BasePage {
async verifyStackByButton({ title }: { title: string }) {
await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`).waitFor({ state: 'visible' });
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();
}
@ -207,7 +209,7 @@ export class ToolbarPage extends BasePage {
commenter: ['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()]) {
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_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();
}
async isSharedBasePublicAccessEnabled() {
return await this.get()
.locator(`[data-testid="nc-share-base-sub-modal"]`)
.locator('.ant-switch')
.nth(0)
.isChecked();
}
async clickShareBaseEditorAccess() {
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() {
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 }) {
await this.clickShare();
await this.share.clickShareBase();
await this.share.clickShareBasePublicAccess();
if (role === 'editor') {
if (!(await this.share.isSharedBasePublicAccessEnabled())) await this.share.clickShareBasePublicAccess();
if (role === 'editor' && !(await this.share.isSharedBaseEditorAccessEnabled())) {
await this.share.clickShareBaseEditorAccess();
} else if (role === 'viewer' && (await this.share.isSharedBaseEditorAccessEnabled())) {
await this.share.clickShareBaseEditorAccess();
}
await this.share.clickCopyLink();

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

@ -8,36 +8,36 @@ export abstract class ErdBasePage extends BasePage {
async clickShowColumnNames() {
await this.get().locator(`.nc-erd-showColumns-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async dbClickShowColumnNames() {
await this.get().locator(`.nc-erd-showColumns-label`).dblclick();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowPkAndFk() {
await this.get().locator(`.nc-erd-showPkAndFk-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowSqlViews() {
await this.get().locator(`.nc-erd-showViews-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowMMTables() {
await this.get().locator(`.nc-erd-showMMTables-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowJunctionTableNames() {
await this.get().locator(`.nc-erd-showJunctionTableNames-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
await (await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
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({

7
tests/playwright/setup/db.ts

@ -11,16 +11,13 @@ const isSqlite = (context: NcContext) => context.dbType === 'sqlite';
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 pg_credentials = (context: NcContext) => ({
user: 'postgres',
host: 'localhost',
// todo: Hack to resolve issue with pg resetting
database: `sakila_${context.workerId}`,
database: `sakila${context.workerId}`,
password: 'password',
port: 5432,
});
@ -66,4 +63,4 @@ async function sqliteExec(query) {
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 () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.createTable({ title: 'Sheet1', projectTitle: context.project.title });
// subsequent table creation fails; hence delay
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: '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: {
index: number;
@ -214,9 +225,10 @@ test.describe('LTAR create & update', () => {
await dashboard.grid.cell.verifyVirtualCell({
index: param.index,
columnHeader: 'Cities',
count: param.value['Cities'].length,
value: param.value['Cities'],
count: param.value.Cities.length,
options: { singular: 'City', plural: 'Cities' },
});
if (param.value.SLT) {
await dashboard.grid.cell.verify({
index: param.index,
@ -235,10 +247,7 @@ test.describe('LTAR create & update', () => {
* https://github.com/nocodb/nocodb/issues/4220
*
*/
test.skip('Existing LTAR table verification', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
test('Existing LTAR table verification', async () => {
// open table
await dashboard.treeView.openTable({ title: 'Country' });
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';
// To be enabled after shared base is implemented
test.describe.skip('Shared base', () => {
test.describe('Shared base', () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
@ -18,10 +18,11 @@ test.describe.skip('Shared base', () => {
// todo: Wait till the page is loaded
await dashboard.rootPage.waitForTimeout(2000);
await dashboard.validateProjectMenu({
role: role.toLowerCase(),
mode: 'shareBase',
});
// fix me! this is currently disabled
// await dashboard.validateProjectMenu({
// role: role.toLowerCase(),
// mode: 'shareBase',
// });
await dashboard.treeView.openTable({ title: 'Country', mode: 'shareBase' });
@ -67,7 +68,7 @@ test.describe.skip('Shared base', () => {
let url = '';
// share button visible only if a table is opened
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);
// access shared base link
@ -84,16 +85,9 @@ test.describe.skip('Shared base', () => {
withoutPrefix: true,
});
await projectPage.openProject({ title: context.project.title, withoutPrefix: true });
await dashboard.closeTab({ title: 'Team & Auth' });
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.treeView.openProject({ title: context.project.title });
await dashboard.treeView.openTable({ title: 'Country' });
url = await dashboard.grid.topbar.getSharedBaseUrl({ role: 'viewer' });
await dashboard.rootPage.waitForTimeout(2000);
// access shared base link

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

@ -7,14 +7,11 @@ import {
sqliteSakilaSqlViews,
} from '../../../tests/utils/sakila';
import { DashboardPage } from '../../../pages/Dashboard';
import { SettingsSubTab, SettingTab } from '../../../pages/Dashboard/Settings';
import setup, { unsetup } from '../../../setup';
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
test.describe.skip('Erd', () => {
test.describe('Erd', () => {
let dashboard: DashboardPage;
let context: any;
let sakilaTables, sakilaSqlViews;
@ -42,16 +39,9 @@ test.describe.skip('Erd', () => {
});
const toggleMM = async () => {
await dashboard.treeView.projectSettings({});
await dashboard.treeView.projectSettings({ title: context.project.title });
await dashboard.settings.miscellaneous.clickShowM2MTables();
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 () => {
@ -69,9 +59,7 @@ test.describe.skip('Erd', () => {
await toggleMM();
await openProjectErd();
const erd: SettingsErdPage = dashboard.settings.dataSources.erd;
await erd.dbClickShowColumnNames();
const erd = dashboard.details.relations;
if (isPg(context)) {
await erd.verifyNodesCount(sakilaTables.length);
@ -105,22 +93,23 @@ test.describe.skip('Erd', () => {
});
// 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({
tableName: `actor`,
columns: actorLTARColumns,
});
await erd.verifyColumns({
tableName: `payment`,
columns: paymentLTARColumns,
});
// Fix me! Disabled currently
// await erd.clickShowColumnNames();
// await erd.clickShowJunctionTableNames();
// await erd.clickShowJunctionTableNames();
//
// await erd.verifyColumns({
// tableName: `actor`,
// columns: actorLTARColumns,
// });
// await erd.verifyColumns({
// tableName: `payment`,
// columns: paymentLTARColumns,
// });
// Enable show column names and disable pk/fk
await erd.clickShowColumnNames();
// await erd.clickShowColumnNames();
await erd.clickShowPkAndFk();
await erd.verifyColumns({
@ -165,38 +154,39 @@ test.describe.skip('Erd', () => {
await erd.verifyNodeDoesNotExist({ tableName: `film_actor` });
// Fix me! Easter egg menu is disabled currently
// // Verify MM tables
await erd.clickShowMMTables();
await erd.clickShowJunctionTableNames();
await erd.clickShowJunctionTableNames();
await erd.verifyNodesCount(isPg(context) ? 21 : 16);
await erd.verifyEdgesCount({
count: isPg(context) ? 42 : 24,
circleCount: isPg(context) ? 40 : 22,
rectangleCount: isPg(context) ? 44 : 26,
});
await erd.verifyNode({ tableName: `film_actor` });
// Verify show junction table names
await erd.clickShowJunctionTableNames();
await erd.verifyJunctionTableLabel({
tableName: `film_actor`,
tableTitle: 'filmactor',
});
// await erd.clickShowMMTables();
// await erd.clickShowJunctionTableNames();
// await erd.clickShowJunctionTableNames();
//
// await erd.verifyNodesCount(isPg(context) ? 21 : 16);
// await erd.verifyEdgesCount({
// count: isPg(context) ? 42 : 24,
// circleCount: isPg(context) ? 40 : 22,
// rectangleCount: isPg(context) ? 44 : 26,
// });
//
// await erd.verifyNode({ tableName: `film_actor` });
//
// // Verify show junction table names
// await erd.clickShowJunctionTableNames();
// await erd.verifyJunctionTableLabel({
// tableName: `film_actor`,
// tableTitle: 'filmactor',
// });
});
test('Verify ERD Table view, and verify column operations are reflected to the ERD view', async () => {
await openErdOfATable('Country');
const erd = dashboard.grid.toolbar.actions.erd;
const erd = dashboard.details.relations;
await erd.clickShowColumnNames();
// Verify tables with default config
await erd.verifyColumns({
tableName: `country`,
columns: ['country_id', 'country', 'last_update', 'cities'],
});
await erd.verifyColumns({
tableName: `city`,
columns: ['city_id', 'city', 'country_id', 'last_update', 'country', 'addresses'],
@ -208,7 +198,6 @@ test.describe.skip('Erd', () => {
tableName: `country`,
columns: ['country', 'last_update', 'cities'],
});
await erd.verifyColumns({
tableName: `city`,
columns: ['city', 'last_update', 'country', 'addresses'],
@ -217,7 +206,6 @@ test.describe.skip('Erd', () => {
// Verify with all columns disabled
await erd.clickShowColumnNames();
await erd.verifyColumns({ tableName: `country`, columns: ['cities'] });
await erd.verifyColumns({
tableName: `city`,
columns: ['country', 'addresses'],
@ -226,25 +214,26 @@ test.describe.skip('Erd', () => {
// Enable All columns
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
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`,
columnName: 'test_column',
});
await dashboard.grid.toolbar.actions.erd.close();
/////////////////////////////////////////////////////////////////
await dashboard.grid.topbar.btn_data.click();
// Update column
await dashboard.grid.column.openEdit({ title: 'test_column' });
@ -252,66 +241,58 @@ test.describe.skip('Erd', () => {
await dashboard.grid.column.save({
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`,
columnName: 'new_test_column',
});
await dashboard.grid.toolbar.actions.erd.close();
/////////////////////////////////////////////////////////////////
await dashboard.grid.topbar.btn_data.click();
// Delete column
await dashboard.grid.column.delete({ title: 'new_test_column' });
// Verify in Settings ERD and table ERD
await openProjectErd();
await dashboard.settings.dataSources.erd.verifyNode({
await dashboard.grid.topbar.btn_details.click();
await openErdOfATable('Country');
await erd.clickShowColumnNames();
await erd.verifyNode({
tableName: `country`,
columnNameShouldNotExist: 'new_test_column',
});
await dashboard.settings.close();
});
test('Verify table operations sync with ERD', async () => {
await openProjectErd();
await dashboard.settings.close();
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.viewSidebar.openDeveloperTab({ option: 'ERD' });
await dashboard.grid.toolbar.actions.erd.verifyNode({
await dashboard.details.relations.verifyNode({
tableName: `country`,
columnNameShouldNotExist: 'new_test_column',
});
await dashboard.grid.toolbar.actions.erd.close();
await dashboard.details.relations.close();
// Create table and verify ERD
await dashboard.treeView.createTable({ title: 'Test', projectTitle: context.project.title });
// Verify in Settings ERD and table ERD
await dashboard.treeView.openProject({ title: context.project.title });
await openProjectErd();
await dashboard.settings.dataSources.erd.verifyNode({
await dashboard.details.relations.verifyNode({
tableName: `Test`,
});
await dashboard.settings.close();
await dashboard.details.relations.close();
// Delete table and verify ERD
await dashboard.treeView.deleteTable({ title: 'Test' });
await openProjectErd();
await dashboard.settings.dataSources.erd.verifyNodeDoesNotExist({
await dashboard.details.relations.verifyNodeDoesNotExist({
tableName: `Test`,
});
// 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();
await dashboard.details.relations.close();
});
});
@ -343,12 +324,8 @@ const pgPaymentTableColumns = [
];
const actorLTARColumns = ['filmactors', 'films'];
const actorNonPkFkColumns = ['first_name', 'last_name', 'last_update', 'films', 'filmactors'];
const paymentLTARColumns = ['customer', 'rental', 'staff'];
const pgPaymentNonPkFkColumns = ['amount', 'payment_date', 'customer', 'rental', 'staff'];
const paymentNonPkFkColumns = [...pgPaymentNonPkFkColumns, 'last_update'];
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,
dataType: param?.dataType,
openModal: true,
skipWaitingResponse: true,
});
// verify filtered rows
@ -257,7 +258,7 @@ test.describe('Filter Tests: Numerical', () => {
await numBasedFilterTest('Rating', '3', '2');
});
test.skip('Filter: Duration', async () => {
test('Filter: Duration', async () => {
await numBasedFilterTest('Duration', '00:01', '01:03');
});

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

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

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

@ -41,6 +41,8 @@ const langMenu = [
'zh-Hant.json',
];
// i18n menu not enabled for EE
//
test.describe.skip('Common', () => {
let context: any;
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 setup, { NcContext, unsetup } from '../../../setup';
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 settings: SettingsPage;
let context: NcContext;
let dbExec;
let metaData: MetaDataPage;
test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: false });
dashboard = new DashboardPage(page, context.project);
settings = dashboard.settings;
metaData = dashboard.projectView.dataSources.metaData;
switch (context.dbType) {
case 'sqlite':
@ -35,34 +36,33 @@ test.describe.skip('Meta sync', () => {
test('Meta sync', async () => {
test.setTimeout(process.env.CI ? 100000 : 70000);
await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.DataSources });
await settings.dataSources.openMetaSync();
await dashboard.projectView.tab_dataSources.click();
await dashboard.projectView.dataSources.openMetaSync({ rowIndex: 0 });
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 settings.dataSources.metaData.clickReload();
await metaData.clickReload();
await dashboard.rootPage.waitForTimeout(1000);
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: `table1`,
state: 'New table',
});
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: isPg(context) ? 22 : 17,
model: `table2`,
state: 'New table',
});
await settings.dataSources.metaData.sync();
await settings.dataSources.metaData.verifyRow({
await metaData.sync();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
state: 'No change identified',
});
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: isPg(context) ? 22 : 17,
model: 'Table2',
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`
);
}
await settings.dataSources.metaData.clickReload();
await settings.dataSources.metaData.verifyRow({
await metaData.clickReload();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
state: 'New relation added',
});
//verify after sync
await settings.dataSources.metaData.sync();
await settings.dataSources.metaData.verifyRow({
await metaData.sync();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
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 INDEX fk1_idx`);
}
await settings.dataSources.metaData.clickReload();
await settings.dataSources.metaData.verifyRow({
await metaData.clickReload();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
state: 'Relation removed',
});
//verify after sync
await settings.dataSources.metaData.sync();
await settings.dataSources.metaData.verifyRow({
await metaData.sync();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
state: 'No change identified',
@ -125,16 +125,16 @@ test.describe.skip('Meta sync', () => {
await dbExec(`ALTER TABLE table1 ADD COLUMN newCol INT`);
}
await settings.dataSources.metaData.clickReload();
await settings.dataSources.metaData.verifyRow({
await metaData.clickReload();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: `Table1`,
state: 'New column(newCol)',
});
//verify after sync
await settings.dataSources.metaData.sync();
await settings.dataSources.metaData.verifyRow({
await metaData.sync();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
state: 'No change identified',
@ -149,16 +149,16 @@ test.describe.skip('Meta sync', () => {
await dbExec(`ALTER TABLE table1 RENAME COLUMN newCol TO newColName`);
}
await settings.dataSources.metaData.clickReload();
await settings.dataSources.metaData.verifyRow({
await metaData.clickReload();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: `Table1`,
state: 'New column(newColName), Column removed(newCol)',
});
//verify after sync
await settings.dataSources.metaData.sync();
await settings.dataSources.metaData.verifyRow({
await metaData.sync();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
state: 'No change identified',
@ -168,16 +168,16 @@ test.describe.skip('Meta sync', () => {
// todo: Add for sqlite
if (!isSqlite(context)) {
await dbExec(`ALTER TABLE table1 DROP COLUMN newColName`);
await settings.dataSources.metaData.clickReload();
await settings.dataSources.metaData.verifyRow({
await metaData.clickReload();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: `Table1`,
state: 'Column removed(newColName)',
});
//verify after sync
await settings.dataSources.metaData.sync();
await settings.dataSources.metaData.verifyRow({
await metaData.sync();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: 'Table1',
state: 'No change identified',
@ -187,51 +187,51 @@ test.describe.skip('Meta sync', () => {
// Delete table
await dbExec(`DROP TABLE table1`);
await dbExec(`DROP TABLE table2`);
await settings.dataSources.metaData.clickReload();
await settings.dataSources.metaData.verifyRow({
await metaData.clickReload();
await metaData.verifyRow({
index: isPg(context) ? 21 : 16,
model: `table1`,
state: 'Table removed',
});
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: isPg(context) ? 22 : 17,
model: `table2`,
state: 'Table removed',
});
//verify after sync
await settings.dataSources.metaData.sync();
await metaData.sync();
if (isSqlite(context)) {
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: 16,
model: 'CustomerList',
state: 'No change identified',
});
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: 17,
model: 'FilmList',
state: 'No change identified',
});
}
if (isPg(context)) {
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: 21,
model: 'ActorInfo',
state: 'No change identified',
});
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: 22,
model: 'CustomerList',
state: 'No change identified',
});
} else if (isMysql(context)) {
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: 16,
model: 'ActorInfo',
state: 'No change identified',
});
await settings.dataSources.metaData.verifyRow({
await metaData.verifyRow({
index: 17,
model: 'CustomerList',
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);`
);
await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.DataSources });
await settings.dataSources.openMetaSync();
await dashboard.projectView.tab_dataSources.click();
await dashboard.projectView.dataSources.openMetaSync({ rowIndex: 0 });
await settings.dataSources.metaData.clickReload();
await settings.dataSources.metaData.sync();
await settings.close();
await metaData.clickReload();
await metaData.sync();
await metaData.close();
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 setup, { unsetup } from '../../../setup';
test.describe.skip('Table Column Operations', () => {
test.describe('Swagger', () => {
let dashboard: DashboardPage;
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`;
await dashboard.rootPage.goto(link, { waitUntil: 'networkidle' });
const swagger = await dashboard.rootPage;
const swagger = dashboard.rootPage;
// authorize with token information
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 setup, { NcContext, unsetup } from '../../../setup';
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 { getBrowserTimezoneOffset } from '../../utils/general';
import config from '../../../playwright.config';
@ -116,7 +116,7 @@ test.describe.serial('Timezone-XCDB : Japan/Tokyo', () => {
test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: true });
dashboard = new DashboardPage(page, context.project);
if (!isSqlite(context) && !isHub()) return;
if (!isSqlite(context)) return;
try {
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
*/
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.openTable({ title: 'dateTimeTable' });
@ -189,7 +189,7 @@ test.describe.serial('Timezone-XCDB : Japan/Tokyo', () => {
*/
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()}`);
// 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' });
});
// fix me! is flaky, and need to be validated
test.skip('Column width', async ({ page }) => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
test('Column width', async ({ page }) => {
await dashboard.treeView.openTable({ title: 'numberBased' });
const originalWidth = await dashboard.grid.column.getWidth({ title: 'Number' });
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);
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 { airtableApiBase, airtableApiKey } from '../../../constants';
import setup, { unsetup } from '../../../setup';
import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { ProjectsPage } from '../../../pages/ProjectsPage';
import { Api, ProjectListType } from 'nocodb-sdk';
import { ProjectInfo, ProjectInfoApiUtil } from '../../../tests/utils/projectInfoApiUtil';
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.skip('Project operations', () => {
test.describe('Project operations', () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
let api: Api<any>;
let projectPage: ProjectsPage;
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) {
try {
const ws = await api.workspace.list();
const projectList = await api.workspaceProject.list(ws.list[0].id);
const projectList = await getProjectList();
const project = projectList.list.find((p: any) => p.title === name);
if (project) {
@ -34,29 +38,25 @@ test.describe.skip('Project operations', () => {
}
async function createTestProjectWithData(testProjectName: string) {
await dashboard.clickHome();
await projectPage.createProject({ name: testProjectName, withoutPrefix: true });
await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: context.project.title });
await dashboard.leftSidebar.createProject({ title: testProjectName });
await dashboard.treeView.openProject({ title: testProjectName });
await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: testProjectName });
await dashboard.importAirtable.import({
key: airtableApiKey,
baseId: airtableApiBase,
});
await dashboard.rootPage.waitForTimeout(1000);
// await quickVerify({ dashboard, airtableImport: true, context });
}
async function cleanupTestData(dupeProjectName: string, testProjectName: string) {
await dashboard.clickHome();
await projectPage.deleteProject({ title: dupeProjectName, withoutPrefix: true });
await projectPage.deleteProject({ title: testProjectName, withoutPrefix: true });
await dashboard.treeView.deleteProject({ title: dupeProjectName });
await dashboard.treeView.deleteProject({ title: testProjectName });
}
test.beforeEach(async ({ page }) => {
page.setDefaultTimeout(70000);
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
projectPage = new ProjectsPage(page);
toolbar = dashboard.grid.toolbar;
api = new Api({
baseURL: `http://localhost:8080/`,
@ -70,57 +70,35 @@ test.describe.skip('Project operations', () => {
await unsetup(context);
});
test.skip('rename, delete', async () => {
// Already verified as part of workspace tests
test('rename, delete', async () => {
// if project already exists, delete it
await deleteIfExists('project-firstName');
await dashboard.clickHome();
await projectPage.createProject({ name: 'project-firstName', withoutPrefix: true });
await dashboard.clickHome();
await projectPage.renameProject({
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 });
await dashboard.leftSidebar.createProject({ title: 'project-firstName' });
await dashboard.treeView.renameProject({ title: 'project-firstName', newTitle: 'project-rename' });
await dashboard.treeView.openProject({ title: 'project-rename' });
await dashboard.treeView.deleteProject({ title: 'project-rename' });
});
test('project_duplicate', async () => {
// 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';
await deleteIfExists(testProjectName);
await deleteIfExists(dupeProjectName);
// // data creation for orginial test project
// // data creation for original test project
await createTestProjectWithData(testProjectName);
// create duplicate
await dashboard.clickHome();
await projectPage.duplicateProject({
name: testProjectName,
withoutPrefix: true,
includeData: true,
includeViews: true,
});
await projectPage.openProject({ title: dupeProjectName, withoutPrefix: true });
// await quickVerify({ dashboard, airtableImport: true, context });
// duplicate duplicate
await dashboard.treeView.duplicateProject({ title: testProjectName });
await dashboard.treeView.openProject({ title: testProjectName });
// compare
let projectList: ProjectListType;
if (isHub()) {
const ws = await api.workspace.list();
projectList = await api.workspaceProject.list(ws.list[0].id);
} 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 projectList = await getProjectList();
const testProjectId = projectList.list.find((p: any) => p.title === testProjectName);
const dupeProjectId = projectList.list.find((p: any) => p.title.startsWith(testProjectName + ' copy'));
const projectInfoOp: ProjectInfoApiUtil = new ProjectInfoApiUtil(context.token);
const original: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(testProjectId.id);
const duplicate: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(dupeProjectId.id);
@ -163,12 +141,15 @@ test.describe.skip('Project operations', () => {
'.users.1.roles',
'.users.2.roles',
]);
const orginalProjectInfo: ProjectInfo = arr[0];
const originalProjectInfo: ProjectInfo = arr[0];
const duplicateProjectInfo: ProjectInfo = arr[1];
expect(deepCompare(orginalProjectInfo, duplicateProjectInfo, ignoredFields, ignoredKeys, '', false)).toBeTruthy();
expect(
deepCompare(originalProjectInfo, duplicateProjectInfo, ignoredFields, ignoredKeys, '', false)
).toBeTruthy();
});
// cleanup test-data
await cleanupTestData(dupeProjectName, testProjectName);
// fix me! skip project cleanup
// await cleanupTestData(dupeProjectId.title, testProjectId.title);
});
});

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

@ -1,20 +1,20 @@
import { expect, test } from '@playwright/test';
import { Api, TableListType, TableType } from 'nocodb-sdk';
import { DashboardPage } from '../../../pages/Dashboard';
import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
import { deepCompare } from '../../../tests/utils/objectCompareUtil';
import setup, { unsetup } from '../../../setup';
import { ProjectInfoApiUtil, TableInfo } from '../../../tests/utils/projectInfoApiUtil';
import { isHub } from '../../../setup/db';
import { isEE } from '../../../setup/db';
import { AuditPage } from '../../../pages/Dashboard/ProjectView/Audit';
test.describe('Table Operations', () => {
let dashboard: DashboardPage, settings: SettingsPage;
let dashboard: DashboardPage, audit: AuditPage;
let context: any;
test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: false });
dashboard = new DashboardPage(page, context.project);
settings = dashboard.settings;
audit = dashboard.projectView.dataSources.audit;
});
test.afterEach(async () => {
@ -28,23 +28,26 @@ test.describe('Table Operations', () => {
await dashboard.treeView.deleteTable({ title: 'tablex' });
await dashboard.treeView.verifyTable({ title: 'tablex', exists: false });
if (!isHub()) {
if (!isEE()) {
// Audit logs in clickhouse; locally wont be accessible
await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.Audit });
await settings.audit.verifyRow({
await dashboard.treeView.openProject({ title: context.project.title });
await dashboard.projectView.tab_dataSources.click();
await dashboard.projectView.dataSources.openAudit({ rowIndex: 0 });
await audit.verifyRow({
index: 0,
opType: 'TABLE',
opSubtype: 'DELETE',
user: 'user@nocodb.com',
user: `user-${process.env.TEST_PARALLEL_INDEX}@nocodb.com`,
});
await settings.audit.verifyRow({
await audit.verifyRow({
index: 1,
opType: 'TABLE',
opSubtype: 'CREATE',
user: 'user@nocodb.com',
user: `user-${process.env.TEST_PARALLEL_INDEX}@nocodb.com`,
});
await settings.close();
await audit.close();
}
await dashboard.treeView.renameTable({ title: 'City', newTitle: 'Cityx' });

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

@ -16,12 +16,10 @@ test.describe('Grid view locked', () => {
await unsetup(context);
});
test.skip('ReadOnly lock & collaboration mode', async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: 'Team & Auth' });
test('ReadOnly lock & collaboration mode', async () => {
await dashboard.treeView.openTable({ title: 'Country' });
await dashboard.grid.toolbar.viewsMenu.verifyCollaborativeMode();
await dashboard.grid.verifyCollaborativeMode();
// enable view lock
await dashboard.grid.toolbar.viewsMenu.click({
@ -30,7 +28,7 @@ test.describe('Grid view locked', () => {
});
// verify view lock
await dashboard.grid.toolbar.viewsMenu.verifyLockMode();
await dashboard.grid.verifyLockMode();
// enable collaborative view
await dashboard.grid.toolbar.viewsMenu.click({
@ -38,7 +36,7 @@ test.describe('Grid view locked', () => {
subMenu: 'Collaborative View',
});
await dashboard.grid.toolbar.viewsMenu.verifyCollaborativeMode();
await dashboard.grid.verifyCollaborativeMode();
});
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 { getDefaultPwd } from '../../../tests/utils/general';
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
test.describe('Form view', () => {
@ -188,44 +188,39 @@ test.describe('Form view', () => {
});
const url = dashboard.rootPage.url();
// fix me! for app store, need super admin login.
if (isHub()) {
return;
}
// activate SMTP plugin
await accountAppStorePage.goto();
// install SMTP
await accountAppStorePage.install({ name: 'SMTP' });
await accountAppStorePage.configureSMTP({
email: 'a@b.com',
host: 'smtp.gmail.com',
port: '587',
});
await dashboard.verifyToast({
message: 'Successfully installed and email notification will use SMTP configuration',
});
// revisit form view
await page.goto(url);
// enable 'email-me' option
await dashboard.viewSidebar.openView({ title: 'CountryForm' });
await form.emailMeRadioButton.click();
await form.verifyAfterSubmitMenuState({
emailMe: true,
submitAnotherForm: false,
showBlankForm: false,
});
// Uninstall SMTP
await accountAppStorePage.goto();
await accountAppStorePage.uninstall({ name: 'SMTP' });
await dashboard.verifyToast({
message: 'Plugin uninstalled successfully',
});
// await accountAppStorePage.goto();
//
// // install SMTP
// await accountAppStorePage.install({ name: 'SMTP' });
// await accountAppStorePage.configureSMTP({
// email: 'a@b.com',
// host: 'smtp.gmail.com',
// port: '587',
// });
// await dashboard.verifyToast({
// message: 'Successfully installed and email notification will use SMTP configuration',
// });
//
// // revisit form view
// await page.goto(url);
//
// // enable 'email-me' option
// await dashboard.viewSidebar.openView({ title: 'CountryForm' });
// await form.emailMeRadioButton.click();
// await form.verifyAfterSubmitMenuState({
// emailMe: true,
// submitAnotherForm: false,
// showBlankForm: false,
// });
//
// // Uninstall SMTP
// await accountAppStorePage.goto();
// await accountAppStorePage.uninstall({ name: 'SMTP' });
//
// await dashboard.verifyToast({
// message: 'Plugin uninstalled successfully',
// });
});
test('Form share, verify attachment file', async () => {

Loading…
Cancel
Save