yanweiyuan3
2023-08-09 588bc7829387dfc761cc25f06f77d4c81818bd10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/**
 * @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 };