From 858584bcf0db96433770ecea50d4aaff6252e911 Mon Sep 17 00:00:00 2001 From: Marouan Bentaleb <38426175+marouanbentaleb@users.noreply.github.com> Date: Wed, 24 Oct 2018 09:32:23 +0100 Subject: [PATCH] [ADF-3652][ADF-3653] Automated tests for people widget (#3880) * [ADF-3652] Automated test for starting a process with people widget * Fixing failing tests * [ADF-3653] Automated test for people widget in completed task --- .../process_services/processDetailsPage.js | 8 +- .../adf/process_services/taskDetailsPage.ts | 12 +- .../adf/process_services/taskFiltersPage.js | 4 +- .../adf/process_services/widgets/people.ts | 40 ++++++ .../adf/process_services/widgets/widget.ts | 5 + .../form_people_widget.e2e.ts | 127 ++++++++++++++++++ e2e/resources/apps/appWithUser.zip | Bin 0 -> 9102 bytes e2e/util/resources.js | 10 ++ 8 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 e2e/pages/adf/process_services/widgets/people.ts create mode 100644 e2e/process-services/form_people_widget.e2e.ts create mode 100644 e2e/resources/apps/appWithUser.zip diff --git a/e2e/pages/adf/process_services/processDetailsPage.js b/e2e/pages/adf/process_services/processDetailsPage.js index 2e69cebb1a..6a8d58ffee 100644 --- a/e2e/pages/adf/process_services/processDetailsPage.js +++ b/e2e/pages/adf/process_services/processDetailsPage.js @@ -43,6 +43,7 @@ var ProcessDetailsPage = function () { var cancelProcessButton = element(by.css('div[data-automation-id="header-status"] > button')); //Tasks var activeTask = element(by.css('div[data-automation-id="active-tasks"]')); + var completedTask = element(by.css('div[data-automation-id="completed-tasks"]')); var taskTitle = element(by.css("h2[class='activiti-task-details__header']")); this.checkProcessTitleIsDisplayed = function () { @@ -134,7 +135,12 @@ var ProcessDetailsPage = function () { this.clickOnActiveTask = function () { Util.waitUntilElementIsVisible(activeTask); - activeTask.click(); + return activeTask.click(); + }; + + this.clickOnCompletedTask = function () { + Util.waitUntilElementIsClickable(completedTask); + return completedTask.click(); }; this.checkActiveTaskTitleIsDisplayed = function () { diff --git a/e2e/pages/adf/process_services/taskDetailsPage.ts b/e2e/pages/adf/process_services/taskDetailsPage.ts index 94696157b5..61e0709239 100644 --- a/e2e/pages/adf/process_services/taskDetailsPage.ts +++ b/e2e/pages/adf/process_services/taskDetailsPage.ts @@ -46,6 +46,7 @@ export class TaskDetailsPage { taskDetailsSection = element(by.css('div[data-automation-id="adf-tasks-details"]')); taskDetailsEmptySection = element(by.css('div[data-automation-id="adf-tasks-details--empty"]')); completeTask = element(by.css('button[id="adf-no-form-complete-button"]')); + completeFormTask = element(by.css('button[id="adf-form-complete"]')); taskDetailsTitle = element(by.css('h2[class="activiti-task-details__header"] span')); auditLogButton = element(by.css('button[adf-task-audit]')); noPeopleInvolved = element(by.id('no-people-label')); @@ -254,7 +255,6 @@ export class TaskDetailsPage { checkUserIsSelected(user) { let row = element(by.cssContainingText('div[class*="search-list-container"] div[class*="people-full-name"]', user)); - let selectedRow = this.getRowsUser(user).element(by.css('ancestor::tr[class*="is-selected"]')); Util.waitUntilElementIsVisible(row); return this; } @@ -383,4 +383,14 @@ export class TaskDetailsPage { return this.completeTask.click(); } + checkCompleteFormButtonIsDisplayed() { + Util.waitUntilElementIsVisible(this.completeFormTask); + return this.completeFormTask; + } + + clickCompleteFormTask() { + Util.waitUntilElementIsClickable(this.completeFormTask); + return this.completeFormTask.click(); + } + } diff --git a/e2e/pages/adf/process_services/taskFiltersPage.js b/e2e/pages/adf/process_services/taskFiltersPage.js index 6526b7e87e..615b18aec8 100644 --- a/e2e/pages/adf/process_services/taskFiltersPage.js +++ b/e2e/pages/adf/process_services/taskFiltersPage.js @@ -73,14 +73,14 @@ var TaskFiltersPage = function () { Util.waitUntilElementIsVisible(queuedTask); return queuedTask; }; - + this.clickMyTaskTaskFilter = function() { Util.waitUntilElementIsVisible(myTasks); return myTasks.click(); }; this.clickCompletedTaskFilter = function() { - Util.waitUntilElementIsVisible(completedTask); + Util.waitUntilElementIsClickable(completedTask); return completedTask.click(); }; diff --git a/e2e/pages/adf/process_services/widgets/people.ts b/e2e/pages/adf/process_services/widgets/people.ts new file mode 100644 index 0000000000..ff0cd67c03 --- /dev/null +++ b/e2e/pages/adf/process_services/widgets/people.ts @@ -0,0 +1,40 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed 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 { element, by } from 'protractor'; +import Util = require('../../../../util/util'); + +export class People { + + peopleField = element(by.css('input[data-automation-id="adf-people-search-input"]')); + firstResult = element(by.id('adf-people-widget-user-0')); + + checkPeopleFieldIsDisplayed() { + return Util.waitUntilElementIsVisible(this.peopleField); + } + + fillPeopleField(value) { + Util.waitUntilElementIsClickable(this.peopleField); + return this.peopleField.sendKeys(value); + } + + selectUserFromDropdown() { + Util.waitUntilElementIsVisible(this.firstResult); + return this.firstResult.click(); + } + +} diff --git a/e2e/pages/adf/process_services/widgets/widget.ts b/e2e/pages/adf/process_services/widgets/widget.ts index a94d1d8aca..353d4c8bb9 100644 --- a/e2e/pages/adf/process_services/widgets/widget.ts +++ b/e2e/pages/adf/process_services/widgets/widget.ts @@ -24,6 +24,7 @@ import { RadioButtons } from './radioButtons'; import { Hyperlink } from './hyperlink'; import { Dropdown } from './dropdown'; import { DynamicTable } from './dynamicTable'; +import { People } from './people'; export class Widget { @@ -63,4 +64,8 @@ export class Widget { return new DynamicTable(); } + peopleWidget() { + return new People(); + } + } diff --git a/e2e/process-services/form_people_widget.e2e.ts b/e2e/process-services/form_people_widget.e2e.ts new file mode 100644 index 0000000000..0306956441 --- /dev/null +++ b/e2e/process-services/form_people_widget.e2e.ts @@ -0,0 +1,127 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed 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 { LoginPage } from '../pages/adf/loginPage'; +import { ProcessServicesPage } from '../pages/adf/process_services/processServicesPage'; +import ProcessFiltersPage = require('../pages/adf/process_services/processFiltersPage'); +import { Widget } from '../pages/adf/process_services/widgets/widget'; +import StartProcess = require('../pages/adf/process_services/startProcessPage'); +import ProcessDetailsPage = require('../pages/adf/process_services/processDetailsPage'); +import { TaskDetailsPage } from '../pages/adf/process_services/taskDetailsPage'; +import { AppNavigationBarPage } from '../pages/adf/process_services/appNavigationBarPage'; + +import TestConfig = require('../test.config'); +import resources = require('../util/resources'); + +import AlfrescoApi = require('alfresco-js-api-node'); +import { AppsActions } from '../actions/APS/apps.actions'; +import { UsersActions } from '../actions/users.actions'; +import { browser } from 'protractor'; + +describe('Form widgets - People', () => { + + let loginPage = new LoginPage(); + let processServicesPage = new ProcessServicesPage(); + let processUserModel; + let app = resources.Files.APP_WITH_USER_WIDGET; + let processFiltersPage = new ProcessFiltersPage(); + let appModel; + let alfrescoJsApi; + let widget = new Widget(); + let startProcess = new StartProcess(); + let processDetailsPage = new ProcessDetailsPage(); + let taskDetails = new TaskDetailsPage(); + let appNavigationBar = new AppNavigationBarPage(); + + beforeAll(async (done) => { + let users = new UsersActions(); + let appsActions = new AppsActions(); + + alfrescoJsApi = new AlfrescoApi({ + provider: 'BPM', + hostBpm: TestConfig.adf.url + }); + + await alfrescoJsApi.login(TestConfig.adf.adminEmail, TestConfig.adf.adminPassword); + + processUserModel = await users.createTenantAndUser(alfrescoJsApi); + + await alfrescoJsApi.login(processUserModel.email, processUserModel.password); + + appModel = await appsActions.importPublishDeployApp(alfrescoJsApi, app.file_location); + + loginPage.loginToProcessServicesUsingUserModel(processUserModel); + + done(); + }); + + afterAll(async (done) => { + + await alfrescoJsApi.login(TestConfig.adf.adminEmail, TestConfig.adf.adminPassword); + + await alfrescoJsApi.activiti.adminTenantsApi.deleteTenant(processUserModel.tenantId); + + done(); + }); + + beforeEach(() => { + processServicesPage.goToProcessServices().goToApp(appModel.name) + .clickProcessButton(); + processFiltersPage.clickCreateProcessButton(); + processFiltersPage.clickNewProcessDropdown(); + + widget.peopleWidget().checkPeopleFieldIsDisplayed(); + widget.peopleWidget().fillPeopleField(processUserModel.firstName); + widget.peopleWidget().selectUserFromDropdown(); + }); + + it('[C286577] Should be able to start a process with people widget', async () => { + + startProcess.clickFormStartProcessButton(); + processDetailsPage.clickOnActiveTask(); + + browser.controlFlow().execute(async () => { + let taskId = await taskDetails.getId(); + let taskForm = await alfrescoJsApi.activiti.taskApi.getTaskForm(taskId); + let userEmail = taskForm['fields'][0].fields['1'][0].value.email; + expect(userEmail).toEqual(processUserModel.email); + }); + }); + + it('[C286576] Should be able to see user in completed task', async () => { + + startProcess.enterProcessName(app.processName); + startProcess.clickFormStartProcessButton(); + + processDetailsPage.clickOnActiveTask(); + taskDetails.checkCompleteFormButtonIsDisplayed(); + taskDetails.clickCompleteFormTask(); + + appNavigationBar.clickProcessButton(); + processFiltersPage.clickCompletedFilterButton(); + processFiltersPage.selectFromProcessList(app.processName); + + processDetailsPage.clickOnCompletedTask(); + + browser.controlFlow().execute(async () => { + let taskId = await taskDetails.getId(); + let taskForm = await alfrescoJsApi.activiti.taskApi.getTaskForm(taskId); + let userEmail = taskForm['fields'][0].fields['1'][0].value.email; + expect(userEmail).toEqual(processUserModel.email); + }); + }); +}); diff --git a/e2e/resources/apps/appWithUser.zip b/e2e/resources/apps/appWithUser.zip new file mode 100644 index 0000000000000000000000000000000000000000..743412ee209119051c18fc8f76b70da6fca0d04c GIT binary patch literal 9102 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