You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2021/05/19 19:23:58 UTC

[GitHub] [trafficcontrol] ocket8888 commented on a change in pull request #5870: Migrate user tests from E2E to integration

ocket8888 commented on a change in pull request #5870:
URL: https://github.com/apache/trafficcontrol/pull/5870#discussion_r635502168



##########
File path: traffic_portal/test/integration/Data/users.ts
##########
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {randomize} from "../config";
+
+export const users = {
+    tests: [
+        {
+            logins: [
+                {
+                    description: "Admin Role",
+                    username: "TPAdmin",
+                    password: "pa$$word"
+                }
+            ],
+            check: [
+                {
+                    description: "check CSV link from CDN page",
+                    Name: "Export as CSV"
+                }
+            ],
+            toggle: [
+                {
+                    description: "hide first table column",
+                    Name: "Email"
+                },
+                {
+                    description: "redisplay first table column",
+                    Name: "Email"
+                }
+            ],
+            add: [
+                {
+                    description: "create a new User",
+                    FullName: "TPCreateUser1",
+                    Username: "User1",
+                    Email: "test@cdn.tc.com",
+                    Role: "admin",
+                    Tenant: "- tenantSame",
+                    Password: "qwe@123#rty",
+                    ConfirmPassword: "qwe@123#rty",
+                    PublicSSHKey: "",
+                    validationMessage: "User created"
+                },
+            ],
+            register: [
+                {
+                    description: "create a registered User",
+                    Email: "test2@cdn.tc.com",
+                    Role: "admin",
+                    Tenant: "- tenantSame",
+                    validationMessage: "Sent user registration to {{ test2@cdn.tc.com"+randomize+"}} with the following permissions [ role: admin | tenant: tenantSame"+randomize+" ]"
+                }
+            ],
+            update: [
+                {
+                    description: "update user's fullname",
+                    Username: "User1",
+                    NewFullName: "TPUpdatedUser1",
+                    validationMessage: "user was updated."
+                },
+            ],
+            updateRegisterUser: [
+                {
+                    description: "update registered user's fullname",
+                    Email: "test2@cdn.tc.com",
+                    NewFullName: "TPRegisterUser1",
+                    validationMessage: "user was updated."
+                }
+            ],
+        },
+    ]
+};

Review comment:
       Missing newline at EOF

##########
File path: traffic_portal/test/integration/PageObjects/BasePage.po.ts
##########
@@ -39,6 +39,7 @@ export class BasePage {
   private btnCancel =  element(by.className('close')).element(by.xpath("//span[text()='×']"));
   private btnUpdate = element(by.xpath("//button[text()='Update']"))
   private btnSubmit = element(by.xpath("//button[text()='Submit']"));
+  private btnRegister = element(by.xpath("//button[text()='Send Registration']"));

Review comment:
       You can just use [`by.buttonText`](https://www.protractortest.org/#/api?view=ProtractorBy.prototype.buttonText) to get a button with some specific text.

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();

Review comment:
       nit: `snp` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/Data/users.ts
##########
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {randomize} from "../config";
+
+export const users = {
+    tests: [
+        {
+            logins: [
+                {
+                    description: "Admin Role",
+                    username: "TPAdmin",
+                    password: "pa$$word"
+                }
+            ],
+            check: [
+                {
+                    description: "check CSV link from CDN page",
+                    Name: "Export as CSV"
+                }
+            ],
+            toggle: [
+                {
+                    description: "hide first table column",
+                    Name: "Email"
+                },
+                {
+                    description: "redisplay first table column",
+                    Name: "Email"
+                }
+            ],
+            add: [
+                {
+                    description: "create a new User",
+                    FullName: "TPCreateUser1",
+                    Username: "User1",
+                    Email: "test@cdn.tc.com",
+                    Role: "admin",
+                    Tenant: "- tenantSame",
+                    Password: "qwe@123#rty",
+                    ConfirmPassword: "qwe@123#rty",
+                    PublicSSHKey: "",
+                    validationMessage: "User created"
+                },
+            ],
+            register: [
+                {
+                    description: "create a registered User",
+                    Email: "test2@cdn.tc.com",
+                    Role: "admin",
+                    Tenant: "- tenantSame",
+                    validationMessage: "Sent user registration to {{ test2@cdn.tc.com"+randomize+"}} with the following permissions [ role: admin | tenant: tenantSame"+randomize+" ]"

Review comment:
       nit: template string would be easier to read and write

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();
+        await snp.NavigateToUsersPage();
+    }
+
+    async OpenUserMenu() {

Review comment:
       Missing return type on call signature

##########
File path: traffic_portal/test/integration/PageObjects/BasePage.po.ts
##########
@@ -77,6 +78,14 @@ export class BasePage {
       return false;
     }
   }
+  async ClickRegister(){

Review comment:
       Missing return type on call signature. I also like to specify `public`/`protected`/`private`, but that's entirely up to you.

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {

Review comment:
       Missing return type on call signature

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();
+        await snp.NavigateToUsersPage();
+    }
+
+    async OpenUserMenu() {
+        let snp = new SideNavigationPage();
+        await snp.ClickUserAdminMenu();
+    }
+
+    public async CheckCSV(name: string): Promise<boolean> {
+        return element(by.cssContainingText("span", name)).isPresent();
+    }
+
+    public async CheckToggle(name: string): Promise<boolean> {
+        let result = false;
+        await this.btnTableColumn.click();
+        //if the box is already checked, uncheck it and return false
+        if (await browser.isElementPresent(element(by.xpath("//th[text()='" + name + "']"))) === true) {

Review comment:
       Don't need to compare boolean values to `true`, you can just use them as the condition.

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {
+        let snp = new SideNavigationPage();
+        let name = nameEmail + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('email')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async UpdateUser(user: UpdateUser): Promise<boolean  | undefined> {
+        let basePage = new BasePage();
+        switch (user.description) {
+            case "update user's fullname":
+                await this.txtFullName.clear();
+                await this.txtFullName.sendKeys(user.NewFullName);
+                await basePage.ClickUpdate();
+                break;
+            default:
+                return undefined;
+        }
+        return await basePage.GetOutputMessage().then(value => user.validationMessage === value);
+    }
+
+    public async RegisterUser(user: RegisterUser): Promise<boolean> {
+        let result = false;
+        let basePage = new BasePage();
+        let snp = new SideNavigationPage();

Review comment:
       nit: `snp` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {

Review comment:
       Missing return type on call signature

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {
+        let snp = new SideNavigationPage();
+        let name = nameEmail + this.randomize;

Review comment:
       nit: `name` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;

Review comment:
       nit: `name` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {
+        let snp = new SideNavigationPage();
+        let name = nameEmail + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('email')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async UpdateUser(user: UpdateUser): Promise<boolean  | undefined> {

Review comment:
       It looks like the tests actually only check that the resolution of this is "truthy" or literally `true`, so why not just return a `Promise<boolean>` instead of allowing it to be `undefined`? Allowing `undefined` could be confusing, for example, if `UpdateUser` returns a `Promise<boolean>`, these are the same check:
   
   ```typescript
   expect(await usersPage.UpdateUser(u)).toBeTrue(); // or .toBe(true)
   expect(await usersPage.UpdateUser(u)).not.toBeFalse(); // or .toBe(false)
   ```
   
   ... but if it returns a `Promise<boolean | undefined>`, they are **not** the same. These pairs of checks also have the same problem where they're identical for `Promise<boolean>` but not `Promise<boolean>`:
   
   ```typescript
   expect(await usersPage.UpdateUser(u)).toBeFalse();
   expect(await usersPage.UpdateUser(u)).toBe(false);
   expect(await usersPage.UpdateUser(u)).toBeFalsey()
   ```

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {
+        let snp = new SideNavigationPage();
+        let name = nameEmail + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('email')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async UpdateUser(user: UpdateUser): Promise<boolean  | undefined> {
+        let basePage = new BasePage();

Review comment:
       nit: `basePage` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/specs/Users.spec.ts
##########
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { browser } from 'protractor';
+
+import { LoginPage } from '../PageObjects/LoginPage.po';
+import { UsersPage } from '../PageObjects/UsersPage.po';
+import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
+import { users } from "../Data/users";
+
+const loginPage = new LoginPage();
+const topNavigation = new TopNavigationPage();
+const usersPage = new UsersPage();
+
+users.tests.forEach(async usersData =>{
+    usersData.logins.forEach(login => {
+        describe('Traffic Portal - Users - ' + login.description, function(){
+            it('can login', async () => {
+                browser.get(browser.params.baseUrl);
+                await loginPage.Login(login);
+                expect(await loginPage.CheckUserName(login)).toBeTruthy();
+            });
+            it('can open users page', async () => {
+                await usersPage.OpenUserMenu()
+                await usersPage.OpenUserPage();
+            });
+            usersData.check.forEach(check => {
+                it(check.description, async () => {
+                    expect(await usersPage.CheckCSV(check.Name)).toBe(true);
+                    await usersPage.OpenUserPage();
+                });
+            });
+            usersData.toggle.forEach(toggle => {
+                it(toggle.description, async () => {
+                    if(toggle.description.includes('hide')){
+                        expect(await usersPage.CheckToggle(toggle.Name)).toBe(false);
+                        await usersPage.OpenUserPage();
+                    }else{
+                        expect(await usersPage.CheckToggle(toggle.Name)).toBe(true);
+                        await usersPage.OpenUserPage();
+                    }
+                });
+            })
+            usersData.add.forEach(add => {
+                it(add.description, async () => {
+                    expect(await usersPage.CreateUser(add)).toBeTruthy();
+                    await usersPage.OpenUserPage();
+                });
+            });
+            usersData.update.forEach(update => {
+                it(update.description, async () => {
+                    await usersPage.SearchUser(update.Username);
+                    expect(await usersPage.UpdateUser(update)).toBe(true);
+                    await usersPage.OpenUserPage();
+                });
+            });
+            usersData.register.forEach(register => {
+                it(register.description, async () => {
+                    expect(await usersPage.RegisterUser(register)).toBeTruthy();
+                    await usersPage.OpenUserPage();
+                });
+            });
+            usersData.updateRegisterUser.forEach(updateRegisterUser => {
+                it(updateRegisterUser.description, async () => {
+                    await usersPage.SearchEmailUser(updateRegisterUser.Email);
+                    expect(await usersPage.UpdateRegisterUser(updateRegisterUser)).toBe(true);
+                    await usersPage.OpenUserPage();
+                });
+            });
+            it('can logout', async () => {
+                expect(await topNavigation.Logout()).toBeTruthy();
+            });
+        });
+    });
+});

Review comment:
       Missing newline at EOF

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {
+        let snp = new SideNavigationPage();
+        let name = nameEmail + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('email')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async UpdateUser(user: UpdateUser): Promise<boolean  | undefined> {
+        let basePage = new BasePage();
+        switch (user.description) {
+            case "update user's fullname":
+                await this.txtFullName.clear();
+                await this.txtFullName.sendKeys(user.NewFullName);
+                await basePage.ClickUpdate();
+                break;
+            default:
+                return undefined;
+        }
+        return await basePage.GetOutputMessage().then(value => user.validationMessage === value);
+    }
+
+    public async RegisterUser(user: RegisterUser): Promise<boolean> {
+        let result = false;
+        let basePage = new BasePage();
+        let snp = new SideNavigationPage();
+        await this.btnRegisterNewUser.click();
+        await this.txtEmail.sendKeys(user.Email + this.randomize);
+        await this.txtRole.sendKeys(user.Role);
+        await this.txtTenant.sendKeys(user.Tenant + this.randomize);
+        await basePage.ClickRegister();
+        if (await basePage.GetOutputMessage() === user.existsMessage) {
+            await snp.NavigateToUsersPage();
+            result = true;
+        } else if (await basePage.GetOutputMessage() === user.validationMessage) {
+            result = true;
+        } else {
+            result = false;
+        }
+        return result;
+    }
+
+    public async UpdateRegisterUser(user: UpdateRegisterUser): Promise<boolean | undefined> {
+        let basePage = new BasePage();
+        switch (user.description) {
+            case "update registered user's fullname":
+                await this.txtFullName.sendKeys(user.NewFullName);
+                await basePage.ClickUpdate();
+                break;
+            default:
+                return undefined;
+        }
+        return await basePage.GetOutputMessage().then(value => user.validationMessage === value);

Review comment:
       nit: you don't need to `await` a Promise you're returning.

##########
File path: traffic_portal/test/integration/PageObjects/BasePage.po.ts
##########
@@ -77,6 +78,14 @@ export class BasePage {
       return false;
     }
   }
+  async ClickRegister(){
+    if(await this.btnRegister.isEnabled() == true){

Review comment:
       `element(locator).isEnabled()` returns a `Promise<boolean>`, so you don't need to compare it to `true`; when the Promise resolves (which it will do immediately in the logical flow since it's being `await`ed) it will be literally one of `true` or `false`.
   
   Plus using `==` to compare - especially with boolean values - is hazard-prone. For example:
   ```javascript
   // outputs: true
   console.log(1 == true);
   ```

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();

Review comment:
       nit: `snp` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();
+        await snp.NavigateToUsersPage();
+    }
+
+    async OpenUserMenu() {
+        let snp = new SideNavigationPage();
+        await snp.ClickUserAdminMenu();
+    }
+
+    public async CheckCSV(name: string): Promise<boolean> {
+        return element(by.cssContainingText("span", name)).isPresent();
+    }
+
+    public async CheckToggle(name: string): Promise<boolean> {
+        let result = false;
+        await this.btnTableColumn.click();
+        //if the box is already checked, uncheck it and return false
+        if (await browser.isElementPresent(element(by.xpath("//th[text()='" + name + "']"))) === true) {
+            await element(by.xpath("//label[text()=' " + name + "']")).click();

Review comment:
       same as above RE: `by.xpath` vs `by.cssContainingText`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();
+        await snp.NavigateToUsersPage();

Review comment:
       this works fine as-is, but if it's more intuitive for you, you should knowyou can just directly return this Promise to run asynchronously and resolve the Promise returned by this method when it resolves. It'll work the same way, essentially, since this method is `async`, but might make more sense since I asked for a return type for the function to actually `return` something explicitly. (To be clear, it is returning something implicitly already).

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {

Review comment:
       Missing return type on call signature

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();
+        await snp.NavigateToUsersPage();
+    }
+
+    async OpenUserMenu() {
+        let snp = new SideNavigationPage();

Review comment:
       nit: `snp` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {
+        let snp = new SideNavigationPage();

Review comment:
       nit: `snp` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -61,22 +109,94 @@ export class UsersPage extends BasePage {
       await this.btnCreateNewUser.click();
       await this.txtFullName.sendKeys(user.FullName + this.randomize);
       await this.txtUserName.sendKeys(user.Username + this.randomize);
-      await this.txtEmail.sendKeys(user.FullName + this.randomize + user.Email);
+      await this.txtEmail.sendKeys(user.Email + this.randomize);
       await this.txtRole.sendKeys(user.Role);
       await this.txtTenant.sendKeys(user.Tenant+this.randomize);
       await this.txtPassword.sendKeys(user.Password);
       await this.txtConfirmPassword.sendKeys(user.ConfirmPassword);
       await this.txtPublicSSHKey.sendKeys(user.PublicSSHKey);
       await basePage.ClickCreate();
-      if(await basePage.GetOutputMessage() == user.existsMessage){
+      if(await basePage.GetOutputMessage() === user.existsMessage){
         await snp.NavigateToUsersPage();
         result = true;
-      }else if(await basePage.GetOutputMessage() == user.validationMessage){
+      }else if(await basePage.GetOutputMessage() === user.validationMessage){
         result = true;
       }else{
         result = false;
       }
       return result;
     }
 
+    public async SearchUser(nameUser: string) {
+        let snp = new SideNavigationPage();
+        let name = nameUser + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('username')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async SearchEmailUser(nameEmail: string) {
+        let snp = new SideNavigationPage();
+        let name = nameEmail + this.randomize;
+        await snp.NavigateToUsersPage();
+        await this.txtSearch.clear();
+        await this.txtSearch.sendKeys(name);
+        await element.all(by.repeater('u in ::users')).filter(function (row) {
+            return row.element(by.name('email')).getText().then(function (val) {
+                return val === name;
+            });
+        }).first().click();
+    }
+
+    public async UpdateUser(user: UpdateUser): Promise<boolean  | undefined> {
+        let basePage = new BasePage();
+        switch (user.description) {
+            case "update user's fullname":
+                await this.txtFullName.clear();
+                await this.txtFullName.sendKeys(user.NewFullName);
+                await basePage.ClickUpdate();
+                break;
+            default:
+                return undefined;
+        }
+        return await basePage.GetOutputMessage().then(value => user.validationMessage === value);
+    }
+
+    public async RegisterUser(user: RegisterUser): Promise<boolean> {
+        let result = false;
+        let basePage = new BasePage();

Review comment:
       nit: `basePage` is never reassigned, could be `const`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();
+        await snp.NavigateToUsersPage();
+    }
+
+    async OpenUserMenu() {
+        let snp = new SideNavigationPage();
+        await snp.ClickUserAdminMenu();
+    }
+
+    public async CheckCSV(name: string): Promise<boolean> {
+        return element(by.cssContainingText("span", name)).isPresent();
+    }
+
+    public async CheckToggle(name: string): Promise<boolean> {
+        let result = false;
+        await this.btnTableColumn.click();
+        //if the box is already checked, uncheck it and return false
+        if (await browser.isElementPresent(element(by.xpath("//th[text()='" + name + "']"))) === true) {
+            await element(by.xpath("//label[text()=' " + name + "']")).click();
+            result = false;
+        } else {
+            //if the box is unchecked, then check it and return true
+            await element(by.xpath("//label[text()=' " + name + "']")).click();

Review comment:
       same as above RE: `by.xpath` vs `by.cssContainingText`

##########
File path: traffic_portal/test/integration/PageObjects/UsersPage.po.ts
##########
@@ -46,13 +68,39 @@ export class UsersPage extends BasePage {
     private txtPassword = element(by.name('uPass'));
     private txtConfirmPassword = element(by.name('confirmPassword'));
     private txtPublicSSHKey = element(by.name('publicSshKey'));
+    private txtSearch = element(by.id('usersTable_filter')).element(by.css('label input'));
+    private btnTableColumn = element(by.css('[title="Select Table Columns"]'));
     private randomize = randomize;
 
-    async OpenUserPage(){
-      let snp = new SideNavigationPage();
-      await snp.ClickUserAdminMenu();
-      await snp.NavigateToUsersPage();
-     }
+    async OpenUserPage() {
+        let snp = new SideNavigationPage();
+        await snp.NavigateToUsersPage();
+    }
+
+    async OpenUserMenu() {
+        let snp = new SideNavigationPage();
+        await snp.ClickUserAdminMenu();
+    }
+
+    public async CheckCSV(name: string): Promise<boolean> {
+        return element(by.cssContainingText("span", name)).isPresent();
+    }
+
+    public async CheckToggle(name: string): Promise<boolean> {
+        let result = false;
+        await this.btnTableColumn.click();
+        //if the box is already checked, uncheck it and return false
+        if (await browser.isElementPresent(element(by.xpath("//th[text()='" + name + "']"))) === true) {

Review comment:
       nit: instead of using an XPath here, you could just do like you did above with the CheckCSV span:
   
   ```typescript
   if (await element(by.cssContainingText("th", name)).isPresent()) {
       // ...
   }
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org