From b2134c0ff9433425d7ad1eb16ebae7dc20639e8f Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:38:24 +0530 Subject: [PATCH 1/2] test: sync Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/pages/Account/AppStore.ts | 3 +- .../pages/Dashboard/BarcodeOverlay/index.ts | 2 +- .../pages/Dashboard/BulkUpdate/index.ts | 17 +- .../pages/Dashboard/Details/ErdPage.ts | 107 +++++++++++ .../pages/Dashboard/Details/index.ts | 3 + .../pages/Dashboard/Grid/columnHeader.ts | 46 +++++ .../playwright/pages/Dashboard/Grid/index.ts | 25 +++ .../Dashboard/ProjectView/DataSourcePage.ts | 21 ++- .../{Settings => ProjectView}/Metadata.ts | 22 ++- .../pages/Dashboard/QrCodeOverlay/index.ts | 2 +- .../pages/Dashboard/Settings/DataSources.ts | 2 +- tests/playwright/pages/Dashboard/TreeView.ts | 43 ++++- .../Dashboard/common/Cell/AttachmentCell.ts | 4 +- .../pages/Dashboard/common/Cell/DateCell.ts | 2 +- .../Dashboard/common/Cell/DateTimeCell.ts | 2 +- .../pages/Dashboard/common/Cell/RatingCell.ts | 2 +- .../Dashboard/common/Cell/SelectOptionCell.ts | 2 +- .../pages/Dashboard/common/Cell/TimeCell.ts | 4 +- .../pages/Dashboard/common/Cell/YearCell.ts | 2 +- .../pages/Dashboard/common/Cell/index.ts | 39 ++-- .../pages/Dashboard/common/Footbar/index.ts | 10 ++ .../Dashboard/common/LeftSidebar/index.ts | 10 ++ .../Dashboard/common/ProjectMenu/index.ts | 2 +- .../pages/Dashboard/common/Toolbar/Filter.ts | 39 ++-- .../pages/Dashboard/common/Toolbar/Groupby.ts | 2 +- .../Dashboard/common/Toolbar/SearchData.ts | 2 +- .../pages/Dashboard/common/Toolbar/Sort.ts | 4 +- .../Dashboard/common/Toolbar/ViewMenu.ts | 47 +---- .../pages/Dashboard/common/Toolbar/index.ts | 32 +++- .../pages/Dashboard/common/Topbar/Share.ts | 16 ++ .../pages/Dashboard/common/Topbar/index.ts | 7 +- .../pages/Dashboard/commonBase/Erd.ts | 14 +- tests/playwright/setup/db.ts | 7 +- .../columns/columnLinkToAnotherRecord.spec.ts | 27 ++- .../tests/db/features/baseShare.spec.ts | 26 ++- .../playwright/tests/db/features/erd.spec.ts | 167 ++++++++---------- .../tests/db/features/filters.spec.ts | 3 +- .../db/features/keyboardShortcuts.spec.ts | 56 +++--- .../tests/db/features/language.spec.ts | 2 + .../tests/db/features/metaSync.spec.ts | 95 +++++----- .../tests/db/features/swagger.spec.ts | 4 +- .../tests/db/features/timezone.spec.ts | 8 +- .../tests/db/features/undo-redo.spec.ts | 18 +- .../db/general/projectOperations.spec.ts | 95 ++++------ .../tests/db/general/tableOperations.spec.ts | 4 +- .../tests/db/general/viewMenu.spec.ts | 10 +- .../tests/db/views/viewForm.spec.ts | 71 ++++---- 47 files changed, 652 insertions(+), 476 deletions(-) create mode 100644 tests/playwright/pages/Dashboard/Details/ErdPage.ts create mode 100644 tests/playwright/pages/Dashboard/Grid/columnHeader.ts rename tests/playwright/pages/Dashboard/{Settings => ProjectView}/Metadata.ts (69%) diff --git a/tests/playwright/pages/Account/AppStore.ts b/tests/playwright/pages/Account/AppStore.ts index 0ac3ff39d2..8cd56ea7a7 100644 --- a/tests/playwright/pages/Account/AppStore.ts +++ b/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 diff --git a/tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts b/tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts index ce70f07bcc..8cfb514240 100644 --- a/tests/playwright/pages/Dashboard/BarcodeOverlay/index.ts +++ b/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() { diff --git a/tests/playwright/pages/Dashboard/BulkUpdate/index.ts b/tests/playwright/pages/Dashboard/BulkUpdate/index.ts index fffa35ca7c..d04447ddad 100644 --- a/tests/playwright/pages/Dashboard/BulkUpdate/index.ts +++ b/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) { diff --git a/tests/playwright/pages/Dashboard/Details/ErdPage.ts b/tests/playwright/pages/Dashboard/Details/ErdPage.ts new file mode 100644 index 0000000000..e327b9cfdc --- /dev/null +++ b/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' }); + } +} diff --git a/tests/playwright/pages/Dashboard/Details/index.ts b/tests/playwright/pages/Dashboard/Details/index.ts index 15a7fe4ccc..205442f483 100644 --- a/tests/playwright/pages/Dashboard/Details/index.ts +++ b/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"]`); diff --git a/tests/playwright/pages/Dashboard/Grid/columnHeader.ts b/tests/playwright/pages/Dashboard/Grid/columnHeader.ts new file mode 100644 index 0000000000..b12f23cfe8 --- /dev/null +++ b/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); + } +} diff --git a/tests/playwright/pages/Dashboard/Grid/index.ts b/tests/playwright/pages/Dashboard/Grid/index.ts index c59971c7b6..223ef77885 100644 --- a/tests/playwright/pages/Dashboard/Grid/index.ts +++ b/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() { diff --git a/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts b/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts index 31c07b91ba..d7274ae435 100644 --- a/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts +++ b/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts @@ -1,15 +1,18 @@ import BasePage from '../../Base'; import { ProjectViewPage } from './index'; import { Locator } from '@playwright/test'; +import { MetaDataPage } from './Metadata'; export class DataSourcePage extends BasePage { readonly projectView: ProjectViewPage; readonly databaseType: Locator; + readonly metaData: MetaDataPage; constructor(projectView: ProjectViewPage) { super(projectView.rootPage); this.projectView = projectView; this.databaseType = this.get().locator('.nc-extdb-db-type'); + this.metaData = new MetaDataPage(this); } get() { @@ -28,12 +31,22 @@ 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(); + // await this.rootPage.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(); + // 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(); } } diff --git a/tests/playwright/pages/Dashboard/Settings/Metadata.ts b/tests/playwright/pages/Dashboard/ProjectView/Metadata.ts similarity index 69% rename from tests/playwright/pages/Dashboard/Settings/Metadata.ts rename to tests/playwright/pages/Dashboard/ProjectView/Metadata.ts index b93c629a1d..fb821c4897 100644 --- a/tests/playwright/pages/Dashboard/Settings/Metadata.ts +++ b/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, diff --git a/tests/playwright/pages/Dashboard/QrCodeOverlay/index.ts b/tests/playwright/pages/Dashboard/QrCodeOverlay/index.ts index 7c34a45aec..5909a89347 100644 --- a/tests/playwright/pages/Dashboard/QrCodeOverlay/index.ts +++ b/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() { diff --git a/tests/playwright/pages/Dashboard/Settings/DataSources.ts b/tests/playwright/pages/Dashboard/Settings/DataSources.ts index 1da4c02416..d1b5ce646e 100644 --- a/tests/playwright/pages/Dashboard/Settings/DataSources.ts +++ b/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; diff --git a/tests/playwright/pages/Dashboard/TreeView.ts b/tests/playwright/pages/Dashboard/TreeView.ts index a3e88ec960..bfa5046f81 100644 --- a/tests/playwright/pages/Dashboard/TreeView.ts +++ b/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(); + } } diff --git a/tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts b/tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts index 6f2a73d29b..bbee30240c 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts +++ b/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; diff --git a/tests/playwright/pages/Dashboard/common/Cell/DateCell.ts b/tests/playwright/pages/Dashboard/common/Cell/DateCell.ts index 76259cb872..90b9171c37 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/DateCell.ts +++ b/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(); } diff --git a/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts b/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts index 2d12b39d12..227c939384 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts +++ b/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 }); diff --git a/tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts b/tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts index 69b63ad919..696a37394e 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts +++ b/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); } diff --git a/tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts b/tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts index 01a0cdc999..8ce4a2f27e 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts +++ b/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); diff --git a/tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts b/tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts index 897fd91a67..283217e83a 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts +++ b/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) { diff --git a/tests/playwright/pages/Dashboard/common/Cell/YearCell.ts b/tests/playwright/pages/Dashboard/common/Cell/YearCell.ts index d622bf1e63..292d7bf3bb 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/YearCell.ts +++ b/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(); diff --git a/tests/playwright/pages/Dashboard/common/Cell/index.ts b/tests/playwright/pages/Dashboard/common/Cell/index.ts index 73366bcf08..084638c2ab 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/index.ts +++ b/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); diff --git a/tests/playwright/pages/Dashboard/common/Footbar/index.ts b/tests/playwright/pages/Dashboard/common/Footbar/index.ts index 88ce6c2f39..c2de855e3e 100644 --- a/tests/playwright/pages/Dashboard/common/Footbar/index.ts +++ b/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 }); + } } diff --git a/tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts b/tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts index 64b4a77c62..07ea7356a5 100644 --- a/tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts +++ b/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(); } diff --git a/tests/playwright/pages/Dashboard/common/ProjectMenu/index.ts b/tests/playwright/pages/Dashboard/common/ProjectMenu/index.ts index c63c9b82f8..0886d12a19 100644 --- a/tests/playwright/pages/Dashboard/common/ProjectMenu/index.ts +++ b/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); diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts index 6c2aab7911..c99a8d8282 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts +++ b/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`); diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts index 21b27ec86d..a65b634849 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts +++ b/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); diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/SearchData.ts b/tests/playwright/pages/Dashboard/common/Toolbar/SearchData.ts index f592407728..233f36c18c 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/SearchData.ts +++ b/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); } } diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts index a4791e78d9..90cfc0d33e 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts +++ b/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; diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts b/tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts index f91c4c4235..09c4abe126 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts +++ b/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', - }); - } } diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/index.ts b/tests/playwright/pages/Dashboard/common/Toolbar/index.ts index 89f4f02765..591ab13838 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/index.ts +++ b/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(); + } } diff --git a/tests/playwright/pages/Dashboard/common/Topbar/Share.ts b/tests/playwright/pages/Dashboard/common/Topbar/Share.ts index a9afc5b5ff..edf354c006 100644 --- a/tests/playwright/pages/Dashboard/common/Topbar/Share.ts +++ b/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(); } diff --git a/tests/playwright/pages/Dashboard/common/Topbar/index.ts b/tests/playwright/pages/Dashboard/common/Topbar/index.ts index e629096b7a..0c8eed126e 100644 --- a/tests/playwright/pages/Dashboard/common/Topbar/index.ts +++ b/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(); diff --git a/tests/playwright/pages/Dashboard/commonBase/Erd.ts b/tests/playwright/pages/Dashboard/commonBase/Erd.ts index 503a544475..239c7a594a 100644 --- a/tests/playwright/pages/Dashboard/commonBase/Erd.ts +++ b/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({ diff --git a/tests/playwright/setup/db.ts b/tests/playwright/setup/db.ts index 8d39eb5035..9433e85b0d 100644 --- a/tests/playwright/setup/db.ts +++ b/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 }; diff --git a/tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts b/tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts index 5a7a1bd297..5225ff88ba 100644 --- a/tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts +++ b/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({ diff --git a/tests/playwright/tests/db/features/baseShare.spec.ts b/tests/playwright/tests/db/features/baseShare.spec.ts index 101f0f90b4..a3a8b69989 100644 --- a/tests/playwright/tests/db/features/baseShare.spec.ts +++ b/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 diff --git a/tests/playwright/tests/db/features/erd.spec.ts b/tests/playwright/tests/db/features/erd.spec.ts index 9516301dfd..9760d42d12 100644 --- a/tests/playwright/tests/db/features/erd.spec.ts +++ b/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']; diff --git a/tests/playwright/tests/db/features/filters.spec.ts b/tests/playwright/tests/db/features/filters.spec.ts index af751aee29..2a95b8aab4 100644 --- a/tests/playwright/tests/db/features/filters.spec.ts +++ b/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'); }); diff --git a/tests/playwright/tests/db/features/keyboardShortcuts.spec.ts b/tests/playwright/tests/db/features/keyboardShortcuts.spec.ts index a565b4e65f..eff00c706a 100644 --- a/tests/playwright/tests/db/features/keyboardShortcuts.spec.ts +++ b/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' }); diff --git a/tests/playwright/tests/db/features/language.spec.ts b/tests/playwright/tests/db/features/language.spec.ts index dbea871edb..4c48384260 100644 --- a/tests/playwright/tests/db/features/language.spec.ts +++ b/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; diff --git a/tests/playwright/tests/db/features/metaSync.spec.ts b/tests/playwright/tests/db/features/metaSync.spec.ts index db55e8fc2d..92759967ee 100644 --- a/tests/playwright/tests/db/features/metaSync.spec.ts +++ b/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' }); diff --git a/tests/playwright/tests/db/features/swagger.spec.ts b/tests/playwright/tests/db/features/swagger.spec.ts index 377fdc5d0c..f27187be96 100644 --- a/tests/playwright/tests/db/features/swagger.spec.ts +++ b/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(); diff --git a/tests/playwright/tests/db/features/timezone.spec.ts b/tests/playwright/tests/db/features/timezone.spec.ts index 687e17c548..1e2ea3c63b 100644 --- a/tests/playwright/tests/db/features/timezone.spec.ts +++ b/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 diff --git a/tests/playwright/tests/db/features/undo-redo.spec.ts b/tests/playwright/tests/db/features/undo-redo.spec.ts index 96f01b38a5..641688f28c 100644 --- a/tests/playwright/tests/db/features/undo-redo.spec.ts +++ b/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 }); diff --git a/tests/playwright/tests/db/general/projectOperations.spec.ts b/tests/playwright/tests/db/general/projectOperations.spec.ts index 6dc22ad9ca..92b153c64e 100644 --- a/tests/playwright/tests/db/general/projectOperations.spec.ts +++ b/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; - 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 = projectInfoOp.extractProjectInfo(testProjectId.id); const duplicate: Promise = 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); }); }); diff --git a/tests/playwright/tests/db/general/tableOperations.spec.ts b/tests/playwright/tests/db/general/tableOperations.spec.ts index 3204d79607..38661f68b1 100644 --- a/tests/playwright/tests/db/general/tableOperations.spec.ts +++ b/tests/playwright/tests/db/general/tableOperations.spec.ts @@ -5,7 +5,7 @@ import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings'; import { deepCompare } from '../../../tests/utils/objectCompareUtil'; import setup, { unsetup } from '../../../setup'; import { ProjectInfoApiUtil, TableInfo } from '../../../tests/utils/projectInfoApiUtil'; -import { isHub } from '../../../setup/db'; +import { isEE } from '../../../setup/db'; test.describe('Table Operations', () => { let dashboard: DashboardPage, settings: SettingsPage; @@ -28,7 +28,7 @@ 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 }); diff --git a/tests/playwright/tests/db/general/viewMenu.spec.ts b/tests/playwright/tests/db/general/viewMenu.spec.ts index fe7ec8767d..97c949bac7 100644 --- a/tests/playwright/tests/db/general/viewMenu.spec.ts +++ b/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 () => { diff --git a/tests/playwright/tests/db/views/viewForm.spec.ts b/tests/playwright/tests/db/views/viewForm.spec.ts index 9b6a3b7d2e..d7a5ae5806 100644 --- a/tests/playwright/tests/db/views/viewForm.spec.ts +++ b/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 () => { From f5e8f5e5fa89b44efd3597b1927f31c3bf4b29fe Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:07:28 +0530 Subject: [PATCH 2/2] test: audit fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- .../pages/Dashboard/ProjectView/Audit.ts | 76 +++++++++++++++++++ .../Dashboard/ProjectView/DataSourcePage.ts | 14 +++- .../tests/db/general/tableOperations.spec.ts | 23 +++--- 3 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 tests/playwright/pages/Dashboard/ProjectView/Audit.ts diff --git a/tests/playwright/pages/Dashboard/ProjectView/Audit.ts b/tests/playwright/pages/Dashboard/ProjectView/Audit.ts new file mode 100644 index 0000000000..c9482c0e5e --- /dev/null +++ b/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'); + } +} diff --git a/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts b/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts index d7274ae435..9ad71297f7 100644 --- a/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts +++ b/tests/playwright/pages/Dashboard/ProjectView/DataSourcePage.ts @@ -2,17 +2,20 @@ 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() { @@ -40,13 +43,18 @@ export class DataSourcePage extends BasePage { } async openERD({ rowIndex }: { rowIndex: number }) { - // hardwired - // await this.rootPage.locator('button.nc-action-btn:has-text("Relations")').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(); } + + 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(); + } } diff --git a/tests/playwright/tests/db/general/tableOperations.spec.ts b/tests/playwright/tests/db/general/tableOperations.spec.ts index 38661f68b1..96d5086976 100644 --- a/tests/playwright/tests/db/general/tableOperations.spec.ts +++ b/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 { 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 () => { @@ -30,21 +30,24 @@ test.describe('Table Operations', () => { 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' });