/**
|
* @file S&OP Dialog component to wrap common methods the team encounter during development
|
* @description Dialog class extending libappbase's DialogBase.
|
* All S&OP page objects inherit from our own class (inheriting e2e/libappbase), but we can propose common methods to them.
|
* @author Clarence (clarence.chan@3ds.com)
|
* @copyright Dassault Systèmes
|
*/
|
import { DialogBase } from '../libappbase/dialogbase';
|
import { LogMessage } from '../libappbase/logmessage';
|
import { Timeout } from '../libmp/appmp';
|
import { ButtonSOP } from './buttonsop';
|
import { UIActionSOP } from './objectsop';
|
import { UtilSOP } from './utilsop';
|
import { browser } from '../e2elib/node_modules/protractor';
|
|
export enum DialogBatchEditActionLink {
|
EnableBatchEdit = 'Edit',
|
DisableBatchEdit = 'Revert',
|
}
|
|
/**
|
* Dialog key value pairs of UI name and its value. Created to mimic the generic type DialogFieldsType.
|
*/
|
export interface DialogUIKeyValues {
|
[propName: string]: string;
|
}
|
|
export class DialogSOP<DialogFieldsType> extends DialogBase {
|
protected readonly _uiMap = new Map<string, UIActionSOP>(); // Tracks UI element to set value and verify value (e.g key = Start , mapped to a DateTimeSelector object in dialog)
|
private readonly _btnOK: ButtonSOP;
|
|
/**
|
* Replace all values in object entries (e.g overwrite 'false' to all interface elements).
|
*
|
* @param fields Key-value pair of values to enter per UI element. Key = the UI element to update.
|
* @param replaceValue Value to replace in all object entries.
|
* @returns The reconstructed object entries with the new replaced value.
|
*/
|
public static replaceDialogFieldsTypeValues(fields: any, replaceValue: string): DialogUIKeyValues {
|
let result = {}; // Rebuild the interface to overwrite value to all entries
|
// Not interested in value from Object.entries, thus put underscore to prevent lint error
|
for (const [key, _value] of Object.entries(fields)) {
|
result = {...result, [key]: replaceValue};
|
}
|
|
return result;
|
}
|
|
/**
|
* Create dialog by defining name, optionally providing OK and Cancel button names.
|
* Since most dialogs OK and Cancel buttons have same name, this makes the script cleaner without need to define the names.
|
*
|
* @param dialogName Name of dialog to initialize
|
* @param btnOkName Button name of positive action to save changes
|
* @param btnCancelName Button name of negative action to cancel changes
|
*/
|
public constructor(dialogName: string, btnOkName: string = 'btnOk', btnCancelName: string = 'btnCancel') {
|
super(dialogName, btnOkName, btnCancelName);
|
this._btnOK = new ButtonSOP(btnOkName); // ABSAPI: duplicate ok button since S&OP we need buttons to be of ButtonSOP types (remove once migrate to QDialog)
|
}
|
|
/**
|
* Click on action link text of UI element in dialog (e.g action link text of an edit field).
|
*
|
* @param actionLinkText Text defined on action link
|
* @param values Key-value pair of values to enter per UI element.
|
*/
|
public async clickActionLinkText(values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
await uiObj.clickActionLinkText(v);
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Override libappbase clickOK to use browser.wait with timeout.
|
*/
|
public async clickOK(waitTime?: number | undefined): Promise<boolean> {
|
return browser.wait(async () => {
|
return super.clickOK(waitTime);
|
}, Timeout.Short, 'Unable to click OK button. Timeout waiting for button to be enabled.');
|
}
|
|
public async getDialogValues(values: DialogFieldsType): Promise<DialogUIKeyValues> {
|
let getValues: DialogUIKeyValues = {};
|
|
// "v" value not used, thus underscore to prevent compile warning
|
for (const [k, _v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
const value = await uiObj.getValueString();
|
getValues = {...getValues, [k]: value}; // Overwrite the UI value passed in via argument
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
|
return getValues;
|
}
|
|
public async hoverElementVerifyTooltip(values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
await uiObj.verifyTooltip(v);
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Set UI element values in dialog (e.g set value to an edit field).
|
*
|
* @param values Key-value pair of values to enter per UI element. Key = the UI element to update, Value = the value to update.
|
* Derived dialog to provide the interface to ensure scriptor is guided to what keys/UI element
|
* can be updated else a typo by scriptor in spec file goes undetected and waste times debugging what goes wrong.
|
*
|
* @example export interface DialogCurrencyFields {
|
* Currency?: string;
|
* Start?: string;
|
* Rate?: string;
|
* RateLabel?: string;
|
* }
|
*/
|
public async updateDialogValues(values: DialogFieldsType, randomSetValue: boolean = false): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
// console.log(`@@@@@@@@@ ${k} --------------- ${v}`)
|
if (randomSetValue) {
|
await uiObj.toggleValue!();
|
} else {
|
await uiObj.setValue(v);
|
}
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
public async updateDialogValuesBatchEdit(batchEditAction: DialogBatchEditActionLink, values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
// Click action link regardless enable or disable batch edit (the component to throw error if action link text does not match)
|
await uiObj.clickActionLinkText(batchEditAction);
|
|
// If enable batch edit, follow-up with setting value to dialog field
|
if (batchEditAction === DialogBatchEditActionLink.EnableBatchEdit) {
|
await uiObj.setValue(v);
|
}
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Verify dialog fields support batch edit by checking if action link present.
|
* If disabled, the action link should show Edit for user to click to input value.
|
* If enabled, the action link should show Revert for user to toggle off batch edit.
|
*
|
* @param values Key-value pair of values to enter per UI element. Key = the UI element to update, Value = the value to update.
|
*/
|
public async verifyBatchEditEnabled(values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
const expectedEnabled = v === 'true';
|
// If enabled, expect action link text to be Revert for user to click to disable it (vice versa)
|
const expectedActionLinkText = expectedEnabled ? DialogBatchEditActionLink.DisableBatchEdit : DialogBatchEditActionLink.EnableBatchEdit;
|
|
await uiObj.verifyBatchEditEnabled(expectedEnabled, expectedActionLinkText);
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Verify button disabled and optionally providing tooltip to verify.
|
*
|
* @param button Button to verify disabled.
|
* @param tooltip Disabled tooltip to verify.
|
* @param fullMatch By default false = partial comparison when verifying tooltip.
|
*/
|
public async verifyButtonDisabled(button: ButtonSOP, tooltip?: string, fullMatch: boolean = false): Promise<void> {
|
const [isBtnClickable, disabledTooltip] = await button.getIsClickable(true, Timeout.ButtonState, false);
|
|
expect(isBtnClickable).toBe(false, LogMessage.btn_isEnabled(await button.getComponentLabel()));
|
// Only if tooltip passed in, we check if it matches
|
if (tooltip) {
|
if (fullMatch) {
|
expect(disabledTooltip).toBe(tooltip, LogMessage.tooltip_notMatched(tooltip, disabledTooltip));
|
} else {
|
expect(disabledTooltip).toContain(tooltip, LogMessage.tooltip_notMatched(tooltip, disabledTooltip));
|
}
|
}
|
}
|
|
/**
|
* Verify button enabled. If disabled, fail by mentioning the disabled tooltip if any.
|
*/
|
public async verifyButtonEnabled(button: ButtonSOP): Promise<void> {
|
const [isBtnClickable, disabledTooltip] = await button.getIsClickable(true, Timeout.ButtonState);
|
const label = await button.getComponentLabel();
|
|
expect(isBtnClickable).toBe(true, LogMessage.btn_notClickable(label, disabledTooltip));
|
}
|
|
/**
|
* Verify UI element enabled/disabled.
|
*
|
* @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = the expected value.
|
*/
|
public async verifyDialogEnabled(values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
await uiObj.verifyEnabled(v === 'true');
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Verify UI element values in dialog (e.g verify value of an edit field).
|
*
|
* @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = the expected value.
|
* Derived dialog to provide the interface to ensure scriptor is guided to what keys/UI element
|
* can be verified else a typo by scriptor in spec file goes undetected and waste times debugging what goes wrong.
|
*
|
* @example export interface DialogCurrencyFields {
|
* Currency?: string;
|
* Start?: string;
|
* Rate?: string;
|
* RateLabel?: string;
|
* }
|
*/
|
public async verifyDialogValues(values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
await uiObj.verifyValue(v);
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Verify UI element visibility in dialog.
|
*
|
* @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = the expected value.
|
*/
|
public async verifyDialogUIVisible(values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
await uiObj.verifyVisible(v);
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Verify UI element if has mask error.
|
*
|
* @param values Key-value pair of values to verify per UI element. Key = the UI element to verify, Value = if has mask error.
|
*/
|
public async verifyHasMaskError(values: DialogFieldsType): Promise<void> {
|
for (const [k, v] of Object.entries(values)) {
|
const uiObj = this._uiMap.get(k);
|
if (uiObj) {
|
await uiObj.verifyHasMaskError(v === 'true');
|
} else {
|
throw Error(`Dialog ${await this.getTitle()}: Object mapping not defined for UI element '${k}'. Define mapping in dialog constructor.`);
|
}
|
}
|
}
|
|
/**
|
* Verify OK button disabled and optionally providing tooltip to verify.
|
*
|
* @param tooltip Disabled tooltip to verify.
|
* @param fullMatch By default false = partial comparison when verifying tooltip.
|
*/
|
public async verifyOKDisabled(tooltip?: string, fullMatch: boolean = false): Promise<void> {
|
return this.verifyButtonDisabled(this._btnOK, tooltip, fullMatch);
|
}
|
|
/**
|
* Verify OK button enabled. If disabled, fail by mentioning the disabled tooltip if any.
|
*/
|
public async verifyOKEnabled(): Promise<void> {
|
return this.verifyButtonEnabled(this._btnOK);
|
}
|
|
/**
|
* Returns the name of fields defined in each subclass DialogType. Usually used when passing UI name into step description method for formatting.
|
* Prevent hardcoding field name in scripts as this method ensures compile time error if any field names changes.
|
*
|
* @param name Field name to reference.
|
* @returns Convert to string and return the field name.
|
*/
|
public fieldName(name: keyof DialogFieldsType): string {
|
return name.toString();
|
}
|
}
|
|
// Step description to re-use in spec file to prevent scriptor re-write each time
|
const stepDialog = {
|
clickActionLinkText: (fields: any): string => `Click action link for fields ${UtilSOP.getInterfaceObjectAsKeyValueArray(fields)}.`,
|
clickCancel: (): string => 'Click Cancel to dismiss dialog.',
|
clickButton: (buttonName: string): string => `Click button ${buttonName}.`,
|
clickOK: (buttonName: string = 'OK'): string => stepDialog.clickButton(buttonName),
|
clickTab: (tabTitle: string): string => `Click tab "${tabTitle}".`,
|
|
updateDialogValues: (dialogName: string, fields: any): string => {
|
const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields);
|
return `In dialog ${dialogName}, set ${arr.join(', ')}.`;
|
},
|
updateDialogValuesBatchEdit: (dialogName: string, actionLinkText: DialogBatchEditActionLink, fields: any): string => {
|
const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields);
|
return `In dialog ${dialogName}, click each field's action link ${actionLinkText} and set value as such: ${arr.join(', ')}.`;
|
},
|
verifyAvailableValues: (dialogName: string, uiName: string, values: string[]): string => `In dialog ${dialogName}, verify values '${values.join(', ')}' exist in field ${uiName}.`,
|
verifyBatchEditEnabled: (dialogName: string, fields: any): string => {
|
const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields);
|
return `In dialog ${dialogName}, verify fields are on batch edit mode and enabled state as such: ${arr.join(', ')}.`;
|
},
|
verifyDialogEnabled: (dialogName: string, fields: any): string => {
|
const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields);
|
return `In dialog ${dialogName}, verify fields enabled ${arr.join(', ')}.`;
|
},
|
verifyDialogUIVisible: (dialogName: string, fields: any): string => {
|
const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields);
|
return `In dialog ${dialogName}, verify fields visibility ${arr.join(', ')}.`;
|
},
|
verifyDialogValues: (dialogName: string, fields: any): string => {
|
const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields);
|
return `In dialog ${dialogName}, verify ${arr.join(', ')}.`;
|
},
|
verifyHasMaskError: (dialogName: string, fields: any): string => {
|
const arr: string[] = UtilSOP.getInterfaceObjectAsKeyValueArray(fields);
|
return `In dialog ${dialogName}, verify has mask error for ${arr.join(', ')}.`;
|
},
|
verifyValueExists: (dialogName: string, uiName: string, value: string): string => `In dialog ${dialogName}, verify value '${value}' exist in field ${uiName}.`,
|
verifyValueNotExist: (dialogName: string, uiName: string, value: string): string => `In dialog ${dialogName}, verify value '${value}' not exist in field ${uiName}.`,
|
|
verifyButtonDisabled: (buttonName: string, preconditionText?: string): string => {
|
const displayPreconditionText = preconditionText ? ` with precondition = ${preconditionText}` : '';
|
return `Verify ${buttonName} disabled${displayPreconditionText}.`;
|
},
|
verifyButtonEnabled: (buttonName: string): string => `Verify ${buttonName} enabled.`,
|
|
verifyOKDisabled: (buttonName: string = 'OK', preconditionText?: string): string => stepDialog.verifyButtonDisabled(buttonName, preconditionText),
|
verifyOKEnabled: (buttonName: string = 'OK'): string => stepDialog.verifyButtonEnabled(buttonName),
|
};
|
|
export { stepDialog as StepDialog };
|