Browse Source

test: [cypress] User roles validation (menu options, schema/ data access, views, comments)

Signed-off-by: dstala <sivadstala@gmail.com>
pull/524/head
dstala 3 years ago
parent
commit
c4507ed719
  1. 367
      cypress/integration/user_role_spec.js
  2. 97
      cypress/support/page_objects/mainPage.js
  3. 90
      cypress/support/page_objects/navigation.js

367
cypress/integration/user_role_spec.js

@ -1,27 +1,49 @@
import { loginPage, projectsPage } from "../support/page_objects/navigation"
import { mainPage } from "../support/page_objects/mainPage"
// database
// validation details
// advSettings: left navigation bar (audit, metadata, auth, transient view modes)
// editSchema: create table, add/update/delete column
// editData: add/ update/ delete row, cell contents
// editComment: add comment
// shareView: right navigation bar (share options)
const roles = {
owner: {
name: 'owner',
credentials: { username: 'user@nocodb.com', password: 'Password123.' },
validations: { advSettings: true, editSchema: true, editData: true, editComment: true, shareView: true }
},
creator: {
name: 'creator',
credentials: { username: 'creator@nocodb.com', password: 'Password123.' },
validations: { advSettings: true, editSchema: true, editData: true, editComment: true, shareView: true }
},
editor: {
name: 'editor',
credentials: { username: 'editor@nocodb.com', password: 'Password123.' },
validations: { advSettings: false, editSchema: false, editData: true, editComment: true, shareView: false }
},
commenter: {
name: 'commenter',
credentials: { username: 'commenter@nocodb.com', password: 'Password123.' },
validations: { advSettings: false, editSchema: false, editData: false, editComment: true, shareView: false }
},
viewer: {
name: 'viewer',
credentials: { username: 'viewer@nocodb.com', password: 'Password123.' },
validations: { advSettings: false, editSchema: false, editData: false, editComment: false, shareView: false }
}
}
const roleOwner = 0
const roleCreator = 1
const roleEditor = 2
const roleCommenter = 3
const roleViewer = 4
const roleString = ['owner', 'creator', 'editor', 'commenter', 'viewer']
// to store URL links for new user (role based) Sign Up
let linkText = []
const userCredentials = [
{ username: 'user@nocodb.com', password: 'Password123.' },
{ username: 'creator@nocodb.com', password: 'Password123.' },
{ username: 'editor@nocodb.com', password: 'Password123.' },
{ username: 'commenter@nocodb.com', password: 'Password123.' },
{ username: 'viewer@nocodb.com', password: 'Password123.' }]
// add new user to specified role
//
const addUser = (userCred, role) => {
let linkText
// click on New User button, feed details
cy.get('button:contains("New User")').first().click()
cy.get('label:contains(Email)').next('input').type(userCred.username).trigger('input')
@ -37,10 +59,10 @@ const addUser = (userCred, role) => {
// get URL, invoke
cy.getActiveModal().find('.v-alert').then(($obj) => {
linkText[role] = $obj.text()
cy.log(linkText[role])
linkText = $obj.text()
cy.log(linkText)
cy.visit(linkText[role])
cy.visit(linkText)
})
// SignUp is taken care under userRoleCreation()
@ -50,20 +72,20 @@ const addUser = (userCred, role) => {
}
const userRoleCreation = (role) => {
it(`User creation: ${roleString[role]}`, () => {
const userRoleCreation = (roleType) => {
it(`User creation: ${roleType}`, () => {
loginPage.signIn(userCredentials[roleOwner])
loginPage.signIn(roles.owner.credentials)
projectsPage.openProject('sakilaDb')
// Team & Auth tab is open by default
//
addUser(userCredentials[role], roleString[role])
addUser(roles[roleType].credentials, roleType)
// Redirected to new URL, feed details
//
cy.get('input[type="text"]').type(userCredentials[role].username)
cy.get('input[type="password"]').type(userCredentials[role].password)
cy.get('input[type="text"]').type(roles[roleType].credentials.username)
cy.get('input[type="password"]').type(roles[roleType].credentials.password)
cy.get('button:contains("SIGN UP")').click()
cy.url({ timeout: 6000 }).should('contain', '#/project')
@ -73,15 +95,13 @@ const userRoleCreation = (role) => {
})
}
describe(`User role validation`, () => {
describe('Test project creation, user creation', () => {
let projectName = ''
// Create project & create different user-roles
//
it('Create Project', () => {
loginPage.signUp(userCredentials[roleOwner])
loginPage.signUp(roles.owner.credentials)
const projectParams = { dbType: 1, apiType: 0, name: 'sakilaDb' }
const databaseParams = {
@ -94,140 +114,239 @@ describe(`User role validation`, () => {
}
projectName = projectsPage.createProject(projectParams, databaseParams)
userRoleCreation(roleCreator)
userRoleCreation(roleEditor)
userRoleCreation(roleCommenter)
userRoleCreation(roleViewer)
})
// Schema related validations
// - Add/delete table
// - Add/Update/delete column
//
const editSchema = (validationString) => {
userRoleCreation('creator')
userRoleCreation('editor')
userRoleCreation('commenter')
userRoleCreation('viewer')
})
let columnName = 'City'
const genTest = (roleType) => {
// create table options
//
cy.get('.add-btn').should(validationString)
cy.get('.v-tabs-bar').eq(0).find('button.mdi-plus-box').should(validationString)
describe(`User role validation`, () => {
// open existing table-column
// project configuration settings
//
cy.openTableTab(columnName)
const advancedSettings = (roleType) => {
// delete table option
//
cy.get('.nc-table-delete-btn').should(validationString)
// let validationString = (true == roleValidation[roleIdx].advSettings) ? 'exist' : 'not.exist'
let validationString = (true == roles[roleType].validations.advSettings) ? 'exist' : 'not.exist'
// add new column option
//
cy.get('.new-column-header').should(validationString)
// hardwired to be enabled for all roles
mainPage.navigationDraw(mainPage.AUDIT).should('exist')
// update column (edit/ delete menu)
//
cy.get(`th:contains(${columnName}) .mdi-menu-down`).should(validationString)
mainPage.navigationDraw(mainPage.APPSTORE).should(validationString)
mainPage.navigationDraw(mainPage.TEAM_N_AUTH).should(validationString)
mainPage.navigationDraw(mainPage.PROJ_METADATA).should(validationString)
}
mainPage.navigationDraw(mainPage.ROLE_VIEW).should(validationString)
if ('exist' == validationString) {
mainPage.navigationDraw(mainPage.ROLE_VIEW).contains('editor')
mainPage.navigationDraw(mainPage.ROLE_VIEW).contains('commenter')
mainPage.navigationDraw(mainPage.ROLE_VIEW).contains('viewer')
}
// Table data related validations
// - Add/delete/modify row
//
const editData = (validationString) => {
cy.get('button:contains("New User")').should(validationString)
let columnName = 'City'
}
cy.openTableTab(columnName)
// add new row option (from menu header)
// Schema related validations
// - Add/delete table
// - Add/Update/delete column
//
cy.get('.nc-add-new-row-btn').should(validationString)
const editSchema = (roleType) => {
// update row option (right click)
//
cy.get(`tbody > :nth-child(4) > [data-col="City"]`).rightclick()
cy.get('.menuable__content__active').should(validationString)
let columnName = 'City'
let validationString = (true == roles[roleType].validations.editSchema) ? 'exist' : 'not.exist'
if (validationString == 'exist') {
// create table options
//
cy.get('.add-btn').should(validationString)
cy.get('.v-tabs-bar').eq(0).find('button.mdi-plus-box').should(validationString)
// right click options will exist (only for 'exist' case)
// open existing table-column
//
cy.getActiveMenu().contains('Insert New Row').should(validationString)
cy.getActiveMenu().contains('Delete Row').should(validationString)
cy.getActiveMenu().contains('Delete Selected Rows').should(validationString)
cy.get('body').type('{esc}')
cy.openTableTab(columnName)
// update cell contents option using row expander should be enabled
// delete table option
//
cy.get('.nc-row-expand-icon').eq(4).click({ force: true })
cy.getActiveModal().find('button').contains('Save Row').should('exist')
cy.get('body').type('{esc}')
cy.get('.nc-table-delete-btn').should(validationString)
// add new column option
//
cy.get('.new-column-header').should(validationString)
// update column (edit/ delete menu)
//
cy.get(`th:contains(${columnName}) .mdi-menu-down`).should(validationString)
}
else {
// update cell contents option using row expander should be disabled
// Table data related validations
// - Add/delete/modify row
//
const editData = (roleType) => {
let columnName = 'City'
let validationString = (true == roles[roleType].validations.editData) ? 'exist' : 'not.exist'
cy.openTableTab(columnName)
// add new row option (from menu header)
//
cy.get('.nc-row-expand-icon').eq(4).click({ force: true })
cy.getActiveModal().find('button:disabled').contains('Save Row').should('exist')
cy.get('body').type('{esc}')
cy.get('.nc-add-new-row-btn').should(validationString)
// update row option (right click)
//
cy.get(`tbody > :nth-child(4) > [data-col="City"]`).rightclick()
cy.get('.menuable__content__active').should(validationString)
if (validationString == 'exist') {
// right click options will exist (only for 'exist' case)
//
cy.getActiveMenu().contains('Insert New Row').should(validationString)
cy.getActiveMenu().contains('Delete Row').should(validationString)
cy.getActiveMenu().contains('Delete Selected Rows').should(validationString)
cy.get('body').type('{esc}')
// update cell contents option using row expander should be enabled
//
//cy.get('.nc-row-expand-icon').eq(4).click({ force: true })
cy.get('.v-input.row-checkbox').eq(4).next().next().click({ force: true })
cy.getActiveModal().find('button').contains('Save Row').should('exist')
cy.get('body').type('{esc}')
}
else {
// update cell contents option using row expander should be disabled
//
//cy.get('.nc-row-expand-icon').eq(4).click({ force: true })
cy.get('.v-input.row-checkbox').eq(4).next().next().click({ force: true })
cy.getActiveModal().find('button:disabled').contains('Save Row').should('exist')
cy.get('body').type('{esc}')
}
// double click cell entries to edit
//
cy.get(`tbody > :nth-child(4) > [data-col="City"]`).dblclick().find('input').should(validationString)
}
// double click cell entries to edit
// read &/ update comment
// Viewer: only allowed to read
// Everyone else: read &/ update
//
cy.get(`tbody > :nth-child(4) > [data-col="City"]`).dblclick().find('input').should(validationString)
}
const editComment = (roleType) => {
it('Validation: Owner', () => {
loginPage.signIn(userCredentials[roleOwner])
projectsPage.openProject('sakilaDb')
let columnName = 'City'
let validationString = (true == roles[roleType].validations.editComment) ? 'Comment added successfully' : 'Not allowed'
editSchema('exist')
editData('exist')
})
cy.openTableTab(columnName)
it('Validation: Creator', () => {
loginPage.signIn(userCredentials[roleCreator])
projectsPage.openProject('sakilaDb')
// click on comment icon & type comment
//
editSchema('exist')
editData('exist')
})
cy.get('.v-input.row-checkbox').eq(4).next().next().click({ force: true })
//cy.get('.nc-row-expand-icon').eq(4).click({ force: true })
cy.getActiveModal().find('.mdi-comment-multiple-outline').should('exist').click()
cy.getActiveModal().find('.comment-box').type('Comment-1{enter}')
it.only('Validation: editor', () => {
loginPage.signIn(userCredentials[roleEditor])
projectsPage.openProject('sakilaDb')
// Expected response:
// Viewer: Not allowed
// Everyone else: Comment added successfully
//
cy.get('body').contains(validationString, { timeout: 2000 }).should('exist')
cy.get('body').type('{esc}')
}
editSchema('not.exist')
editData('exist')
})
// right navigation menu bar
// Editor/Viewer/Commenter : can only view 'existing' views
// Rest: can create/edit
const viewMenu = (roleType) => {
let columnName = 'City'
let navDrawListCnt = 2
let navDrawListItemCnt = 5
cy.openTableTab(columnName)
let validationString = (true == roles[roleType].validations.shareView) ? 'exist' : 'not.exist'
// validate if Share button is visible at header tool bar
cy.get('header.v-toolbar').eq(0).find('button:contains("Share")').should(validationString)
// Owner, Creator will have two navigation drawer (on each side of center panel)
if (validationString == 'exist') {
navDrawListCnt = 4
navDrawListItemCnt = 16
}
cy.get('.v-navigation-drawer__content').eq(1).find('[role="list"]').should('have.length', navDrawListCnt)
cy.get('.v-navigation-drawer__content').eq(1).find('.v-list-item').should('have.length', navDrawListItemCnt)
// redundant
// cy.get('.v-navigation-drawer__content').eq(1).find('.v-list-item').eq(0).contains('Views').should('exist')
// cy.get('.v-navigation-drawer__content').eq(1).find('.v-list-item').eq(1).contains('City').should('exist')
// cy.get(`.nc-create-grid-view`).should(validationString)
// cy.get(`.nc-create-gallery-view`).should(validationString)
}
it('Validation: commenter', () => {
loginPage.signIn(userCredentials[roleCommenter])
projectsPage.openProject('sakilaDb')
editSchema('not.exist')
editData('not.exist')
})
it('Validation: Viewer', () => {
loginPage.signIn(userCredentials[roleViewer])
projectsPage.openProject('sakilaDb')
///////////////////////////////////////////////////////
// Test suite
editSchema('not.exist')
editData('not.exist')
})
it(`[${roles[roleType].name}] SignIn, Open project SakilaDb`, () => {
loginPage.signIn(roles[roleType].credentials)
projectsPage.openProject('sakilaDb')
})
// clean up
//
it('Delete Project', () => {
projectsPage.deleteProject(projectName)
})
it(`[${roles[roleType].name}] Left navigation menu, New User add`, () => {
advancedSettings(roleType)
})
})
it(`[${roles[roleType].name}] Schema: create table, add/modify/delete column`, () => {
editSchema(roleType)
})
it(`[${roles[roleType].name}] Data: add/modify/delete row, update cell contents`, (/*done*/) => {
// known issue: to be fixed
// right click raising alarm 'not allowed' for viewer
//
// cy.on('uncaught:exception', (err, runnable) => {
// expect(err.message).to.include('Not allowed')
// done()
// return false
// })
if (roleType != 'editor')
editData(roleType)
// done()
})
it(`[${roles[roleType].name}] Comments: view/add`, () => {
// Fix me!
if (roleType != 'viewer')
editComment(roleType)
})
it(`[${roles[roleType].name}] Right navigation menu, share view`, () => {
viewMenu(roleType)
})
})
}
genTest('owner')
genTest('creator')
genTest('editor')
genTest('commenter')
genTest('viewer')
/**

97
cypress/support/page_objects/mainPage.js

@ -0,0 +1,97 @@
// main page
export class _mainPage {
constructor() {
// Top Right items
this.SHARE = 0
this.THEME_BODY = 1
this.THEME_HEADER = 2
this.ALERT = 3
this.LANGUAGE = 4
this.USER = 5
// Top Left items
this.HOME = 0
this.GIT_HOME = 1
this.GIT_STAR = 2
this.GIT_DOCS = 3
this.AUDIT = 0
this.APPSTORE = 2
this.TEAM_N_AUTH = 3
this.PROJ_METADATA = 4
this.ROLE_VIEW = 5
// this.EDITOR = 'editor'
// this.COMMENTER = 'commenter'
// this.VIEWER = 'viewer'
}
toolBarTopLeft(toolBarItem) {
cy.get('header.v-toolbar').eq(0).find('a').then((obj) => {
cy.wrap(obj).eq(toolBarItem).click()
// just for test
// only Home page is internal link
// all others open in another tab
//
// if (toolBarItem == toolBar.HOME) {
// cy.url({ timeout: 6000 }).should('contain', '#/project')
// cy.wait(6000)
// }
})
}
toolBarTopRight(toolBarItem) {
cy.get('header.v-toolbar').eq(0).find('button').then((obj) => {
cy.wrap(obj).eq(toolBarItem).click()
cy.wait(500)
// just for test
// if (toolBarItem == toolBar.SHARE ||
// toolBarItem == toolBar.ALERT ||
// toolBarItem == toolBar.LANGUAGE ||
// toolBarItem == toolBar.USER
// ) { cy.get('body').type('{esc}') }
})
}
navigationDraw(item) {
if (item == this.ROLE_VIEW)
return cy.get('.nc-nav-drawer').find('.v-list').eq(3)
else
return cy.get('.nc-nav-drawer').find('.v-list > .v-list-item').eq(item)
}
}
export const mainPage = new _mainPage;
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Raju Udava <sivadstala@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

90
cypress/support/page_objects/navigation.js

@ -1,12 +1,3 @@
/**
* file: navigation.js
* purpose: signUp/ projects page navigation options
* author: raju udava
* date: 06 Sep 2020
*
**/
///////////////////////////////////////////////////////////
// Sign in/ Sign up page
@ -78,6 +69,7 @@ export class _projectsPage {
//
// {dbType, apiType, name}
// for external database, {databaseType, hostAddress, portNumber, username, password, databaseName}
// Open existing project
// TODO: add projectName validation
@ -90,22 +82,28 @@ export class _projectsPage {
}
// Create new project
// Input: {dbType, apiType, name}
// Input:
// projectData {dbType, apiType, name}
// dbCredentials {databaseType, hostAddress, portNumber, username, password, databaseName}
// Returns: projectName
//
createProject(projectData) {
// To configure
// SSL & advanced parameters
// Database type selection
//
createProject(projectData, cred) {
cy.get('body', { timeout: 2000 })
if (NC_DB_NONE == projectData.dbType) {
let projectName = projectData.name
let projectName = projectData.name
if (projectData.name == '')
projectName = 'test_proj' + Date.now()
if (projectData.name == '')
projectName = 'test_proj' + Date.now()
// click on "New Project"
cy.get(':nth-child(5) > .v-btn').click()
// click on "New Project"
cy.get(':nth-child(5) > .v-btn').click()
if (NC_DB_NONE == projectData.dbType) {
// Subsequent form, select (+ Create) option
cy.get('.nc-create-xc-db-project').click({ force: true })
@ -128,9 +126,39 @@ export class _projectsPage {
}
else {
// Existing database connections
// TBD
// Subsequent form, select (+ Create by connection to external database) option
cy.get('.nc-create-external-db-project').click({ force: true })
// feed project name
//cy.get('.nc-metadb-project-name').type(projectName)
cy.contains('Enter Project Name').parent().find('input').clear().type(projectName)
// Radio button: defaults to NC_REST
if (NC_GQL == projectData.apiType) {
cy.contains('GRAPHQL APIs').closest('label').click();
}
// External database credentials
// cy.contains('Database Type').parent().find('input').eq(1).click()
// cy.wait(100)
// cy.get('body').contains(' MySQL ').parents('div').click()
if (cred.hostAddress != '') cy.contains('Host Address').parent().find('input').clear().type(cred.hostAddress)
if (cred.portNumber != '') cy.contains('Port Number').parent().find('input').clear().type(cred.portNumber)
if (cred.username != '') cy.contains('Username').parent().find('input').clear().type(cred.username)
if (cred.password != '') cy.contains('Password').parent().find('input').clear().type(cred.password)
if (cred.databaseName != '') cy.contains('Database : create if not exists').parent().find('input').clear().type(cred.databaseName)
// Test database connection
cy.contains('Test Database Connection').click()
// Create project
cy.contains('Ok & Save Project', { timeout: 6000 }).click()
// takes a while to load project
this.waitHomePageLoad()
return projectName
}
}
@ -209,7 +237,7 @@ export class _projectsPage {
}
waitHomePageLoad() {
cy.url({ timeout: 12000 }).should('contain', '?type=roles')
cy.url({ timeout: 25000 }).should('contain', '?type=roles')
}
waitDeletePageLoad() {
@ -219,3 +247,25 @@ export class _projectsPage {
export const loginPage = new _loginPage;
export const projectsPage = new _projectsPage;
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Raju Udava <sivadstala@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

Loading…
Cancel
Save