Browse Source

Merge pull request #6414 from nocodb/nc-fix/pw-upgrade-and-reset-logic-cleanup

Pw upgrade and reset logic cleanup
pull/6427/head
Muhammed Mustafa 1 year ago committed by GitHub
parent
commit
9be5855122
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      tests/playwright/pages/Dashboard/Gallery/index.ts
  2. 74
      tests/playwright/pages/Dashboard/TreeView.ts
  3. 7
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  4. 5
      tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts
  5. 2
      tests/playwright/quickTests/commonTest.ts
  6. 65
      tests/playwright/setup/index.ts
  7. 2
      tests/playwright/setup/knexHelper.ts
  8. 1
      tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts
  9. 1
      tests/playwright/tests/db/features/baseShare.spec.ts
  10. 2
      tests/playwright/tests/db/features/erd.spec.ts
  11. 8
      tests/playwright/tests/db/features/import.spec.ts
  12. 4
      tests/playwright/tests/db/features/metaLTAR.spec.ts
  13. 64
      tests/playwright/tests/db/general/projectOperations.spec.ts
  14. 2
      tests/playwright/tests/db/general/tableOperations.spec.ts
  15. 2
      tests/playwright/tests/db/views/viewForm.spec.ts

7
tests/playwright/pages/Dashboard/Gallery/index.ts

@ -24,7 +24,12 @@ export class GalleryPage extends BasePage {
}
async openExpandedRow({ index }: { index: number }) {
await this.card(index).click();
await this.card(index).click({
position: {
x: 1,
y: 1,
},
});
await (await this.rootPage.locator('.ant-drawer-body').elementHandle())?.waitForElementState('stable');
}

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

@ -1,6 +1,8 @@
import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '.';
import BasePage from '../Base';
import { NcContext } from '../../setup';
import { isEE } from '../../setup/db';
export class TreeViewPage extends BasePage {
readonly dashboard: DashboardPage;
@ -25,7 +27,7 @@ export class TreeViewPage extends BasePage {
.locator('[data-testid="nc-sidebar-add-project-entity"]');
}
getProjectContextMenu({ projectTitle }: { projectTitle: string }) {
private getProjectContextMenu({ projectTitle }: { projectTitle: string }) {
return this.dashboard
.get()
.getByTestId(`nc-sidebar-project-title-${projectTitle}`)
@ -214,7 +216,9 @@ export class TreeViewPage extends BasePage {
await settingsMenu.locator(`.nc-sidebar-project-project-settings`).click();
}
async quickImport({ title, projectTitle }: { title: string; projectTitle: string }) {
async quickImport({ title, projectTitle, context }: { title: string; projectTitle: string; context: NcContext }) {
projectTitle = this.scopedProjectTitle({ title: projectTitle, context });
await this.getProjectContextMenu({ projectTitle }).hover();
await this.getProjectContextMenu({ projectTitle }).click();
const importMenu = this.dashboard.get().locator('.ant-dropdown-menu');
@ -269,9 +273,12 @@ export class TreeViewPage extends BasePage {
).toHaveCount(1);
}
async validateRoleAccess(param: { role: string; projectTitle?: string; tableTitle?: string }) {
async validateRoleAccess(param: { role: string; projectTitle?: string; tableTitle?: string; context: NcContext }) {
const context = param.context;
param.projectTitle = param.projectTitle ?? context.project.title;
const count = param.role.toLowerCase() === 'creator' || param.role.toLowerCase() === 'owner' ? 1 : 0;
const pjtNode = await this.getProject({ index: 0, title: param.projectTitle });
const pjtNode = await this.getProject({ title: param.projectTitle });
await pjtNode.hover();
// add new table button & context menu is visible only for owner & creator
@ -284,59 +291,46 @@ export class TreeViewPage extends BasePage {
await expect(tblNode.locator('.nc-tbl-context-menu')).toHaveCount(count);
}
async openProject({ title, projectCount }: { title: string; projectCount?: number }) {
const nodes = this.get().locator(`.project-title-node`);
// at times, page is not rendered yet when trying to open project
// hence retry logic to wait for expected number of projects to be available
if (projectCount) {
let retryCount = 0;
while (retryCount < 5) {
if ((await nodes.count()) === projectCount) break;
await this.rootPage.waitForTimeout(retryCount * 500);
retryCount++;
}
}
async openProject({ title, context }: { title: string; context: NcContext }) {
title = this.scopedProjectTitle({ title, context });
// loop through nodes.count() to find the node with title
for (let i = 0; i < (await nodes.count()); i++) {
const node = nodes.nth(i);
const nodeTitle = await node.innerText();
// check if nodeTitle contains title
if (nodeTitle.toLowerCase().includes(title.toLowerCase())) {
// click on node
await node.waitFor({ state: 'visible' });
await node.click();
break;
}
}
await this.get().getByTestId(`nc-sidebar-project-title-${title}`).click();
await this.rootPage.waitForTimeout(1000);
}
private async getProject(param: { index: number; title?: string }) {
if (param.title) {
return this.get().getByTestId(`nc-sidebar-project-title-${param.title}`);
}
scopedProjectTitle({ title, context }: { title: string; context: NcContext }) {
if (isEE()) return title;
return this.get().locator(`.project-title-node`).nth(param.index);
if (title.toLowerCase().startsWith('xcdb')) return `${title}`;
return title === context.project.title ? context.project.title : `nc-${context.workerId}-${title}`;
}
private async getProject(param: { title?: string }) {
return this.get().getByTestId(`nc-sidebar-project-title-${param.title}`);
}
async renameProject(param: { newTitle: string; title: string }) {
async renameProject(param: { newTitle: string; title: string; context: NcContext }) {
param.title = this.scopedProjectTitle({ title: param.title, context: param.context });
param.newTitle = this.scopedProjectTitle({ title: param.newTitle, context: param.context });
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("Rename")`).click();
const projectNodeInput = (await this.getProject({ index: 0, title: param.title })).locator('input');
const projectNodeInput = (await this.getProject({ title: param.title })).locator('input');
await projectNodeInput.clear();
await projectNodeInput.fill(param.newTitle);
await projectNodeInput.press('Enter');
}
async deleteProject(param: { title: string }) {
async deleteProject(param: { title: string; context: NcContext }) {
param.title = this.scopedProjectTitle({ title: param.title, context: param.context });
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();
@ -346,7 +340,9 @@ export class TreeViewPage extends BasePage {
await this.rootPage.locator('div.ant-modal-content').locator(`button.ant-btn:has-text("Delete")`).click();
}
async duplicateProject(param: { title: string }) {
async duplicateProject(param: { title: string; context: NcContext }) {
param.title = this.scopedProjectTitle({ title: param.title, context: param.context });
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');
@ -354,5 +350,7 @@ export class TreeViewPage extends BasePage {
await contextMenu.locator(`.ant-dropdown-menu-item:has-text("Duplicate")`).click();
await this.rootPage.locator('div.ant-modal-content').locator(`button.ant-btn:has-text("Confirm")`).click();
await this.rootPage.waitForTimeout(10000);
}
}

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

@ -366,10 +366,9 @@ export class CellPageObject extends BasePage {
await cell.locator('.nc-datatype-link').click();
await this.waitForResponse({
uiAction: async () =>
this.rootPage
.locator(`[data-testid="nc-child-list-item"]`)
.nth((await this.rootPage.locator(`[data-testid="nc-child-list-item"]`).count()) - 1)
.click(),
this.rootPage.locator(`[data-testid="nc-child-list-item"]`).last().click({
force: true,
}),
requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});

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

@ -2,6 +2,8 @@ import { Locator } from '@playwright/test';
import { DashboardPage } from '../../index';
import BasePage from '../../../Base';
import { getTextExcludeIconText } from '../../../../tests/utils/general';
import { isEE } from '../../../../setup/db';
import { NcContext } from '../../../../setup';
export class LeftSidebarPage extends BasePage {
readonly project: any;
@ -30,7 +32,8 @@ export class LeftSidebarPage extends BasePage {
return this.dashboard.get().locator('.nc-sidebar');
}
async createProject({ title }: { title: string }) {
async createProject({ title, context }: { title: string; context: NcContext }) {
title = isEE() ? title : `nc-${context.workerId}-${title}`;
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);

2
tests/playwright/quickTests/commonTest.ts

@ -249,7 +249,7 @@ const quickVerify = async ({
// await dashboard.clickHome();
// const workspacePage = new WorkspacePage(dashboard.rootPage);
// await workspacePage.projectDelete({ title: context.project.title });
await dashboard.treeView.deleteProject({ title: context.project.title });
await dashboard.treeView.deleteProject({ title: context.project.title, context });
}
};

65
tests/playwright/setup/index.ts

@ -8,9 +8,6 @@ import { isEE } from './db';
import { resetSakilaPg } from './knexHelper';
import path from 'path';
// Use local reset logic instead of remote
const enableLocalInit = true;
// MySQL Configuration
const mysqlConfig = {
client: 'mysql2',
@ -217,8 +214,18 @@ async function localInit({
const ws = await api['workspace'].list();
for (const w of ws.list) {
// check if w.title starts with workspaceTitle
if (w.title.startsWith(`ws_pgExtREST_p${process.env.TEST_PARALLEL_INDEX}`)) {
if (w.title.startsWith(`ws_pgExtREST${process.env.TEST_PARALLEL_INDEX}`)) {
try {
const projects = await api.workspaceProject.list(w.id);
for (const project of projects.list) {
try {
await api.project.delete(project.id);
} catch (e) {
console.log(`Error deleting project: ws delete`, project);
}
}
await api['workspace'].delete(w.id);
} catch (e) {
console.log(`Error deleting workspace: ${w.id}`, `user-${parallelId}@nocodb.com`, isSuperUser);
@ -237,7 +244,7 @@ async function localInit({
for (const p of projects.list) {
// check if p.title starts with projectTitle
if (
p.title.startsWith(`pgExtREST_p${process.env.TEST_PARALLEL_INDEX}`) ||
p.title.startsWith(`pgExtREST${process.env.TEST_PARALLEL_INDEX}`) ||
p.title.startsWith(`xcdb_p${process.env.TEST_PARALLEL_INDEX}`)
) {
try {
@ -349,38 +356,25 @@ const setup = async ({
const workerIndex = process.env.TEST_WORKER_INDEX;
const parallelIndex = process.env.TEST_PARALLEL_INDEX;
const workerId = `_p${parallelIndex}_w${workerIndex}_c${(+workerIndex + 1) * 1000 + workerCount[parallelIndex]}`;
workerCount[+parallelIndex]++;
const workerId = parallelIndex;
// console.log(process.env.TEST_PARALLEL_INDEX, '#Setup', workerId);
try {
// Localised reset logic
if (enableLocalInit) {
response = await localInit({
workerId,
isEmptyProject,
projectType,
isSuperUser,
dbType,
});
}
// Remote reset logic
else {
response = await axios.post(`http://localhost:8080/api/v1/meta/test/reset`, {
parallelId: process.env.TEST_PARALLEL_INDEX,
workerId: workerId,
dbType,
projectType,
isEmptyProject,
});
}
response = await localInit({
workerId: parallelIndex,
isEmptyProject,
projectType,
isSuperUser,
dbType,
});
} catch (e) {
console.error(`Error resetting project: ${process.env.TEST_PARALLEL_INDEX}`, e);
}
if (response.status !== 200 || !response.data?.token || !response.data?.project) {
console.error('Failed to reset test data', response.data, response.status, enableLocalInit, dbType);
console.error('Failed to reset test data', response.data, response.status, dbType);
throw new Error('Failed to reset test data');
}
const token = response.data.token;
@ -445,22 +439,7 @@ const setup = async ({
return { project, token, dbType, workerId, rootUser, workspace } as NcContext;
};
export const unsetup = async (context: NcContext): Promise<void> => {
if (context.token && context.project) {
// try to delete the project
try {
// Init SDK using token
const api = new Api({
baseURL: `http://localhost:8080/`,
headers: {
'xc-auth': context.token,
},
});
await api.project.delete(context.project.id);
} catch (e) {}
}
};
export const unsetup = async (context: NcContext): Promise<void> => {};
// Reference
// packages/nocodb/src/lib/services/test/TestResetService/resetPgSakilaProject.ts

2
tests/playwright/setup/knexHelper.ts

@ -36,7 +36,7 @@ export async function resetSakilaPg(database: string) {
try {
await initializeSakilaPg(database);
} catch (e) {
console.error(`Error resetting pg sakila db: Worker ${database}`);
console.error(`Error resetting pg sakila db: Worker ${database}`, e);
}
}

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

@ -170,6 +170,7 @@ test.describe('LTAR create & update', () => {
// Unlink LTAR cells
for (let i = 0; i < expected2.length; i++) {
for (let j = 0; j < expected2[i].length; j++) {
await dashboard.rootPage.waitForTimeout(500);
await dashboard.grid.cell.unlinkVirtualCell({
index: j,
columnHeader: colHeaders2[i],

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

@ -35,6 +35,7 @@ test.describe('Shared base', () => {
await dashboard.treeView.validateRoleAccess({
role: role.toLowerCase(),
context,
});
await dashboard.grid.verifyRoleAccess({

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

@ -279,7 +279,7 @@ test.describe('Erd', () => {
// 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 dashboard.treeView.openProject({ title: context.project.title, context });
await openProjectErd();
await dashboard.details.relations.verifyNode({
tableName: `Test`,

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

@ -21,7 +21,7 @@ test.describe('Import', () => {
});
test('Airtable', async () => {
await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: context.project.title });
await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: context.project.title, context });
await dashboard.importAirtable.import({
key: airtableApiKey,
baseId: airtableApiBase,
@ -31,7 +31,7 @@ test.describe('Import', () => {
});
test('CSV', async () => {
await dashboard.treeView.quickImport({ title: 'CSV file', projectTitle: context.project.title });
await dashboard.treeView.quickImport({ title: 'CSV file', projectTitle: context.project.title, context });
});
test('Excel', async () => {
@ -46,7 +46,7 @@ test.describe('Import', () => {
{ name: 'Sheet4', columns: col },
];
await dashboard.treeView.quickImport({ title: 'Microsoft Excel', projectTitle: context.project.title });
await dashboard.treeView.quickImport({ title: 'Microsoft Excel', projectTitle: context.project.title, context });
await dashboard.importTemplate.import({
file: `${process.cwd()}/fixtures/sampleFiles/simple.xlsx`,
result: expected,
@ -66,6 +66,6 @@ test.describe('Import', () => {
});
test('JSON', async () => {
await dashboard.treeView.quickImport({ title: 'JSON file', projectTitle: context.project.title });
await dashboard.treeView.quickImport({ title: 'JSON file', projectTitle: context.project.title, context });
});
});

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

@ -58,7 +58,7 @@ test.describe.serial('Test table', () => {
// create a new xcdb project
const xcdb = await createXcdb(context);
await dashboard.rootPage.reload();
await dashboard.treeView.openProject({ title: 'Xcdb', projectCount: 2 });
await dashboard.treeView.openProject({ title: 'xcdb', context });
api = new Api({
baseURL: `http://localhost:8080/`,
@ -216,7 +216,7 @@ test.describe.serial('Test table', () => {
});
test('Delete record - single, over UI', async () => {
await dashboard.treeView.openProject({ title: 'Xcdb', projectCount: 2 });
await dashboard.treeView.openProject({ title: 'xcdb', context });
await dashboard.treeView.openTable({ title: 'Table0' });
await grid.deleteRow(0);

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

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../../pages/Dashboard';
import { airtableApiBase, airtableApiKey } from '../../../constants';
import setup, { unsetup } from '../../../setup';
import setup, { NcContext, unsetup } from '../../../setup';
import { Api, ProjectListType } from 'nocodb-sdk';
import { ProjectInfo, ProjectInfoApiUtil } from '../../../tests/utils/projectInfoApiUtil';
import { deepCompare } from '../../../tests/utils/objectCompareUtil';
@ -9,38 +9,25 @@ import { isEE } from '../../../setup/db';
test.describe('Project operations', () => {
let dashboard: DashboardPage;
let context: any;
let context: NcContext;
let api: Api<any>;
test.setTimeout(100000);
async function getProjectList() {
async function getProjectList(workspaceId?: string) {
let projectList: ProjectListType;
if (isEE() && api['workspaceProject']) {
const ws = await api['workspace'].list();
projectList = await api['workspaceProject'].list(ws.list[1].id);
projectList = await api['workspaceProject'].list(workspaceId);
} else {
projectList = await api.project.list();
}
return projectList;
}
async function deleteIfExists(name: string) {
try {
const projectList = await getProjectList();
const project = projectList.list.find((p: any) => p.title === name);
if (project) {
await api.project.delete(project.id);
console.log('deleted project: ', project.id);
}
} catch (e) {
console.log('Error: ', e);
}
}
async function createTestProjectWithData(testProjectName: string) {
await dashboard.leftSidebar.createProject({ title: testProjectName });
await dashboard.treeView.openProject({ title: testProjectName });
await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: testProjectName });
await dashboard.leftSidebar.createProject({ title: testProjectName, context });
await dashboard.treeView.openProject({ title: testProjectName, context });
await dashboard.treeView.quickImport({ title: 'Airtable', projectTitle: testProjectName, context });
await dashboard.importAirtable.import({
key: airtableApiKey,
baseId: airtableApiBase,
@ -49,8 +36,8 @@ test.describe('Project operations', () => {
}
async function cleanupTestData(dupeProjectName: string, testProjectName: string) {
await dashboard.treeView.deleteProject({ title: dupeProjectName });
await dashboard.treeView.deleteProject({ title: testProjectName });
await dashboard.treeView.deleteProject({ title: dupeProjectName, context });
await dashboard.treeView.deleteProject({ title: testProjectName, context });
}
test.beforeEach(async ({ page }) => {
@ -71,34 +58,33 @@ test.describe('Project operations', () => {
});
test('rename, delete', async () => {
// if project already exists, delete it
await deleteIfExists('project-firstName');
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' });
await dashboard.leftSidebar.createProject({ title: 'project-firstName', context });
await dashboard.treeView.renameProject({ title: 'project-firstName', newTitle: 'project-rename', context });
await dashboard.treeView.openProject({ title: 'project-rename', context });
await dashboard.treeView.deleteProject({ title: 'project-rename', context });
});
test('project_duplicate', async () => {
// if project already exists, delete it to avoid test failures due to residual data
const testProjectName = 'Project-To-Import-Export';
const dupeProjectName: string = testProjectName + ' copy';
await deleteIfExists(testProjectName);
await deleteIfExists(dupeProjectName);
const random = Math.floor(Math.random() * 1000000);
const testProjectName = `Project-To-Import-Export-${random}`;
const scopedProjectName = dashboard.treeView.scopedProjectTitle({
title: testProjectName,
context,
});
// // data creation for original test project
await createTestProjectWithData(testProjectName);
// duplicate duplicate
await dashboard.treeView.duplicateProject({ title: testProjectName });
await dashboard.treeView.openProject({ title: testProjectName });
await dashboard.treeView.duplicateProject({ title: testProjectName, context });
await dashboard.treeView.openProject({ title: testProjectName, context });
// compare
const projectList = await getProjectList();
const projectList = await getProjectList(context.workspace?.id);
const testProjectId = projectList.list.find((p: any) => p.title === testProjectName);
const dupeProjectId = projectList.list.find((p: any) => p.title.startsWith(testProjectName + ' copy'));
const testProjectId = projectList.list.find((p: any) => p.title === scopedProjectName);
const dupeProjectId = projectList.list.find((p: any) => p.title.startsWith(scopedProjectName + ' copy'));
const projectInfoOp: ProjectInfoApiUtil = new ProjectInfoApiUtil(context.token);
const original: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(testProjectId.id);
const duplicate: Promise<ProjectInfo> = projectInfoOp.extractProjectInfo(dupeProjectId.id);

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

@ -31,7 +31,7 @@ test.describe('Table Operations', () => {
if (!isEE()) {
// Audit logs in clickhouse; locally wont be accessible
await dashboard.treeView.openProject({ title: context.project.title });
await dashboard.treeView.openProject({ title: context.project.title, context });
await dashboard.projectView.tab_dataSources.click();
await dashboard.projectView.dataSources.openAudit({ rowIndex: 0 });

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

@ -381,7 +381,7 @@ test.describe('Form view with LTAR', () => {
await dashboard.leftSidebar.openWorkspace({ title: context.workspace.title });
await dashboard.rootPage.waitForTimeout(500);
}
await dashboard.treeView.openProject({ title: context.project.title });
await dashboard.treeView.openProject({ title: context.project.title, context });
await dashboard.rootPage.waitForTimeout(500);
await dashboard.treeView.openTable({ title: 'Country' });

Loading…
Cancel
Save