From c1a42596ddf388edd22c8a601bec8bc321880b66 Mon Sep 17 00:00:00 2001 From: VitoAlbano Date: Sat, 12 Oct 2024 23:20:30 +0100 Subject: [PATCH] [ci:force] - fixing lint --- lib/extensions/tsconfig.lib.json | 3 +- lib/extensions/tsconfig.spec.json | 2 +- lib/insights/tsconfig.lib.json | 3 +- lib/insights/tsconfig.spec.json | 2 +- lib/js-api/src/utils/is-browser.ts | 2 +- .../identity/group-identity.service.ts | 138 +++++ .../src/lib/protractor/core/models/tenant.ts | 34 ++ .../core/pages/data-table-component.page.ts | 569 ++++++++++++++++++ .../protractor/core/pages/snackbar.page.ts | 65 ++ .../src/lib/protractor/core/test-element.ts | 214 +++++++ .../lib/protractor/core/utils/array.util.ts | 31 + .../protractor/core/utils/protractor.util.ts | 31 + .../pages/group-cloud-component.page.ts | 115 ++++ lib/testing/src/lib/shared/api/api.service.ts | 131 ++++ .../src/lib/shared/utils/string.util.ts | 125 ++++ lib/testing/tsconfig.lib.json | 14 + 16 files changed, 1474 insertions(+), 5 deletions(-) create mode 100644 lib/testing/src/lib/protractor/core/actions/identity/group-identity.service.ts create mode 100644 lib/testing/src/lib/protractor/core/models/tenant.ts create mode 100644 lib/testing/src/lib/protractor/core/pages/data-table-component.page.ts create mode 100644 lib/testing/src/lib/protractor/core/pages/snackbar.page.ts create mode 100644 lib/testing/src/lib/protractor/core/test-element.ts create mode 100644 lib/testing/src/lib/protractor/core/utils/array.util.ts create mode 100644 lib/testing/src/lib/protractor/core/utils/protractor.util.ts create mode 100644 lib/testing/src/lib/protractor/process-services-cloud/pages/group-cloud-component.page.ts create mode 100644 lib/testing/src/lib/shared/api/api.service.ts create mode 100644 lib/testing/src/lib/shared/utils/string.util.ts create mode 100644 lib/testing/tsconfig.lib.json diff --git a/lib/extensions/tsconfig.lib.json b/lib/extensions/tsconfig.lib.json index 13687b012a..06b4468f71 100644 --- a/lib/extensions/tsconfig.lib.json +++ b/lib/extensions/tsconfig.lib.json @@ -8,5 +8,6 @@ "@alfresco/js-api": ["../../../dist/libs/js-api"], "@alfresco/js-api/*": ["../../../dist/libs/js-api/*"] } - } + }, + "include": ["src/**/*.ts", "index.ts"] } diff --git a/lib/extensions/tsconfig.spec.json b/lib/extensions/tsconfig.spec.json index 7d643cc4b8..a4222287d3 100644 --- a/lib/extensions/tsconfig.spec.json +++ b/lib/extensions/tsconfig.spec.json @@ -6,5 +6,5 @@ "useDefineForClassFields": false }, "files": ["src/test.ts"], - "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"] + "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "index.ts"] } diff --git a/lib/insights/tsconfig.lib.json b/lib/insights/tsconfig.lib.json index a26e6a261c..56f880a88a 100644 --- a/lib/insights/tsconfig.lib.json +++ b/lib/insights/tsconfig.lib.json @@ -15,5 +15,6 @@ }, "target": "ES2022", "useDefineForClassFields": false - } + }, + "include": ["src/**/*.ts", "index.ts"] } diff --git a/lib/insights/tsconfig.spec.json b/lib/insights/tsconfig.spec.json index 7d643cc4b8..a4222287d3 100644 --- a/lib/insights/tsconfig.spec.json +++ b/lib/insights/tsconfig.spec.json @@ -6,5 +6,5 @@ "useDefineForClassFields": false }, "files": ["src/test.ts"], - "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"] + "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "index.ts"] } diff --git a/lib/js-api/src/utils/is-browser.ts b/lib/js-api/src/utils/is-browser.ts index e29e41b361..f16f87e949 100644 --- a/lib/js-api/src/utils/is-browser.ts +++ b/lib/js-api/src/utils/is-browser.ts @@ -15,4 +15,4 @@ * limitations under the License. */ -export const isBrowser = (): boolean => typeof window !== 'undefined' && typeof window.document !== 'undefined'; +export const isBrowser = (): boolean => typeof window?.document !== 'undefined'; diff --git a/lib/testing/src/lib/protractor/core/actions/identity/group-identity.service.ts b/lib/testing/src/lib/protractor/core/actions/identity/group-identity.service.ts new file mode 100644 index 0000000000..0eda89ce6c --- /dev/null +++ b/lib/testing/src/lib/protractor/core/actions/identity/group-identity.service.ts @@ -0,0 +1,138 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { ApiService } from '../../../../shared/api/api.service'; +import { StringUtil } from '../../../../shared/utils/string.util'; +import { ApiUtil } from '../../../../shared/api/api.util'; +import { Logger } from '../../utils/logger'; +import { browser } from 'protractor'; + +export class GroupIdentityService { + api: ApiService; + + constructor(api: ApiService) { + this.api = api; + } + + async createIdentityGroup(groupName = StringUtil.generateRandomString(5)): Promise { + await this.createGroup(groupName); + const group = await this.getGroupInfoByGroupName(groupName); + return group; + } + + async deleteIdentityGroup(groupId): Promise { + await this.deleteGroup(groupId); + } + + async createGroup(groupName: string): Promise { + const path = '/groups'; + const method = 'POST'; + const queryParams = {}; + const postBody = { + name: `${groupName}-${browser.params.groupSuffix}` + }; + const data = await this.api.performIdentityOperation(path, method, queryParams, postBody); + return data; + } + + async deleteGroup(groupId: string): Promise { + const path = `/groups/${groupId}`; + const method = 'DELETE'; + const queryParams = {}; + const postBody = {}; + const data = await this.api.performIdentityOperation(path, method, queryParams, postBody); + return data; + } + + async getGroupInfoByGroupName(groupName: string): Promise { + Logger.log(`Get GroupInfoByGroupName ${groupName}`); + + const predicate = (result: any) => !!result; + + const apiCall = async () => { + try { + const path = `/groups`; + const method = 'GET'; + const queryParams = { search: groupName }; + const postBody = {}; + + const data = await this.api.performIdentityOperation(path, method, queryParams, postBody); + + Logger.log(`Data ${JSON.stringify(data)}`); + + return data[0]; + Logger.error('Group not found'); + } catch (error) { + Logger.error('Group not found'); + return null; + } + }; + + return ApiUtil.waitForApi(apiCall, predicate); + } + + async assignRole(groupId: string, roleId: string, roleName: string): Promise { + Logger.log(`Assign to group ${groupId} Role ${roleName}`); + + const path = `/groups/${groupId}/role-mappings/realm`; + const method = 'POST'; + const queryParams = {}; + const postBody = [{ id: roleId, name: roleName }]; + + const data = await this.api.performIdentityOperation(path, method, queryParams, postBody); + return data; + } + + /** + * Add client roles. + * @param groupId ID of the target group + * @param clientId ID of the client + * @param roleId ID of the clientRole + * @param roleName of the clientRole + * @returns Promise + */ + async addClientRole(groupId: string, clientId: string, roleId: string, roleName: string): Promise { + const path = `/groups/${groupId}/role-mappings/clients/${clientId}`; + const method = 'POST'; + const queryParams = {}; + const postBody = [ + { + id: roleId, + name: roleName, + composite: false, + clientRole: true, + containerId: clientId + } + ]; + return this.api.performIdentityOperation(path, method, queryParams, postBody); + } + + /** + * Gets the client ID using the app name. + * @param applicationName Name of the app + * @returns client ID string + */ + async getClientIdByApplicationName(applicationName: string): Promise { + const path = `/clients`; + const method = 'GET'; + const queryParams = { clientId: applicationName }; + const postBody = {}; + + const data = await this.api.performIdentityOperation(path, method, queryParams, postBody); + return data[0].id; + } +} diff --git a/lib/testing/src/lib/protractor/core/models/tenant.ts b/lib/testing/src/lib/protractor/core/models/tenant.ts new file mode 100644 index 0000000000..4d3d37501e --- /dev/null +++ b/lib/testing/src/lib/protractor/core/models/tenant.ts @@ -0,0 +1,34 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { StringUtil } from '../../../shared/utils/string.util'; + +/** + * Create tenant JSON Object + * @param details - JSON object used to overwrite the default values + */ +export class Tenant { + active = true; + configuration = 'DefaultConfig'; + domain = 'DefaultDomain'; + maxUsers = 10; + name = StringUtil.generateRandomString(); + + constructor(details?: any) { + Object.assign(this, details); + } +} diff --git a/lib/testing/src/lib/protractor/core/pages/data-table-component.page.ts b/lib/testing/src/lib/protractor/core/pages/data-table-component.page.ts new file mode 100644 index 0000000000..584088c009 --- /dev/null +++ b/lib/testing/src/lib/protractor/core/pages/data-table-component.page.ts @@ -0,0 +1,569 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { browser, by, element, protractor, ElementFinder, ElementArrayFinder, $, $$ } from 'protractor'; +import { BrowserVisibility } from '../utils/browser-visibility'; +import { BrowserActions } from '../utils/browser-actions'; +import { Logger } from '../utils/logger'; +import { materialLocators } from './public-api'; + +const MAX_LOADING_TIME = 120000; + +export class DataTableComponentPage { + rootElement: ElementFinder; + list: ElementArrayFinder; + contents: ElementArrayFinder; + tableBody: ElementFinder; + allColumns: ElementArrayFinder; + selectedRowNumber: ElementFinder; + allSelectedRows: ElementArrayFinder; + selectAll: ElementFinder; + selectAllChecked: ElementFinder; + emptyList: ElementFinder; + emptyListTitle: ElementFinder; + emptyListSubtitle: ElementFinder; + noContentContainer: ElementFinder; + mainMenuButton: ElementFinder; + + rows = `adf-datatable div[class*='adf-datatable-body'] adf-datatable-row[class*='adf-datatable-row']`; + + constructor(rootElement = $$('adf-datatable').first()) { + this.rootElement = rootElement; + this.list = this.rootElement.$$(`div[class*='adf-datatable-body'] adf-datatable-row[class*='adf-datatable-row']`); + this.contents = this.rootElement.$$('.adf-datatable-body span'); + this.tableBody = this.rootElement.$$(`.adf-datatable-body`).first(); + this.allColumns = this.rootElement.$$('div[data-automation-id*="auto_header_content_id"]'); + this.mainMenuButton = this.rootElement.$('[data-automation-id="adf-datatable-main-menu-button"]'); + this.selectedRowNumber = this.rootElement.$(`adf-datatable-row[class*='is-selected'] div[data-automation-id*='text_']`); + this.allSelectedRows = this.rootElement.$$(`adf-datatable-row[class*='is-selected']`); + this.selectAllChecked = this.rootElement.$(`div[class*='adf-datatable-header'] ${materialLocators.Checkbox.root}.mat-mdc-checkbox-checked`); + this.selectAll = this.rootElement.$(`div[class*='adf-datatable-header'] ${materialLocators.Checkbox.root}`); + this.emptyList = this.rootElement.$(`adf-empty-content`); + this.emptyListTitle = this.rootElement.$(`.adf-empty-content__title`); + this.emptyListSubtitle = this.rootElement.$(`.adf-empty-content__subtitle`); + this.noContentContainer = $(`div[class*='adf-no-content-container']`); + } + + geCellElementDetail(detail: string): ElementArrayFinder { + return $$(`adf-datatable div[title="${detail}"] span`); + } + + async checkAllRowsButtonIsDisplayed(): Promise { + await BrowserVisibility.waitUntilElementIsVisible(this.selectAll); + } + + async checkAllRows(): Promise { + await BrowserActions.click(this.selectAll); + await BrowserVisibility.waitUntilElementIsVisible(this.selectAllChecked); + } + + async uncheckAllRows(): Promise { + await BrowserActions.click(this.selectAll); + await BrowserVisibility.waitUntilElementIsNotVisible(this.selectAllChecked); + } + + async clickCheckbox(columnName: string, columnValue: string): Promise { + const checkbox = this.getRowCheckbox(columnName, columnValue); + await BrowserActions.click(checkbox); + } + + async checkRowIsNotChecked(columnName: string, columnValue: string): Promise { + const rowSelector = this.getRowCheckboxChecked(columnName, columnValue); + await BrowserVisibility.waitUntilElementIsNotVisible(rowSelector); + } + + async checkRowIsChecked(columnName: string, columnValue: string): Promise { + const rowCheckbox = this.getRowCheckboxChecked(columnName, columnValue); + await BrowserVisibility.waitUntilElementIsVisible(rowCheckbox); + } + + getRowCheckbox(columnName: string, columnValue: string): ElementFinder { + return this.getRow(columnName, columnValue).$(materialLocators.Checkbox.root); + } + + getRowCheckboxChecked(columnName: string, columnValue: string): ElementFinder { + return this.getRow(columnName, columnValue).$('mat-checkbox.mat-mdc-checkbox-checked'); + } + + async checkNoRowIsSelected(): Promise { + await BrowserVisibility.waitUntilElementIsNotVisible(this.selectedRowNumber); + } + + async getNumberOfSelectedRows(): Promise { + return this.allSelectedRows.count(); + } + + async selectRow(columnName: string, columnValue: string): Promise { + await BrowserActions.closeMenuAndDialogs(); + const row = this.getRow(columnName, columnValue); + await BrowserActions.click(row); + } + + async selectRowWithKeyboard(columnName: string, columnValue: string): Promise { + await browser.actions().sendKeys(protractor.Key.COMMAND).perform(); + await this.selectRow(columnName, columnValue); + await browser.actions().sendKeys(protractor.Key.NULL).perform(); + } + + async checkRowIsSelected(columnName: string, columnValue: string): Promise { + const selectedRow = this.getCellElementByValue(columnName, columnValue).element( + by.xpath(`ancestor::adf-datatable-row[contains(@class, 'is-selected')]`) + ); + await BrowserVisibility.waitUntilElementIsVisible(selectedRow); + } + + async checkRowIsNotSelected(columnName: string, columnValue: string): Promise { + const selectedRow = this.getCellElementByValue(columnName, columnValue).element( + by.xpath(`ancestor::adf-datatable-row[contains(@class, 'is-selected')]`) + ); + await BrowserVisibility.waitUntilElementIsNotVisible(selectedRow); + } + + async getColumnValueForRow(identifyingColumn: string, identifyingValue: string, columnName: string): Promise { + const row = this.getRow(identifyingColumn, identifyingValue); + await BrowserVisibility.waitUntilElementIsVisible(row); + const rowColumn = row.$(`div[title="${columnName}"] span`); + return BrowserActions.getText(rowColumn); + } + + /** + * Check the list is sorted. + * @param sortOrder 'ASC' if the list is await expected to be sorted ascending and 'DESC' for descending + * @param columnTitle titleColumn column + * @param listType 'string' for string typed lists and 'number' for number typed (int, float) lists + * @returns 'true' if the list is sorted as await expected and 'false' if it isn't + */ + async checkListIsSorted(sortOrder: string, columnTitle: string, listType: string = 'STRING'): Promise { + const column = $$(`div.adf-datatable-cell[title='${columnTitle}'] span`); + await BrowserVisibility.waitUntilElementIsVisible(column.first()); + const initialList: string[] = []; + + const length = await column.count(); + + for (let i = 0; i < length; i++) { + const text: string = await BrowserActions.getText(column.get(i)); + if (text.length !== 0) { + initialList.push(text.toLowerCase()); + } + } + + let sortedList = [...initialList]; + if (listType.toLocaleLowerCase() === 'string') { + sortedList = sortedList.sort(); + } else if (listType.toLocaleLowerCase() === 'number') { + sortedList = sortedList.sort((a, b) => parseInt(a, 10) - parseInt(b, 10)); + } else if (listType.toLocaleLowerCase() === 'priority') { + sortedList = sortedList.sort(this.sortPriority); + } + + if (['desc', 'descending'].includes(sortOrder.toLocaleLowerCase())) { + sortedList = sortedList.reverse(); + } + + return initialList.toString() === sortedList.toString(); + } + + sortPriority(a: string, b: string) { + if (a === b) { + return 0; + } + + if (a.toLocaleLowerCase() === 'none') { + return -1; + } + + if (a.toLocaleLowerCase() === 'low') { + if (b === 'none') { + return 1; + } else { + return -1; + } + } + + if (a.toLocaleLowerCase() === 'normal') { + if (b.toLocaleLowerCase() === 'high') { + return -1; + } else { + return 1; + } + } + + return 1; + } + + async rightClickOnRow(columnName: string, columnValue: string): Promise { + await this.rightClickOnItem(columnName, columnValue); + await BrowserVisibility.waitUntilElementIsVisible($('#adf-context-menu-content')); + } + + async getTooltip(columnName: string, columnValue: string): Promise { + return BrowserActions.getAttribute(this.getCellElementByValue(columnName, columnValue), 'title'); + } + + async rightClickOnItem(columnName: string, columnValue: string): Promise { + const row = this.getRow(columnName, columnValue); + await BrowserActions.rightClick(row); + } + + getFileHyperlink(filename: string): ElementFinder { + return element(by.cssContainingText('adf-name-column[class*="adf-datatable-link"] span', filename)); + } + + async numberOfRows(): Promise { + try { + await this.waitForFirstRow(); + return this.rootElement.$$(this.rows).count(); + } catch (e) { + return 0; + } + } + + async waitForFirstRow(): Promise { + await BrowserVisibility.waitUntilElementIsVisible(this.rootElement.$$(this.rows).first()); + } + + async getAllRowsColumnValues(column: string): Promise { + let columnValues: string[] = []; + const columnLocator = $$( + `adf-datatable div[class*='adf-datatable-body'] adf-datatable-row[class*='adf-datatable-row'] div[title="${column}"] span` + ); + + await BrowserVisibility.waitUntilElementIsPresent(columnLocator.first(), 1000); + try { + await BrowserVisibility.waitUntilElementIsPresent(columnLocator.first(), 1000); + columnValues = await columnLocator.filter(async (el) => el.isPresent()).map(async (el) => el.getText()); + } catch (error) { + Logger.log(error); + } + + return columnValues; + } + + async getRowsWithSameColumnValues(columnName: string, columnValue: string) { + const columnLocator = `div[title='${columnName}'] div[data-automation-id="text_${columnValue}"] span`; + await BrowserVisibility.waitUntilElementIsVisible(this.rootElement.$$(columnLocator).first()); + return this.rootElement.$$(columnLocator).getText(); + } + + async doubleClickRow(columnName: string, columnValue: string): Promise { + const row = this.getRow(columnName, columnValue); + await BrowserActions.click(row); + await browser.actions().sendKeys(protractor.Key.ENTER).perform(); + } + + async waitForTableBody(): Promise { + await BrowserVisibility.waitUntilElementIsVisible(this.tableBody); + } + + // @deprecated Use Playwright API instead + async getFirstElementDetail(detail: string): Promise { + const firstNode = $$(`adf-datatable div[title="${detail}"] span`).first(); + return BrowserActions.getText(firstNode); + } + + /** + * Sort the list by name column. + * @param sortOrder 'ASC' to sort the list ascendant and 'DESC' for descendant + * @param titleColumn column title + */ + async sortByColumn(sortOrder: string, titleColumn: string): Promise { + const locator = $(`div[data-automation-id="auto_id_${titleColumn}"]`); + await BrowserVisibility.waitUntilElementIsVisible(locator); + const result = await BrowserActions.getAttribute(locator, 'class'); + + if (sortOrder.toLocaleLowerCase() === 'asc') { + if (!result.includes('sorted-asc')) { + if (result.includes('sorted-desc') || result.includes('sortable')) { + await BrowserActions.click(locator); + } + } + } else { + if (result.includes('sorted-asc')) { + await BrowserActions.click(locator); + } else if (result.includes('sortable')) { + await BrowserActions.click(locator); + await BrowserActions.click(locator); + } + } + } + + async checkContentIsDisplayed(columnName: string, columnValue: string, retry = 0): Promise { + Logger.log(`Wait content is displayed ${columnName} ${columnValue} retry: ${retry}`); + const row = this.getCellElementByValue(columnName, columnValue); + + try { + await BrowserVisibility.waitUntilElementIsVisible(row); + } catch (error) { + if (retry < 3) { + retry++; + await this.checkContentIsDisplayed(columnName, columnValue, retry); + } else { + throw error; + } + } + } + + async checkContentIsNotDisplayed(columnName: string, columnValue: string, retry = 0): Promise { + Logger.log(`Wait content is displayed ${columnName} ${columnValue} retry: ${retry}`); + const row = this.getCellElementByValue(columnName, columnValue); + + try { + await BrowserVisibility.waitUntilElementIsNotVisible(row); + } catch (error) { + if (retry < 3) { + retry++; + await this.checkContentIsNotDisplayed(columnName, columnValue, retry); + } else { + throw error; + } + } + } + + getRow(columnName: string, columnValue: string): ElementFinder { + return this.rootElement + .all( + by.xpath( + `//div[starts-with(@title, '${columnName}')]//div[contains(@data-automation-id, '${columnValue}')]//ancestor::adf-datatable-row[contains(@class, 'adf-datatable-row')]` + ) + ) + .first(); + } + + // @deprecated use Playwright instead + getRowByIndex(index: number): ElementFinder { + return this.rootElement.element( + by.xpath(`//div[contains(@class,'adf-datatable-body')]//adf-datatable-row[contains(@class,'adf-datatable-row')][${index}]`) + ); + } + + async contentInPosition(position: number): Promise { + await BrowserVisibility.waitUntilElementIsVisible(this.contents.first()); + return BrowserActions.getText(this.contents.get(position - 1)); + } + + getCellElementByValue(columnName: string, columnValue: string, columnPrefix = 'text_'): ElementFinder { + return this.rootElement.$$(`div[title="${columnName}"] div[data-automation-id="${columnPrefix}${columnValue}"] span`).first(); + } + + async tableIsLoaded(): Promise { + await BrowserVisibility.waitUntilElementIsVisible(this.rootElement); + } + + async waitTillContentLoaded(): Promise { + if (await this.isSpinnerPresent()) { + Logger.log('wait datatable loading spinner disappear'); + await BrowserVisibility.waitUntilElementIsNotVisible( + this.rootElement.element(by.tagName(materialLocators.Progress.spinner.root)), + MAX_LOADING_TIME + ); + + if (await this.isEmpty()) { + Logger.log('empty page'); + } else { + await this.waitFirstElementPresent(); + } + } else if (await this.isEmpty()) { + Logger.log('empty page'); + } else { + try { + Logger.log('wait datatable loading spinner is present'); + await BrowserVisibility.waitUntilElementIsVisible(this.rootElement.element(by.tagName(materialLocators.Progress.spinner.root)), 2000); + await BrowserVisibility.waitUntilElementIsNotVisible( + this.rootElement.element(by.tagName(materialLocators.Progress.spinner.root)), + MAX_LOADING_TIME + ); + } catch (error) { + Logger.error('Loading spinner is not present'); + } + + if (await this.isEmpty()) { + Logger.log('empty page'); + } else { + await this.waitFirstElementPresent(); + } + } + } + + async waitTillContentLoadedInfinitePagination(): Promise { + await browser.sleep(500); + + if (await this.isInfiniteSpinnerPresent()) { + Logger.log('wait datatable loading spinner disappear'); + await BrowserVisibility.waitUntilElementIsNotVisible(element(by.tagName(materialLocators.Progress.bar.root))); + + if (await this.isEmpty()) { + Logger.log('empty page'); + } else { + await this.waitFirstElementPresent(); + } + } else { + try { + Logger.log('wait datatable loading spinner is present'); + await BrowserVisibility.waitUntilElementIsVisible(element(by.tagName(materialLocators.Progress.bar.root))); + } catch (error) { + Logger.error('Infinite pagination spinner is not present'); + } + if (await this.isEmpty()) { + Logger.log('empty page'); + } else { + await this.waitFirstElementPresent(); + } + } + } + + // @deprecated use Playwright instead + async isColumnDisplayed(columnTitle: string): Promise { + const isColumnDisplayed = (await this.allColumns).some(async (column) => { + const columnText = await column.getText(); + return columnText === columnTitle; + }); + + return isColumnDisplayed; + } + + // @deprecated use Playwright instead + async getNumberOfColumns(): Promise { + return this.allColumns.count(); + } + + async getNumberOfRows(): Promise { + return this.list.count(); + } + + getCellByRowNumberAndColumnName(rowNumber: number, columnName: string): ElementFinder { + return this.list.get(rowNumber).$$(`div[title="${columnName}"] span`).first(); + } + + getCellByRowContentAndColumn(rowColumn: string, rowContent: string, columnName: string): ElementFinder { + return this.getRow(rowColumn, rowContent).$(`div[title='${columnName}']`); + } + + async selectRowByContent(content: string): Promise { + const row = this.getCellByContent(content); + await BrowserActions.click(row); + } + + async checkRowByContentIsSelected(folderName: string): Promise { + const selectedRow = this.getCellByContent(folderName).element(by.xpath(`ancestor::adf-datatable-row[contains(@class, 'is-selected')]`)); + await BrowserVisibility.waitUntilElementIsVisible(selectedRow); + } + + async checkRowByContentIsNotSelected(folderName: string): Promise { + const selectedRow = this.getCellByContent(folderName).element(by.xpath(`ancestor::adf-datatable-row[contains(@class, 'is-selected')]`)); + await BrowserVisibility.waitUntilElementIsNotVisible(selectedRow); + } + + getCellByContent(content: string): ElementFinder { + return this.rootElement + .all(by.cssContainingText(`adf-datatable-row[class*='adf-datatable-row'] div[class*='adf-datatable-cell']`, content)) + .first(); + } + + async checkCellByHighlightContent(content: string): Promise { + const cell = this.rootElement.element( + by.cssContainingText( + `adf-datatable-row[class*='adf-datatable-row'] div[class*='adf-name-location-cell-name'] span.adf-highlight`, + content + ) + ); + await BrowserVisibility.waitUntilElementIsVisible(cell); + } + + async clickRowByContent(name: string): Promise { + const resultElement = this.rootElement.$$(`div[data-automation-id='${name}']`).first(); + await BrowserActions.click(resultElement); + } + + async clickRowByContentCheckbox(name: string): Promise { + const resultElement = this.rootElement + .$$(`div[data-automation-id='${name}']`) + .first() + .element(by.xpath(`ancestor::adf-datatable-row/label/${materialLocators.Checkbox.root}`)); + browser.actions().mouseMove(resultElement); + await BrowserActions.click(resultElement); + } + + async checkRowContentIsDisplayed(content: string): Promise { + const resultElement = this.rootElement.$$(`div[data-automation-id='${content}']`).first(); + await BrowserVisibility.waitUntilElementIsVisible(resultElement); + } + + async checkRowContentIsNotDisplayed(content: string): Promise { + const resultElement = this.rootElement.$$(`div[data-automation-id='${content}']`).first(); + await BrowserVisibility.waitUntilElementIsNotVisible(resultElement); + } + + async checkRowContentIsDisabled(content: string): Promise { + const resultElement = this.rootElement.$$(`div[data-automation-id='${content}'] div.adf-cell-value img[aria-label='Disabled']`).first(); + await BrowserVisibility.waitUntilElementIsVisible(resultElement); + } + + async doubleClickRowByContent(name: string): Promise { + const resultElement = this.rootElement.$$(`div[data-automation-id='${name}']`).first(); + await BrowserActions.click(resultElement); + await browser.actions().sendKeys(protractor.Key.ENTER).perform(); + } + + async isEmpty(): Promise { + await browser.sleep(500); + + let isDisplayed; + + try { + isDisplayed = await this.emptyList.isDisplayed(); + } catch (error) { + isDisplayed = false; + } + + Logger.log(`empty page isDisplayed ${isDisplayed}`); + + return isDisplayed; + } + + private async isSpinnerPresent(): Promise { + let isSpinnerPresent; + + try { + isSpinnerPresent = await this.rootElement.element(by.tagName(materialLocators.Progress.spinner.root)).isDisplayed(); + } catch (error) { + isSpinnerPresent = false; + } + + return isSpinnerPresent; + } + + private async isInfiniteSpinnerPresent(): Promise { + let isSpinnerPresent; + + try { + isSpinnerPresent = await this.rootElement.element(by.tagName(materialLocators.Progress.bar.root)).isDisplayed(); + } catch (error) { + isSpinnerPresent = false; + } + + return isSpinnerPresent; + } + + private async waitFirstElementPresent(): Promise { + try { + Logger.log('wait first element is present'); + await BrowserVisibility.waitUntilElementIsVisible(this.contents.first()); + } catch (error) { + Logger.log('Possible empty page'); + } + } +} diff --git a/lib/testing/src/lib/protractor/core/pages/snackbar.page.ts b/lib/testing/src/lib/protractor/core/pages/snackbar.page.ts new file mode 100644 index 0000000000..46c4cf9a40 --- /dev/null +++ b/lib/testing/src/lib/protractor/core/pages/snackbar.page.ts @@ -0,0 +1,65 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { $, $$ } from 'protractor'; +import { BrowserVisibility } from '../utils/browser-visibility'; +import { BrowserActions } from '../utils/browser-actions'; + +/** @deprecated Use Playwright API instead */ +export class SnackbarPage { + notificationSnackBar = $$(`[data-automation-id='adf-snackbar-message-content']`).first(); + snackBarAction = $(`[data-automation-id='adf-snackbar-message-content-action-button']`); + snackBarContainerCss = $$('adf-snackbar-content'); + decorativeIconSnackBar = $(`[data-automation-id='adf-snackbar-decorative-icon']`); + + async waitForSnackBarToAppear(timeout = 5000) { + return BrowserVisibility.waitUntilElementIsVisible(this.snackBarContainerCss.first(), timeout, 'snackbar did not appear'); + } + + async waitForSnackBarToClose(timeout = 15000) { + return BrowserVisibility.waitUntilElementIsNotVisible(this.snackBarContainerCss.first(), timeout); + } + + async getSnackBarMessage(): Promise { + await this.waitForSnackBarToAppear(); + return this.notificationSnackBar.getText(); + } + + async getSnackBarActionMessage(): Promise { + await this.waitForSnackBarToAppear(); + return this.snackBarAction.getText(); + } + + async getSnackBarDecorativeIcon(): Promise { + await this.waitForSnackBarToAppear(); + return this.decorativeIconSnackBar.getText(); + } + + async clickSnackBarAction(): Promise { + await this.waitForSnackBarToAppear(); + await BrowserActions.click(this.snackBarAction); + } + + async isNotificationSnackBarDisplayed(): Promise { + try { + await BrowserVisibility.waitUntilElementIsVisible(this.notificationSnackBar, 2000); + return true; + } catch { + return false; + } + } +} diff --git a/lib/testing/src/lib/protractor/core/test-element.ts b/lib/testing/src/lib/protractor/core/test-element.ts new file mode 100644 index 0000000000..ec1b6af79a --- /dev/null +++ b/lib/testing/src/lib/protractor/core/test-element.ts @@ -0,0 +1,214 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { by, element, ElementFinder, protractor, $ } from 'protractor'; +import { BrowserActions } from './utils/browser-actions'; +import { BrowserVisibility } from './utils/browser-visibility'; + +/** + * Provides a wrapper for the most common operations with the page elements. + */ +export class TestElement { + constructor(public elementFinder: ElementFinder) {} + + /** + * Create a new instance with the element located by the id + * @param id The id of the element + * @returns test element wrapper + */ + static byId(id: string): TestElement { + return new TestElement(element(by.id(id))); + } + + /** + * Create a new instance with the element located by the CSS class name + * @param selector The CSS class name to lookup + * @returns test element wrapper + */ + static byCss(selector: string): TestElement { + return new TestElement($(selector)); + } + + /** + * Create a new instance with the element that contains specific text + * @param selector the CSS selector + * @param text the text within the target element + * @returns test element wrapper + */ + static byText(selector: string, text: string): TestElement { + return new TestElement(element(by.cssContainingText(selector, text))); + } + + /** + * Create a new instance with the element with specific HTML tag name + * @param selector the HTML tag name + * @returns test element wrapper + */ + static byTag(selector: string): TestElement { + return new TestElement(element(by.tagName(selector))); + } + + /** + * Performs a click on this element + * @returns a promise that will be resolved when the click command has completed + */ + async click() { + return BrowserActions.click(this.elementFinder); + } + + /** + * Checks that an element is present on the DOM of a page and visible + * @param waitTimeout How long to wait for the condition to be true + * @returns a promise that will be resolved when the element is present on the DOM of a page and visible + */ + async isVisible(waitTimeout?: number): Promise { + try { + await BrowserVisibility.waitUntilElementIsVisible(this.elementFinder, waitTimeout); + return true; + } catch { + return false; + } + } + + /** + * Waits until the element is present on the DOM of a page and visible + * @param waitTimeout How long to wait for the condition to be true + * @returns a promise that will be resolved when the element is present on the DOM of a page and visible + */ + async waitVisible(waitTimeout?: number): Promise { + return BrowserVisibility.waitUntilElementIsVisible(this.elementFinder, waitTimeout); + } + + /** + * Waits until the element is either invisible or not present on the DOM + * @param waitTimeout How long to wait for the condition to be true + * @returns a promise that will be resolved when the element is either invisible or not present on the DOM + */ + async waitNotVisible(waitTimeout?: number): Promise { + return BrowserVisibility.waitUntilElementIsNotVisible(this.elementFinder, waitTimeout); + } + + /** + * Checks that an element is present on the DOM of a page + * @param waitTimeout How long to wait for the condition to be true + * @returns a promise that will be resolved when the element is present on the DOM of a page + */ + async isPresent(waitTimeout?: number): Promise { + try { + await BrowserVisibility.waitUntilElementIsPresent(this.elementFinder, waitTimeout); + return true; + } catch { + return false; + } + } + + /** + * Waits until the element is present on the DOM of a page + * @param waitTimeout How long to wait for the condition to be true + * @returns a promise that will be resolved when the element is present on the DOM of a page + */ + async waitPresent(waitTimeout?: number): Promise { + return BrowserVisibility.waitUntilElementIsPresent(this.elementFinder, waitTimeout); + } + + /** + * Waits until the element is not attached to the DOM of a page + * @param waitTimeout How long to wait for the condition to be true + * @returns a promise that will be resolved when the element is not attached to the DOM of a page + */ + async waitNotPresent(waitTimeout?: number): Promise { + return BrowserVisibility.waitUntilElementIsNotPresent(this.elementFinder, waitTimeout); + } + + /** + * Waits until the given text is present in the element’s value + * @param value the text to check + * @returns a promise that will be resolved when the element has the given value + */ + async waitHasValue(value: string): Promise { + return BrowserVisibility.waitUntilElementHasValue(this.elementFinder, value); + } + + /** + * Query whether the DOM element represented by this instance is enabled. + * @returns a promise that will be resolved with the enabled state of the element + */ + async isEnabled(): Promise { + return this.elementFinder.isEnabled(); + } + /** + * Query whether the DOM element represented by this instance is disabled. + * @returns a promise that will be resolved with the disabled state of the element + */ + async isDisabled(): Promise { + return !(await this.elementFinder.isEnabled()); + } + + /** + * Test whether this element is currently displayed. + * @returns a promise that will be resolved with the displayed state of the element + */ + async isDisplayed(): Promise { + try { + await this.elementFinder.isDisplayed(); + return true; + } catch { + return false; + } + } + + /** + * Query for the value of the given attribute of the element. + * @param attributeName The name of the attribute to query. + * @returns a promise that will be resolved with the value of the attribute + */ + async getAttribute(attributeName: string): Promise { + return BrowserActions.getAttribute(this.elementFinder, attributeName); + } + + /** + * Get the visible (i.e. not hidden by CSS) innerText of this element, including sub-elements, without any leading or trailing whitespace. + * @returns a promise that will be resolved with the visible innerText of the element + */ + async getText(): Promise { + return BrowserActions.getText(this.elementFinder); + } + + /** + * Gets the `value` attribute for the given input element + * @returns input value + */ + getInputValue(): Promise { + return BrowserActions.getInputValue(this.elementFinder); + } + + /** + * Enter the text + * @param text the text to enter + */ + async typeText(text: string): Promise { + await BrowserActions.clearSendKeys(this.elementFinder, text); + } + + /** + * Clears the input using Ctrl+A and Backspace combination + */ + async clearInput() { + await this.elementFinder.clear(); + await this.elementFinder.sendKeys(' ', protractor.Key.CONTROL, 'a', protractor.Key.NULL, protractor.Key.BACK_SPACE); + } +} diff --git a/lib/testing/src/lib/protractor/core/utils/array.util.ts b/lib/testing/src/lib/protractor/core/utils/array.util.ts new file mode 100644 index 0000000000..3cd0c68b89 --- /dev/null +++ b/lib/testing/src/lib/protractor/core/utils/array.util.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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. + */ + +export class ArrayUtil { + /** + * Returns TRUE if the first array contains all elements from the second one. + * @param superset source array + * @param subset target array + * @returns `true` if array is included, otherwise `false` + */ + static arrayContainsArray(superset: any[], subset: any[]): boolean { + if (0 === subset.length) { + return false; + } + return subset.every((value) => superset.indexOf(value) >= 0); + } +} diff --git a/lib/testing/src/lib/protractor/core/utils/protractor.util.ts b/lib/testing/src/lib/protractor/core/utils/protractor.util.ts new file mode 100644 index 0000000000..fe4cca2590 --- /dev/null +++ b/lib/testing/src/lib/protractor/core/utils/protractor.util.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { ElementFinder, browser } from 'protractor'; + +/** + * Tagged template to convert a sting to an `ElementFinder`. + * @example ```const item = byCss`.adf-breadcrumb-item-current`;``` + * @example ```const item = byCss`${variable}`;``` + * @param literals literals + * @param placeholders placeholders + * @returns Instance of `ElementFinder` type. + */ +export const byCss = (literals: TemplateStringsArray, ...placeholders: string[]): ElementFinder => { + const selector = literals[0] || placeholders[0]; + return browser.$(selector); +}; diff --git a/lib/testing/src/lib/protractor/process-services-cloud/pages/group-cloud-component.page.ts b/lib/testing/src/lib/protractor/process-services-cloud/pages/group-cloud-component.page.ts new file mode 100644 index 0000000000..a883cdb4ef --- /dev/null +++ b/lib/testing/src/lib/protractor/process-services-cloud/pages/group-cloud-component.page.ts @@ -0,0 +1,115 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { by, element, $, ElementFinder, $$ } from 'protractor'; +import { BrowserVisibility } from '../../core/utils/browser-visibility'; +import { BrowserActions } from '../../core/utils/browser-actions'; +import { FormFields } from '../../core/pages/form/form-fields'; +import { TestElement } from '../../core/test-element'; +import { materialLocators } from '../../public-api'; + +export class GroupCloudComponentPage { + groupCloudSearch = $('input[data-automation-id="adf-cloud-group-search-input"]'); + groupField = $('group-cloud-widget .adf-readonly'); + formFields = new FormFields(); + + getGroupRowLocatorByName = async (name: string): Promise => + $$(`${materialLocators.Option.root}[data-automation-id="adf-cloud-group-chip-${name}"]`).first(); + + async searchGroups(name: string): Promise { + await BrowserActions.clearSendKeys(this.groupCloudSearch, name, 100); + } + + async searchGroupsToExisting(name: string) { + await BrowserActions.clearSendKeys(this.groupCloudSearch, name, 100); + } + + async getGroupsFieldContent(): Promise { + return BrowserActions.getInputValue(this.groupCloudSearch); + } + + async selectGroupFromList(name: string): Promise { + const groupRow = await this.getGroupRowLocatorByName(name); + + await BrowserActions.click(groupRow); + await BrowserVisibility.waitUntilElementIsNotVisible(groupRow); + } + + async checkGroupIsDisplayed(name: string): Promise { + const groupRow = await this.getGroupRowLocatorByName(name); + await BrowserVisibility.waitUntilElementIsVisible(groupRow); + } + + async checkGroupIsNotDisplayed(name: string): Promise { + const groupRow = await this.getGroupRowLocatorByName(name); + await BrowserVisibility.waitUntilElementIsNotVisible(groupRow); + } + + async checkSelectedGroup(group: string): Promise { + try { + await TestElement.byText(`${materialLocators.Chip.grid.row.root}[data-automation-id*="adf-cloud-group-chip-"]`, group).waitVisible(); + return true; + } catch (e) { + return false; + } + } + + async checkGroupNotSelected(group: string): Promise { + await BrowserVisibility.waitUntilElementIsNotVisible( + element(by.cssContainingText(`${materialLocators.Chip.grid.row.root}[data-automation-id*="adf-cloud-group-chip-"]`, group)) + ); + } + + async removeSelectedGroup(group: string): Promise { + const locator = $( + `${materialLocators.Chip.grid.row.root}[data-automation-id*="adf-cloud-group-chip-${group}"] ${materialLocators.Icon.root}` + ); + await BrowserActions.click(locator); + } + + async isGroupWidgetVisible(fieldId: string): Promise { + try { + await this.formFields.checkWidgetIsVisible(fieldId); + return true; + } catch { + return false; + } + } + + async checkGroupWidgetIsReadOnly(): Promise { + try { + await BrowserVisibility.waitUntilElementIsVisible(this.groupField); + return true; + } catch { + return false; + } + } + + async checkGroupActiveField(name: string): Promise { + try { + await BrowserActions.clearSendKeys(this.groupField, name); + return true; + } catch { + return false; + } + } + + async checkNoResultsFoundError(): Promise { + const errorLocator = $('[data-automation-id="adf-cloud-group-no-results"]'); + await BrowserVisibility.waitUntilElementIsVisible(errorLocator); + } +} diff --git a/lib/testing/src/lib/shared/api/api.service.ts b/lib/testing/src/lib/shared/api/api.service.ts new file mode 100644 index 0000000000..4a58226140 --- /dev/null +++ b/lib/testing/src/lib/shared/api/api.service.ts @@ -0,0 +1,131 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { AlfrescoApi, AlfrescoApiConfig } from '@alfresco/js-api'; +import { LoggerLike } from '../utils/logger'; + +export interface ApiServiceConfig { + appConfig: AlfrescoApiConfig; + users: { + [key: string]: { + username: string; + password: string; + }; + }; +} +export class ApiService { + apiService: AlfrescoApi; + + constructor(private config: ApiServiceConfig, private logger: LoggerLike) { + this.logger.log('Api Service configuration' + JSON.stringify(this.config)); + this.apiService = new AlfrescoApi(this.config.appConfig); + } + + getInstance(): AlfrescoApi { + return this.apiService; + } + + async login(username: string, password: string): Promise { + await this.apiService.login(username, password); + } + + /** + * Login using one of the account profiles from the `browser.params.testConfig`. + * Example: loginWithProfile('admin') + * @param profileName profile name + */ + async loginWithProfile(profileName: string): Promise { + const profile = this.config.users[profileName]; + if (profile) { + this.logger.log( + `try to login with ${profile.username} on HOST: ${this.apiService.config.hostEcm} AUTHTYPE: ${this.apiService.config.authType} PROVIDER: ${this.apiService.config.provider}` + ); + try { + await this.apiService.login(profile.username, profile.password); + this.logger.log(`Successfuly logged in as ${profile.username}`); + } catch (error) { + this.logger.error(`Failed to login with ${profile.username}`, error?.message); + throw new Error(`Login failed with ${profile.username}`); + } + } else { + throw new Error(`Login profile "${profileName}" not found on "browser.params.testConfig".`); + } + } + + // @deprecated + async performBpmOperation(path: string, method: string, queryParams: any, postBody: any): Promise { + return new Promise((resolve, reject) => { + const uri = this.config.appConfig.hostBpm + path; + const pathParams = {}; + const formParams = {}; + const contentTypes = ['application/json']; + const accepts = ['application/json']; + + const headerParams = { + // eslint-disable-next-line @typescript-eslint/naming-convention + Authorization: 'bearer ' + this.apiService.oauth2Auth.token + }; + + this.apiService.processClient + .callCustomApi(uri, method, pathParams, queryParams, headerParams, formParams, postBody, contentTypes, accepts, Object) + .then((data) => resolve(data)) + .catch((err) => reject(err)); + }); + } + + // @deprecated + async performIdentityOperation(path: string, method: string, queryParams: any, postBody: any): Promise { + return new Promise((resolve, reject) => { + const uri = this.config.appConfig.oauth2.host.replace('/realms', '/admin/realms') + path; + const pathParams = {}; + const formParams = {}; + const contentTypes = ['application/json']; + const accepts = ['application/json']; + + const headerParams = { + // eslint-disable-next-line @typescript-eslint/naming-convention + Authorization: 'bearer ' + this.apiService.oauth2Auth.token + }; + + return this.apiService.processClient + .callCustomApi(uri, method, pathParams, queryParams, headerParams, formParams, postBody, contentTypes, accepts, Object) + .then((data) => resolve(data)) + .catch((err) => reject(err)); + }); + } + + // @deprecated + async performECMOperation(path: string, method: string, queryParams: any, postBody: any): Promise { + return new Promise((resolve, reject) => { + const uri = this.config.appConfig.hostEcm + path; + const pathParams = {}; + const formParams = {}; + const contentTypes = ['application/json']; + const accepts = ['application/json']; + + const headerParams = { + // eslint-disable-next-line @typescript-eslint/naming-convention + Authorization: 'bearer ' + this.apiService.oauth2Auth.token + }; + + this.apiService.contentClient + .callCustomApi(uri, method, pathParams, queryParams, headerParams, formParams, postBody, contentTypes, accepts, Object) + .then((data) => resolve(data)) + .catch((err) => reject(err)); + }); + } +} diff --git a/lib/testing/src/lib/shared/utils/string.util.ts b/lib/testing/src/lib/shared/utils/string.util.ts new file mode 100644 index 0000000000..0dfddec118 --- /dev/null +++ b/lib/testing/src/lib/shared/utils/string.util.ts @@ -0,0 +1,125 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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. + */ + +export class StringUtil { + static generatePasswordString(length: number = 8): string { + let text = ''; + const lowerCaseLimit = Math.floor(length / 2); + text += StringUtil.generateRandomCharset(lowerCaseLimit, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); + text += StringUtil.generateRandomCharset(length - lowerCaseLimit, 'abcdefghijklmnopqrstuvwxyz'); + + return text; + } + + /** + * Generates a random string. + * @param length If this parameter is not provided the length is set to 8 by default. + * @returns random string + */ + static generateRandomString(length: number = 8): string { + return StringUtil.generateRandomCharset(length, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'); + } + + /** + * Generates a random lowercase string. + * @param length If this parameter is not provided the length is set to 8 by default. + * @returns random string + */ + static generateRandomLowercaseString(length: number = 8): string { + return StringUtil.generateRandomCharset(length, 'abcdefghijklmnopqrstuvwxyz0123456789'); + } + + /** + * Generates a random email address following the format: abcdef@activiti.test.com + * @param domain email domain + * @param length email length + * @returns email address + */ + static generateRandomEmail(domain: string, length: number = 5): string { + let email = StringUtil.generateRandomCharset(length, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'); + email += domain; + return email.toLowerCase(); + } + + /** + * Generates a random string - digits only. + * @param length If this parameter is not provided the length is set to 8 by default. + * @returns random string + */ + static generateRandomStringDigits(length: number = 8): string { + return StringUtil.generateRandomCharset(length, '0123456789'); + } + + /** + * Generates a random string - non-latin characters only. + * @param length If this parameter is not provided the length is set to 3 by default. + * @returns random string + */ + static generateRandomStringNonLatin(length: number = 3): string { + return StringUtil.generateRandomCharset(length, '密码你好𠮷'); + } + + /** + * Generates a random string. + * @param length If this parameter is not provided the length is set to 8 by default. + * @param charSet to use + * @returns random string + */ + static generateRandomCharset(length: number = 8, charSet: string): string { + let text = ''; + + for (let i = 0; i < length; i++) { + text += charSet.charAt(Math.floor(Math.random() * charSet.length)); + } + + return text; + } + + /** + * Generates a sequence of files with name: baseName + index + extension (e.g.) baseName1.txt, baseName2.txt, ... + * @param startIndex start index + * @param endIndex end index + * @param baseName the base name of all files + * @param extension the extension of the file + * @returns list of file names + */ + static generateFilesNames(startIndex: number, endIndex: number, baseName: string, extension: string): string[] { + const fileNames: string[] = []; + for (let i = startIndex; i <= endIndex; i++) { + fileNames.push(baseName + i + extension); + } + return fileNames; + } + + /** + * Generates a random name for a process + * @param length {int} If this parameter is not provided the length is set to 5 by default. + * @returns process name + */ + static generateProcessName(length: number = 5): string { + return 'process_' + StringUtil.generateRandomString(length); + } + + /** + * Generates a random name for a user task + * @param length If this parameter is not provided the length is set to 5 by default. + * @returns task name + */ + static generateUserTaskName(length: number = 5): string { + return 'userTask_' + StringUtil.generateRandomString(length); + } +} diff --git a/lib/testing/tsconfig.lib.json b/lib/testing/tsconfig.lib.json new file mode 100644 index 0000000000..13c33bfb2b --- /dev/null +++ b/lib/testing/tsconfig.lib.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "resolveJsonModule": true, + "paths": { + "@alfresco/js-api": ["../../../dist/libs/js-api"], + "@alfresco/js-api/*": ["../../../dist/libs/js-api/*"] + } + }, + "include": ["src/**/*.ts", "index.ts"] +}