Browse Source

feat(testing): Added ERD test and cleanups

pull/3848/head
Muhammed Mustafa 2 years ago
parent
commit
20144f4240
  1. 2
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  2. 18
      packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts
  3. 28
      scripts/playwright/pages/Base.ts
  4. 2
      scripts/playwright/pages/Dashboard/Grid/index.ts
  5. 16
      scripts/playwright/pages/Dashboard/Settings/Erd.ts
  6. 20
      scripts/playwright/pages/Dashboard/Settings/Miscellaneous.ts
  7. 40
      scripts/playwright/pages/Dashboard/Settings/index.ts
  8. 19
      scripts/playwright/pages/Dashboard/Toolbar/Actions/Erd.ts
  9. 22
      scripts/playwright/pages/Dashboard/Toolbar/Actions/index.ts
  10. 15
      scripts/playwright/pages/Dashboard/Toolbar/index.ts
  11. 29
      scripts/playwright/pages/Dashboard/TreeView.ts
  12. 84
      scripts/playwright/pages/Dashboard/commonBase/Erd.ts
  13. 15
      scripts/playwright/pages/Dashboard/index.ts
  14. 3
      scripts/playwright/playwright.config.ts
  15. 373
      scripts/playwright/tests/erd.spec.ts
  16. 14
      scripts/playwright/tests/metaSync.spec.ts
  17. 4
      scripts/playwright/tests/tableOperations.spec.ts
  18. 21
      scripts/playwright/tests/utils/sakila.ts
  19. 5
      scripts/playwright/tests/viewForm.spec.ts

2
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -103,7 +103,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
</a-button>
<template #overlay>
<a-menu class="ml-6 !text-sm !px-0 !py-2 !rounded">
<a-menu class="ml-6 !text-sm !px-0 !py-2 !rounded" pw-data="toolbar-actions">
<a-menu-item-group>
<a-sub-menu
v-if="isUIAllowed('view-type')"

18
packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts

@ -1575,15 +1575,15 @@ class SqliteClient extends KnexClient {
const _func = this.tableUpdate.name;
log.api(`${_func}:args:`, args);
for (let retry = 0; retry < 3; retry++) {
try {
return await this._tableUpdate(args);
} catch (e) {
console.log('retrying:tableUpdate', e);
}
// Wait for 300ms
await new Promise((resolve) => setTimeout(resolve, 300));
}
// for (let retry = 0; retry < 3; retry++) {
// try {
// return await this._tableUpdate(args);
// } catch (e) {
// console.log('retrying:tableUpdate', e);
// }
// // Wait for 300ms
// await new Promise((resolve) => setTimeout(resolve, 300));
// }
try {
return await this._tableUpdate(args);

28
scripts/playwright/pages/Base.ts

@ -5,7 +5,7 @@ type ResponseSelector = (json: any) => boolean;
export default abstract class BasePage {
readonly rootPage: Page;
abstract get(args: any): Locator;
abstract get(args?: any): Locator;
constructor(rootPage: Page) {
this.rootPage = rootPage;
@ -25,6 +25,32 @@ export default abstract class BasePage {
// .waitFor({ state: "detached" });
}
async waitForResponse({
requestHttpMethod,
requestUrlPathToMatch,
responseJsonMatcher,
}: {
requestHttpMethod: string;
requestUrlPathToMatch: string;
responseJsonMatcher?: ResponseSelector;
}) {
await this.rootPage.waitForResponse(async (res) => {
let isResJsonMatched = true;
if(responseJsonMatcher){
try {
isResJsonMatched = responseJsonMatcher(await res.json());
} catch (e) {
return false;
}
}
return (
res.request().method() === requestHttpMethod &&
res.request().url().includes(requestUrlPathToMatch) &&
isResJsonMatched
);
});
}
async waitForResponseJson({
responseSelector,
}: {

2
scripts/playwright/pages/Dashboard/Grid/index.ts

@ -20,7 +20,7 @@ export class GridPage extends BasePage {
this.addNewTableButton = dashboardPage.get().locator(".nc-add-new-table");
this.column = new ColumnPageObject(this);
this.cell = new CellPageObject(this);
this.toolbar = dashboardPage.toolbar;
this.toolbar = new ToolbarPage(this.dashboard);
}
get() {

16
scripts/playwright/pages/Dashboard/Settings/Erd.ts

@ -0,0 +1,16 @@
import { SettingsPage } from ".";
import { ErdBasePage } from "../commonBase/Erd";
export class SettingsErdPage extends ErdBasePage {
readonly settings: SettingsPage;
constructor(settings: SettingsPage) {
super(settings.rootPage);
this.settings = settings;
}
get() {
return this.rootPage.locator(`[pw-data="nc-settings-subtab-ERD View"]`);
}
}

20
scripts/playwright/pages/Dashboard/Settings/Miscellaneous.ts

@ -0,0 +1,20 @@
import { expect } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
export class MiscSettingsPage extends BasePage {
private readonly settings: SettingsPage;
constructor(settings: SettingsPage) {
super(settings.rootPage);
this.settings = settings;
}
get() {
return this.settings.get().locator(`[pw-data="nc-settings-subtab-Miscellaneous"]`);
}
async clickShowM2MTables() {
await this.get().locator('input[type="checkbox"]').click();
}
}

40
scripts/playwright/pages/Dashboard/Settings/index.ts

@ -1,21 +1,30 @@
import { DashboardPage } from "..";
import BasePage from "../../Base";
import { AuditSettingsPage } from "./Audit";
import { DashboardPage } from '..';
import BasePage from '../../Base';
import { AuditSettingsPage } from './Audit';
import { SettingsErdPage } from './Erd';
import { MetaDataPage } from './Metadata';
import { AppStoreSettingsPage } from "./AppStore";
import { MetaDataPage } from "./Metadata";
import { MiscSettingsPage } from './Miscellaneous';
const tabInfo = {
"Team & Auth": "teamAndAuth",
"App Store": "appStore",
"Project Metadata": "projMetaData",
Audit: "audit",
};
export enum SettingTab {
TeamAuth = 'teamAndAuth',
AppStore = 'appStore',
ProjectMetadata = 'projMetaData',
Audit = 'audit',
}
export enum SettingsSubTab {
ERD = 'erd',
Miscellaneous = 'misc',
}
export class SettingsPage extends BasePage {
private readonly dashboard: DashboardPage;
readonly audit: AuditSettingsPage;
readonly appStore: AppStoreSettingsPage;
readonly metaData: MetaDataPage;
readonly miscellaneous: MiscSettingsPage;
readonly erd: SettingsErdPage;
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
@ -23,14 +32,21 @@ export class SettingsPage extends BasePage {
this.audit = new AuditSettingsPage(this);
this.appStore = new AppStoreSettingsPage(this);
this.metaData = new MetaDataPage(this);
this.miscellaneous = new MiscSettingsPage(this);
this.erd = new SettingsErdPage(this);
}
get() {
return this.rootPage.locator(".nc-modal-settings");
}
async selectTab({ title }: { title: string }) {
await this.get().locator(`li[data-menu-id="${tabInfo[title]}"]`).click();
async selectTab({tab, subTab}: {tab: SettingTab, subTab?: SettingsSubTab}) {
await this.get().locator(`li[data-menu-id="${tab}"]`).click();
if(subTab) await this.get().locator(`li[data-menu-id="${subTab}"]`).click();
}
async selectSubTab({subTab}: {subTab: SettingsSubTab}) {
await this.get().locator(`li[data-menu-id="${subTab}"]`).click();
}
async close() {

19
scripts/playwright/pages/Dashboard/Toolbar/Actions/Erd.ts

@ -0,0 +1,19 @@
import { ToolbarActionsPage } from ".";
import { ErdBasePage } from "../../commonBase/Erd";
export class ToolbarActionsErdPage extends ErdBasePage {
readonly toolbarActions: ToolbarActionsPage;
constructor(toolbarActions: ToolbarActionsPage) {
super(toolbarActions.rootPage);
this.toolbarActions = toolbarActions;
}
get() {
return this.rootPage.locator(`.erd-single-table-modal`);
}
async close() {
await this.get().locator('.nc-modal-close').click();
}
}

22
scripts/playwright/pages/Dashboard/Toolbar/Actions/index.ts

@ -0,0 +1,22 @@
import BasePage from "../../../Base";
import { ToolbarPage } from "..";
import { ToolbarActionsErdPage } from "./Erd";
export class ToolbarActionsPage extends BasePage {
readonly toolbar: ToolbarPage;
readonly erd: ToolbarActionsErdPage;
constructor(toolbar: ToolbarPage) {
super(toolbar.rootPage);
this.toolbar = toolbar;
this.erd = new ToolbarActionsErdPage(this);
}
get() {
return this.rootPage.locator(`[pw-data="toolbar-actions"]`);
}
async click(label: string) {
await this.get().locator(`span:has-text("${label}")`).click();
}
}

15
scripts/playwright/pages/Dashboard/Toolbar/index.ts

@ -8,6 +8,7 @@ import { ToolbarViewMenuPage } from "./ViewMenu";
import * as fs from "fs";
import { DashboardPage } from "..";
import { GridPage } from "../Grid";
import { ToolbarActionsPage } from "./Actions";
export class ToolbarPage extends BasePage {
readonly dashboard: DashboardPage;
@ -16,7 +17,7 @@ export class ToolbarPage extends BasePage {
readonly filter: ToolbarFilterPage;
readonly shareView: ToolbarShareViewPage;
readonly viewsMenu: ToolbarViewMenuPage;
readonly grid: GridPage;
readonly actions: ToolbarActionsPage;
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
@ -26,13 +27,23 @@ export class ToolbarPage extends BasePage {
this.filter = new ToolbarFilterPage(this);
this.shareView = new ToolbarShareViewPage(this);
this.viewsMenu = new ToolbarViewMenuPage(this);
this.grid = dashboard.grid;
this.actions = new ToolbarActionsPage(this);
}
get() {
return this.rootPage.locator(`.nc-table-toolbar`);
}
async clickActions() {
const menuOpen = await this.actions.get().isVisible();
await this.get().locator(`button.nc-actions-menu-btn`).click();
// Wait for the menu to close
if (menuOpen) await this.fields.get().waitFor({ state: "hidden" });
}
async clickFields() {
const menuOpen = await this.fields.get().isVisible();

29
scripts/playwright/pages/Dashboard/TreeView.ts

@ -23,20 +23,37 @@ export class TreeViewPage extends BasePage {
// assumption: first view rendered is always GRID
//
async openTable({ title }: { title: string }) {
await this.get().locator(`.nc-project-tree-tbl-${title}`).click();
if(await this.get().locator('.active.nc-project-tree-tbl').count() > 0) {
if(await this.get().locator('.active.nc-project-tree-tbl').innerText() === title) {
// table already open
return;
}
}
await this.get().locator(`.nc-project-tree-tbl-${title}`).click({
noWaitAfter: true,
});
await this.waitForResponse({
requestHttpMethod: "GET",
requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
responseJsonMatcher: (json) => json.title === title,
});
await this.dashboard.waitForTabRender({ title });
}
async createTable({ title }: { title: string }) {
await this.get().locator(".nc-add-new-table").click();
await this.dashboard.get().locator(".ant-modal-body").waitFor();
await this.dashboard.get().locator('.nc-modal-table-create').locator(".ant-modal-body").waitFor();
await this.dashboard
.get()
.locator('[placeholder="Enter table name"]')
.fill(title);
await this.dashboard.get().locator('button:has-text("Submit")').click();
await this.dashboard.get().locator('button:has-text("Submit")').click(),
await this.waitForResponseJson({responseSelector:(json) => json.title === title && json.type === 'table'}),
await this.dashboard.waitForTabRender({ title });
}
@ -68,7 +85,11 @@ export class TreeViewPage extends BasePage {
.locator('div.nc-project-menu-item:has-text("Delete")')
.click();
await this.dashboard.get().locator('button:has-text("Yes")').click();
await this.toastWait({ message: "Deleted table successfully" });
// await this.toastWait({ message: "Deleted table successfully" });
await this.waitForResponse({
requestHttpMethod: "DELETE",
requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
});
}
async renameTable({ title, newTitle }: { title: string; newTitle: string }) {

84
scripts/playwright/pages/Dashboard/commonBase/Erd.ts

@ -0,0 +1,84 @@
import { expect } from '@playwright/test';
import BasePage from '../../Base';
export abstract class ErdBasePage extends BasePage {
vueFlow() {
return this.get().locator('.vue-flow__viewport');
}
async clickShowColumnNames() {
await this.get().locator(`.nc-erd-showColumns-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async dbClickShowColumnNames() {
await this.get().locator(`.nc-erd-showColumns-label`).dblclick();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowPkAndFk() {
await this.get().locator(`.nc-erd-showPkAndFk-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowSqlViews() {
await this.get().locator(`.nc-erd-showViews-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowMMTables() {
await this.get().locator(`.nc-erd-showMMTables-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async clickShowJunctionTableNames() {
await this.get().locator(`.nc-erd-showJunctionTableNames-checkbox`).click();
(await this.vueFlow().elementHandle())?.waitForElementState('stable');
}
async verifyEasterEggNotShown() {
await expect(await this.get().locator('.nc-erd-showMMTables-checkbox')).not.toBeVisible()
}
async verifyNode({tableName, columnName, columnNameShouldNotExist}: {tableName: string; columnName?: string, columnNameShouldNotExist?: string}) {
expect(await this.get().locator(`.nc-erd-table-node-${tableName}`)).toBeVisible();
if (columnName) {
expect(await this.get().locator(`.nc-erd-table-node-${tableName}-column-${columnName}`)).toBeVisible();
}
if(columnNameShouldNotExist) {
expect(await this.get().locator(`.nc-erd-table-node-${tableName}-column-${columnNameShouldNotExist}`)).not.toBeVisible();
}
}
async verifyNodeDoesNotExist({tableName}: {tableName: string}) {
expect(await this.get().locator(`.nc-erd-table-node-${tableName}`)).not.toBeVisible();
}
async verifyColumns({tableName, columns}: {tableName: string; columns: string[]}) {
for (const column of columns) {
await this.verifyNode({tableName, columnName: column});
}
}
async verifyNodesCount(count: number) {
expect(await this.get().locator('.nc-erd-table-node').count()).toBe(count);
}
async verifyEdgesCount({
count,
circleCount,
rectangleCount,
}: {
count: number;
circleCount: number;
rectangleCount: number;
}) {
expect(await this.get().locator('.vue-flow__edge').count()).toBe(count);
expect(await this.get().locator('.nc-erd-edge-circle').count()).toBe(circleCount);
expect(await this.get().locator('.nc-erd-edge-rect').count()).toBe(rectangleCount);
}
async verifyJunctionTableLabel({tableTitle, tableName}: {tableName: string; tableTitle: string}) {
expect(await this.vueFlow().locator(`.nc-erd-table-label-${tableTitle}-${tableName}`).locator('text')).toBeVisible();
}
}

15
scripts/playwright/pages/Dashboard/index.ts

@ -75,22 +75,7 @@ export class DashboardPage extends BasePage {
}
async waitForTabRender({ title }: { title: string }) {
// wait for the column, active tab animation will be started
await this.get().locator('[pw-data="grid-id-column"]').waitFor();
await this.tabBar
.locator(`.ant-tabs-tab-active:has-text("${title}")`)
.waitFor();
// wait active tab animation to finish
await expect
.poll(async () => {
return await this.tabBar
.locator(`[data-pw="nc-root-tabs-${title}"]`)
.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue("color");
});
})
.toBe("rgb(67, 81, 232)"); // active tab text color
await this.get()
.locator('[pw-data="grid-load-spinner"]')

3
scripts/playwright/playwright.config.ts

@ -26,10 +26,11 @@ const config: PlaywrightTestConfig = {
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: 2,
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 2 : 4,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */

373
scripts/playwright/tests/erd.spec.ts

@ -0,0 +1,373 @@
import { test, expect } from "@playwright/test";
import { mysqlSakilaSqlViews, mysqlSakilaTables, pgSakilaSqlViews, pgSakilaTables, sqliteSakilaSqlViews } from "./utils/sakila";
import { DashboardPage } from "../pages/Dashboard";
import { SettingsSubTab, SettingTab } from "../pages/Dashboard/Settings";
import setup from "../setup";
import { isMysql, isPg, isSqlite } from "../setup/db";
import { GridPage } from "../pages/Dashboard/Grid";
import { SettingsErdPage } from "../pages/Dashboard/Settings/Erd";
test.describe.only("Erd", () => {
let dashboard: DashboardPage;
let context: any;
let project: any;
let sakilaTables, sakilaSqlViews;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
project = context.project
if (isPg(context)) {
sakilaTables = pgSakilaTables;
sakilaSqlViews = pgSakilaSqlViews;
} else if(isMysql(context)) {
sakilaTables = mysqlSakilaTables;
sakilaSqlViews = mysqlSakilaSqlViews;
} else if(isSqlite(context)) {
sakilaTables = mysqlSakilaTables.map((tableName) => `${project.prefix}${tableName}`);
sakilaSqlViews = sqliteSakilaSqlViews.map((viewName) => `${project.prefix}${viewName}`);
}
});
// todo: Hack, edges are not getting rendered properly
const openSettingsErdWithEdgesRendered = async () => {
await dashboard.gotoSettings();
await dashboard.settings.selectTab({tab: SettingTab.ProjectMetadata, subTab: SettingsSubTab.Miscellaneous});
await dashboard.settings.miscellaneous.clickShowM2MTables();
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.ERD});
// Todo: Otherwise edges wont be rendered
await dashboard.rootPage.waitForTimeout(800);
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.Miscellaneous});
await dashboard.rootPage.waitForTimeout(800);
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.ERD});
// Todo: Otherwise edges wont be rendered
await dashboard.rootPage.waitForTimeout(200);
}
// todo: remove this. Need for edges to be rendered
const openErdOfATableWithEdgesRendered = async (tableName: string) => {
await dashboard.treeView.openTable({title: tableName});
await dashboard.grid.toolbar.clickActions();
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.erd.close();
await dashboard.treeView.openTable({title: 'Actor'});
await dashboard.grid.toolbar.clickActions();
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.erd.close();
await dashboard.treeView.openTable({title: tableName});
await dashboard.grid.toolbar.clickActions();
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.grid.toolbar.actions.click("ERD View");
}
test("Verify default config, all columns disabled, only PK and FK disabled, Sql views and MM table option, junction table names", async () => {
await openSettingsErdWithEdgesRendered();
const erd: SettingsErdPage = dashboard.settings.erd;
await erd.dbClickShowColumnNames();
if (isPg(context)) {
await erd.verifyNodesCount(mysqlSakilaTables.length);
await erd.verifyEdgesCount({
count: 32,
circleCount: 29,
rectangleCount: 35,
});
} else {
await erd.verifyNodesCount(mysqlSakilaTables.length);
await erd.verifyEdgesCount({
count: 14,
circleCount: 11,
rectangleCount: 17,
});
}
for(const tableName of sakilaTables) {
await erd.verifyNode({tableName});
}
// Verify Actor table
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}actor`, columns: actorTableColumn});
// Verify Payment table
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}payment`, columns: isPg(context) ? pgPaymentTableColumns : mysqlPaymentTableColumns});
// 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: `${isSqlite(context) ? project.prefix: ''}actor`, columns: actorLTARColumns});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}payment`, columns: paymentLTARColumns});
// Enable show column names and disable pk/fk
await erd.clickShowColumnNames();
await erd.clickShowPkAndFk();
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}actor`, columns: actorNonPkFkColumns});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}payment`, columns: isPg(context) ? pgPaymentNonPkFkColumns : paymentNonPkFkColumns});
await erd.clickShowPkAndFk();
// Verify views
await erd.clickShowSqlViews();
if(isPg(context)) {
await erd.verifyNodesCount(sakilaTables.length + sakilaSqlViews.length);
await erd.verifyEdgesCount({
count: 32,
circleCount: 29,
rectangleCount: 35,
});
} else {
await erd.verifyNodesCount(sakilaTables.length + sakilaSqlViews.length);
await erd.verifyEdgesCount({
count: 14,
circleCount: 11,
rectangleCount: 17,
});
}
for(const tableName of [...sakilaTables, ...sakilaSqlViews]) {
await erd.verifyNode({tableName});
}
// Verify ActorInfo SQL View
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}sales_by_store`, columns: salesByStoreColumns});
await erd.clickShowSqlViews(); // disable sql views
await erd.verifyNodeDoesNotExist({tableName: `${isSqlite(context) ? project.prefix: ''}store`});
// // 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) ? 44: 26,
circleCount: isPg(context) ? 40: 22,
rectangleCount: isPg(context) ? 48: 30,
});
await erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}store`});
// Verify show junction table names
await erd.clickShowJunctionTableNames();
await erd.verifyJunctionTableLabel({tableName: `${isSqlite(context) ? project.prefix: ''}film_actor`, tableTitle: 'filmactor'});
});
test("Verify ERD Table view, and verify column operations are reflected to the ERD view", async () => {
await openErdOfATableWithEdgesRendered("Country");
const erd = dashboard.grid.toolbar.actions.erd;
// Verify tables with default config
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columns: [
'country_id',
'country',
'last_update',
'city_list'
]});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}city`, columns: [
'city_id',
'city',
'country_id',
'last_update',
'country',
'address_list'
]});
// Verify with PK/FK disabled
await erd.clickShowPkAndFk();
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columns: [
'country',
'last_update',
'city_list'
]});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}city`, columns: [
'city',
'last_update',
'country',
'address_list'
]});
// Verify with all columns disabled
await erd.clickShowColumnNames();
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columns: [
'city_list'
]});
await erd.verifyColumns({tableName: `${isSqlite(context) ? project.prefix: ''}city`, columns: [
'country',
'address_list'
]});
// Enable All columns
await erd.clickShowColumnNames();
await erd.close();
// Add column
await dashboard.grid.column.create({title: "test_column"});
// Verify in Settings ERD and table ERD
await openSettingsErdWithEdgesRendered();
await dashboard.settings.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'test_column'});
await dashboard.settings.close();
await dashboard.treeView.openTable({title: "Country"});
await dashboard.grid.toolbar.clickActions();
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'test_column'});
await dashboard.grid.toolbar.actions.erd.close();
// Update column
await dashboard.grid.column.openEdit({ title: "test_column" });
await dashboard.grid.column.fillTitle({ title: "new_test_column" });
await dashboard.grid.column.save({
isUpdated: true,
});
// Verify in Settings ERD and table ERD
await openSettingsErdWithEdgesRendered();
await dashboard.settings.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'new_test_column'});
await dashboard.settings.close();
await dashboard.treeView.openTable({title: "Country"});
await dashboard.grid.toolbar.clickActions();
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.erd.verifyNode({tableName: `${isSqlite(context) ? project.prefix: ''}country`, columnName: 'new_test_column'});
await dashboard.grid.toolbar.actions.erd.close();
// Delete column
await dashboard.grid.column.delete({title: "new_test_column"});
// Verify in Settings ERD and table ERD
await openSettingsErdWithEdgesRendered();
await dashboard.settings.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix: ''}country`,
columnNameShouldNotExist: 'new_test_column'
});
await dashboard.settings.close();
})
test("Verify table operations sync with ERD", async () => {
await openSettingsErdWithEdgesRendered();
await dashboard.settings.close()
await dashboard.treeView.openTable({title: "Country"});
await dashboard.grid.toolbar.clickActions();
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.grid.toolbar.actions.click("ERD View");
await dashboard.grid.toolbar.actions.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix: ''}country`,
columnNameShouldNotExist: 'new_test_column'
});
await dashboard.grid.toolbar.actions.erd.close();
// Create table and verify ERD
await dashboard.treeView.createTable({title: "Test"});
// Verify in Settings ERD and table ERD
await openSettingsErdWithEdgesRendered();
await dashboard.settings.erd.verifyNode({
tableName: `${isSqlite(context) ? project.prefix: ''}Test`,
});
await dashboard.settings.close();
// Delete table and verify ERD
await dashboard.treeView.deleteTable({title: "Test"});
await openSettingsErdWithEdgesRendered();
await dashboard.settings.erd.verifyNodeDoesNotExist({
tableName: `${isSqlite(context) ? project.prefix: ''}Test`,
});
// Verify that `show mm table` option disabled will not trigger easter in ERD options
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.Miscellaneous});
await dashboard.settings.miscellaneous.clickShowM2MTables(); // disable
await dashboard.settings.selectSubTab({subTab: SettingsSubTab.ERD});
await dashboard.settings.erd.dbClickShowColumnNames();
await dashboard.settings.erd.verifyEasterEggNotShown();
await dashboard.settings.close();
})
});
const actorTableColumn = [
'actor_id',
'first_name',
'last_name',
'last_update',
'film_list'
]
const mysqlPaymentTableColumns = [
'payment_id',
'customer_id',
'staff_id',
'rental_id',
'amount',
'payment_date',
'last_update',
'customer',
'rental',
'staff'
]
const pgPaymentTableColumns = [
'payment_id',
'customer_id',
'staff_id',
'rental_id',
'amount',
'payment_date',
'customer',
'rental',
'staff'
]
const actorLTARColumns = [
'filmactor_list',
'film_list'
];
const actorNonPkFkColumns = [
'first_name',
'last_name',
'last_update',
'film_list',
'filmactor_list'
];
const paymentLTARColumns = [
'customer',
'rental',
'staff'
];
const pgPaymentNonPkFkColumns = [
'amount',
'payment_date',
'customer',
'rental',
'staff'
];
const paymentNonPkFkColumns = [
...pgPaymentNonPkFkColumns,
'last_update'
];
const salesByStoreColumns = [
'store',
'manager',
'total_sales'
];

14
scripts/playwright/tests/metaSync.spec.ts

@ -1,8 +1,8 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { SettingsPage } from "../pages/Dashboard/Settings";
import setup, { NcContext } from "../setup";
import { isSqlite, mysqlExec, sqliteExec } from "../setup/db";
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { SettingsPage, SettingTab } from '../pages/Dashboard/Settings';
import setup, { NcContext } from '../setup';
import { isSqlite, mysqlExec, sqliteExec } from '../setup/db';
// todo: Enable when view bug is fixed
test.describe("Meta sync", () => {
@ -33,7 +33,7 @@ test.describe("Meta sync", () => {
test.setTimeout(process.env.CI ? 100000 : 70000);
await dashboard.gotoSettings();
await settings.selectTab({ title: "Project Metadata" });
await settings.selectTab({tab: SettingTab['Project Metadata']});
await dbExec(
`CREATE TABLE ${projectPrefix}table1 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`
@ -222,7 +222,7 @@ test.describe("Meta sync", () => {
);
await dashboard.gotoSettings();
await settings.selectTab({ title: "Project Metadata" });
await settings.selectTab({tab: SettingTab.ProjectMetadata});
await settings.metaData.clickReload();
await settings.metaData.sync();

4
scripts/playwright/tests/tableOperations.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { SettingsPage } from '../pages/Dashboard/Settings';
import { SettingsPage, SettingTab } from '../pages/Dashboard/Settings';
import setup from '../setup';
@ -22,7 +22,7 @@ test.describe('Table Operations', () => {
await dashboard.treeView.verifyTableDoesNotExist({title: "tablex"});
await dashboard.gotoSettings();
await settings.selectTab({title: 'Audit'});
await settings.selectTab({tab: SettingTab.Audit});
await settings.audit.verifyRow({index: 0, opType: 'TABLE', opSubtype: 'DELETED', user: 'user@nocodb.com'});
await settings.audit.verifyRow({index: 1, opType: 'TABLE', opSubtype: 'CREATED', user: 'user@nocodb.com'});
await settings.close();

21
scripts/playwright/tests/utils/sakila.ts

@ -0,0 +1,21 @@
const mysqlSakilaTables = [
'actor', 'address', 'category', 'city', 'country', 'customer', 'film', 'film_text', 'language', 'payment', 'rental', 'staff'
]
const mysqlSakilaSqlViews = [
'actor_info', 'customer_list', 'film_list', 'nicer_but_slower_film_list', 'sales_by_film_category', 'sales_by_store', 'staff_list'
]
const pgSakilaTables = [
'actor', 'address', 'category', 'city', 'country', 'customer', 'film', 'language', 'payment', 'payment_p2007_01', 'payment_p2007_02', 'payment_p2007_03', 'payment_p2007_04', 'payment_p2007_05', 'payment_p2007_06', 'rental', 'staff'
]
const pgSakilaSqlViews = [
'actor_info', 'customer_list', 'film_list', 'nicer_but_slower_film_list', 'sales_by_film_category', 'sales_by_store', 'staff_list'
]
const sqliteSakilaSqlViews = [
'customer_list', 'film_list', 'staff_list', 'sales_by_store', 'sales_by_film_category'
]
export { mysqlSakilaTables, mysqlSakilaSqlViews, pgSakilaTables, pgSakilaSqlViews, sqliteSakilaSqlViews }

5
scripts/playwright/tests/viewForm.ts → scripts/playwright/tests/viewForm.spec.ts

@ -1,5 +1,6 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import { SettingTab } from "../pages/Dashboard/Settings";
import setup from "../setup";
test.describe("Form view", () => {
@ -142,7 +143,7 @@ test.describe("Form view", () => {
// activate SMTP plugin
await dashboard.gotoSettings();
await dashboard.settings.selectTab({ title: "App Store" });
await dashboard.settings.selectTab({ tab: SettingTab.AppStore });
await dashboard.settings.appStore.install({ name: "SMTP" });
await dashboard.settings.appStore.configureSMTP({
email: "a@b.com",
@ -166,7 +167,7 @@ test.describe("Form view", () => {
// reset SMTP
await dashboard.gotoSettings();
await dashboard.settings.selectTab({ title: "App Store" });
await dashboard.settings.selectTab({ tab: SettingTab.AppStore });
await dashboard.settings.appStore.uninstall({ name: "SMTP" });
await dashboard.toastWait({
Loading…
Cancel
Save