d5-testing-library 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,23 @@
1
1
  // src/config.ts
2
+ import * as process from "node:process";
3
+ var driversMap = /* @__PURE__ */ new Map();
4
+ var createConfigProxy = (config2) => {
5
+ return new Proxy(config2, {
6
+ get(_target, prop) {
7
+ if (prop === "driver") {
8
+ return driversMap.get(process.pid);
9
+ }
10
+ return Reflect.get(...arguments);
11
+ },
12
+ set(_target, prop, value) {
13
+ if (prop === "driver") {
14
+ driversMap.set(process.pid, value);
15
+ return true;
16
+ }
17
+ return Reflect.set(...arguments);
18
+ }
19
+ });
20
+ };
2
21
  var Config = class _Config {
3
22
  static instance;
4
23
  appConfig;
@@ -10,7 +29,7 @@ var Config = class _Config {
10
29
  }
11
30
  config = (cfg) => {
12
31
  if (!cfg)
13
- return this.appConfig;
32
+ return createConfigProxy(this.appConfig);
14
33
  this.appConfig = cfg;
15
34
  };
16
35
  };
@@ -59,7 +78,7 @@ var AbstractPage = class extends AbstractElement {
59
78
  // src/utils/locators.ts
60
79
  import { By } from "selenium-webdriver";
61
80
  var byTestId = (testId, element = "div") => {
62
- return By.css(`${element}[data-testid=${testId}]`);
81
+ return By.css(`${element}[data-testid="${testId}"]`);
63
82
  };
64
83
 
65
84
  // src/utils/createDriver.ts
@@ -81,6 +100,14 @@ var elementIsNotPresent = function elementIsNotPresent2(locator) {
81
100
  });
82
101
  };
83
102
 
103
+ // src/utils/waitElementIsVisible.ts
104
+ import { until } from "selenium-webdriver";
105
+ async function waitElementIsVisible({ element, timeout, errorMessage, driver }) {
106
+ driver = driver ?? config().driver;
107
+ const condition = until.elementIsVisible(element);
108
+ await driver.wait(condition.fn, timeout ?? 1e4, errorMessage ?? condition.description());
109
+ }
110
+
84
111
  // src/pageObjects/AbstractElementWithParent.ts
85
112
  var AbstractElementWithParent = class extends AbstractElement {
86
113
  getParentElement;
@@ -91,7 +118,23 @@ var AbstractElementWithParent = class extends AbstractElement {
91
118
  }
92
119
  async getElement() {
93
120
  const parentEl = await this.getParentElement();
94
- return parentEl.findElement(this.elementBy);
121
+ await waitElementIsVisible({
122
+ element: parentEl,
123
+ driver: this.driver
124
+ });
125
+ const el = await parentEl.findElement(this.elementBy);
126
+ await waitElementIsVisible({
127
+ element: el,
128
+ driver: this.driver
129
+ });
130
+ return el;
131
+ }
132
+ async getAttribute(attr) {
133
+ const el = await this.getElement();
134
+ return await el.getAttribute(attr);
135
+ }
136
+ async getClasses() {
137
+ return await this.getAttribute("class");
95
138
  }
96
139
  };
97
140
 
@@ -177,7 +220,7 @@ var AbstractForm = class extends AbstractPage {
177
220
  };
178
221
 
179
222
  // src/pageObjects/elements/Forms/FormEdit.ts
180
- import { By as By7 } from "selenium-webdriver";
223
+ import { By as By11 } from "selenium-webdriver";
181
224
 
182
225
  // src/pageObjects/elements/Editors/AbstractEditor.ts
183
226
  import { By as By5, Key } from "selenium-webdriver";
@@ -190,8 +233,20 @@ var AbstractFormElement = class extends AbstractElementWithParent {
190
233
  }
191
234
  };
192
235
 
236
+ // src/pageObjects/elements/Editors/ClearStrategy/InputClearStrategy.ts
237
+ var InputClearStrategy = class {
238
+ async clear(editor) {
239
+ const inputEl = await editor.getInputElement();
240
+ await inputEl.clear();
241
+ }
242
+ };
243
+
193
244
  // src/pageObjects/elements/Editors/AbstractEditor.ts
194
245
  var AbstractDXEditorWithInput = class extends AbstractFormElement {
246
+ clearStrategy = new InputClearStrategy();
247
+ setClearStrategy(strategy) {
248
+ this.clearStrategy = strategy;
249
+ }
195
250
  async getInputElement() {
196
251
  const controlEl = await this.getElement();
197
252
  return controlEl.findElement(By5.className("dx-texteditor-input"));
@@ -202,13 +257,20 @@ var AbstractDXEditorWithInput = class extends AbstractFormElement {
202
257
  }
203
258
  async setInputValue(value) {
204
259
  const inputEl = await this.getInputElement();
205
- await inputEl.click();
260
+ await click(inputEl, this.driver);
206
261
  await inputEl.sendKeys(value, Key.ENTER);
207
262
  }
263
+ async clear() {
264
+ await this.clearStrategy.clear(this);
265
+ }
208
266
  };
209
267
 
210
268
  // src/pageObjects/elements/Editors/TextEditor.ts
211
269
  var TextEditor = class extends AbstractDXEditorWithInput {
270
+ constructor(getParentElement, elementBy, driver) {
271
+ super(getParentElement, elementBy, driver);
272
+ this.setClearStrategy(new InputClearStrategy());
273
+ }
212
274
  getValue() {
213
275
  return this.getInputValue();
214
276
  }
@@ -219,6 +281,10 @@ var TextEditor = class extends AbstractDXEditorWithInput {
219
281
 
220
282
  // src/pageObjects/elements/Editors/NumberEditor.ts
221
283
  var NumberEditor = class extends AbstractDXEditorWithInput {
284
+ constructor(getParentElement, elementBy, driver) {
285
+ super(getParentElement, elementBy, driver);
286
+ this.setClearStrategy(new InputClearStrategy());
287
+ }
222
288
  async getValue() {
223
289
  const value = await super.getInputValue();
224
290
  return value == "" ? null : Number.parseFloat(value);
@@ -234,22 +300,22 @@ var CheckBox = class extends AbstractFormElement {
234
300
  const inputEl = await this.getElement();
235
301
  const result = await inputEl.getAttribute("aria-checked");
236
302
  if (result === "true")
237
- return true;
303
+ return 1;
238
304
  if (result === "false")
239
- return false;
240
- return void 0;
305
+ return 0;
306
+ return null;
241
307
  }
242
308
  async setValue(value) {
243
309
  const inputEl = await this.getElement();
244
310
  const currentValue = await this.getValue();
245
311
  if (currentValue !== value) {
246
- await inputEl.click();
312
+ await click(inputEl, this.driver);
247
313
  }
248
314
  }
249
315
  };
250
316
 
251
317
  // src/pageObjects/elements/Editors/SelectBoxEditor.ts
252
- import { By as By6, Key as Key2 } from "selenium-webdriver";
318
+ import { By as By7, Key as Key2 } from "selenium-webdriver";
253
319
 
254
320
  // src/utils/xpathHelper.ts
255
321
  var XPathHelper = class {
@@ -261,19 +327,53 @@ var XPathHelper = class {
261
327
  }
262
328
  };
263
329
 
330
+ // src/pageObjects/elements/Editors/ClearStrategy/ClearButtonStrategy.ts
331
+ import { By as By6 } from "selenium-webdriver";
332
+ var DX_TEXTEDITOR_EMPTY = "dx-texteditor-empty";
333
+ var DX_CLEAR_BUTTON_AREA = "dx-clear-button-area";
334
+ var ClearButtonStrategy = class {
335
+ editorContainerClass;
336
+ constructor(editorContainerClass) {
337
+ this.editorContainerClass = editorContainerClass;
338
+ }
339
+ async isEmptyEditorField(editor) {
340
+ const parentEl = await editor.getParentElement();
341
+ const editorContainer = await parentEl.findElement(By6.className(this.editorContainerClass));
342
+ const classAttribute = await editorContainer.getAttribute("class");
343
+ return classAttribute.includes(DX_TEXTEDITOR_EMPTY);
344
+ }
345
+ async clear(editor) {
346
+ if (await this.isEmptyEditorField(editor)) {
347
+ throw new Error("This field is already empty");
348
+ }
349
+ const clearBtnSelector = `.${this.editorContainerClass} .${DX_CLEAR_BUTTON_AREA}`;
350
+ const parentEl = await editor.getParentElement();
351
+ const clearBtn = await parentEl.findElement(By6.css(clearBtnSelector));
352
+ await click(clearBtn, editor.driver);
353
+ }
354
+ };
355
+
264
356
  // src/pageObjects/elements/Editors/SelectBoxEditor.ts
265
357
  var SELECT_BOX_OPTION_TEXT_CONTAINER_CLASS = "d5-multi-select-item";
266
358
  var SELECT_BOX_ELEMENT_ROLE = "option";
359
+ var combinedValueXpath = (value) => `${XPathHelper.getRoleXpath(SELECT_BOX_ELEMENT_ROLE)}${XPathHelper.getClassAndValueXpath(
360
+ SELECT_BOX_OPTION_TEXT_CONTAINER_CLASS,
361
+ value
362
+ )}`;
363
+ var DX_SELECTBOX = "dx-selectbox";
267
364
  var SelectBoxEditor = class extends AbstractDXEditorWithInput {
365
+ constructor(getParentElement, elementBy, driver) {
366
+ super(getParentElement, elementBy, driver);
367
+ this.setClearStrategy(new ClearButtonStrategy(DX_SELECTBOX));
368
+ }
268
369
  async setSelectInputValue(value) {
269
370
  const inputEl = await this.getInputElement();
270
- await inputEl.click();
371
+ await click(inputEl, this.driver);
271
372
  await inputEl.sendKeys(value, Key2.ENTER);
272
- const combinedValueXpath = XPathHelper.getRoleXpath(SELECT_BOX_ELEMENT_ROLE) + XPathHelper.getClassAndValueXpath(SELECT_BOX_OPTION_TEXT_CONTAINER_CLASS, value);
273
373
  await this.driver.wait(async () => {
274
374
  try {
275
- const optElement = await inputEl.findElement(By6.xpath(combinedValueXpath));
276
- await optElement.click();
375
+ const optElement = await inputEl.findElement(By7.xpath(combinedValueXpath(value)));
376
+ await click(optElement, this.driver);
277
377
  return true;
278
378
  } catch (e) {
279
379
  return false;
@@ -298,7 +398,7 @@ var SwitchEditor = class extends AbstractFormElement {
298
398
  return 1;
299
399
  if (result === "OFF")
300
400
  return 0;
301
- return void 0;
401
+ return null;
302
402
  }
303
403
  async setValue(value) {
304
404
  if (value !== 0 && value !== 1) {
@@ -307,7 +407,7 @@ var SwitchEditor = class extends AbstractFormElement {
307
407
  const inputEl = await this.getElement();
308
408
  const currentValue = await this.getValue();
309
409
  if (currentValue !== value) {
310
- await inputEl.click();
410
+ await click(inputEl, this.driver);
311
411
  await this.driver.wait(async () => {
312
412
  const updatedValue = await this.getValue();
313
413
  return updatedValue !== currentValue;
@@ -316,21 +416,205 @@ var SwitchEditor = class extends AbstractFormElement {
316
416
  }
317
417
  };
318
418
 
419
+ // src/pageObjects/elements/Editors/TagBox.ts
420
+ import { By as By8 } from "selenium-webdriver";
421
+ var TAGS_SELECTOR = ".dx-tag .tag-text";
422
+ var DX_TAG_CONTAINER = "dx-tag-container";
423
+ var DX_TAGBOX = "dx-tagbox";
424
+ var DX_TEXTEDITOR_EMPTY2 = "dx-texteditor-empty";
425
+ var BTN_OK_SELECTOR = ".dx-tagbox-popup-wrapper .dx-popup-bottom .dx-button.dx-popup-done";
426
+ var TagBox = class extends AbstractDXEditorWithInput {
427
+ constructor(getParentElement, elementBy, driver) {
428
+ super(getParentElement, elementBy, driver);
429
+ this.setClearStrategy(new ClearButtonStrategy(DX_TAGBOX));
430
+ }
431
+ async setTagBoxInputValue(values) {
432
+ const lastElement = values[values.length - 1];
433
+ const inputEl = await this.getInputElement();
434
+ await inputEl.click();
435
+ for (const value of values) {
436
+ await this.selectOption(inputEl, value, lastElement);
437
+ }
438
+ await this.confirmSelection();
439
+ }
440
+ async selectOption(inputEl, value, lastElement) {
441
+ await inputEl.sendKeys(value);
442
+ await this.driver.wait(async () => {
443
+ try {
444
+ const optElement = await inputEl.findElement(By8.xpath(combinedValueXpath(value)));
445
+ await click(optElement, this.driver);
446
+ return true;
447
+ } catch (e) {
448
+ return false;
449
+ }
450
+ });
451
+ if (value !== lastElement) {
452
+ await inputEl.clear();
453
+ }
454
+ }
455
+ async confirmSelection() {
456
+ const inputEl = await this.getInputElement();
457
+ const option = await inputEl.findElement(By8.xpath(XPathHelper.getRoleXpath(SELECT_BOX_ELEMENT_ROLE)));
458
+ const btnOk = await this.driver.findElement(By8.css(BTN_OK_SELECTOR));
459
+ await this.driver.wait(async () => {
460
+ return await option.getAttribute("aria-selected") === "true";
461
+ });
462
+ await click(btnOk, this.driver);
463
+ }
464
+ async getTagBoxInputValue() {
465
+ if (await this.isTagBoxEmpty()) {
466
+ return [];
467
+ }
468
+ const parentEl = await this.getParentElement();
469
+ const inputEl = await parentEl.findElement(By8.className(DX_TAG_CONTAINER));
470
+ const tags = await inputEl.findElements(By8.css(TAGS_SELECTOR));
471
+ const values = [];
472
+ for (const tag of tags) {
473
+ const text = await tag.getText();
474
+ values.push(text);
475
+ }
476
+ return values;
477
+ }
478
+ async isTagBoxEmpty() {
479
+ const parentEl = await this.getParentElement();
480
+ const tagboxContainer = await parentEl.findElement(By8.className(DX_TAGBOX));
481
+ const classAttribute = await tagboxContainer.getAttribute("class");
482
+ return classAttribute.includes(DX_TEXTEDITOR_EMPTY2);
483
+ }
484
+ async getValue() {
485
+ const value = await this.getTagBoxInputValue();
486
+ return value.length ? value : null;
487
+ }
488
+ async setValue(values) {
489
+ if (!Array.isArray(values)) {
490
+ throw new Error("The provided value is not an array");
491
+ }
492
+ await this.setTagBoxInputValue(values);
493
+ }
494
+ async remove(itemText) {
495
+ const tagValues = await this.getValue();
496
+ const tagExists = tagValues.includes(itemText);
497
+ if (!tagExists) {
498
+ throw new Error(`The tag with the text: ${itemText} was not found`);
499
+ }
500
+ const parentEl = await this.getParentElement();
501
+ const inputEl = await parentEl.findElement(By8.className(DX_TAG_CONTAINER));
502
+ const clearBtnTestId = byTestId(`clear-button-${itemText}`);
503
+ const clearBtn = await inputEl.findElement(clearBtnTestId);
504
+ await click(clearBtn, this.driver);
505
+ }
506
+ };
507
+
508
+ // src/pageObjects/elements/Editors/ButtonGroup.ts
509
+ import { By as By9 } from "selenium-webdriver";
510
+ var ITEM_CLASS = "dx-buttongroup-item";
511
+ var SELECTED_CLASS = "dx-state-selected";
512
+ var ButtonGroup = class extends AbstractFormElement {
513
+ async getItems() {
514
+ return (await this.getElement()).findElements(By9.className(ITEM_CLASS));
515
+ }
516
+ getDataValue(el) {
517
+ return el.getAttribute("data-value");
518
+ }
519
+ async getValue() {
520
+ const inputEl = await this.getElement();
521
+ const selectedEl = await inputEl.findElement(By9.className(`${ITEM_CLASS} ${SELECTED_CLASS}`));
522
+ return this.getDataValue(selectedEl);
523
+ }
524
+ async setValue(value) {
525
+ const items = await this.getItems();
526
+ let buttonEl = void 0;
527
+ for (const item of items) {
528
+ const text = await this.getDataValue(item);
529
+ if (text == value) {
530
+ buttonEl = item;
531
+ break;
532
+ }
533
+ }
534
+ if (!buttonEl)
535
+ throw new Error(`Cannot find item in button group with text: ${value}`);
536
+ await click(buttonEl, this.driver);
537
+ }
538
+ };
539
+
540
+ // src/pageObjects/elements/Editors/BooleanSelector.ts
541
+ var BooleanSelector = class extends ButtonGroup {
542
+ // @ts-ignore
543
+ async getValue() {
544
+ const value = await super.getValue();
545
+ return value === "null" ? null : value === "1" ? 1 : 0;
546
+ }
547
+ // @ts-ignore
548
+ async setValue(value) {
549
+ const val = value == null ? "null" : value ? "1" : "0";
550
+ await super.setValue(val);
551
+ }
552
+ };
553
+
554
+ // src/pageObjects/elements/Editors/DateEditor.ts
555
+ import { By as By10, Key as Key3 } from "selenium-webdriver";
556
+ var DateEditor = class extends AbstractDXEditorWithInput {
557
+ constructor(getParentElement, elementBy, driver) {
558
+ super(getParentElement, elementBy, driver);
559
+ this.setClearStrategy(new InputClearStrategy());
560
+ }
561
+ async getControlElement() {
562
+ const controlEl = await this.getElement();
563
+ return controlEl.findElement(By10.className("dx-dropdowneditor-input-wrapper"));
564
+ }
565
+ async getDateElement() {
566
+ const controlEl = await this.getControlElement();
567
+ return controlEl.findElement(By10.css("input[type=hidden]"));
568
+ }
569
+ async setDateValue(value) {
570
+ const dateEl = await this.getInputElement();
571
+ const stringDate = value.toJSON().split("T")[0];
572
+ const dateArr = stringDate.split("-");
573
+ const [year, month, day] = dateArr;
574
+ const keys = day + month + year;
575
+ await click(dateEl, this.driver);
576
+ const actions = this.driver.actions({ async: true });
577
+ await actions.move({ origin: dateEl }).sendKeys(Key3.HOME).perform();
578
+ await actions.move({ origin: dateEl }).sendKeys(keys, Key3.ENTER).perform();
579
+ }
580
+ async getDateValue() {
581
+ const inputEl = await this.getDateElement();
582
+ return inputEl.getAttribute("value");
583
+ }
584
+ async getValue() {
585
+ const value = await this.getDateValue();
586
+ return value == "" ? null : new Date(value);
587
+ }
588
+ setValue(value) {
589
+ if (typeof value === "object") {
590
+ return this.setDateValue(value);
591
+ }
592
+ throw new Error(`Value is incorrect. It should be Object`);
593
+ }
594
+ };
595
+
319
596
  // src/pageObjects/elements/Editors/editorFactory.ts
320
597
  var editorFactory = async (getParentElement, elementBy, driver) => {
321
598
  const elementInstance = new AbstractFormElement(getParentElement, elementBy, driver);
322
- const element = await elementInstance.getElement();
323
- const classSting = await element.getAttribute("class");
599
+ const classSting = await elementInstance.getClasses();
324
600
  if (classSting.includes("text-control"))
325
601
  return new TextEditor(getParentElement, elementBy, driver);
326
602
  if (classSting.includes("number-control"))
327
603
  return new NumberEditor(getParentElement, elementBy, driver);
328
604
  if (classSting.includes("check-box"))
329
605
  return new CheckBox(getParentElement, elementBy, driver);
606
+ if (classSting.includes("multi-select-control"))
607
+ return new TagBox(getParentElement, elementBy, driver);
330
608
  if (classSting.includes("select-control"))
331
609
  return new SelectBoxEditor(getParentElement, elementBy, driver);
332
610
  if (classSting.includes("switch-control"))
333
611
  return new SwitchEditor(getParentElement, elementBy, driver);
612
+ if (classSting.includes("date-control"))
613
+ return new DateEditor(getParentElement, elementBy, driver);
614
+ if (classSting.includes("buttons-group"))
615
+ return new BooleanSelector(getParentElement, elementBy, driver);
616
+ if (classSting.includes("button-group-field"))
617
+ return new ButtonGroup(getParentElement, elementBy, driver);
334
618
  throw new Error("Unknown type of editor. Please write ticket to the support");
335
619
  };
336
620
 
@@ -341,6 +625,9 @@ function createDataTestId(formName, itemType, itemName) {
341
625
  function createFieldTestId(formName, itemName) {
342
626
  return createDataTestId(formName, "form_field" /* FORM_FIELD */, itemName);
343
627
  }
628
+ function createGroupTestId(formName, itemName) {
629
+ return createDataTestId(formName, "group" /* GROUP */, itemName);
630
+ }
344
631
  function createTableId(formName) {
345
632
  return `${"table" /* TABLE */}-${formName}`;
346
633
  }
@@ -377,14 +664,82 @@ var FormField = class extends AbstractElementWithParent {
377
664
  await this.initEditor();
378
665
  return this.editor.setValue(value);
379
666
  }
667
+ /**
668
+ * Очищает значение в поле
669
+ * @example
670
+ * await formEdit.field('NumberFld').clear();
671
+ */
672
+ async clear() {
673
+ await this.initEditor();
674
+ if (!this.editor.clear) {
675
+ throw new Error("The clear method is not available for this field type.");
676
+ }
677
+ return this.editor.clear();
678
+ }
679
+ /**
680
+ * Удаляет тег по переданному тексту
681
+ * @example
682
+ * await formEdit.field('TagBox').remove('test');
683
+ */
684
+ async remove(itemText) {
685
+ await this.initEditor();
686
+ if (!this.editor.remove) {
687
+ throw new Error("This method is only available for the TagBox field.");
688
+ }
689
+ return this.editor.remove(itemText);
690
+ }
691
+ };
692
+
693
+ // src/pageObjects/elements/Groups/AbstractGroup.ts
694
+ var AbstractDXGroup = class extends AbstractFormElement {
695
+ };
696
+
697
+ // src/pageObjects/elements/Groups/Panel.ts
698
+ var Panel = class extends AbstractDXGroup {
699
+ async isDisplayed() {
700
+ const groupElement = await this.getElement();
701
+ return await groupElement.isDisplayed();
702
+ }
703
+ };
704
+
705
+ // src/pageObjects/elements/Groups/groupFactory.ts
706
+ var groupFactory = async (getParentElement, elementBy, driver) => {
707
+ const elementInstance = new AbstractFormElement(getParentElement, elementBy, driver);
708
+ const classSting = await elementInstance.getClasses();
709
+ if (classSting.includes("panel-group"))
710
+ return new Panel(getParentElement, elementBy, driver);
711
+ throw new Error("Unknown type of group. Please write ticket to the support");
712
+ };
713
+
714
+ // src/pageObjects/elements/FormGroup.ts
715
+ var FormGroup = class extends AbstractElementWithParent {
716
+ editor;
717
+ constructor(getParentElement, formName, name, driver) {
718
+ super(getParentElement, driver);
719
+ this.elementBy = byTestId(createGroupTestId(formName, name));
720
+ }
721
+ async initEditor() {
722
+ if (!this.editor) {
723
+ this.editor = await groupFactory(this.getParentElement, this.elementBy, this.driver);
724
+ }
725
+ }
726
+ /**
727
+ * Возвращает булевое значение доступна ли группа
728
+ * @example
729
+ * expect(await tableList.group('MainSuperPanel').isDisplayed();
730
+ */
731
+ async isDisplayed() {
732
+ await this.initEditor();
733
+ return this.editor.isDisplayed();
734
+ }
380
735
  };
381
736
 
382
737
  // src/pageObjects/elements/Forms/FormEdit.ts
383
738
  var FormEdit = class extends AbstractForm {
384
- saveButtonBy = By7.id("button-save");
739
+ saveButtonBy = By11.id("button-save");
385
740
  constructor(driver) {
386
741
  super(driver);
387
- this.popupContainerBy = By7.className(`popup form-edit-${this.formName}`);
742
+ this.popupContainerBy = By11.className(`popup form-edit-${this.formName}`);
388
743
  }
389
744
  /**
390
745
  * Находит поле формы редактирования по имени
@@ -396,6 +751,28 @@ var FormEdit = class extends AbstractForm {
396
751
  field(name) {
397
752
  return new FormField(this.getContainerElement, this.formName, name, this.driver);
398
753
  }
754
+ /**
755
+ * Проверяет не отключена ли кнопка Сохранить
756
+ * @example
757
+ * const formEdit = new MyForm();
758
+ * //...
759
+ * await formEdit.isSaveButtonDisabled();
760
+ */
761
+ async isSaveButtonDisabled() {
762
+ const saveButton = await this.driver.findElement(this.saveButtonBy);
763
+ const result = await saveButton.getAttribute("aria-disabled");
764
+ return result === "true";
765
+ }
766
+ /**
767
+ * Находит группу формы редактирования по имени
768
+ * @example
769
+ * const formEdit = new MyForm();
770
+ * //...
771
+ * expect(await formEdit.group('Name');
772
+ */
773
+ group(name) {
774
+ return new FormGroup(this.getContainerElement, this.formName, name, this.driver);
775
+ }
399
776
  /**
400
777
  * Нажатие на кнопку Сохранить
401
778
  * @example
@@ -404,41 +781,42 @@ var FormEdit = class extends AbstractForm {
404
781
  * await formEdit.saveButtonClick();
405
782
  */
406
783
  async saveButtonClick() {
407
- await this.driver.findElement(this.saveButtonBy).click();
784
+ const saveButton = await this.driver.findElement(this.saveButtonBy);
785
+ await this.driver.wait(async () => !await this.isSaveButtonDisabled(), void 0, "Button is disabled");
786
+ await click(saveButton, this.driver);
408
787
  }
409
788
  };
410
789
 
411
790
  // src/pageObjects/elements/Forms/ListForm.ts
412
- import { By as By13 } from "selenium-webdriver";
791
+ import { By as By17 } from "selenium-webdriver";
413
792
 
414
793
  // src/pageObjects/elements/Table/Table.ts
415
- import { By as By10 } from "selenium-webdriver";
794
+ import { By as By14 } from "selenium-webdriver";
416
795
 
417
796
  // src/pageObjects/elements/Table/utils.ts
418
- import { By as By8 } from "selenium-webdriver";
797
+ import { By as By12 } from "selenium-webdriver";
419
798
  var getRowPathByIndex = (index) => {
420
- return By8.xpath(`//tr[@aria-rowindex="${transformInputIndex(index)}"]`);
799
+ return By12.css(`tr[aria-rowindex="${transformInputIndex(index)}"]`);
421
800
  };
801
+ var getColumnByIndexCssSelector = (columnIndex) => `td[aria-colindex="${transformInputIndex(columnIndex)}"]`;
422
802
  var getColumnPathByIndex = (index) => {
423
- return By8.xpath(`//td[@aria-colindex="${transformInputIndex(index)}"]`);
424
- };
425
- var getColumnPathByCaption = (caption) => {
426
- return By8.xpath(`//td[@role="columnheader"][@aria-label="Column ${caption}"]`);
803
+ return By12.css(getColumnByIndexCssSelector(index));
427
804
  };
805
+ var getColumnByCaptionCssSelector = (caption) => `td[role="columnheader"][aria-label="Column ${caption}"]`;
428
806
  var getCellPathByIndex = (index) => {
429
- return By8.xpath(`//td[@role="gridcell"][@aria-colindex="${transformInputIndex(index)}"]`);
807
+ return By12.css(`td[role="gridcell"][aria-colindex="${transformInputIndex(index)}"]`);
430
808
  };
431
809
  var transformInputIndex = (index) => index + 1;
432
810
  var transformOutputIndex = (index) => +index - 1;
433
811
 
434
812
  // src/pageObjects/elements/Table/elements/Row.ts
435
- import { By as By9 } from "selenium-webdriver";
813
+ import { By as By13 } from "selenium-webdriver";
436
814
 
437
815
  // src/pageObjects/elements/Table/elements/Cell.ts
438
816
  var Cell = class extends AbstractElementWithParent {
439
817
  index;
440
- constructor(getParentElement, index, driver) {
441
- super(getParentElement, driver);
818
+ constructor(rowEl, index, driver) {
819
+ super(() => Promise.resolve(rowEl), driver);
442
820
  this.index = index;
443
821
  this.elementBy = getCellPathByIndex(index);
444
822
  }
@@ -452,6 +830,9 @@ var Cell = class extends AbstractElementWithParent {
452
830
  };
453
831
 
454
832
  // src/pageObjects/elements/Table/elements/Row.ts
833
+ var DX_SELECTION_CLASS = "dx-selection";
834
+ var DX_ROW_EDIT_CLASS = "dx-edit-row";
835
+ var DX_DATA_ROW_CLASS = "dx-data-row";
455
836
  var Row = class extends AbstractElementWithParent {
456
837
  index;
457
838
  getColumnByCaption;
@@ -461,14 +842,11 @@ var Row = class extends AbstractElementWithParent {
461
842
  this.elementBy = getRowPathByIndex(index);
462
843
  this.getColumnByCaption = getColumnByCaption;
463
844
  }
464
- getRowElement = () => {
465
- return this.driver.findElement(this.elementBy);
466
- };
467
845
  async getCellByIndex(index) {
468
846
  const rowEl = await this.getElement();
469
847
  const cell = rowEl.findElement(getCellPathByIndex(index));
470
848
  if (cell) {
471
- return new Cell(this.getRowElement, index, this.driver);
849
+ return new Cell(rowEl, index, this.driver);
472
850
  }
473
851
  throw new Error(`Cannot find cell by index: ${index}`);
474
852
  }
@@ -482,7 +860,11 @@ var Row = class extends AbstractElementWithParent {
482
860
  }
483
861
  async select() {
484
862
  const rowEl = await this.getElement();
485
- await rowEl.click();
863
+ await click(rowEl, this.driver);
864
+ await this.driver.wait(async () => {
865
+ const classes = await rowEl.getAttribute("class");
866
+ return classes.includes(DX_SELECTION_CLASS);
867
+ }, 500, "Cannot select row");
486
868
  }
487
869
  async isSelected() {
488
870
  const rowEl = await this.getElement();
@@ -494,34 +876,38 @@ var Row = class extends AbstractElementWithParent {
494
876
  }
495
877
  async getCells() {
496
878
  const rowEl = await this.getElement();
497
- const cells = await rowEl.findElements(By9.css(`td[role="gridcell"]`));
879
+ const cells = await rowEl.findElements(By13.css(`td[role="gridcell"]`));
498
880
  const result = [];
499
881
  cells.forEach((_, index) => {
500
- const newCell = new Cell(this.getRowElement, index, this.driver);
882
+ const newCell = new Cell(rowEl, index, this.driver);
501
883
  result.push(newCell);
502
884
  });
503
885
  return result;
504
886
  }
505
- async clickInlineButton(rowClass, buttonClass) {
506
- const fixedTable = await this.getParentElement().findElement(
507
- By9.css(".dx-fixed-columns .dx-datagrid-content.dx-datagrid-content-fixed")
887
+ async clickInlineButton(buttonClass) {
888
+ const FIXED_TABLE_CSS_SELECTOR = ".dx-fixed-columns .dx-datagrid-content.dx-datagrid-content-fixed";
889
+ const buttonCssSelector = `.${DX_DATA_ROW_CLASS}[aria-rowindex="${transformInputIndex(this.index)}"] .${buttonClass}`;
890
+ const button = (await this.getParentElement()).findElement(
891
+ By13.css(`${FIXED_TABLE_CSS_SELECTOR} ${buttonCssSelector}`)
508
892
  );
509
- const rowEl = await fixedTable.findElement(By9.className(rowClass));
510
- const button = await rowEl.findElement(By9.className(buttonClass));
511
- if (button) {
512
- return button.click();
513
- }
514
- throw new Error(`Cannot find ${buttonClass} button`);
893
+ return click(button, this.driver);
894
+ }
895
+ async checkEditingMode(isEditing, msg) {
896
+ await this.driver.wait(async () => {
897
+ return (await this.getClasses()).includes(DX_ROW_EDIT_CLASS) === isEditing;
898
+ }, 2e3, msg);
515
899
  }
516
900
  async edit() {
517
- await this.select();
518
- return this.clickInlineButton("dx-selection", "edit");
901
+ await this.clickInlineButton("edit");
902
+ await this.checkEditingMode(true, "Row did not switch to edit mode");
519
903
  }
520
904
  async save() {
521
- return this.clickInlineButton("dx-edit-row", "save");
905
+ await this.clickInlineButton("save");
906
+ await this.checkEditingMode(false, "Row did not saved");
522
907
  }
523
908
  async cancel() {
524
- return this.clickInlineButton("dx-edit-row", "cancel");
909
+ await this.clickInlineButton("cancel");
910
+ await this.checkEditingMode(false, "Row did not switch to normal mode");
525
911
  }
526
912
  };
527
913
 
@@ -544,20 +930,21 @@ var Column = class extends AbstractElementWithParent {
544
930
 
545
931
  // src/pageObjects/elements/Table/Table.ts
546
932
  var Table = class extends AbstractElementWithParent {
933
+ formName;
547
934
  constructor(getParentElement, formName, driver) {
548
935
  super(getParentElement, driver);
549
- this.elementBy = By10.id(createTableId(formName));
936
+ this.elementBy = By14.id(createTableId(formName));
937
+ this.formName = formName;
550
938
  }
551
- async getHeaderElement() {
552
- const parentEl = await this.getElement();
553
- return parentEl.findElement(By10.className("dx-header-row"));
939
+ getHeaderCssSelector() {
940
+ return ".dx-datagrid-headers .dx-datagrid-content:not(.dx-datagrid-content-fixed)";
554
941
  }
555
942
  async getDataGrid() {
556
943
  const parentEl = await this.getElement();
557
- return parentEl.findElement(By10.css(`div[role="grid"]`));
944
+ return parentEl.findElement(By14.css(`div[role="grid"]`));
558
945
  }
559
946
  getTableElement = () => {
560
- return this.driver.findElement(this.elementBy);
947
+ return this.getElement();
561
948
  };
562
949
  async getRowByIndex(index) {
563
950
  const gridContainer = await this.getDataGrid();
@@ -568,16 +955,14 @@ var Table = class extends AbstractElementWithParent {
568
955
  throw new Error(`Cannot find row by index: ${index}`);
569
956
  }
570
957
  async getColumnByIndex(index) {
571
- const headerContainer = await this.getHeaderElement();
572
- const column = await headerContainer.findElement(getColumnPathByIndex(index));
958
+ const column = await (await this.getElement()).findElement(By14.css(`${this.getHeaderCssSelector()} ${getColumnByIndexCssSelector(index)}`));
573
959
  if (column) {
574
960
  return new Column(this.getTableElement, index, this.driver);
575
961
  }
576
962
  throw new Error(`Cannot find column by index: ${index}`);
577
963
  }
578
964
  async getColumnByCaption(caption) {
579
- const headerContainer = await this.getHeaderElement();
580
- const column = await headerContainer.findElement(getColumnPathByCaption(caption));
965
+ const column = await (await this.getElement()).findElement(By14.css(`${this.getHeaderCssSelector()} ${getColumnByCaptionCssSelector(caption)}`));
581
966
  if (column) {
582
967
  const index = await column.getAttribute("aria-colindex");
583
968
  return new Column(this.getTableElement, transformOutputIndex(index), this.driver);
@@ -594,7 +979,7 @@ var Table = class extends AbstractElementWithParent {
594
979
  }
595
980
  async getRows() {
596
981
  const gridContainer = await this.getDataGrid();
597
- const rows = await gridContainer.findElements(By10.className("dx-row dx-data-row"));
982
+ const rows = await gridContainer.findElements(By14.className("dx-row dx-data-row"));
598
983
  const result = [];
599
984
  rows.forEach((_, index) => {
600
985
  const newRow = new Row(this.getTableElement, index, this.driver, this.getColumnByCaption.bind(this));
@@ -603,8 +988,7 @@ var Table = class extends AbstractElementWithParent {
603
988
  return result;
604
989
  }
605
990
  async getColumns() {
606
- const headerContainer = await this.getHeaderElement();
607
- const columns = await headerContainer.findElements(By10.css(`td[role="columnheader"]`));
991
+ const columns = await (await this.getElement()).findElements(By14.css(`${this.getHeaderCssSelector()} td[role="columnheader"]`));
608
992
  const result = [];
609
993
  columns.forEach((_, index) => {
610
994
  const newCol = new Column(this.getTableElement, index, this.driver);
@@ -612,25 +996,40 @@ var Table = class extends AbstractElementWithParent {
612
996
  });
613
997
  return result;
614
998
  }
615
- async element() {
999
+ element() {
616
1000
  return this.getElement();
617
1001
  }
1002
+ /**
1003
+ * Находит поле когда форма находится в состоянии редактирования
1004
+ * @example
1005
+ * const form = new MyForm();
1006
+ * //...
1007
+ * expect(await form.field('Name').getValue()).toBe('test value');
1008
+ */
1009
+ field(name) {
1010
+ return new FormField(this.getTableElement, this.formName, name, this.driver);
1011
+ }
618
1012
  };
619
1013
 
620
1014
  // src/pageObjects/elements/TableToolbar/TableToolbar.ts
621
- import { By as By12 } from "selenium-webdriver";
1015
+ import { By as By16 } from "selenium-webdriver";
622
1016
 
623
1017
  // src/pageObjects/elements/TableToolbar/TableToolbarButton.ts
624
- import { By as By11 } from "selenium-webdriver";
1018
+ import { By as By15 } from "selenium-webdriver";
625
1019
  var TOOLBAR_BUTTON_CLASS = "toolbar-button";
626
1020
  var TableToolbarButton = class extends AbstractElementWithParent {
1021
+ name;
627
1022
  constructor(getParentElement, name, driver) {
628
1023
  super(getParentElement, driver);
629
- this.elementBy = By11.id(`${TOOLBAR_BUTTON_CLASS}-${name}`);
1024
+ this.name = name;
1025
+ this.elementBy = By15.id(`${TOOLBAR_BUTTON_CLASS}-${name}`);
630
1026
  }
631
1027
  async click() {
632
1028
  const button = await this.getElement();
633
- return button.click();
1029
+ await this.driver.wait(async () => {
1030
+ return await button.isDisplayed() && !await this.isDisabled();
1031
+ }, 1e3, `Button ${this.name} cannot be clicked. It is not displayed or it is disabled`);
1032
+ return click(button, this.driver);
634
1033
  }
635
1034
  async isDisabled() {
636
1035
  const button = await this.getElement();
@@ -644,14 +1043,14 @@ var TABLE_TOOLBAR_CLASS = "d5-table-toolbar";
644
1043
  var TableToolbar = class extends AbstractElementWithParent {
645
1044
  constructor(getParentElement, formName, driver) {
646
1045
  super(getParentElement, driver);
647
- this.elementBy = By12.id(`${TABLE_TOOLBAR_CLASS}-${formName}`);
1046
+ this.elementBy = By16.id(`${TABLE_TOOLBAR_CLASS}-${formName}`);
648
1047
  }
649
1048
  getTableToolbarElement = () => {
650
1049
  return this.driver.findElement(this.elementBy);
651
1050
  };
652
1051
  async button(name) {
653
1052
  const toolbar = await this.getTableToolbarElement();
654
- const button = await toolbar.findElement(By12.id(`${TOOLBAR_BUTTON_CLASS}-${name}`));
1053
+ const button = await toolbar.findElement(By16.id(`${TOOLBAR_BUTTON_CLASS}-${name}`));
655
1054
  if (button) {
656
1055
  return new TableToolbarButton(this.getTableToolbarElement, name, this.driver);
657
1056
  }
@@ -663,7 +1062,7 @@ var TableToolbar = class extends AbstractElementWithParent {
663
1062
  var ListForm = class extends AbstractForm {
664
1063
  constructor(driver) {
665
1064
  super(driver);
666
- this.popupContainerBy = By13.className(`popup form-inline-${this.formName}`);
1065
+ this.popupContainerBy = By17.className(`popup form-inline-${this.formName}`);
667
1066
  }
668
1067
  /**
669
1068
  * Находит таблицу
@@ -675,6 +1074,16 @@ var ListForm = class extends AbstractForm {
675
1074
  table() {
676
1075
  return new Table(this.getContainerElement, this.formName, this.driver);
677
1076
  }
1077
+ /**
1078
+ * Находит группу на лист форме по имени
1079
+ * @example
1080
+ * const formEdit = new MyForm();
1081
+ * //...
1082
+ * expect(await formEdit.group('Name');
1083
+ */
1084
+ group(name) {
1085
+ return new FormGroup(this.getContainerElement, this.formName, name, this.driver);
1086
+ }
678
1087
  /**
679
1088
  * Находит таблицу
680
1089
  * @example
@@ -698,12 +1107,27 @@ async function signIn(driver) {
698
1107
  return webDriver;
699
1108
  }
700
1109
 
1110
+ // src/utils/actions.ts
1111
+ var click = async (webElement, driver) => {
1112
+ driver = driver ?? config().driver;
1113
+ const actions = driver.actions({ async: true });
1114
+ await actions.move({ origin: webElement }).click().perform();
1115
+ };
1116
+
701
1117
  // src/pageObjects/pages/LoginPage.ts
702
1118
  var LOGIN_PATH = "/login";
703
1119
  var LoginPage = class extends AbstractPage {
704
1120
  usernameField = byTestId("loginField", "input");
705
1121
  passwordField = byTestId("passwordField", "input");
706
1122
  loginButton = byTestId("loginButton");
1123
+ async locate() {
1124
+ await super.locate();
1125
+ const login = await this.driver.findElement(this.usernameField);
1126
+ await waitElementIsVisible({
1127
+ element: login,
1128
+ driver: this.driver
1129
+ });
1130
+ }
707
1131
  get path() {
708
1132
  return LOGIN_PATH;
709
1133
  }
@@ -714,7 +1138,7 @@ var LoginPage = class extends AbstractPage {
714
1138
  await this.driver.findElement(this.passwordField).sendKeys(password);
715
1139
  }
716
1140
  async clickLoginButton() {
717
- await this.driver.findElement(this.loginButton).click();
1141
+ await click(await this.driver.findElement(this.loginButton), this.driver);
718
1142
  }
719
1143
  async loginWith(username, password) {
720
1144
  await this.enterUsername(username);
@@ -735,9 +1159,11 @@ export {
735
1159
  SubsystemsPanel,
736
1160
  TextEditor,
737
1161
  byTestId,
1162
+ click,
738
1163
  config,
739
1164
  createDriver,
740
1165
  editorFactory,
741
1166
  elementIsNotPresent,
742
- signIn
1167
+ signIn,
1168
+ waitElementIsVisible
743
1169
  };