Browse Source

test: role access validation

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/3848/head
Raju Udava 2 years ago committed by Muhammed Mustafa
parent
commit
f2496e39da
  1. 8
      scripts/playwright/fixtures/template.spec.ts
  2. 28
      scripts/playwright/pages/Dashboard/ExpandedForm/index.ts
  3. 33
      scripts/playwright/pages/Dashboard/Grid/Column/index.ts
  4. 37
      scripts/playwright/pages/Dashboard/Grid/index.ts
  5. 27
      scripts/playwright/pages/Dashboard/Settings/Acl.ts
  6. 54
      scripts/playwright/pages/Dashboard/Settings/Teams.ts
  7. 44
      scripts/playwright/pages/Dashboard/Settings/index.ts
  8. 24
      scripts/playwright/pages/Dashboard/TreeView.ts
  9. 8
      scripts/playwright/pages/Dashboard/ViewSidebar/index.ts
  10. 46
      scripts/playwright/pages/Dashboard/common/Cell/index.ts
  11. 47
      scripts/playwright/pages/Dashboard/common/Toolbar/index.ts
  12. 68
      scripts/playwright/pages/Dashboard/index.ts
  13. 114
      scripts/playwright/tests/rolesCreate.spec.ts

8
scripts/playwright/fixtures/template.spec.ts

@ -1,22 +1,22 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
test.describe("Test block name", () => {
test.describe.only("Test block name", () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
toolbar = dashboard.grid.toolbar;
});
test("Test case name", async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.openTable({ title: "Country" });
// ...
// ..
});
});

28
scripts/playwright/pages/Dashboard/ExpandedForm/index.ts

@ -8,12 +8,18 @@ export class ExpandedFormPage extends BasePage {
readonly dashboard: DashboardPage;
readonly addNewTableButton: Locator;
readonly copyUrlButton: Locator;
readonly toggleCommentsButton: Locator;
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
this.dashboard = dashboard;
this.addNewTableButton = this.dashboard.get().locator(".nc-add-new-table");
this.copyUrlButton = this.dashboard.get().locator(".nc-copy-row-url");
this.copyUrlButton = this.dashboard
.get()
.locator(".nc-copy-row-url:visible");
this.toggleCommentsButton = this.dashboard
.get()
.locator(".nc-toggle-comments:visible");
}
get() {
@ -93,4 +99,24 @@ export class ExpandedFormPage extends BasePage {
.locator(`.nc-drawer-expanded-form .ant-drawer-content`)
.count();
}
async validateRoleAccess(param: { role: string }) {
console.log(param.role);
if (param.role === "commenter" || param.role === "viewer") {
expect(
await this.get().locator('button:has-text("Save Row")')
).toBeDisabled();
} else {
expect(
await this.get().locator('button:has-text("Save Row")')
).toBeEnabled();
}
if (param.role === "viewer") {
expect(await this.toggleCommentsButton.count()).toBe(0);
} else {
expect(await this.toggleCommentsButton.count()).toBe(1);
}
// press escape to close the expanded form
await this.rootPage.keyboard.press("Escape");
}
}

33
scripts/playwright/pages/Dashboard/Grid/Column/index.ts

@ -212,7 +212,13 @@ export class ColumnPageObject extends BasePage {
await this.rootPage.waitForTimeout(200);
}
async verify({ title, isVisible = true }: { title: string; isVisible?: boolean }) {
async verify({
title,
isVisible = true,
}: {
title: string;
isVisible?: boolean;
}) {
if (!isVisible) {
return expect(
await this.rootPage.locator(`th[data-title="${title}"]`)
@ -222,4 +228,29 @@ export class ColumnPageObject extends BasePage {
this.rootPage.locator(`th[data-title="${title}"]`)
).toContainText(title);
}
async verifyRoleAccess(param: { role: string }) {
expect(
await this.grid.get().locator(".nc-column-add:visible").count()
).toBe(param.role === "creator" ? 1 : 0);
expect(
await this.grid.get().locator(".nc-ui-dt-dropdown:visible").count()
).toBe(param.role === "creator" ? 3 : 0);
if (param.role === "creator") {
await this.grid
.get()
.locator(".nc-ui-dt-dropdown:visible")
.first()
.click();
expect(
await this.rootPage.locator(".nc-dropdown-column-operations").count()
).toBe(1);
await this.grid
.get()
.locator(".nc-ui-dt-dropdown:visible")
.first()
.click();
}
}
}

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

@ -72,33 +72,34 @@ export class GridPage extends BasePage {
await this._fillRow({ index, columnHeader, value: rowValue });
const clickOnColumnHeaderToSave = this
.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
const clickOnColumnHeaderToSave = this.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch:[ "POST"],
httpMethodsToMatch: ["POST"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
});
}
async editRow({
index = 0,
columnHeader = "Title",
value,
}: { index?: number; columnHeader?: string; value: string }) {
}: {
index?: number;
columnHeader?: string;
value: string;
}) {
await this._fillRow({ index, columnHeader, value });
const clickOnColumnHeaderToSave = this
.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
const clickOnColumnHeaderToSave = this.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
@ -211,7 +212,7 @@ export class GridPage extends BasePage {
httpMethodsToMatch: ["GET"],
requestUrlPathToMatch: "/views/",
responseJsonMatcher: (resJson) => resJson?.pageInfo,
})
});
await this.waitLoading();
}
@ -298,4 +299,12 @@ export class GridPage extends BasePage {
.locator(".nc-action-icon.nc-arrow-expand")
).toBeVisible();
}
async validateRoleAccess(param: { role: string }) {
await this.column.verifyRoleAccess(param);
await this.cell.verifyRoleAccess(param);
expect(await this.get().locator(".nc-grid-add-new-cell").count()).toBe(
param.role === "creator" || param.role === "editor" ? 1 : 0
);
}
}

27
scripts/playwright/pages/Dashboard/Settings/Acl.ts

@ -0,0 +1,27 @@
import { Locator, expect } from "@playwright/test";
import { SettingsPage } from ".";
import BasePage from "../../Base";
export class AclPage 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-UI Access Control"]`);
}
async toggle({ table, role }: { table: string; role: string }) {
await this.get().locator(`.nc-acl-${table}-${role}-chkbox`).click();
}
async save() {
this.get().locator(`button:has-text("Save")`).click();
await this.toastWait({ message: "Updated UI ACL for tables successfully" });
}
}

54
scripts/playwright/pages/Dashboard/Settings/Teams.ts

@ -0,0 +1,54 @@
import { Locator, expect } from "@playwright/test";
import { SettingsPage } from ".";
import BasePage from "../../Base";
import { writeFileAsync } from "xlsx";
import { ToolbarPage } from "../common/Toolbar";
export class TeamsPage extends BasePage {
private readonly settings: SettingsPage;
readonly inviteTeamBtn: Locator;
readonly inviteTeamModal: Locator;
constructor(settings: SettingsPage) {
super(settings.rootPage);
this.settings = settings;
this.inviteTeamBtn = this.get().locator(`button:has-text("Invite Team")`);
this.inviteTeamModal = this.rootPage.locator(
`.nc-modal-invite-user-and-share-base`
);
}
get() {
return this.settings
.get()
.locator(`[pw-data="nc-settings-subtab-Users Management"]`);
}
async invite({ email, role }: { email: string; role: string }) {
await this.inviteTeamBtn.click();
await this.inviteTeamModal
.locator(`input[placeholder="E-mail"]`)
.fill(email);
await this.inviteTeamModal.locator(`.nc-user-roles`).click();
const userRoleModal = this.rootPage.locator(`.nc-dropdown-user-role`);
await userRoleModal.locator(`.nc-role-option:has-text("${role}")`).click();
await this.inviteTeamModal.locator(`button:has-text("Invite")`).click();
await this.toastWait({ message: "Successfully updated the user details" });
return await this.inviteTeamModal.locator(`.ant-alert-message`).innerText();
}
async closeInvite() {
// two btn-icon-only in invite modal: close & copy url
await this.inviteTeamModal
.locator(`button.ant-btn-icon-only`)
.first()
.click();
}
async inviteMore() {
await this.inviteTeamModal
.locator(`button:has-text("Invite More")`)
.click();
}
}

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

@ -1,21 +1,24 @@
import { DashboardPage } from '..';
import BasePage from '../../Base';
import { AuditSettingsPage } from './Audit';
import { SettingsErdPage } from './Erd';
import { MetaDataPage } from './Metadata';
import { DashboardPage } from "..";
import BasePage from "../../Base";
import { AuditSettingsPage } from "./Audit";
import { SettingsErdPage } from "./Erd";
import { MetaDataPage } from "./Metadata";
import { AppStoreSettingsPage } from "./AppStore";
import { MiscSettingsPage } from './Miscellaneous';
import { MiscSettingsPage } from "./Miscellaneous";
import { TeamsPage } from "./Teams";
import { AclPage } from "./Acl";
export enum SettingTab {
TeamAuth = 'teamAndAuth',
AppStore = 'appStore',
ProjectMetadata = 'projMetaData',
Audit = 'audit',
TeamAuth = "teamAndAuth",
AppStore = "appStore",
ProjectMetadata = "projMetaData",
Audit = "audit",
}
export enum SettingsSubTab {
ERD = 'erd',
Miscellaneous = 'misc',
ERD = "erd",
Miscellaneous = "misc",
ACL = "acl",
}
export class SettingsPage extends BasePage {
@ -25,6 +28,8 @@ export class SettingsPage extends BasePage {
readonly metaData: MetaDataPage;
readonly miscellaneous: MiscSettingsPage;
readonly erd: SettingsErdPage;
readonly teams: TeamsPage;
readonly acl: AclPage;
constructor(dashboard: DashboardPage) {
super(dashboard.rootPage);
@ -34,18 +39,27 @@ export class SettingsPage extends BasePage {
this.metaData = new MetaDataPage(this);
this.miscellaneous = new MiscSettingsPage(this);
this.erd = new SettingsErdPage(this);
this.teams = new TeamsPage(this);
this.acl = new AclPage(this);
}
get() {
return this.rootPage.locator(".nc-modal-settings");
}
async selectTab({tab, subTab}: {tab: SettingTab, subTab?: SettingsSubTab}) {
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();
if (subTab)
await this.get().locator(`li[data-menu-id="${subTab}"]`).click();
}
async selectSubTab({subTab}: {subTab: SettingsSubTab}) {
async selectSubTab({ subTab }: { subTab: SettingsSubTab }) {
await this.get().locator(`li[data-menu-id="${subTab}"]`).click();
}

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

@ -161,4 +161,28 @@ export class TreeViewPage extends BasePage {
.locator(`.ant-dropdown-menu-title-content:has-text("${title}")`)
.click();
}
async validateRoleAccess(param: { role: string }) {
// Add new table button
expect(await this.get().locator(`.nc-add-new-table`).count()).toBe(
param.role === "creator" ? 1 : 0
);
// Import menu
expect(await this.get().locator(`.nc-import-menu`).count()).toBe(
param.role === "creator" ? 1 : 0
);
// Invite Team button
expect(await this.get().locator(`.nc-share-base`).count()).toBe(
param.role === "creator" ? 1 : 0
);
// Right click context menu
await this.get().locator(`.nc-project-tree-tbl-Country`).click({
button: "right",
});
expect(
await this.rootPage
.locator(`.nc-dropdown-tree-view-context-menu:visible`)
.count()
).toBe(param.role === "creator" ? 1 : 0);
}
}

8
scripts/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -162,4 +162,12 @@ export class ViewSidebarPage extends BasePage {
.click();
await this.toastWait({ message: "View created successfully" });
}
async validateRoleAccess(param: { role: string }) {
let count = param.role === "creator" ? 1 : 0;
expect(await this.createGridButton.count()).toBe(count);
expect(await this.createGalleryButton.count()).toBe(count);
expect(await this.createFormButton.count()).toBe(count);
expect(await this.createKanbanButton.count()).toBe(count);
}
}

46
scripts/playwright/pages/Dashboard/common/Cell/index.ts

@ -24,14 +24,14 @@ export class CellPageObject extends BasePage {
index?: number;
columnHeader: string;
}): Locator {
if(this.parent instanceof SharedFormPage) {
if (this.parent instanceof SharedFormPage) {
return this.parent
.get()
.locator(`[pw-data="nc-form-input-cell-${columnHeader}"]`);
.get()
.locator(`[pw-data="nc-form-input-cell-${columnHeader}"]`);
} else {
return this.parent
.get()
.locator(`td[data-pw="cell-${columnHeader}-${index}"]`);
.get()
.locator(`td[data-pw="cell-${columnHeader}-${index}"]`);
}
}
@ -167,4 +167,40 @@ export class CellPageObject extends BasePage {
await cell.click();
await cell.locator(".nc-icon.unlink-icon").click();
}
async verifyRoleAccess(param: { role: string }) {
console.log("verifyRoleAccess", param);
// normal text cell
const cell = await this.get({ index: 0, columnHeader: "Country" });
// editable cell
await cell.dblclick();
expect(await cell.locator(`input`).count()).toBe(
param.role === "creator" || param.role === "editor" ? 1 : 0
);
// right click context menu
await cell.click({ button: "right" });
expect(
await this.rootPage
.locator(`.nc-dropdown-grid-context-menu:visible`)
.count()
).toBe(param.role === "creator" || param.role === "editor" ? 1 : 0);
// virtual cell
const vCell = await this.get({ index: 0, columnHeader: "City List" });
await vCell.hover();
// in-cell add
expect(await vCell.locator(".nc-action-icon.nc-plus:visible").count()).toBe(
param.role === "creator" || param.role === "editor" ? 1 : 0
);
// in-cell expand (all have access)
expect(
await vCell.locator(".nc-action-icon.nc-arrow-expand:visible").count()
).toBe(1);
await vCell.click();
// unlink
expect(await vCell.locator(".nc-icon.unlink-icon:visible").count()).toBe(
param.role === "creator" || param.role === "editor" ? 1 : 0
);
}
}

47
scripts/playwright/pages/Dashboard/common/Toolbar/index.ts

@ -138,4 +138,51 @@ export class ToolbarPage extends BasePage {
async clickAddEditStack() {
await this.get().locator(`.nc-kanban-add-edit-stack-menu-btn`).click();
}
async validateViewsMenu(param: { role: string }) {
let menuItems = {
creator: [
"Download",
"Upload",
"Shared View List",
"Webhooks",
"Get API Snippet",
"ERD View",
],
editor: ["Download", "Upload", "Get API Snippet", "ERD View"],
commenter: ["Download as CSV", "Download as XLSX"],
viewer: ["Download as CSV", "Download as XLSX"],
};
let vMenu = await this.rootPage.locator(
".nc-dropdown-actions-menu:visible"
);
for (let item of menuItems[param.role]) {
await expect(vMenu).toContainText(item);
}
}
async validateRoleAccess(param: { role: string }) {
await this.clickActions();
await this.validateViewsMenu({
role: param.role,
});
let menuItems = {
creator: ["Fields", "Filter", "Sort", "Share View"],
editor: ["Fields", "Filter", "Sort"],
commenter: ["Fields", "Filter", "Sort", "Download"],
viewer: ["Fields", "Filter", "Sort", "Download"],
};
let text = await this.get().innerText();
for (let item of menuItems[param.role]) {
expect(text).toContain(item);
}
expect(await this.get().locator(".nc-add-new-row-btn").count()).toBe(
param.role === "creator" || param.role === "editor" ? 1 : 0
);
}
}

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

@ -168,4 +168,72 @@ export class DashboardPage extends BasePage {
await this.rootPage.locator(`input.nc-metadb-project-name`).fill(name);
await this.rootPage.locator(`input.nc-metadb-project-name`).press("Enter");
}
async signOut() {
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
let projMenu = await this.rootPage.locator(".nc-dropdown-project-menu");
await projMenu.locator('[data-menu-id="account"]:visible').click();
await this.rootPage
.locator('div.nc-project-menu-item:has-text("Sign Out"):visible')
.click();
await this.rootPage.locator('[data-cy="nc-form-signin"]').waitFor();
}
async signUp({ email, password }: { email: string; password: string }) {
const signUp = this.rootPage;
await signUp.locator('button:has-text("SIGN UP")').waitFor();
await signUp
.locator(`input[placeholder="Enter your work email"]`)
.fill(email);
await signUp
.locator(`input[placeholder="Enter your password"]`)
.fill(password);
await signUp.locator(`button:has-text("SIGN UP")`).click();
}
async openProject({ title }: { title?: string }) {
const project = this.rootPage;
await project.locator(`td.ant-table-cell:has-text("${title}")`).click();
}
async validateProjectMenu(param: { role: string }) {
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
let pMenu = this.rootPage.locator(`.nc-dropdown-project-menu:visible`);
// menu items
let menuItems = {
creator: [
"Copy Project Info",
"Swagger: REST APIs",
"Copy Auth Token",
"Team & Settings",
"Themes",
"Preview as",
"Language",
"Account",
],
editor: [
"Copy Project Info",
"Swagger: REST APIs",
"Copy Auth Token",
"Language",
"Account",
],
commenter: [
"Copy Project Info",
"Copy Auth Token",
"Language",
"Account",
],
viewer: ["Copy Project Info", "Copy Auth Token", "Language", "Account"],
};
// common items
for (let item of menuItems[param.role]) {
await expect(pMenu).toContainText(item);
}
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
}
}

114
scripts/playwright/tests/rolesCreate.spec.ts

@ -0,0 +1,114 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import {
SettingsPage,
SettingsSubTab,
SettingTab,
} from "../pages/Dashboard/Settings";
let roleDb = [
{ email: "creator@nocodb.com", role: "creator", url: "" },
{ email: "editor@nocodb.com", role: "editor", url: "" },
{ email: "commenter@nocodb.com", role: "commenter", url: "" },
{ email: "viewer@nocodb.com", role: "viewer", url: "" },
];
async function roleSignup(roleIdx: number, db: any) {
console.log(roleDb[roleIdx].url);
await db.signOut();
await db.rootPage.goto(roleDb[roleIdx].url);
await db.signUp({
email: roleDb[roleIdx].email,
password: "Password123.",
});
await db.openProject({ title: "externalREST0" });
// close 'Team & Auth' tab
if (roleDb[roleIdx].role === "creator") {
await db.closeTab({ title: "Team & Auth" });
}
}
test.describe("User roles", () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let settings: SettingsPage;
let context: any;
test.beforeAll(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
toolbar = dashboard.grid.toolbar;
settings = dashboard.settings;
});
test("Create role", async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.gotoSettings();
await settings.selectTab({ tab: SettingTab.TeamAuth });
for (let i = 0; i < roleDb.length; i++) {
roleDb[i].url = await settings.teams.invite({
email: roleDb[i].email,
role: roleDb[i].role,
});
await settings.teams.closeInvite();
}
await settings.close();
// configure access control
await dashboard.gotoSettings();
await settings.selectTab({
tab: SettingTab.ProjectMetadata,
subTab: SettingsSubTab.ACL,
});
await settings.acl.toggle({ table: "Language", role: "editor" });
await settings.acl.toggle({ table: "Language", role: "commenter" });
await settings.acl.toggle({ table: "Language", role: "viewer" });
await settings.acl.toggle({ table: "CustomerList", role: "editor" });
await settings.acl.toggle({ table: "CustomerList", role: "commenter" });
await settings.acl.toggle({ table: "CustomerList", role: "viewer" });
await settings.acl.save();
await settings.close();
});
async function roleTest(roleIdx: number, db: any) {
await roleSignup(roleIdx, dashboard);
await dashboard.validateProjectMenu({
role: roleDb[roleIdx].role,
});
await dashboard.treeView.openTable({ title: "Country" });
await dashboard.viewSidebar.validateRoleAccess({
role: roleDb[roleIdx].role,
});
await toolbar.validateRoleAccess({
role: roleDb[roleIdx].role,
});
await dashboard.treeView.validateRoleAccess({
role: roleDb[roleIdx].role,
});
await dashboard.grid.validateRoleAccess({
role: roleDb[roleIdx].role,
});
await dashboard.grid.openExpandedRow({ index: 0 });
await dashboard.expandedForm.validateRoleAccess({
role: roleDb[roleIdx].role,
});
}
test("Role Test", async () => {
for (let i = 0; i < roleDb.length; i++) {
await roleTest(i, dashboard);
}
});
});
Loading…
Cancel
Save