Перейти к содержанию

Автоматизация Desktop клиента (TessaClient)

В этой статье собраны материалы касающиеся автоматизации Windows приложений и прежде всего TessaClient.

Note

Возможности автоматизации, описанные далее, доступны в TESSA начиная с версии 3.6.0.9.

Краткое введение в Автоматизацию приложений

Автоматизация Windows приложений предоставляет специалистам по тестированию инструмент для написания сценариев тестирования приложений, опирающиеся на ключевые элементы графического пользовательского интерфейса приложения и эмуляцию взаимодействия пользователя с ними.

Ключевыми понятиями автоматизации являются:

  • дерево автоматизации - представляет приложение в виде древовидной структуры схожей с DOM для XML или JSON.
    Корнем дерева автоматизации всегда является рабочий стол, узлами которого являются все запущенные приложения.
  • узел дерева автоматизации или элемент автоматизации - логический элемент, соответствующий элементу управления (окну, полю ввода, текстовой метке, списку, меню и т.п.), обладающий строго определённым набором свойств и поддерживающий строго определённый набор шаблонов (автоматизации).
  • свойство автоматизации - доступное только для чтения значение, характеризующее элемент автоматизации и отражающее состояние элемента управления (пользовательского интерфейса), к которому относится данный элемент автоматизации.
  • шаблон автоматизации - логический объект, объединяющий свойства автоматизации и/или методы по изменению свойств и/или состояния элемента управления, абстрагирующий предоставляемые элементом управления функциональные возможности от его конкретного типа.

Подробнее об автоматизации вы можете прочитать в MSDN по этой ссылке.

Некоторые инструменты просмотра информации об автоматизации

Существует довольно большой набор инструментов, позволяющих анализировать и просматривать дерево автоматизации приложения. Далее будут перечислены наиболее актуальные из них в порядке убывания рекомендаций по использованию (верхнее является наиболее предпочтительным для использования, однако, выбор остаётся за вами).

  • VisualUIAVerifyNative - инструмент анализа автоматизации приложений, входящий в состав Windows SDK. Он позволяет анализировать дерево автоматизации, в котором свойства элементов и поддерживаемые ими шаблоны представлены в удобном табличном виде. Позволяет вызывать методы шаблонов.
    Подробнее о том, как им пользоваться написано здесь, а инструкции, как его установить и запустить размещены на этой странице.
  • UISpy - предшественник VisualUIAVerifyNative, ранее также входил в состав Windows SDK (последняя известная сборка в которую он включен Windows SDK for Vista). Также специализирован для анализа дерева автоматизации, однако, сведения представляет в текстовом виде. Позволяет вызывать методы шаблонов из отдельного диалогового окна.
  • Snoop - многофункциональный инструмент анализа WPF приложений. В одном из режимов работы поддерживает отображение и работу с деревом автоматизации. К основным недостаткам относится возможность работы с элементами автоматизации только для окон верхнего уровня (прямых потомков рабочего стола), отобразить дерево автоматизации для дочернего окна (например, диалога подтверждения) данный инструмент уже не сможет.

Набор программ для написания и запуска сценариев автоматизации

Для написания сценариев автоматизации вам понадобятся:

  • Winium.Desktop.Driver - приложение, являющееся сервером для клиентских сценариев автоматизации. В то же время оно является клиентом для сервера автоматизации приложений Windows.
    Вы можете:
    • загрузить его по данной ссылке;
    • или самостоятельно собрать его из исходных текстов в следующих репозиториях:
      • Winium.Desktop - непосредственно репозиторий драйвера;
      • Winium.Cruciatus - библиотека поддержки работы с автоматизацией. Вы также можете загрузить собранный nuget-пакет;
  • Winium или Selenium - клиенты для подключения к Winium.Desktop.Driver. Рекомендуется использовать Winium, поскольку он специализирован для работы с драйвером. Однако, также можно использовать и Selenium версии 2.45.
    Внимание!
    Клиент Winium доступен только для языков C# и Java, для остальных языков используйте Selenium.
    Важно!
    Гарантированно поддерживаемой драйвером версией Selenium является 2.45. Пожалуйста, используйте её.
    Вы можете самостоятельно собрать клиент Winium из следующих репозиториев:
    • Winium - основной репозиторий, содержит классы наследники от Selenium, упрощающие создание клиента для Winium.Desktop.Driver, учитывающие его настройки, а также возможность запуска самого драйвера;
    • Winium.Elements - репозиторий классов - помощников для различных элементов управления. В некоторых случаях может быть полезно использовать его классы.

Для языка C# вы можете использовать собранные nuget-пакеты:

Далее представлена общая схема работы сценариев автоматизации приложений.

Вы можете писать сценарии автоматизации на любом языке программирования, поддерживающем условно старую версию протокола Selenium (версий 2 и 3), поскольку именно она поддерживается в настоящий момент в Winium.Desktop.Driver.

Как отмечалось ранее, расширенная поддержка клиентских сценариев предоставлена в специализированных клиентах Winium, поддерживающих только языки C# и Java.

Сводная таблица по именам и идентификаторам автоматизации различных элементов рабочих мест, представлений и карточек

Далее представлена сводная справочная таблица, содержащая правила формирования свойств AutomationId и Name для TessaClient на основе данных TessaAdmin.

Место AutomationId Name
Заголовки вкладок главного окна приложения
Рабочие места поле Id с префиксом Workplace: локализованное имя
Карточки поле Id с префиксом Card: локализованный номер
Дерево рабочего места
Статические узлы поле Id с префиксом TreeItem: локализованное имя
Подмножества идентификаторы из свойств представлений с префиксом TreeItem: имена из свойств представлений
Элементы подмножеств идентификаторы данных из представлений с префиксом TreeItem: данные из представлений
Представления
Строка идентификатор карточки с префиксом идентифицирующей колонки значение не идентифицирующей колонки
Ячейка идентификатор колонки значение колонки
Карточки
Вкладка поле Название поле Заголовок
Блок, элемент управления поле Алиас поле Заголовок

Для того, чтобы вам было проще находить элементы управления, всегда задавайте для них уникальные Алиасы.

Примеры сценариев автоматизации для различных языков программирования

Далее будут представлены примеры сценариев автоматизации TessaClient, написанные на различных языках программирования. Часть из них использует расширенные возможности клиента Winium, другим достаточно стандартных возможностей Selenium и расширений по запуску скриптов, которые поддерживает Winium.Desktop.Driver. Все поддерживаемые скрипты перечислены на этой странице.

Пример автоматизации на языке Java

В данном примере будут наиболее полно представлены возможности по автоматизации TessaClient:

  • автоматизация рабочих мест;
  • автоматизация представлений;
  • автоматизация карточек;
  • автоматизация боковых панелей с плитками.

Для работы сценария потребуются:

  • пакеты:
    • winium-webdriver версии 0.1.0-1;
    • winium-elements-desktop версии 0.2.0-1;
  • драйвер Winium.Desktop.Driver.

Каркас класса для тестирования

Рассмотрим класс для тестирования. В нём приведены все необходимые импорты, базовые свойства и конструктор. Все методы, которые мы будем рассматривать, принадлежат данному классу и вы можете помещать их в приведённый ниже каркас для получения полной версии класса.

package tessa.java.automation.sample;

import java.net.MalformedURLException; import java.net.URL; import java.util.List;

import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.winium.DesktopOptions; import org.openqa.selenium.winium.WiniumDriver; import winium.elements.desktop.ComboBox; import winium.elements.desktop.extensions.WebElementExtensions;

/** * Класс для тестирования TessaClient */ public class TessaClientTester { /** * Базовая задержка при выполнении операций. */ private int delayInterval;

/** * Путь до тестируемого приложения. */ private String applicationPath;

/** * Флаг, что нужно подключаться к запущенному приложению. */ private boolean attachToLaunched;

/** * Драйвер для тестирования. */ WiniumDriver driver;

/** * Главное окно приложения. */ WebElement mainWindow;

/** * Создаёт новый класс для тестирования TessaClient. * @param delayInterval интервал задержек. * @param applicationPath путь к тестируемому приложению. Если указана пустая строка, приложение не нужно запускать. */ public TessaClientTester(int delayInterval, String applicationPath) { this.delayInterval = delayInterval; this.applicationPath = applicationPath == null ? "" : applicationPath.trim(); this.attachToLaunched = applicationPath.isBlank(); } }

Методы инициализации и финализации

В методе инициализации выполняется:

  • настройка подключения к Winium.Desktop.Driver-у;
  • установка специфичных для Winium настроек, регламентирующих, необходимо ли подключаться к уже запущенному приложению, или же его требуется сначала запустить;
  • поиск главного окна приложения по части имени его заголовка.

Сам метод приведён ниже.

/** * Подготовка к тестированию. * Инициализация драйвера, поиск главного окна приложения. */ private void Initialize() throws MalformedURLException, InterruptedException { // Инициализируем настройки Winium по умолчанию. DesktopOptions options = new DesktopOptions(); // Если необходимо подключиться к уже запущенному TessaClient-у. if (this.attachToLaunched) { // Устанавливаем соответствующий флаг и пустой путь к приложению. options.setDebugConnectToRunningApp(true); options.setApplicationPath(""); } else { // Иначе, устанавливаем путь до него. options.setApplicationPath(this.applicationPath); } // Подключаемся к драйверу. this.driver = new WiniumDriver(new URL("http://localhost:9999"), options);

// Если необходимо запустить приложение, то необходимо подождать некоторое время до его открытия. if (!this.attachToLaunched) { Thread.sleep(3000); }

// Получаем окно TessaClient поиском по имени приложения. this.mainWindow = driver.findElement(By.xpath("/*[@ClassName='Window' and starts-with(@Name, 'TessaClient')]")); }

Метод финализации просто завершает работу с драйвером.

/** * Завершение тестирования. */ private void Finalize() { this.driver.quit(); }

Помощники по работе с элементами

Вначале рассмотрим помощник наведения курсора мыши на переданный элемент.

/** * Помощник наведения мыши на целевой элемент с задержкой. * @param element - целевой элемент. */ private void HoverMouse(WebElement element) throws InterruptedException { new Actions(this.driver) .moveToElement(element) .build() .perform(); Thread.sleep(this.delayInterval); }

Далее идёт ряд помощников по различным нажатиям клавиш мыши на элементе.

/** * Помощник нажатия на элемент с наведением на него курсора, задержкой, нажатием и задержкой. * @param element - целевой элемент. */ private void ElementClickHelper(WebElement element) throws InterruptedException { this.HoverMouse(element); element.click(); Thread.sleep(this.delayInterval); }

/** * Помощник нажатия правой кнопки мыши на элементе с наведением на него курсора, задержкой, нажатием и задержкой. * @param element - целевой элемент. */ private void ElementRightClickHelper(WebElement element) throws InterruptedException { this.HoverMouse(element); new Actions(this.driver) .contextClick(element) .build() .perform(); Thread.sleep(this.delayInterval); } /** * Помощник нажатия на элемент двойным кликом с наведением на него курсора, задержкой, нажатием и задержкой. * @param element - целевой элемент. */ private void ElementDoubleClickHelper(WebElement element) throws InterruptedException { this.HoverMouse(element); new Actions(this.driver) .doubleClick(element) .build() .perform(); Thread.sleep(this.delayInterval); }

Автоматизация рабочего места

В этом примере будут продемонстрированы:

  • открытие нового рабочего места;
  • поиск вкладки с рабочим местом;
  • работа с деревом рабочего места, включая открытие и закрытие подмножеств, а также выбор элемента подмножества;
  • работа с представлениями, включая поиск нужных данных и открытие карточки из представления;
  • работа с панелями навигации в представлениях;
  • работа с боковыми панелями;
  • закрытие вкладки.

Ниже представлен основной метод данного сценария.

/** * Демонстрация взаимодействия с рабочим местом пользователя. */ private void ShowUserWorkplaceOperations() throws InterruptedException { // Демонстрация открытия нового рабочего места пользователя. this.OpenNewWorkspace();

// Заметим, что сейчас у нас два (или более) открытых рабочих места. // Нам нужно получить последнее открытое рабочее место пользователя. // Для этого вначале найдём нужную вкладку. WebElement workspaceTab = this.mainWindow.findElement(By.xpath("*[@AutomationId='TabControl']/*[@ClassName='TabItem' and @AutomationId='Workplace:c3d72683-f6c0-4766-a3d4-1fd9a7fe6827'][last()]"));

// Активируем вкладку. Отметим, что поведение TessaClient по умолчанию - выбрать открытую вкладку. this.ElementClickHelper(workspaceTab);

// Получаем панель текущего рабочего места. WebElement workspace = workspaceTab.findElement(By.className("WorkplaceView"));

// Демонстрация работы с подмножествами в дереве рабочего места. this.ShowSubsetOperations(workspace);

// Демонстрация работы с представлением "Текущие задания". this.ShowCurrentTasks(workspace);

// Демонстрация открытия представления "Контрагенты". this.OpenPartnersView(workspace);

// Теперь в рабочем месте есть дополнительные компоненты. // Получаем "верхнее представление" со списком контрагентов // Здесь как и ранее можно воспользоваться либо именем, либо AutomationId // Имя - это значение поля "Заголовок" представления рабочего места, // а AutomationId - это значение поля "Id", соответственно. // Так верхняя область рабочего места "Контрагенты" имеет: // - имя: $Workplaces_User_Dictionaries_Partners // - AutomationId: fa5a4aea-29b0-4f2d-b0ca-9df535b0a42f

// Получим эту область: // 1-й вариант - по имени WebElement partnersDataView = workspace.findElement(By.name("$Workplaces_User_Dictionaries_Partners")); // 2-й вариант - по AutomationId // WebElement partnersDataView = workspace.findElement(By.id("fa5a4aea-29b0-4f2d-b0ca-9df535b0a42f")); // 3-й вариант (до автоматизации TessaClient) - по xpath с привязкой к визуальному дереву // WebElement partnersDataView = workspace.findElement(By.xpath("./*[@ClassName='DataView']"));

// Демонстрация обновления текущего представление при помощи плитки рабочего места. this.ShowTilesInteractionForRefreshingCurrentDataView();

// Наша задача, найти в этой области известного контрагента - ООО "Ромашка" (976087cd-f9e7-4dff-ae45-01046214098f) // Вначале получим отображённую таблицу с результатами. WebElement partnersTreeView = partnersDataView.findElement(By.className("TreeGridView"));

// Поскольку в примере тестовые данные построены таким образом, что // нужный нам контрагент находится на последней (в данном случае 3-ей) странице, // то продемонстрируем несколько подходов к поиску и навигации.

// В используемом представлении, для его элементов заданы свойства ссылок (секции References), // идентифицирующие строки представления как содержащие данные являющиеся карточками (IsCard). // В этом случае для каждого столбца данных будут установлены: // Name - содержимое колонки обозначенной DisplayValueColumn // AutomationId - в формате "<Имя колонки с идентификатором карточки>:<идентификатор карточки>" // Как уже отмечалось, мы ищем контрагента с: // Именем - "ООО "Ромашка"" и // AutomationId - "PartnerID:976087cd-f9e7-4dff-ae45-01046214098f"

// Поиск контрагента при помощи панели быстрого поиска. WebElement partnerItem = this.ShowQuickSearch(partnersDataView, partnersTreeView);

// Остальные подходы используют панель навигации. // Вначале получим панель навигации. WebElement pagingPanel = partnersDataView.findElement(By.id("ViewPagingPanel"));

// Поиск контрагента в данных представления при помощи постраничной навигации. partnerItem = this.ShowSearchWithNextPageButton(partnersTreeView, pagingPanel);

// Поиск контрагента при помощи перехода к конкретной странице данных. partnerItem = this.ShowSearchWithPageNumber(partnersTreeView, pagingPanel);

// Пример получения элемента автоматизации для строки, одна из ячеек которой содержит заданное имя. // Этот подход можно использовать для представлений у которых в метаинформации о ссылках не указан флаг "IsCard". // partnerItem = partnersTreeView.findElement(By.xpath(".//*[@ClassName='TreeGridViewItem' and descendant::*[@Name='ООО \"Ромашка\"']]"));

// Поиск контрагента при помощи диалога фильтрации. partnerItem = ShowSearchByFilterDialog(partnersDataView, partnersTreeView); Thread.sleep(this.delayInterval);

// Выделим нашего партнёра. this.ElementClickHelper(partnerItem);

// Сейчас наша задача получить область с представлением для вкладки "Договоры" и открыть в ней заданный договор.

// Получаем нужную вкладку. WebElement contractsTab = workspace.findElement(By.name("$Workplaces_User_Registrator_Contracts")); // Активируем её. this.ElementClickHelper(contractsTab);

// Также как и раньше получаем область представления. // Имя - "$Workplaces_User_Registrator_Contracts" // AutomationId - "86b861bf-6a6a-47ac-bb0b-c06859fafd7b" // Получим область с контрактами по AutomationId. // WebElement contractsWithPartnerDataView = contractsTab.findElement(By.id("86b861bf-6a6a-47ac-bb0b-c06859fafd7b")); // Как правило на вкладке располагается только одна область представления, // поэтому получить её можно более простым образом. WebElement contractsWithPartnerDataView = contractsTab.findElement(By.className("DataView"));

// Получим непосредственно таблицу с данными. WebElement contractsWithPartnerTreeView = contractsWithPartnerDataView.findElement(By.className("TreeGridView"));

// Продемонстрируем возможности задания сортировки по указанному столбцу. // Для начала получим строку заголовков таблицы. WebElement contractsWithPartnerTableHeader = contractsWithPartnerTreeView.findElement(By.className("GridViewHeaderRowPresenter")); // Далее получим нужный заголовок, например "Сумма" WebElement contractSumColumnHeader = contractsWithPartnerTableHeader.findElement(By.name("Сумма")); // Отсортируем по убыванию. this.HoverMouse(contractSumColumnHeader);

// Для этого либо кликаем мышью по заголовку с зажатой клавишей Alt. new Actions(driver) .keyDown(Keys.ALT) .click(contractSumColumnHeader) .keyUp(Keys.ALT) .build() .perform();

Thread.sleep(this.delayInterval);

// Либо дважды кликнем по заголовку. this.ElementClickHelper(contractSumColumnHeader); this.ElementClickHelper(contractSumColumnHeader);

// Найдём в ней запись о договоре "Дпр-000002": // Имя - "Дпр-000002" // AutomationId - "DocID:b529b2c2-64e5-42b7-a539-de8a1d26f3f5" WebElement contractRow = contractsWithPartnerTreeView.findElement(By.name("Дпр-000002"));

// Убедимся, что нужная нам строка видна. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", contractRow);

this.ElementClickHelper(contractRow);

// Открываем карточку договора, связанную с найденным договором. this.ElementDoubleClickHelper(contractRow);

// Задержка для открытия карточки Thread.sleep(this.delayInterval * 2);

this.ShowCardOperations();

Thread.sleep(this.delayInterval); // Закрываем открытую ранее вкладку рабочего места пользователя. this.CloseTab(workspaceTab); }

Необходимо обратить внимание, что при работе с рабочими местами, справку по их именам и идентификаторам можно получить непосредственно в программе TessaAdmin.
Так идентификатор автоматизации рабочего места (AutomationId) есть значение свойства Id рабочего места в TessaAdmin с префиксом Workplace:, а имя элемента автоматизации (Name) есть локализованное значение свойства Имя рабочего места в TessaAdmin.

Рассмотрим метод открытия нового рабочего места при помощи кнопки с изображением звёздочки в панели заголовков вкладок.

/** * Демонстрация открытия рабочего места при помощи кнопки рабочего места. */ private void OpenNewWorkspace() throws InterruptedException { // Откроем рабочее место "Пользователь". // Для этого сначала получим кнопку, соответствующую выбору рабочих мест. // Сделать это можно либо по имени, либо по AutomationId. // 1-й способ - по имени. WebElement workplacesButton = this.mainWindow.findElement(By.name("WorkplacesButton")); // 2-й способ - по AutomationId // WebElement workplacesButton = window.findElement(By.id("TessaWorkplaces:WorkplacesButton"));

// Открываем меню. this.ElementClickHelper(workplacesButton);

// В списке доступных рабочих мест находим пункт меню для рабочего места пользователь. // Это можно сделать либо по имени, либо по AutomationId. // Узнать имя и AutomationId можно в TessaAdmin. // При этом имя - содержимое свойства "Имя" для выбранного рабочего места, // а AutomationId - содержимое свойства "Id" с префиксом. // Так для рабочего места "Пользователь": // - имя: "$Workplaces_User"; // - AutomationId: "c3d72683-f6c0-4766-a3d4-1fd9a7fe6827"; // 1-й способ - по имени. WebElement openUserWorkplaceMenuElement = this.mainWindow.findElement(By.name("$WorkplacesUser")); // 2-й способ - по AutomationId. // WebElement openUserWorkplaceMenuElement = window.findElement(By.id("c3d72683-f6c0-4766-a3d4-1fd9a7fe6827"));

// Открываем рабочее место. this.ElementClickHelper(openUserWorkplaceMenuElement); }

Все регулярные или статические (содержащиеся в рабочем месте TessaAdmin) элементы дерева рабочего места также имеют свои узлы и названия. Также, как и в случае рабочих мест, AutomationId формируется из значения поля Id, но уже с префиксом TreeItem:, а Name соответствует локализованному значению поля Имя.

Рассмотрим метод работы с деревом рабочего места и подмножествами.

/** * Демонстрация работы с подмножествами. * @param workspace рабочее место. */ private void ShowSubsetOperations(WebElement workspace) throws InterruptedException { // Получаем дерево рабочего места WebElement workspaceTree = workspace.findElement(By.className("TreeView"));

// Получаем элемент "Регистратор". // 1-й вариант - просто по имени WebElement registrarNode = workspaceTree.findElement(By.name("Регистратор")); // 2-й вариант - по AutomationId (уникальный идентификатор узла дерева рабочего места в TessaAdmin с префиксом "TreeItem") // WebElement registrarNode2 = workspaceTree.findElement(By.id("TreeItem:241099c7-ac45-4d0a-b566-5dd45c4cbb62"));

// Прокручиваем дерево до него. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", registrarNode); Thread.sleep(this.delayInterval);

// "раскрываем его" this.ElementClickHelper(registrarNode);

// Получаем элемент "Договоры". // 1-й вариант - по имени WebElement contractsNode = registrarNode.findElement(By.name("Договоры")); // 2-й вариант - по идентификатору // WebElement contractsNode = registrarNode.findElement(By.id("TreeItem:f33e4797-3cda-4441-9c20-e4b59ce282c0"));

// Прокручиваем дерево до него. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", contractsNode); Thread.sleep(this.delayInterval);

// Теперь добавим в узел "Договоры" подмножество "По состоянию". // Для этого получим кнопку получения доступных подмножеств. // Поскольку кнопка появляется только при наведении мыши на узел дерева, выполним эти действия.

// Здесь также учтём и тот нюанс, что узел дерева включает в свои размеры все дочерние узлы. // Поэтому для правильного позиционирования нужно наводить мышь на заголовок дерева. WebElement contractsHeader = contractsNode.findElement(By.id("MainContent"));

this.HoverMouse(contractsHeader);

// Теперь кнопка видна и мы можем получить её. WebElement showSubsetButton = contractsNode.findElement(By.id("ShowSubsetSelectorButton"));

// Нажмём на кнопку, чтобы отобразить контекстное меню с доступными представлениями. this.ElementClickHelper(showSubsetButton);

// Далее, получаем список с доступными для выбора представлениями. WebElement availableSubsets = this.mainWindow.findElement(By.id("SubsetsList"));

// Находим в этом списке подмножество "По состоянию". // 1-й вариант по имени WebElement subsetByState = availableSubsets.findElement(By.name("По состоянию")); // 2-й вариант по идентификатору. // WebElement subsetByState = availableSubsets.findElement(By.id("ByState"));

// Выбираем найденный элемент. this.ElementClickHelper(subsetByState);

// Теперь узел "Документы" содержит узел "По состоянию". // Вначале найдём узел подмножества "По состоянию". // 1-й способ по имени WebElement subsetByStateNode = contractsNode.findElement(By.name("По состоянию")); // 2-й способ по идентификатору // WebElement subsetByStateNode = contractsNode.findElement(By.id("TreeItem:ByState"));

// Получим узел подмножества "Проект".

// 1-й способ по идентификатору WebElement projectNode = subsetByStateNode.findElement(By.id("TreeItem:0")); // 2-й способ по имени // WebElement projectNode = subsetByStateNode.findElement(By.name("$KrStates_Doc_Draft"));

// Прокручиваем дерево до него. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", projectNode);

// Выберем этот узел. this.ElementClickHelper(projectNode);

// Продемонстрируем, как можно закрыть узел с подмножеством.

// Прокручиваем дерево до него. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", subsetByStateNode);

// Кнопка закрыть появляется только при наведении мыши на данный узел. WebElement subsetByStateHeader = subsetByStateNode.findElement(By.id("MainContent"));

this.HoverMouse(subsetByStateHeader);

// Найдём кнопку закрыть подмножество. WebElement closeSubsetButton = subsetByStateNode.findElement(By.id("CloseSubsetButton"));

// Теперь закроем подмножество. this.ElementClickHelper(closeSubsetButton); }

Узнать идентификаторы подмножеств можно в представлении, ими будут являться значения свойства Алиас (Alias) и Название (Caption).

Обратим внимание на работу с подмножествами, продемонстрированную в примере.

Во-первых, в контекстном меню раскрытия подмножеств. В этом случае AutomationId будет содержать значения свойства Алиас (Alias), а Name - локализованное значение свойства Название (Caption).

Во-вторых, после открытия подмножества, соответствующий элемент появляется в дереве рабочего места. В этом случае AutomationId будет содержать значения свойства Алиас (Alias) с префиксом TreeItem:, а Name - локализованное значение свойства Название (Caption).

Следующее, на что необходимо обратить внимание, это на отображаемые в дереве элементы подмножества. Их идентификаторы и имена тоже можно узнать в TessaAdmin. Они строятся на основании возвращаемых в запросе представления данных, колонки которых указаны в свойствах RefColumn и CaptionColumn.

При этом AutomationId будет содержать значения колонки RefColumn с префиксом TreeItem:, а Name - значение колонки CaptionColumn.

В конце метода продемонстрировано получение скрытой кнопки закрытия подмножества, которая появляется при наведении мыши на подмножество.

Рассмотрим работу с представлением “Текущие задания”, построенном по принципу master-detail.

/** * Демонстрация работы с представлением "Текущие задания". * @param workspace рабочее место. * @throws InterruptedException */ private void ShowCurrentTasks(WebElement workspace) throws InterruptedException { // Получаем дерево рабочего места WebElement workspaceTree = workspace.findElement(By.className("TreeView"));

// Получаем элемент "Отчёты". // 1-й вариант - просто по имени WebElement reportsNode = workspaceTree.findElement(By.name("Отчёты")); // 2-й вариант - по AutomationId (уникальный идентификатор узла дерева рабочего места в TessaAdmin с префиксом "TreeItem") // WebElement reportsNode = workspaceTree.findElement(By.id("TreeItem:e8b49a2b-4371-4a19-8efb-e44d55af2057"));

// Прокручиваем дерево до него. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", reportsNode); Thread.sleep(this.delayInterval);

// "раскрываем его" this.ElementClickHelper(reportsNode);

// Получаем элемент "Текущие задания". // 1-й вариант - по имени WebElement currentTasksNode = reportsNode.findElement(By.name("Текущие задания")); // 2-й вариант - по идентификатору // WebElement contractsNode = reportsNode.findElement(By.id("TreeItem:371000fe-a803-4fbb-bbf8-b10a22e26cce"));

// Прокручиваем дерево до него. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", currentTasksNode); Thread.sleep(this.delayInterval);

// Выбираем элемент. this.ElementClickHelper(currentTasksNode);

// Теперь получим три представления для данного узла. // Задания для всех пользователей. // - задания по всем подразделениям; // - задания по всем пользователям выбранного подразделения; // - задания выбранного типа для выбранного пользователя; // Как и ранее имя и идентификатор можно узнать в приложении TessaAdmin. // 1-й вариант - по идентификатору WebElement allDepartmentsTasksDataView = workspace.findElement(By.id("371000fe-a803-4fbb-bbf8-b10a22e26cce")); WebElement allUsersInDepartmentTasksDataView = workspace.findElement(By.id("d91ab791-4cd4-4e08-9ea4-2a9dd40ab67b")); WebElement userTasksDataView = workspace.findElement(By.id("90b530a3-6fe3-43f7-99c5-289b147bec36")); // 2-й вариант - по имени // WebElement allDepartmentsTasksDataView = workspace.findElement(By.name("$Workplaces_User_Reports_CurrentTasks")); // WebElement allUsersInDepartmentTasksDataView = workspace.findElement(By.name("Текущие задания по пользователям")); // WebElement userTasksDataView = workspace.findElement(By.name("Мои задания"));

// Теперь получим непосредственно табличные данные. WebElement allDepartmentsTasksTableView = allDepartmentsTasksDataView.findElement(By.className("TreeGridView")); WebElement allUsersInDepartmentTasksTableView = allUsersInDepartmentTasksDataView.findElement(By.className("TreeGridView")); WebElement userTasksTableView = userTasksDataView.findElement(By.className("TreeGridView"));

// Выберем ячейку "Подразделение" = "(не задано)" в первой таблице. WebElement allDepartmentsCell = allDepartmentsTasksTableView.findElement(By.xpath("*[@ClassName='TreeGridViewItem']/*[@AutomationId='DeptName' and @Name='(не задано)']"));

this.ElementClickHelper(allDepartmentsCell);

// Во второй таблице выбираем ячейку "В работе" у сотрудника "Admin". WebElement adminInProgressCell = allUsersInDepartmentTasksTableView.findElement(By.xpath("*[@ClassName='TreeGridViewItem' and descendant::*[@AutomationId='UserName' and @Name='Admin']]/*[@AutomationId='InWork']"));

this.ElementClickHelper(adminInProgressCell);

// Убеждаемся, что в третьей таблице есть данные. List<WebElement> userTasksRows = userTasksTableView.findElements(By.xpath("*[@ClassName='TreeGridViewItem']")); }

Здесь демонстрируется работа с содержимым отображаемых на основной рабочей области представлений. Как и раньше узнать идентификаторы и имена представлений можно в TessaAdmin.
Так, свойство AutomationId содержит значение свойства Id, а Name - Заголовок.

Также в данном примере продемонстрирован пример XPath выражений для поиска строк таблицы по содержимому ячеек, и далее поиска ячейки с заданным содержимым в уже найденной строке, а также навигация и выбор этой конкретной ячейки.
В конце метода показано, как получить все отображаемые строки таблицы.

Следующий метод демонстрирует, как найти и выбрать узел “Контрагенты” в дереве рабочего места.
Как было отмечено ранее, идентификаторы и имена статических узлов, представленных в метаданных рабочего места, можно узнать в TessaAdmin. Данный пример также интересен тем, что демонстрирует возможность поиска элементов дерева рабочего места не только по идентификатору и имени, но по значениям свойств дочерних узлов автоматизации.

/** * Демонстрация открытия представления "Контрагенты". * @param workspace рабочее место. */ private void OpenPartnersView(WebElement workspace) throws InterruptedException { // Получаем дерево рабочего места. WebElement workspaceTree = workspace.findElement(By.className("TreeView"));

// Получаем элемент "Справочники". // Сделать это можно несколькими способами, но наиболее простыми являются: // - получение по имени; // - получение по AutomationId. // Узнать имя и AutomationId можно в TessaAdmin. // При этом имя - локализованное содержимое свойства "Имя" для выбранного узла рабочего места в TessaAdmin, // а AutomationId - содержимое свойства "Id" с префиксом "TreeItem:". // Так для "Справочников" Guid = "c8b0cc7d-5376-48a2-bbba-d0382d65a58d" // Соответственно AutomationId будет следующим: "TreeItem:c8b0cc7d-5376-48a2-bbba-d0382d65a58d" // 1-й вариант - просто по имени. WebElement referenceBooksNode = workspaceTree.findElement(By.name("Справочники")); // 2-й вариант - по AutomationId (уникальный идентификатор узла дерева рабочего места в TessaAdmin с префиксом "TreeItem"). // WebElement referenceBooksNode = workspaceTree.findElement(By.id("TreeItem:c8b0cc7d-5376-48a2-bbba-d0382d65a58d")); // 3-й вариант (до автоматизации TessaClient) - по xpath с привязкой к визуальному дереву. // WebElement referenceBooksNode = workspaceTree.findElement(By.xpath(".//*[@ClassName='TreeViewItem' and descendant::*[@Name='Справочники']]"));

// Прокручиваем дерево до него. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", referenceBooksNode); Thread.sleep(this.delayInterval);

// Раскрываем его. this.ElementClickHelper(referenceBooksNode);

// Получаем справочник "Контрагенты". // 1-й вариант - по имени. WebElement partnersNode = referenceBooksNode.findElement(By.name("Контрагенты")); // 2-й вариант - по AutomationId (о получении AutomationId см. выше): "TreeItem:fa5a4aea-29b0-4f2d-b0ca-9df535b0a42f". // WebElement partnersNode = referenceBooksNode.findElement(By.id("TreeItem:fa5a4aea-29b0-4f2d-b0ca-9df535b0a42f")); // 3-й вариант (до автоматизации TessaClient) - по xpath с привязкой к визуальному дереву. // WebElement partnersNode = referenceBooksNode.findElement(By.xpath(".//*[@ClassName='TreeViewItem' and descendant::*[@Name='Контрагенты']]"));

// Выбираем данный элемент (прокрутка будет выполнена автоматически). // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SelectionItemPattern.Select", partnersNode); Thread.sleep(this.delayInterval); }

Далее метод демонстрирует работу с плиткой левой панели “Обновить”, выполняющей обновление данных, отображаемых в рабочей области представлений.

/** * Демонстрация обновления текущего представление при помощи плитки рабочего места. */ private void ShowTilesInteractionForRefreshingCurrentDataView() throws InterruptedException { // Продемонстрируем как можно обновить текущее представление при помощи плитки рабочего места. // Вначале вызовем левую контекстную панель. // Это можно сделать несколькими способами. // Во-первых, при помощи кнопки заголовков вкладок. // Получим кнопку по её идентификатору. WebElement openLeftPanelHeaderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftPanelButton")); // Также кнопку можно получить по её имени. // WebElement openLeftPanelHeaderButton = this.mainWindow.findElement(By.name("LeftPanelButton"));

this.ElementClickHelper(openLeftPanelHeaderButton); this.ElementClickHelper(openLeftPanelHeaderButton);

// Во-вторых при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // Получим кнопку по её идентификатору. WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftSlider")); // И по её имени. // WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.name("LeftSlider"));

// Откроем левую боковую панель. this.ElementClickHelper(openLeftPanelSliderButton);

// Получим её содержимое. WebElement leftCommandPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём там плитку "Обновить". // Получим плитку по её идентификатору. WebElement refreshTile = leftCommandPanel.findElement(By.id("RefreshAll")); // Получим плитку по её имени. // WebElement refreshTile = leftCommandPanel.findElement(By.name("Обновить"));

// Вызовем действие, связанное с плиткой. this.ElementClickHelper(refreshTile); }

Следующая группа методов демонстрирует различные варианты поиска заданного контрагента.
Мы будем искать контрагента ООО "Ромашка" с идентификатором карточки 976087cd-f9e7-4dff-ae45-01046214098f. Следует отметить, что имя контрагента устанавливается в соответствующее свойство Name, а идентификатор карточки устанавливается в свойство AutomationId с префиксом PartnerID: (AutomationId = PartnerID:976087cd-f9e7-4dff-ae45-01046214098f).

Первый метод из этой группы демонстрирует поиск контрагента при помощи панели быстрого поиска.

/** * Поиск контрагента при помощи панели быстрого поиска. * @param partnersDataView представление с панелями навигации. * @param partnersTreeView табличные данные. * @return искомый контрагент. */ private WebElement ShowQuickSearch(WebElement partnersDataView, WebElement partnersTreeView) throws InterruptedException { // Первый подход с использованием фильтра быстрого поиска. // Получаем поле быстрого поиска. WebElement quickSearchElement = partnersDataView.findElement(By.id("ViewQuickSearchTextBox"));

// Вводим нужное нам значение. // quickSearchElement.sendKeys("Ромашка"); new Actions(driver) .click(quickSearchElement) // '\n' - специфичен для Winium и воспринимается как клавиша "Ввод". .sendKeys("Ромашка\n") .build() .perform();

Thread.sleep(this.delayInterval);

// Получаем искомого контрагента. WebElement partnerItem = partnersTreeView.findElement(By.xpath("*[@Name='ООО \"Ромашка\"']"));

// Внимание! После отмены фильтра с найденным контрагентом нельзя будет выполнять // действия по автоматизации, требующие его отображения!

// Теперь отменим наш фильтр. // Самым простым способом сделать это является ввести пустую строку. // new Actions(driver) // .click(quickSearchElement) // .sendKeys("\n") // .build() // .perform();

// Но мы продемонстрируем и ещё одну возможность. // Получим элемент управления с информацией о применённом фильтре. WebElement viewFilterText = partnersDataView.findElement(By.id("ViewTextFilter")); // Для того, чтобы можно было нажать на кнопку она должна стать видимой. // Это в свою очередь обеспечивается наведением указателя мыши на элемент управления фильтрацией. this.HoverMouse(viewFilterText); // Делаем задержку на анимацию. Thread.sleep(this.delayInterval); // Теперь получим кнопку Очистки фильтра. WebElement clearFilterButton = viewFilterText.findElement(By.id("ClearFilterButton")); // Нажмём её и сбросим фильтр. this.ElementClickHelper(clearFilterButton);

return partnerItem; }

В примере также продемонстрированы две возможности по очистке введённого фильтра.
Во-первых, вводом пустой строки в качестве значения фильтра быстрого поиска.

Во-вторых, при помощи скрытой кнопки “Сбросить фильтр”, находящейся в панели отображения выбранного фильтра.

Второй метод демонстрирует поиск контрагента среди всех отображаемых в таблице данных с использованием постраничной навигации при помощи кнопки “Перейти к следующей странице”.

/** * Поиск контрагента при помощи кнопки "Перейти к следующей странице". * @param partnersTreeView табличные данные. * @param pagingPanel панель навигации. * @return искомый контрагент. */ private WebElement ShowSearchWithNextPageButton(WebElement partnersTreeView, WebElement pagingPanel) throws InterruptedException { // В следующем подходе будем использовать кнопки навигации, // точнее одну кнопку (перейти к следующей странице). // Получим эту кнопку. WebElement nextPageButton = pagingPanel.findElement(By.id("NextPageButton")); // В цикле будем запрашивать данные и анализировать их // на наличие нужной записи (столбца, без анализа данных ячеек). boolean stopIteration = false; while(!stopIteration) { // Получаем именно список элементов, т.к. конкретный элемент может отсутствовать на данной странице. // В этом случае мы просто получим список нулевой длинны. // Если использовать метод findElement, то он в случае неудачи вернёт исключение. // Здесь также возможно несколько вариантов поиска, самый простой по имени, // но его минусы в том, что поиск будет проходить на всю глубину дерева. // 1-й вариант - по имени. // List<WebElement> elements = partnersTreeView.findElements(By.name("ООО \"Ромашка\"")); // 2-й вариант - xpath - по имени, но ограничивающий глубину поиска только 1-м уровнем дочерних элементов. List<WebElement> elements = partnersTreeView.findElements(By.xpath("*[@Name='ООО \"Ромашка\"']")); // Если нам заранее известно, расположение колонок, и нужно искать текст только в колонке // с известным индексом, то сделать это можно следующим образом. // List<WebElement> elements2 = partnersTreeView.findElements(By.xpath("*/*[@Column=0 and @Name='ООО \"Ромашка\"']")); // Проверяем, что мы нашли нужный элемент. if (elements.size() > 0) { return elements.get(0); } // Если мы ещё можем нажимать на кнопку, то продолжаем итерации. stopIteration = !nextPageButton.isEnabled(); // Переходим на новую страницу. this.ElementClickHelper(nextPageButton); } Thread.sleep(this.delayInterval); return null; }

Обратим внимание, что в методе продемонстрировано получение состояния кнопки, а именно, доступна ли она для нажатия. Если кнопка не доступна для нажатия, значит мы перешли к последней странице с данными, и если при этом мы не нашли нужные данные, значит они отсутствуют в таблице.

Следующий метод демонстрирует переключение между страницами при помощи поля ввода номера страницы.

/** * Поиск контрагента при помощи перехода к конкретной странице данных. * @param partnersTreeView таблица с данными. * @param pagingPanel панель навигации. * @return искомый контрагент. */ private WebElement ShowSearchWithPageNumber(WebElement partnersTreeView, WebElement pagingPanel) throws InterruptedException { // Следующий подход использует демонстрирует переход к требуемой странице по её номеру // и поиск нужных данных только на этой странице. // Получаем TextBox с номером страницы: WebElement currentPageNumberTextBox = pagingPanel.findElement(By.id("CurrentPageNumber"));

// Воспользуемся тем фактом, что в первом подходем мы уже нашли данные, // а значит и нужную страницу и продемонстрируем как можно получить её номер. String currentPage = currentPageNumberTextBox.getText();

// Сбросим номер страницы, на первый, чтобы обновить данные. currentPageNumberTextBox.sendKeys("1"); new Actions(driver) .sendKeys(Keys.ENTER) .build() .perform();

Thread.sleep(this.delayInterval * 2);

// Теперь перейдём обратно к нужной странице. currentPageNumberTextBox.sendKeys(currentPage); new Actions(driver) .sendKeys(Keys.RETURN) .build() .perform();

Thread.sleep(this.delayInterval * 2);

// Мы точно знаем, что данные есть. Получим их уже по AutomationId. return partnersTreeView.findElement(By.id("PartnerID:976087cd-f9e7-4dff-ae45-01046214098f")); }

Важной особенностью этого метода является демонстрация различных способов ввода текста в Winium при помощи метода sendKeys. Если метод вызывается у элемента, то перед вводом текста производится предварительная очистка всего предыдущего содержимого элемента. Если метод вызывается из класса Actions, то очистки содержимого не производится, вместо этого вводимый текст добавляется к уже существующему. Кроме того, использование специальных символов из класса Keys доступно только у метода Actions.sendKeys.

Следующий метод показывает возможность поиска контрагента по критериям, устанавливаемым в диалоге фильтрации, а также работу с этим диалогом.

/** * Поиск контрагента с использованием диалога фильтрации. * @param partnersDataView панель отображения данных. * @param partnersTreeView таблица с данными. * @return искомый контрагент. */ private WebElement ShowSearchByFilterDialog(WebElement partnersDataView, WebElement partnersTreeView) throws InterruptedException { // Продемонстрируем дополнительные возможности поиска нужного контрагента при помощи диалога фильтрации. // Вначале получим кнопку для открытия диалога фильтрации. WebElement viewFilterButton = partnersDataView.findElement(By.id("ViewFilterButton"));

// Нажмём на неё и откроем диалог. this.ElementClickHelper(viewFilterButton); Thread.sleep(this.delayInterval);

// Теперь получим открытое окно с заданием параметров фильтрации. WebElement filterDialog = this.mainWindow.findElement(By.id("FilterDialog"));

// Второй вариант - по xpath. // WebElement filterDialog2 = this.mainWindow.findElement(By.xpath("*[@AutomationId='FilterDialog' and @ClassName='Window']"));

// Далее получаем панель с параметрами фильтрации. WebElement filtersPanel = filterDialog.findElement(By.id("FilterView"));

// Получим нужный нам критерий отбора. // Например, по типу контрагента. WebElement partnerFilter = filtersPanel.findElement(By.id("Parameter:$Views_Partners_Type_Param")); // Можно также получить тип контрагента, по его имени. // WebElement partnerTypeFilter = filtersPanel.findElement(By.name("$Views_Partners_Type_Param"));

// Добавим новое условие. this.ElementClickHelper(partnerFilter);

// Найдём добавленный критерий. // WebElement partnerTypeCriteria = filtersPanel.findElement(By.id("Criteria:0:$Views_Partners_Type_Param")); // Но, поскольку мы можем добавлять много критериев, то лучше использовать xpath для поиска последнего добавленного критерия. WebElement partnerCriteria = filtersPanel.findElement(By.xpath("*[@ClassName='Text' and @Name='Criteria:$Views_Partners_Type_Param'][last()]"));

// Теперь установим значение критерия "равен". ComboBox operatorSelector = WebElementExtensions.toComboBox(partnerCriteria.findElement(By.id("CriteriaOperatorSelector"))); // Убеждаемся, что выбрано значение "равен". // Получаем текущий выбранный элемент. // Внимание! В стандартной версии Winium команда работает только при открытом выпадающем списке! RemoteWebElement selectedElement = operatorSelector.findSelected(); // И его значение. String selectedOperator = selectedElement.getAttribute("Name"); // Здесь уже установлено нужное значение.

// Теперь получим редактор значений для выбранного критерия. WebElement criteriaValueEditor = partnerCriteria.findElement(By.id("ValueEditor"));

// Продемонстрируем несколько вариантов заполнения критерия. // Вначале при помощи ввода текста критерия. // Для большей наглядности будем вводить не весь текст, а его часть.

// Получим само поле ввода. WebElement criteriaValue = criteriaValueEditor.findElement(By.id("EntityTextBox")); // Далее вводим текст. new Actions(this.driver) .click(criteriaValue) .sendKeys("Юриди") .build() .perform(); Thread.sleep(this.delayInterval); new Actions(this.driver) .sendKeys(Keys.ENTER) .build() .perform();

Thread.sleep(this.delayInterval);

// Убеждаемся, что текст был дополнен до нужного. String text = criteriaValue.getText();

// Следующий способ - выбор из выпадающего списка. // Находим кнопку с выпадающим списком. WebElement showPopupButton = criteriaValueEditor.findElement(By.id("PopupButton")); // Нажимаем её. this.ElementClickHelper(showPopupButton);

// Находим список вариантов, доступных для выбора. WebElement popupList = filterDialog.findElement(By.id("PopupList"));

// Находим нужный элемент по имени. WebElement item = popupList.findElement(By.name("$PartnerType_Individual")); // Можно также найти его по идентификатору "2". // WebElement item = popupList.findElement(By.id("2"));

// Выбираем этот элемент. this.ElementClickHelper(item);

// Убеждаемся в выборе. text = criteriaValue.getText();

// Следующий способ - выбор из представления. // Находим кнопку выбора из представления. WebElement selectButton = criteriaValueEditor.findElement(By.id("SelectButton")); // Нажимаем на кнопку для отображения диалога выбора из представления. this.ElementClickHelper(selectButton); Thread.sleep(this.delayInterval);

// Находим диалог выбора из представлений в рабочих местах. WebElement workplacesViewsDialog = filterDialog.findElement(By.id("WorkplacesDialog"));

// Получаем отображаемую в диалоге область представления. WebElement partnersTypeDataView = workplacesViewsDialog.findElement(By.className("DataView")); // Далее получаем таблицу с результатами. WebElement partnersTypesTable = partnersTypeDataView.findElement(By.id("TableView")); // Находим в ней запись для индивидуального предпринимателя. item = partnersTypesTable.findElement(By.id("TypeID:3")); // Выбираем его. this.ElementDoubleClickHelper(item);

// Как и прежде, убеждаемся в правильности выбора. text = criteriaValue.getText();

Thread.sleep(this.delayInterval);

// Теперь удалим наш критерий. // Для этого сначала наведём мышь на критерий. this.HoverMouse(partnerCriteria);

// После этого найдём кнопку удаления критерия и нажмём её. WebElement deleteCriteriaButton = partnerCriteria.findElement(By.id("DeleteCriteriaButton")); this.ElementClickHelper(deleteCriteriaButton);

// Теперь введём критерий отбора по ИНН. У нашего контрагента "ООО "Ромашка"" ИНН "1111-1111". partnerFilter = filtersPanel.findElement(By.id("Parameter:$Views_Partners_INN_Param"));

// Добавим новое условие. this.ElementClickHelper(partnerFilter);

// Найдём добавленный критерий. partnerCriteria = filtersPanel.findElement(By.xpath("*[@ClassName='Text' and @Name='Criteria:$Views_Partners_INN_Param'][last()]"));

// Теперь установим значение критерия "равен". operatorSelector = WebElementExtensions.toComboBox(partnerCriteria.findElement(By.id("CriteriaOperatorSelector"))); // Убеждаемся, что выбрано значение "равен". // Получаем текущий выбранный элемент. // Внимание! В стандартной версии Winium команда работает только при открытом выпадающем списке! selectedElement = operatorSelector.findSelected(); // И его значение. selectedOperator = selectedElement.getAttribute("Name"); Thread.sleep(this.delayInterval);

// Здесь уже установлено нужное значение. if (selectedOperator != "равен") { // Выберем нужный тип условия. // Раскроем выпадающий список. this.ElementClickHelper(operatorSelector);

// Найдём в нём нужный элемент. item = operatorSelector.findElement(By.id("Equality")); // Можно найти также по имени. // item = operatorSelector.findElement(By.name("равен"));

//Выберем его. this.ElementClickHelper(item);

// Убедимся в правильности выбора. selectedOperator = operatorSelector.findSelected().getAttribute("Name"); }

// Теперь получим редактор для ввода требуемого значения. criteriaValueEditor = partnerCriteria.findElement(By.id("ValueEditor")); // Обратим внимание, что в отличии от поля "Тип контрагента", где редактор был составным, // редактор для ввода для "ИНН" является простым полем для ввода текста, // поэтому все действия выполняются сразу на нём. criteriaValueEditor.sendKeys("1111-1111");

Thread.sleep(this.delayInterval * 2);

// Применим наш фильтр. // Найдём кнопку завершения диалога с подтверждением выбора. WebElement okButton = filterDialog.findElement(By.id("OkButton"));

// Нажмём её. this.ElementClickHelper(okButton);

// Как и раньше получим искомого партнёра, но уже при помощи поиска по значению ИНН. return partnersTreeView.findElement(By.xpath("*[@ClassName='TreeGridViewItem' and descendant::*[@AutomationId='INN' and @Name='1111-1111']]")); }

В данном примере продемонстрирована работа с:

  • выпадающим списком, в том числе возможность получения значения выбранного в списке элемента без необходимости раскрытия списка;
  • элементом управления с автодополнением (автокомплит) во всех его режимах работы:
    • ввод части текста с последующим его дополнением;
    • выбор из выпадающего списка;
    • выбор из представления в отдельном диалоге.

Далее представлен метод работы с карточкой “Дпр-000002” с идентификатором “b529b2c2-64e5-42b7-a539-de8a1d26f3f5”, открытой из представления.

/** * Демонстрация работы с карточкой, открытой из представления. */ private void ShowCardOperations() throws InterruptedException { // Получим вкладку открытого договора. WebElement contractTab = this.mainWindow.findElement(By.xpath("*[@AutomationId='TabControl']/*[@ClassName='TabItem' and @AutomationId='Card:b529b2c2-64e5-42b7-a539-de8a1d26f3f5'][last()]"));

// Как и раньше убедимся, что отображается содержимое именно этой вкладки. this.ElementClickHelper(contractTab);

// Здесь можно выполнить какую-то работу.

Thread.sleep(this.delayInterval);

// Теперь продемонстрируем как закрыть вкладку. this.CloseTab(contractTab); }

Следующий метод демонстрирует как закрыть любую вкладку на панели вкладок TessaClient.

/** * Метод закрытия вкладки. Внимание! Заголовок вкладки должен быть виден. */ private void CloseTab(WebElement tab) throws InterruptedException { // Теперь продемонстрируем как закрыть вкладку. // Для этого получим кнопку закрытия вкладки. // Но, поскольку кнопка скрыта, вначале, нужно навести курсор на вкладку. this.HoverMouse(tab);

// Теперь находим кнопку, она должна появиться в дереве автоматизации. WebElement tabCloseButton = tab.findElement(By.id("TabItem:CloseButton"));

// И нажимаем на неё. this.ElementClickHelper(tabCloseButton); }

Автоматизация работы с карточкой

В этом примере будут представлены:

  • работа с боковыми панелями;
  • работа с основным содержимым карточки:
    • с кнопками панелей управления;
    • с различными областями (данных и задач) карточки;
    • с различными вкладками карточки:
      • с вкладкой данных, с блоками и элементами управления в них;
      • с вкладкой маршрутов;
      • с вкладкой обсуждений;
    • с заданиями в карточке;
  • работа с различными диалоговыми окнами, включая системные.

Основной метод примера представлен ниже.

/** * Операции с карточкой. * @throws InterruptedException */ private void ShowUserCardOperations() throws InterruptedException { // Демонстрация создания новой карточки "Договор". this.CreateContractCard();

// Получим вкладку созданного договора. WebElement contractTab = this.mainWindow.findElement(By.xpath("*[@AutomationId='TabControl']/*[@ClassName='TabItem' and @Name='Новая карточка'][last()]"));

// Как и раньше убедимся, что отображается содержимое именно этой вкладки. this.ElementClickHelper(contractTab);

// Получим идентификатор карточки, он нам понадобиться в дальнейшем. String cardAutomationId = contractTab.getAttribute("AutomationId");

// Получим основной редактор карточки. WebElement cardEditor = contractTab.findElement(By.id("CardEditor"));

// Демонстрация заполнения основной информации карточки. this.ShowCardMainInfoEditing(cardEditor);

// Демонстрация настройки и запуска процесса согласования в системе маршрутов. this.ShowApprovalProcess(cardEditor);

// Внимание, после этих действий необходимо заново получить дерево автоматизации всей карточки. contractTab = this.mainWindow.findElement(By.id(cardAutomationId)); // Получим основной редактор карточки. cardEditor = contractTab.findElement(By.id("CardEditor"));

// Демонстрация работы с заданиями. this.ShowTasksHandling(cardEditor); Thread.sleep(this.delayInterval);

// Внимание, после этих действий необходимо заново получить дерево автоматизации всей карточки. contractTab = this.mainWindow.findElement(By.id(cardAutomationId)); // Получим основной редактор карточки. cardEditor = contractTab.findElement(By.id("CardEditor"));

// Демонстрация визуализации маршрута. this.ShowRouteVisualization(cardEditor);

// Демонстрация получения вкладки с данными карточки и переключения на неё. WebElement cardTab = this.GetCardTab(cardEditor); // Убеждаемся, что вкладка выбрана. this.ElementClickHelper(cardTab);

// Обновим карточку. this.ShowRefreshCardByTile(); Thread.sleep(this.delayInterval);

// И перейдём к её редактированию. this.ShowEditCardByTile(); Thread.sleep(this.delayInterval);

// Внимание, после этих действий необходимо заново получить дерево автоматизации всей карточки. contractTab = this.mainWindow.findElement(By.id(cardAutomationId)); // Получим основной редактор карточки. cardEditor = contractTab.findElement(By.id("CardEditor"));

// Демонстрация работы со листом согласования. this.ShowApprovalSheetActions(cardEditor);

// Демонстрация регистрации договора. this.ShowContractRegistration(); Thread.sleep(this.delayInterval);

// Внимание, после этих действий необходимо заново получить дерево автоматизации всей карточки. contractTab = this.mainWindow.findElement(By.id(cardAutomationId)); // Получим основной редактор карточки. cardEditor = contractTab.findElement(By.id("CardEditor"));

String fileName = "sample.pdf";

// Демонстрация операций по добавлению и редактированию файла. this.ShowCardFileEditing(cardEditor, fileName);

// Сохраняем карточку. this.ShowSaveCardByTile(); Thread.sleep(this.delayInterval * 4);

// Внимание, после этих действий необходимо заново получить дерево автоматизации всей карточки. contractTab = this.mainWindow.findElement(By.id(cardAutomationId)); // Получим основной редактор карточки. cardEditor = contractTab.findElement(By.id("CardEditor")); // Демонстрация работы с предварительным просмотром файла. this.ShowFilePreviewActions(cardEditor, fileName);

// Демонстрация работы с форумами. this.ShowForumsActions(cardEditor);

// Зарываем карточку. this.CloseTab(contractTab); }

Рассмотрим метод, создающий новую карточку типа документа “Договор” при помощи плитки правой панели.

/** * Демонстрация создания новой карточки "Договор". * @throws InterruptedException */ private void CreateContractCard() throws InterruptedException { // Откроем правую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // Получим кнопку по её идентификатору. WebElement openRightPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:RightSlider")); // И по её имени. // WebElement openRightPanelSliderButton2 = this.mainWindow.findElement(By.name("RightSlider"));

// Откроем правую боковую панель. this.ElementClickHelper(openRightPanelSliderButton);

// Получим её содержимое. WebElement rightCommandPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём там плитку "Создать карточку". // Получим плитку по её идентификатору. WebElement createCardTile = rightCommandPanel.findElement(By.id("CreateCard")); // Получим плитку по её имени. // WebElement createCardTile2 = rightCommandPanel.findElement(By.name("Создать карточку"));

// Наведём на неё курсор мыши, чтобы получить меню второго уровня. this.HoverMouse(createCardTile);

// Получим меню второго уровня. WebElement secondLevelPanel = rightCommandPanel.findElement(By.className("Popup"));

// Получим плитку "Документы" из меню второго уровня. WebElement documentsTile = secondLevelPanel.findElement(By.id("Documents"));

// Наведём на неё курсор мыши, чтобы получить меню третьего уровня. this.HoverMouse(documentsTile);

// Получим меню третьего уровня. WebElement thirdLevelPanel = secondLevelPanel.findElement(By.className("Popup"));

// Получим плитку "Договор" из меню третьего уровня. WebElement contractTile = thirdLevelPanel.findElement(By.id("$KrTypes_DocTypes_Contract"));

// Вызовем действие, связанное с плиткой. this.ElementClickHelper(contractTile);

Thread.sleep(this.delayInterval); }

Обратите внимание на поэтапное получение дочерних панелей с элементами управления, скрытых до момента наведения курсора мыши на плитки, содержащие дочерние плитки.

Следующий метод демонстрирует заполнение основных данных карточки:

  • суммы договора;
  • валюты;
  • контрагента с выбором из представления;
  • сохранения карточки с незаполненным обязательным полем “Название”;
  • получение и анализ возникшей ошибки;
  • заполнение поля “Название”.

/** * Демонстрация заполнения основной информации карточки. * @param cardEditor - редактор карточки. */ private void ShowCardMainInfoEditing(WebElement cardEditor) throws InterruptedException { // Получим область вкладок карточки. WebElement cardTabs = cardEditor.findElement(By.id("CardTabs"));

// Получим вкладку с данными карточки. // 1-й способ по идентификатору - содержимое поля "Название" вкладки в типе карточки TessaAdmin. WebElement cardTab = cardTabs.findElement(By.id("Contract")); // 2-й способ по имени - содержание поля "Заголовок" в типе карточки TessaAdmin. WebElement cardTab2 = cardTabs.findElement(By.name("$CardTypesTabs_Card"));

// Убеждаемся, что вкладка выбрана. this.ElementClickHelper(cardTab);

// Получаем область содержимого выбранной вкладки. WebElement tabContent = cardTab.findElement(By.id("Content")); // Далее получаем форму редактора данных вкладки. WebElement form = tabContent.findElement(By.id("Form")); // Получим панель с блоками (она нам понадобится для прокрутки). WebElement cardPanel = form.findElement(By.id("CardPanel"));

// Получим блок основной информации. // Это можно сделать двумя способами. // 1-й по идентификатору - содержимое поля "Алиас" блока во вкладке TessaAdmin. WebElement mainInfoBlock = cardPanel.findElement(By.id("Block1")); // 2-й по имени - содержимое поля "Заголовок" блока во вкладке TessaAdmin. WebElement mainInfoBlock2 = cardPanel.findElement(By.name("$CardTypes_Blocks_MainInformation"));

// Получим элемент управления для ввода суммы договора. // Также как и для вкладок и блоков, идентификатор задаётся в поле "Алиас", а имя в поле "Заголовок". // Поскольку у целевого элемента управления задан только заголовок, то найдём его по имени. WebElement contractSumElement = mainInfoBlock.findElement(By.name("$CardTypes_Controls_Amount")); // Получим элемент управления для ввода суммы. WebElement valueEditor = contractSumElement.findElement(By.id("ValueEditor")); // Введём сумму контракта. valueEditor.sendKeys("10000"); // Убедимся, что данные введены. String text = valueEditor.getText(); Thread.sleep(this.delayInterval);

// Установим валюту договора (рубли) из выпадающего списка. // Получим элемент управления для выбора валюты. WebElement currencyElement = mainInfoBlock.findElement(By.name("$CardTypes_Controls_Currency")); // Получим кнопку вызова выпадающего списка для выбора валют. WebElement popupButton = currencyElement.findElement(By.id("PopupButton")); // Нажмём её для отображения выпадающего списка доступных валют. this.ElementClickHelper(popupButton);

// Находим список вариантов, доступных для выбора. WebElement popupList = this.mainWindow.findElement(By.id("PopupList"));

// Находим нужный элемент по имени. WebElement item = popupList.findElement(By.name("RUB")); this.ElementClickHelper(item);

// Убеждаемся, что выбрана нужная валюта. // Здесь сначала получаем редактор для валюты и сразу же его текст. text = currencyElement.findElement(By.id("ValueEditor")).findElement(By.id("EntityTextBox")).getText();

// Теперь установим контрагента. // Найдём блок с информацией о контрагенте. // 1-й по идентификатору. WebElement subjectInfoBlock = cardPanel.findElement(By.id("Block3")); // 2-й по имени. WebElement subjectInfoBlock2 = cardPanel.findElement(By.name("$CardTypes_Blocks_Controls_Subject")); // Найдём элемент управления для выбора контрагента по идентификатору (у этого элемента управления он задан). WebElement partnerControl = subjectInfoBlock.findElement(By.id("PartnerControl")); // Мы будем выбирать контрагента из диалога представления, поэтому получим кнопку открытия этого диалога. WebElement selectPartnerButton = partnerControl.findElement(By.id("SelectButton"));

// Убедимся, что нужный нам элемент управления виден. // Это можно сделать несколькими способами. // 1-й способ. При помощи команды развернуть блок. // Внимание! Если блок содержит много элементов управления, // сильно вытянут по вертикали и при этом область просмотра // имеет небольшие размеры по вертикали, воспользуйтесь вторым способом. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! // Аргументы скрипта: // 1) целевой элемент, поддерживающий шаблон "ExpandCollapsePattern"; // 2) булево значение: true - раскрыть (expand), false - свернуть (collapse) элемент. driver.executeScript("automation: ExpandCollapsePattern.Expand", subjectInfoBlock, true); Thread.sleep(this.delayInterval);

// Снова покажем блок основной информации, чтобы продемонстрировать работу следующего способа. driver.executeScript("automation: ExpandCollapsePattern.Expand", mainInfoBlock, true); Thread.sleep(this.delayInterval);

// 2-й способ. При помощи команды прокрутить до элемента. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! // Аргументы скрипта: // 1) целевой элемент. Если он поддерживает шаблон "ScrollItemPattern" второй аргумент не требуется задавать; // 2) вмещающий элемент, поддерживающий шаблон "ScrollPattern". driver.executeScript("automation: ScrollIntoView", partnerControl, cardPanel); Thread.sleep(this.delayInterval);

// 3-й способ. При помощи установки фокуса на элементе. // Важно помнить, что установка фокуса может быть произведена только на элементах, // которые поддерживают эту возможность (кнопки, поля ввода текста и т.п., но не метки, панели и т.п.). // Внимание!!! В стандартной версии Winium данная команда не поддерживается! // Установим фокус на кнопке, чтобы она стала полностью видимой на экране. driver.executeScript("automation: SetFocus", selectPartnerButton); Thread.sleep(this.delayInterval);

// Теперь когда нужный нам элемент управления точно виден на экране, нажмём на него. this.ElementClickHelper(selectPartnerButton); // Подождём открытия диалогового окна. Thread.sleep(this.delayInterval * 2);

// Создадим и выберем нового контрагента. this.ShowPartnerCreationFromDialog(); Thread.sleep(this.delayInterval);

// Демонстрация сохранения карточки и проверки ошибок. this.ShowCardSaveWithErrorDialog();

// Теперь заполним тему договора. // Получим элемент управления "Тема". WebElement subjectControl = subjectInfoBlock.findElement(By.name("$CardTypes_Blocks_Controls_Subject")); // Получим редактор темы. valueEditor = subjectControl.findElement(By.id("ValueEditor")); // Введём тему "Договор подряда" valueEditor.sendKeys("Договор подряда"); Thread.sleep(this.delayInterval); }

При работе с карточкой, также как и при работе с рабочими местами, идентификаторы и имена основных её элементов можно узнать в TessaAdmin.
Так у вкладки в качестве AutomationId установлено значение поля Название, а в качестве Name - значение поля Заголовок.

У блока в качестве AutomationId установлено значение поля Алиас, а в качестве Name - значение поля Заголовок.

У элемента управления в качестве AutomationId установлено значение поля Алиас, а в качестве Name - значение поля Заголовок.

Поэтому рекомендуется всегда заполнять указанные поля у вкладок, блоков и элементов управления.

Также в примере продемонстрированы различные способы убедиться, что элемент видим на отображаемой части формы.

Перейдём к рассмотрению методов, использующихся в этой части сценария, и начнём с выбора контрагента.

/** * Демонстрация выбора контрагента путём его создания в диалоговом окне. */ private void ShowPartnerCreationFromDialog() throws InterruptedException { // Находим диалог выбора из представлений в рабочих местах. WebElement workplacesViewsDialog = this.mainWindow.findElement(By.id("WorkplacesDialog"));

// Получаем отображаемую в диалоге область представления. WebElement partnersTypeDataView = workplacesViewsDialog.findElement(By.className("DataView"));

// Создадим нового контрагента, которого в последствии и выберем. // Получим кнопку создания карточки, добавляемую на панель кнопок расширением представления. WebElement viewCreateCardButton = partnersTypeDataView.findElement(By.id("ViewCreateCardButton")); // Нажмём на неё для открытия модального диалога создания новой карточки контрагента. this.ElementClickHelper(viewCreateCardButton); // Подождём некоторое время для открытия диалогового окна с редактором карточки. Thread.sleep(this.delayInterval * 2);

// Находим диалог создания карточки. WebElement newPartnerCardDialog = workplacesViewsDialog.findElement(By.xpath("*[@ClassName='Window' and @Name='Новая карточка']")); // Находим редактор карточки. WebElement cardEditor = newPartnerCardDialog.findElement(By.id("CardEditor")); // Теперь введём необходимые данные. // Для этого найдём вкладку с данными карточки (по имени). WebElement partnerTab = cardEditor.findElement(By.name("$CardTypesTabs_Card")); // Убедимся, что вкладка активна. this.ElementClickHelper(partnerTab); // Далее получим панель блоков данных карточки. WebElement cardPanel = partnerTab.findElement(By.id("CardPanel")); // Получим блок с основной информацией карточки. WebElement mainInfoBlock = cardPanel.findElement(By.name("$CardTypes_Blocks_MainInformation"));

// Введём название контрагента "ЗАО "Василёк"" // Получим элемент управления для краткого наименования. WebElement shortNameControl = mainInfoBlock.findElement(By.name("$CardTypes_Controls_ShortName")); // В нём получим редактор для ввода значения. WebElement valueEditor = shortNameControl.findElement(By.id("ValueEditor")); // Введём название контрагента. valueEditor.sendKeys("ЗАО \"Василёк\""); Thread.sleep(this.delayInterval);

// Зададим тип контрагента "Индивидуальный предприниматель" выбрав его из выпадающего списка. // Получим элемент управления для задания типа контрагента. WebElement partnerTypeControl = mainInfoBlock.findElement(By.name("$CardTypes_Controls_PartnerType")); // В нём получим кнопку отображения выпадающего списка. WebElement showPopupButton = partnerTypeControl.findElement(By.id("PopupButton")); // Нажмём на кнопку и откроем список. this.ElementClickHelper(showPopupButton);

// Получим выпадающий список. WebElement partnerTypesList = newPartnerCardDialog.findElement(By.id("PopupList")); // Найдем в списке элемент "Индивидуальный предприниматель" по его идентификатору. WebElement partnerTypeItem = partnerTypesList.findElement(By.id("3")); // Выберем элемент. this.ElementClickHelper(partnerTypeItem);

// Теперь сохраним созданного контрагента и выберем его. // Для этого служит специальная кнопка панели инструментов "Сохранить и выбрать". // Получим панель инструментов. WebElement toolbar = cardEditor.findElement(By.id("Toolbar")); // Получим кнопку "Сохранить и выбрать". // 1-й способ по идентификатору. WebElement saveAndSelectButton = toolbar.findElement(By.id("SaveAndSelect")); // 2-й способ по имени. WebElement saveAndSelectButton2 = toolbar.findElement(By.name("$KrTiles_SaveAndSelect")); // Нажмём кнопку для выполнения действия. this.ElementClickHelper(saveAndSelectButton); }

В методе демонстрируется поиск окна выбора данных из представлений.

Далее поиск и нажатие кнопки создания карточки из представления.

Затем поиск диалога с редактором карточки и заполнение данных о новом контрагенте.

По завершении ввода всех необходимых данных производится сохранение карточки с её выбором при помощи кнопки из верхней панели инструментов.

Далее рассмотрим метод сохранения карточки договора с проверкой возникших ошибок.

Сначала рассмотрим два метода сохранения карточки. Первый сохраняет новую карточку.

/** * Демонстрация сохранения новой карточки нажатием на плитку левой панели. */ private void ShowSaveNewCardByTile() throws InterruptedException { // Откроем левую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // Получим кнопку по её идентификатору. WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftSlider")); // И по её имени. WebElement openLeftPanelSliderButton2 = this.mainWindow.findElement(By.name("LeftSlider"));

// Откроем боковую панель. openLeftPanelSliderButton.click();

Thread.sleep(this.delayInterval);

// Получим её содержимое. WebElement leftCommandPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём там плитку "Сохранить новую". // Получим плитку по её идентификатору. WebElement saveCardTile = leftCommandPanel.findElement(By.id("SaveCard")); // Получим плитку по её имени. WebElement saveCardTile2 = leftCommandPanel.findElement(By.name("Сохранить новую"));

// Нажмём её. this.ElementClickHelper(saveCardTile); }

Второй метод сохраняет уже существующую карточку.

/** * Демонстрация сохранения карточки нажатием на плитку левой панели. */ private void ShowSaveCardByTile() throws InterruptedException { // Откроем левую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // Получим кнопку по её идентификатору. WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftSlider")); // И по её имени. WebElement openLeftPanelSliderButton2 = this.mainWindow.findElement(By.name("LeftSlider"));

// Откроем боковую панель. openLeftPanelSliderButton.click();

Thread.sleep(this.delayInterval);

// Получим её содержимое. WebElement leftCommandPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём там плитку "Сохранить новую". // Получим плитку по её идентификатору. WebElement saveCardTile = leftCommandPanel.findElement(By.id("SaveCard")); // Получим плитку по её имени. WebElement saveCardTile2 = leftCommandPanel.findElement(By.name("Сохранить"));

// Нажмём её. this.ElementClickHelper(saveCardTile); }

Как вы можете видеть, надписи на кнопке сохранения новой и уже существующей карточки отличаются, поэтому и отличаются методы поиска плитки по её имени.

Теперь вернёмся к методу сохранения нашей не заполненной до конца карточки.

/** * Демонстрация сохранения карточки с ошибкой и проверки ошибки. */ private void ShowCardSaveWithErrorDialog() throws InterruptedException { // Демонстрация сохранения карточки нажатием на плитку левой панели. this.ShowSaveNewCardByTile();

// Подождём завершения операции. Thread.sleep(this.delayInterval);

// Поскольку обязательное поле "Тема" не заполнено в результате операции сохранения // карточки будет выведено диалоговое окно с описанием ошибки. // Проконтролируем полученную ошибку.

// Получим окно с информацией об ошибке. WebElement validationResultDialog = this.mainWindow.findElement(By.id("ValidationResultDialog")); // Получим список ошибок. // Внимание! Для получения списка ошибок в различных режимах отображения используются разные идентификаторы: // - для списка ошибок в простом режиме используется "ErrorList". // - для списка ошибок в подробном (расширенном) режиме используется "ErrorListExtended". // Продемонстрируем получение того или иного списка ошибок в зависимости от значения флага "Подробно". // Получим флаг. // 1-й способ по идентификатору. WebElement detailsCheckBox = validationResultDialog.findElement(By.id("DetailsCheckBox")); // 2-й способ по имени WebElement detailsCheckBox2 = validationResultDialog.findElement(By.name("Подробно"));

// Теперь получим список ошибок в зависимости от значения флага "Подробно". WebElement errorList = validationResultDialog.findElement(By.id(detailsCheckBox.isSelected() ? "ErrorListExtended" : "ErrorList")); // Получим первую ошибку в списке. // Ошибки нумеруются по возрастанию с единицы. WebElement firstErrorItem = errorList.findElement(By.id("1"));

// Дважды кликаем на строку, чтобы открыть диалог с расширенными данными. this.ElementDoubleClickHelper(firstErrorItem); // Ждём появления диалога с расширенными данными об ошибке. Thread.sleep(this.delayInterval * 2);

// Получаем окно подробной информации об ошибке. WebElement validationResultItemDialog = validationResultDialog.findElement(By.id("ValidationResultItemDialog")); // Убедимся, что поле "Ключ" равно "NullField" и поле "Имя объекта" равно "DocumentCommonInfo.Subject". // Получим поле "Ключ". WebElement validationKey = validationResultItemDialog.findElement(By.id("ValidationKey")); // Получим поле "Имя объекта". WebElement validationObjectName = validationResultItemDialog.findElement(By.id("ValidationObjectName"));

if (validationKey.getText().equals("NullField") && validationObjectName.getText().equals("DocumentCommonInfo.Subject")) { System.out.println("Expected error."); } else { System.out.println("Unexpected error."); // Здесь могут быть дополнительные действия. }

// Закрываем диалог расширенной информации об ошибке. // Получаем кнопку "ОК" WebElement okButton = validationResultItemDialog.findElement(By.id("OkButton")); // Нажимаем на кнопку. this.ElementClickHelper(okButton);

// Закрываем диалог с перечнем всех ошибок. // Получаем кнопку закрыть диалог. WebElement closeButton = validationResultDialog.findElement(By.id("CloseButton")); // Нажимаем на кнопку. this.ElementClickHelper(closeButton); }

Вначале производится поиск диалога, содержащего список всех обнаруженных ошибок.

Далее в зависимости от значения флага Подробно выполняется поиск непосредственно списка ошибок, в котором двойным кликом мыши открывается первый элемент.

После чего производится поиск диалога, содержащего расширенную информацию, и в ней проверяются значения полей Ключ и Имя объекта.

Перейдём к рассмотрению метода настройки маршрута согласования договора.

/** * Демонстрация настройки и запуска процесса согласования в системе маршрутов. * @param cardEditor - редактор карточки. */ private void ShowApprovalProcess(WebElement cardEditor) throws InterruptedException { // Получим область вкладок карточки. WebElement cardTabs = cardEditor.findElement(By.id("CardTabs")); // Получим вкладку с данными маршрута. // 1-й способ по идентификатору - содержимое поля "Название" вкладки в типе карточки TessaAdmin. WebElement approvalTab = cardTabs.findElement(By.id("ApprovalProcess")); // 2-й способ по имени - содержание поля "Заголовок" в типе карточки TessaAdmin. WebElement approvalTab2 = cardTabs.findElement(By.name("$CardTypesTab_ApprovalProcess"));

// Убеждаемся, что вкладка выбрана. this.ElementClickHelper(approvalTab);

// Далее получаем форму редактора данных вкладки. WebElement form = approvalTab.findElement(By.id("Form")); // Получим панель с блоками (она нам понадобится для прокрутки). WebElement cardPanel = form.findElement(By.id("CardPanel"));

// Получим блок этапов маршрута. // Это можно сделать двумя способами. // 1-й по идентификатору - содержимое поля "Алиас" блока во вкладке TessaAdmin. WebElement approvalStagesBlock = cardPanel.findElement(By.id("ApprovalStagesBlock")); // 2-й по имени - содержимое поля "Заголовок" блока во вкладке TessaAdmin. WebElement approvalStagesBlock2 = cardPanel.findElement(By.name("$CardTypes_Blocks_ApprovalStages"));

// Далее найдём кнопку "Добавить" для добавления этапов маршрута. WebElement addStageButton = approvalStagesBlock.findElement(By.id("AddRowButton")); // Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! // Установим фокус на кнопке, чтобы она стала полностью видимой на экране. driver.executeScript("automation: SetFocus", addStageButton); Thread.sleep(this.delayInterval);

// Нажмём на кнопку, чтобы вызвать диалог добавления этапа. this.ElementClickHelper(addStageButton); // Устанавливаем дополнительную задержку для открытия диалога. Thread.sleep(this.delayInterval);

// Демонстрируем заполнение данных о новом этапе. this.ShowAdditionNewApprovalStage();

// Найдём добавленный этап по его названию. // Получим таблицу данных. WebElement stagesTable = approvalStagesBlock.findElement(By.id("DataTable")); // Далее найдём нужную нам строку с данными по названию этапа "Согласование с регистраторами". WebElement stageItem = stagesTable.findElement(By.xpath("*[@ClassName='GridViewItem' and descendant::*[@AutomationId='Name' and @Name='Согласование с регистраторами']]")); // Двойным кликом откроем диалог редактирования этапа. this.ElementDoubleClickHelper(stageItem); // Добавим дополнительную задержку для открытия диалога. Thread.sleep(this.delayInterval);

// Демонстрация редактирования выбранного этапа. this.ShowEditingApprovalStage();

// Демонстрация запуска процесса по настроенному маршруту. this.ShowProcessExecution(); Thread.sleep(this.delayInterval * 2); }

В этом методе демонстрируется переключение на вкладку Маршрут карточки, навигация к блоку Этапы маршрута, поиск и нажатие кнопки Добавить для создания нового этапа.

Далее производится первичная настройка этапа маршрута и его сохранение. Демонстрируется поиск добавленного этапа в таблице этапов по его названию и повторное открытие для окончательной настройки.

После чего производится запуск процесса согласования.

Рассмотрим метод заполнения данных о добавляемом этапе в диалоге.

В нём заполняются:

  • название;
  • согласующие.

Причём при заполнении согласующих демонстрируется выбор из диалога и автодополнение при вводе названия роли.

/** * Демонстрация добавления нового этапа маршрута. */ private void ShowAdditionNewApprovalStage() throws InterruptedException { // Получим диалоговое окно редактирования строки таблицы. WebElement rowEditorDialog = this.mainWindow.findElement(By.id("CardTableRowEditorDialog")); // Далее получаем форму редактора данных. WebElement form = rowEditorDialog.findElement(By.id("Form")); // Получим панель с блоками (она нам понадобится для прокрутки). WebElement cardPanel = form.findElement(By.id("CardPanel"));

// Заполним основные данные этапа. // Получим блок с основными данными. // Это можно сделать двумя способами. // 1-й по идентификатору. WebElement commonInfoBlock = cardPanel.findElement(By.id("StageCommonInfoBlock")); // 2-й по имени. WebElement commonInfoBlock2 = cardPanel.findElement(By.name("$CardTypes_Blocks_ApprovalPhase"));

// Получим элемент управления для ввода названия этапа. WebElement titleControl = commonInfoBlock.findElement(By.name("$CardTypes_Columns_Controls_Title")); // Получим элемент ввода текста. WebElement valueEditor = titleControl.findElement(By.id("ValueEditor")); // Введём название этапа "Согласование с регистраторами". valueEditor.sendKeys("Согласование с регистраторами"); Thread.sleep(this.delayInterval);

// Получим элемент управления для ввода срока выполнения этапа. WebElement timeLimitControl = commonInfoBlock.findElement(By.id("TimeLimitInput")); // Получим элемент ввода текста. valueEditor = timeLimitControl.findElement(By.id("ValueEditor")); // Введём срок 3 дня. valueEditor.sendKeys("3"); Thread.sleep(this.delayInterval);

// Теперь зададим согласующих для этапа. // Получим блок с данными согласующих. WebElement approvalInfoBlock = cardPanel.findElement(By.name("PerformersBlock")); // Получим элемент управления для ввода согласующих. WebElement approvalControl = approvalInfoBlock.findElement(By.name("$UI_KrPerformersSettings_Approvers"));

// Вначале зададим согласующих путём выбора из диалогового окна представлений рабочих мест. // Получим кнопку выбора из представления. WebElement selectButton = approvalControl.findElement(By.id("SelectButton"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! // Установим фокус на кнопке, чтобы она стала полностью видимой на экране. driver.executeScript("automation: SetFocus", selectButton); Thread.sleep(this.delayInterval);

// Нажмём на кнопку, чтобы вызвать диалог выбора согласующих. this.ElementClickHelper(selectButton); // Дополнительная задержка, чтобы дать диалогу открыться. Thread.sleep(this.delayInterval);

// Демонстрация выбора роли для согласования из представлений рабочих мест. this.ShowSettingUpApprovalRoleFromWorkplacesDialog(rowEditorDialog);

// Теперь добавим ещё одну роль согласования "Регистратор документа", // но сделаем это при помощи ввода с клавиатуры и использования // возможности автодополнения. // Получим элемент для ввода ролей согласования. WebElement autocompleteControl = approvalControl.findElement(By.id("ValueEditor")); // Получим элемент для ввода с клавиатуры. valueEditor = autocompleteControl.findElement(By.id("EntityTextBox")); // Введём в него текст с клавиатуры. new Actions(this.driver) .moveToElement(valueEditor) .click() .sendKeys("Регистр") .build() .perform(); Thread.sleep(this.delayInterval); new Actions(this.driver) .sendKeys("\n") .build() .perform();

// Сохраним наш этап. // Находим кнопку "Сохранить". WebElement saveButton = rowEditorDialog.findElement(By.id("SaveButton")); // Нажимаем её. this.ElementClickHelper(saveButton); }

Рассмотрим выбор роли согласующих из представлений в диалоговом окне. Здесь демонстрируется выбор роли “Регистраторы”.

/** * Демонстрация выбора роли согласования из диалога представлений рабочих мест. * @param parentDialog - родительский диалог. */ private void ShowSettingUpApprovalRoleFromWorkplacesDialog(WebElement parentDialog) throws InterruptedException { // Получим открывшийся диалог выбора роли из рабочих мест. WebElement dialog = parentDialog.findElement(By.id("WorkplacesDialog")); // Мы будем выбирать роль "Регистраторы", которая находится в представлениях рабочего места "Пользователь". // Получим элемент управления вкладок рабочих мест. WebElement tabControl = dialog.findElement(By.id("Workplaces")); // Убедимся, что выбрана вкладка "Пользователь". // Попытаемся найти вкладку "Пользователь". // Внимание! В ряде случаев вкладки могут не отображаться, // поэтому проверить какая из них выбрана не представляется возможным. // Алгоритм проверки ниже, учитывает эту возможность и будет работать // даже если заголовки вкладок не будут видны. // Найдём рабочее место пользователя по идентификатору. List<WebElement> tabs = tabControl.findElements(By.id("Workplace:c3d72683-f6c0-4766-a3d4-1fd9a7fe6827")); // В результате мы получим 1 элемент, если заголовки вкладок есть, и 0 в противном случае. if (tabs.size() == 1) { // Проверим, что вкладка выбрана, и если нет, то выберем её. WebElement tab = tabs.get(0); if (!tab.isSelected()) { this.ElementClickHelper(tab); } } // Получим панель содержимого активной вкладки. WebElement workspace = tabControl.findElement(By.id("WorkplaceContent")); // Получаем дерево рабочего места. WebElement workspaceTree = workspace.findElement(By.id("WorkplaceTree"));

// Получаем элемент "Роли". // 1-й вариант - просто по имени WebElement rolesNode = workspaceTree.findElement(By.name("Роли")); // 2-й вариант - по AutomationId (уникальный идентификатор узла дерева рабочего места в TessaAdmin с префиксом "TreeItem") WebElement rolesNode2 = workspaceTree.findElement(By.id("TreeItem:7e47f15b-15da-47a1-8f78-27b1adf72d31"));

// Получаем подмножество "По типу". // 1-й вариант - по имени WebElement byTypeNode = rolesNode.findElement(By.name("По типу")); // 2-й вариант - по идентификатору WebElement byTypeNode2 = rolesNode.findElement(By.id("TreeItem:RoleTypes"));

// Раскроем подмножество для выбора его элементов. // Найдём кнопку раскрыть/свернуть узел. WebElement expandButton = byTypeNode.findElement(By.id("Expander")); // Убедимся, что узел раскрыт, и если нет, то раскроем его. if (!expandButton.isSelected()) { // Узел свёрнут, раскрываем его. this.ElementClickHelper(expandButton); }

// Находим узел "Статическая роль". // 1-й вариант по идентификатору. WebElement staticRoleNode = byTypeNode.findElement(By.id("TreeItem:0")); // 2-й вариант по имени. WebElement staticRoleNode2 = byTypeNode.findElement(By.name("Статическая роль")); // Выбираем узел. this.ElementClickHelper(staticRoleNode);

// Получаем представление справа от дерева. WebElement dataView = workspace.findElement(By.className("DataView")); // Получаем панель с таблицей. WebElement tableView = dataView.findElement(By.id("TableView")); // Получаем саму таблицу. WebElement dataGrid = tableView.findElement(By.id("DataGrid")); // Находим строку для роли "Регистраторы". // 1-й вариант по идентификатору. WebElement registrarItem = dataGrid.findElement(By.id("RoleID:0071b103-0ffa-49da-8776-53b9c654d815")); // 2-й вариант по имени. WebElement registrarItem2 = dataGrid.findElement(By.name("Регистраторы"));

// Выбираем элемент двойным кликом. this.ElementDoubleClickHelper(registrarItem); }

Далее демонстрируется редактирование этапа маршрута.

/** * Демонстрация редактирования этапа маршрута. */ private void ShowEditingApprovalStage() throws InterruptedException { // Получим диалоговое окно редактирования строки таблицы. WebElement rowEditorDialog = this.mainWindow.findElement(By.id("CardTableRowEditorDialog")); // Получим основную форму диалога. WebElement mainForm = rowEditorDialog.findElement(By.className("CardRowFormView")); // Далее получаем форму редактора данных. WebElement form = mainForm.findElement(By.id("Form")); // Получим панель с блоками (она нам понадобится для прокрутки). WebElement cardPanel = form.findElement(By.id("CardPanel"));

// Получим блок настроек этапа. // Это можно сделать двумя способами. // 1-й по идентификатору. WebElement settingsBlock = cardPanel.findElement(By.id("SettingsBlock")); // 2-й по имени. WebElement settingsBlock2 = cardPanel.findElement(By.name("SettingsBlock"));

// Получим блок настройки параметров. // 1-й по идентификатору. WebElement parametersBlock = settingsBlock.findElement(By.id("ApprovalStageFlags")); // 2-й по имени. WebElement parametersBlock2 = settingsBlock.findElement(By.name("$CardTypes_Blocks_Flags"));

// Убедимся, что параметры видно. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", parametersBlock); Thread.sleep(this.delayInterval);

// Получим элемент управления, содержащий вкладки с настройками. WebElement parametersControl = parametersBlock.findElement(By.id("FlagsTabs")); // Получим непосредственно элемент вкладок. WebElement tabControl = parametersControl.findElement(By.id("TabControl")); // Переходим на вкладку "Основные настройки". // Находим вкладку. // 1-й способ по идентификатору. WebElement mainSettingsTab = tabControl.findElement(By.id("CommonSettings")); // 2-й способ по имени. WebElement mainSettingsTab2 = tabControl.findElement(By.name("$CardTypesTabs_MainSettings")); // Переходим на вкладку. this.ElementClickHelper(mainSettingsTab);

// Получим блок флагов во вкладке основных настроек. // 1-й способ по идентификатору. WebElement stageFlagsBlock = mainSettingsTab.findElement(By.id("StageFlags")); // 2-й способ по имени. WebElement stageFlagsBlock2 = mainSettingsTab.findElement(By.name("$CardTypes_Blocks_Flags"));

// Получаем элемент управления "Параллельный этап". // 1-й способ по идентификатору. WebElement parallelFlagControl = stageFlagsBlock.findElement(By.id("IsParallelFlag")); // 2-й способ по имени. WebElement parallelFlagControl2 = stageFlagsBlock.findElement(By.name("$CardTypes_Columns_Controls_Parallel")); // Получаем элемент управления флажок. WebElement checkBox = parallelFlagControl.findElement(By.id("CheckBox")); // Проверяем, установлен ли флажок, и если нужно устанавливаем его. if (!checkBox.isSelected()) { this.ElementClickHelper(checkBox); }

// Теперь установим дополнительные параметры. // Находим вкладку "Дополнительно". // 1-й способ по идентификатору. WebElement additionalSettingsTab = tabControl.findElement(By.id("AdditionalSettings")); // 2-й способ по имени. WebElement additionalSettingsTab2 = tabControl.findElement(By.name("$CardTypesTabs_AdditionalSettings")); // Переходим на вкладку. this.ElementClickHelper(additionalSettingsTab);

// Получаем блок настроек прав доступа для исполнителей этапа. // 1-й способ по идентификатору. WebElement permissionsBlock = additionalSettingsTab.findElement(By.id("DisclaimerBlock")); // 2-й способ по имени. WebElement permissionsBlock2 = additionalSettingsTab.findElement(By.name("$CardTypes_Blocks_SettingPermissionsForPhaseApprovers"));

// Убедимся, что параметры видно. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", permissionsBlock); Thread.sleep(this.delayInterval);

// Получаем элемент управления "Редактировать карточку". // 1-й способ по идентификатору. WebElement editCardControl = permissionsBlock.findElement(By.id("EditCardFlagControl")); // 2-й способ по имени. WebElement editCardControl2 = permissionsBlock.findElement(By.name("$CardTypes_Columns_Controls_EditCard")); // Получаем элемент управления флажок. checkBox = editCardControl.findElement(By.id("CheckBox")); // Проверяем, установлен ли флажок, и если нужно устанавливаем его. if (!checkBox.isSelected()) { this.ElementClickHelper(checkBox); }

// Получаем элемент управления "Редактировать любые файлы". // 1-й способ по идентификатору. WebElement editFilesControl = permissionsBlock.findElement(By.id("EditFilesFlagControl")); // 2-й способ по имени. WebElement editFilesControl2 = permissionsBlock.findElement(By.name("$CardTypes_Columns_Controls_EditAnyFiles")); // Получаем элемент управления флажок. checkBox = editFilesControl.findElement(By.id("CheckBox")); // Проверяем, установлен ли флажок, и если нужно устанавливаем его. if (!checkBox.isSelected()) { this.ElementClickHelper(checkBox); }

// Поиск кнопки "Закрыть" WebElement closeButton = mainForm.findElement(By.id("CloseButton")); // Нажимаем на кнопку и закрываем диалог. this.ElementClickHelper(closeButton); }

Здесь демонстрируется работа с вкладками Основные настройки и Дополнительно, в которых проверяются флаги Параллельный этап, Редактировать карточку и Редактировать любые файлы. Если флаги ещё не установлены, они устанавливаются, после чего диалог закрывается.

Следующий метод демонстрирует запуск процесса по настроенному маршруту при помощи плитки левой панели инструментов.

/** * Демонстрация запуска процесса по маршруту. */ private void ShowProcessExecution() throws InterruptedException { // Откроем левую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // 1-й вариант по идентификатору. WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftSlider")); // 2-й вариант по имени. WebElement openLeftPanelSliderButton2 = this.mainWindow.findElement(By.name("LeftSlider"));

// Откроем боковую панель. this.ElementClickHelper(openLeftPanelSliderButton);

// Получим её содержимое. WebElement leftCommandPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём там плитку "Запустить процесс". // Получим плитку. // 1-й вариант по идентификатору. WebElement startProcessTile = leftCommandPanel.findElement(By.id("$KrButton_StartProcess")); // 2-й вариант по имени. WebElement startProcessTile2 = leftCommandPanel.findElement(By.name("Запустить процесс"));

// Нажмём её. this.ElementClickHelper(startProcessTile);

// Получим окно диалога подтверждения запуска процесса. WebElement messageBox = this.mainWindow.findElement(By.id("TessaMessageBox")); // Получим кнопку "Да". WebElement yesButton = messageBox.findElement(By.id("YesButton")); // Нажимаем кнопку. this.ElementClickHelper(yesButton); }

Также в методе демонстрируется поиск диалога подтверждения запуска процесса и нажатие в нём кнопки Да.

Далее рассмотрим работу с заданиями, которые отправляются при запуске маршрута. Основной метод представлен ниже.

/** * Демонстрация работы с заданиями. * @param cardEditor - редактор карточек. */ private void ShowTasksHandling(WebElement cardEditor) throws InterruptedException { // Поиск области с заданиями. WebElement taskArea = cardEditor.findElement(By.id("TaskArea"));

// Демонстрация поиска и согласования задания. this.ShowApproveTask(taskArea);

// Демонстрация делегирования задания. this.ShowDelegateTask(taskArea);

// Демонстрация отклонения задания. this.ShowDiscardTask(taskArea); }

Рассмотрим задание согласования, которое завершается согласованием документа.

/** * Демонстрация поиска и согласования задания. * @param taskArea - область заданий карточки. */ private void ShowApproveTask(WebElement taskArea) throws InterruptedException { // Ищем задание "Регистратор документа". WebElement task = taskArea.findElement(By.xpath("*[descendant::*[@AutomationId='DefaultTaskInfo' and descendant::*[@AutomationId='MainInfo' and contains(@Name, 'Регистратор документа')]]]")); // Получим идентификатор задачи. String automationId = task.getAttribute("AutomationId");

// Получаем основную форму задания. WebElement taskWorkspace = task.findElement(By.id("TaskWorkspace")); // Получаем кнопку "В работу". // 1-й вариант по идентификатору. WebElement inProgressButton = taskWorkspace.findElement(By.id("Progress")); // 2-й вариант по имени. WebElement inProgressButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_InProgress"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", inProgressButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы взять задание в работу. this.ElementClickHelper(inProgressButton); Thread.sleep(this.delayInterval);

// Внимание! После взятия задания в работу, происходит обновление карточки, // и всех её элементов, поэтому для продолжения необходимо заново получить // все требуемые элементы автоматизации.

// Получим задание. task = taskArea.findElement(By.id(automationId)); // Получаем основную форму задания. taskWorkspace = task.findElement(By.id("TaskWorkspace"));

// Находим кнопку "Согласовать". // 1-й вариант по идентификатору. WebElement approveButton = taskWorkspace.findElement(By.id("NavigateToForm:Approve")); // 2-й вариант по имени. WebElement approveButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_Approve"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", approveButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы перейти к форме согласования задания. this.ElementClickHelper(approveButton);

// Получаем блок для заполнения. // 1-й вариант по идентификатору. WebElement commentBlock = taskWorkspace.findElement(By.id("CommentBlock")); // 2-й вариант по имени. WebElement commentBlock2 = taskWorkspace.findElement(By.name("$CardTypes_Blocks_FillingByUser"));

// Получаем элемент управления для ввода комментария. WebElement commentControl = commentBlock.findElement(By.name("$CardTypes_Blocks_Controls_Comment")); // Получаем текстовый редактор. WebElement valueEditor = commentControl.findElement(By.id("ValueEditor")); // Вводим комментарий "Да". valueEditor.sendKeys("Да"); Thread.sleep(this.delayInterval);

// Находим кнопку "Согласовать" на форме согласования. // 1-й вариант по идентификатору. approveButton = taskWorkspace.findElement(By.id("Complete:Approve")); // 2-й вариант по имени. approveButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_Approve"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", approveButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы перейти к форме согласования задания. this.ElementClickHelper(approveButton); Thread.sleep(this.delayInterval); }

В этом примере демонстрируется:

  • поиск задания на роль Регистратор документа;
  • прокрутка до этого задания, чтобы оно стало видимым в отображаемой на экране области;
  • поиск и нажатие на кнопку В работу, чтобы перейти к работе с заданием;
  • повторный поиск задания, но уже по его идентификатору, т.к. после взятия задания в работу карточка обновляется, и с ней обновляется и дерево автоматизации;
  • поиск и нажатие в задании кнопки Согласовать;
  • заполнение комментария и согласование документа.

Далее рассмотрим пример делегирования задания.

/** * Демонстрация делегирования задания. * @param taskArea - область заданий. */ private void ShowDelegateTask(WebElement taskArea) throws InterruptedException { // Ищем задание по дайджесту "Этап: Согласование с регистраторами". WebElement task = taskArea.findElement(By.name("Этап: Согласование с регистраторами")); // Получим идентификатор задачи. String automationId = task.getAttribute("AutomationId");

// Получаем основную форму задания. WebElement taskWorkspace = task.findElement(By.id("TaskWorkspace")); // Получаем кнопку "В работу". // 1-й вариант по идентификатору. WebElement inProgressButton = taskWorkspace.findElement(By.id("Progress")); // 2-й вариант по имени. WebElement inProgressButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_InProgress"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", inProgressButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы взять задание в работу. this.ElementClickHelper(inProgressButton); Thread.sleep(this.delayInterval);

// Внимание! После взятия задания в работу, происходит обновление карточки, // и всех её элементов, поэтому для продолжения необходимо заново получить // все требуемые элементы автоматизации.

// Получим задание. task = taskArea.findElement(By.id(automationId)); // Получаем основную форму задания. taskWorkspace = task.findElement(By.id("TaskWorkspace"));

// Получаем кнопку "Ещё". // 1-й вариант по идентификатору. WebElement additionalActionsButton = taskWorkspace.findElement(By.id("AdditionalActions")); // 2-й вариант по имени. WebElement additionalActionsButton2 = taskWorkspace.findElement(By.name("$UI_Tasks_CompletionOptions_More"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", additionalActionsButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы взять задание в работу. this.ElementClickHelper(additionalActionsButton);

// Получаем контекстное меню. WebElement actionsPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём пункт "Делегировать". // 1-й вариант по идентификатору. WebElement delegateItem = actionsPanel.findElement(By.id("NavigateToForm:Delegate")); // 2-й вариант по имени. WebElement delegateItem2 = actionsPanel.findElement(By.name("$UITasks_CompletionOptions_Delegate"));

// Нажимаем на кнопку. this.ElementClickHelper(delegateItem);

// Получаем блок данных заполняемых пользователем. WebElement userDataBlock = taskWorkspace.findElement(By.name("$CardTypes_Blocks_FillingByUser")); // Получаем элемент управления ввода ролей. WebElement roleControl = userDataBlock.findElement(By.name("$CardTypes_Controls_Role")); // Получаем элемент ввода текста роли. WebElement valueEditor = roleControl.findElement(By.id("EntityTextBox")); // Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", valueEditor); Thread.sleep(this.delayInterval); // Вводим текст "Автор документа". //valueEditor.sendKeys("Автор документа"); new Actions(this.driver) .sendKeys("Автор документа") .build() .perform(); Thread.sleep(this.delayInterval); new Actions(this.driver) .sendKeys(Keys.ENTER) .build() .perform();

// Получаем кнопку "Делегировать". // 1-й вариант по идентификатору. WebElement delegateButton = taskWorkspace.findElement(By.id("Complete:Delegate")); // 2-й вариант по имени. WebElement delegateButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_Delegate"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", delegateButton); Thread.sleep(this.delayInterval);

// Нажимаем на неё. this.ElementClickHelper(delegateButton); Thread.sleep(this.delayInterval); }

В этом примере демонстрируется:

  • поиск задания по названию этапа и взятие задания в работу;
  • поиск и нажатие на кнопку ещё;
  • поиск и выбор пункта выпадающего списка Делегировать;
  • заполнение данных о роли и делегирование задания.

Далее рассмотрим, как можно отклонить задание.

/** * Демонстрация поиска и отклонения задания. * @param taskArea - область заданий карточки. */ private void ShowDiscardTask(WebElement taskArea) throws InterruptedException { // Ищем задание "Автор документа". WebElement task = taskArea.findElement(By.xpath("*[descendant::*[@AutomationId='DefaultTaskInfo' and descendant::*[@AutomationId='MainInfo' and contains(@Name, 'Автор документа')]]]")); // Получим идентификатор задачи. String automationId = task.getAttribute("AutomationId");

// Получаем основную форму задания. WebElement taskWorkspace = task.findElement(By.id("TaskWorkspace")); // Получаем кнопку "В работу". // 1-й вариант по идентификатору. WebElement inProgressButton = taskWorkspace.findElement(By.id("Progress")); // 2-й вариант по имени. WebElement inProgressButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_InProgress"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", inProgressButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы взять задание в работу. this.ElementClickHelper(inProgressButton); Thread.sleep(this.delayInterval);

// Внимание! После взятия задания в работу, происходит обновление карточки, // и всех её элементов, поэтому для продолжения необходимо заново получить // все требуемые элементы автоматизации.

// Получим задание. task = taskArea.findElement(By.id(automationId)); // Получаем основную форму задания. taskWorkspace = task.findElement(By.id("TaskWorkspace"));

// Находим кнопку "Не согласовать". // 1-й вариант по идентификатору. WebElement disapproveButton = taskWorkspace.findElement(By.id("NavigateToForm:Disapprove")); // 2-й вариант по имени. WebElement disapproveButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_Disapprove"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", disapproveButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы перейти к форме согласования задания. this.ElementClickHelper(disapproveButton);

// Получаем блок для заполнения. // 1-й вариант по идентификатору. WebElement commentBlock = taskWorkspace.findElement(By.id("CommentBlock")); // 2-й вариант по имени. WebElement commentBlock2 = taskWorkspace.findElement(By.name("$CardTypes_Blocks_FillingByUser"));

// Получаем элемент управления для ввода комментария. WebElement commentControl = commentBlock.findElement(By.name("$CardTypes_Blocks_Controls_Comment")); // Получаем текстовый редактор. WebElement valueEditor = commentControl.findElement(By.id("ValueEditor")); // Вводим комментарий "Нет". valueEditor.sendKeys("Нет"); Thread.sleep(this.delayInterval);

// Находим кнопку "Не согласовать" на форме согласования. // 1-й вариант по идентификатору. disapproveButton = taskWorkspace.findElement(By.id("Complete:Disapprove")); // 2-й вариант по имени. disapproveButton2 = taskWorkspace.findElement(By.name("$UITasks_CompletionOptions_Disapprove"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", disapproveButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы перейти к форме согласования задания. this.ElementClickHelper(disapproveButton); Thread.sleep(this.delayInterval); }

Данный пример полностью аналогичен примеру с согласованием, с той лишь разницей, что документ не согласуется.

В следующем примере рассмотрены возможности по работе с дочерними окнами, открытие по кнопке карточки, поиск и закрытие окна.

/** * Демонстрация визуализации маршрута. * @param cardEditor - редактор карточки. */ private void ShowRouteVisualization(WebElement cardEditor) throws InterruptedException { // Получим область вкладок карточки. WebElement cardTabs = cardEditor.findElement(By.id("CardTabs")); // Получим вкладку с данными маршрута. // 1-й способ по идентификатору - содержимое поля "Название" вкладки в типе карточки TessaAdmin. WebElement approvalTab = cardTabs.findElement(By.id("ApprovalProcess")); // Убеждаемся, что вкладка выбрана. this.ElementClickHelper(approvalTab);

// Далее получаем форму редактора данных вкладки. WebElement form = approvalTab.findElement(By.id("Form")); // Получим панель с блоками (она нам понадобится для прокрутки). WebElement cardPanel = form.findElement(By.id("CardPanel"));

// Получим блок этапов маршрута. WebElement approvalStagesBlock = cardPanel.findElement(By.id("ApprovalStagesBlock"));

// Находим кнопку "Визуализировать." WebElement visualizeButton = approvalStagesBlock.findElement(By.id("$CardTypes_Blocks_Visualize"));

// Убеждаемся, что кнопка видима. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", visualizeButton); Thread.sleep(this.delayInterval);

// Нажмём на кнопку, чтобы вызвать диалог добавления этапа. this.ElementClickHelper(visualizeButton); // Устанавливаем дополнительную задержку для открытия диалога. Thread.sleep(this.delayInterval);

// Находим открывшееся окно. WebElement window = this.mainWindow.findElement(By.xpath("*[@ClassName='Window']")); // Ждём. Thread.sleep(this.delayInterval * 2); // Находим кнопку закрыть. WebElement closeButton = window.findElement(By.id("CloseButton")); // Нажимаем на неё. this.ElementClickHelper(closeButton); }

В примере показано, как открыть окно визуализации маршрута карточки по кнопке Визуализировать.

Далее представлен небольшой метод - помощник получения главной вкладки карточки документа.

/** * Демонстрация получения вкладки с данными карточки. * @param cardEditor - редактор карточки. * @return основная вкладка карточки. */ private WebElement GetCardTab(WebElement cardEditor) { // Получим область вкладок карточки. WebElement cardTabs = cardEditor.findElement(By.id("CardTabs")); // Получим вкладку с данными карточки. WebElement cardTab = cardTabs.findElement(By.id("Contract")); // Возвращаем найденную вкладку. return cardTab; }

Следующий метод демонстрирует обновление карточки при помощи нажатия плитки в левой панели инструментов.

/** * Демонстрация обновления карточки нажатием на плитку левой панели. */ private void ShowRefreshCardByTile() throws InterruptedException { // Откроем левую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // Получим кнопку по её идентификатору. WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftSlider")); // И по её имени. WebElement openLeftPanelSliderButton2 = this.mainWindow.findElement(By.name("LeftSlider"));

// Откроем боковую панель (наведением мыши). this.HoverMouse(openLeftPanelSliderButton); // Проверим, что панель появилась. WebElement leftCommandPanel = null; try { leftCommandPanel = this.mainWindow.findElement(By.className("Popup")); } catch (Exception e) { // Если нет, то делаем дополнительный клик по панели. this.ElementClickHelper(openLeftPanelSliderButton); leftCommandPanel = this.mainWindow.findElement(By.className("Popup")); }

// Найдём там плитку "Обновить". // Получим плитку по её идентификатору. WebElement refreshCardTile = leftCommandPanel.findElement(By.id("RefreshCard")); // Получим плитку по её имени. WebElement refreshCardTile2 = leftCommandPanel.findElement(By.name("Обновить"));

// Нажмём её. this.ElementClickHelper(refreshCardTile); }

Особенностью метода является демонстрация открытия левой панели по наведению на неё мыши, проверки фактического открытия панели, и, в случае, если она ещё не открыта, нажатия на кнопку открытия панели.

Следующий метод демонстрирует переход в режим редактирования карточки при помощи плитки левой панели.

/** * Демонстрация редактирования карточки нажатием на плитку левой панели. */ private void ShowEditCardByTile() throws InterruptedException { // Откроем левую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // Получим кнопку по её идентификатору. WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftSlider")); // И по её имени. WebElement openLeftPanelSliderButton2 = this.mainWindow.findElement(By.name("LeftSlider"));

// Откроем боковую панель (наведением мыши). this.HoverMouse(openLeftPanelSliderButton); // Проверим, что панель появилась. WebElement leftCommandPanel = null; try { leftCommandPanel = this.mainWindow.findElement(By.className("Popup")); } catch (Exception e) { // Если нет, то делаем дополнительный клик по панели. this.ElementClickHelper(openLeftPanelSliderButton); leftCommandPanel = this.mainWindow.findElement(By.className("Popup")); }

// Найдём там плитку "Редактировать". // Получим плитку по её идентификатору. WebElement editCardTile = leftCommandPanel.findElement(By.id("KrEditMode")); // Получим плитку по её имени. WebElement editCardTile2 = leftCommandPanel.findElement(By.name("Редактировать"));

// Нажмём её. this.ElementClickHelper(editCardTile); }

Рассмотрим далее работу с листом согласования.

/** * Демонстрация работы со листом согласования. * @param cardEditor - редактор карточки. */ private void ShowApprovalSheetActions(WebElement cardEditor) throws InterruptedException { // Демонстрация получения вкладки с данными карточки и переключения на неё. WebElement cardTab = this.GetCardTab(cardEditor); // Убеждаемся, что вкладка выбрана. this.ElementClickHelper(cardTab);

// Получим элемент управления со списком файлов. WebElement fileList = this.GetCardTabFileList(cardTab); // Найдём в нём список. WebElement filesListBox = fileList.findElement(By.id("FileControlListBox")); // В этом списке найдём файл "Лист согласования". WebElement approvalSheet = filesListBox.findElement(By.xpath("*[contains(@Name, 'Лист согласования')]")); // Нажимаем на нём правой клавишей мыши, чтобы показать контекстное меню. this.ElementRightClickHelper(approvalSheet);

// Окно контекстного меню. WebElement menuPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём в нём "Список версий". // 1-й способ по идентификатору. WebElement versionsListItem = menuPanel.findElement(By.id("ShowVersions")); // 2-й способ по имени. WebElement versionsListItem2 = menuPanel.findElement(By.name("$UIControls_FilesControl_VersionsList"));

// Нажмём на элемент меню чтобы показать диалог со списком версий файла. this.ElementClickHelper(versionsListItem); // Подождём появления окна. Thread.sleep(this.delayInterval);

// Демонстрация работы с окном версий файла. this.ShowFileVersionsWindow(); }

В данном методе демонстрируется:

  • получение и переход на главную вкладку данных карточки;
  • поиск и навигация к блоку данных Файлы;
  • поиск элемента управления со списком файлов;
  • поиск в нём файла Лист согласования.html по имени;
  • открытие контекстного меню файла нажатием на него правой кнопки мыши;
  • поиск и выбор элемента контекстного меню Список версий;
  • демонстрация работы с диалогом версий файла.

Рассмотрим метод - помощник получения элемента управления со списком файлов и навигации к нему.

/** * Демонстрация получения элемента управления для списка файлов на вкладке. * @param cardTab - вкладка карточки. * @return элемент управления для списка файлов. */ private WebElement GetCardTabFileList(WebElement cardTab) throws InterruptedException { // Получаем область содержимого выбранной вкладки. WebElement tabContent = cardTab.findElement(By.id("Content")); // Далее получаем форму редактора данных вкладки. WebElement form = tabContent.findElement(By.id("Form")); // Получим панель с блоками (она нам понадобится для прокрутки). WebElement cardPanel = form.findElement(By.id("CardPanel"));

// Продемонстрируем работу с файлами. // Получим блок с файлами. // 1-й способ по идентификатору. WebElement filesBlock = cardPanel.findElement(By.id("Block5")); // 2-й способ по имени. WebElement filesBlock2 = cardPanel.findElement(By.name("$CardTypes_Blocks_Controls_Files"));

// Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", filesBlock); Thread.sleep(this.delayInterval);

// Получим элемент управления список файлов. // 1-й способ по идентификатору. WebElement filesControl = filesBlock.findElement(By.id("Files")); // 2-й способ по имени. WebElement filesControl2 = filesBlock.findElement(By.name("$CardTypes_Blocks_Controls_Files"));

// Получим элемент управления со списком файлов. WebElement fileList = filesControl.findElement(By.id("FileList")); // Вернём его. return fileList; }

Поскольку далее мы ещё будем работать со списком файлов, то набор действий поиска и навигации к нужному элементу дерева автоматизации карточки был выделен в отдельный метод.

Далее рассмотрим работу с диалогом версий файла.

/** * Демонстрация работы с окном версий файла. */ private void ShowFileVersionsWindow() throws InterruptedException { // Получим окно. WebElement fileVersionsWindow = this.mainWindow.findElement(By.xpath("*[starts-with(@AutomationId,'FileVersions') and @ClassName='Window']")); // Получим список версий. WebElement versionList = fileVersionsWindow.findElement(By.id("VersionList")); // Найдём в списке элемент "Печатный лист согласования.html". WebElement item = versionList.findElement(By.name("Печатный лист согласования.html"));

// Нажмём правую клавишу мыши, для отображения контекстного меню. this.ElementRightClickHelper(item);

// Окно контекстного меню. WebElement menuPanel = fileVersionsWindow.findElement(By.className("Popup"));

// Найдём в нём "Список версий". // 1-й способ по идентификатору. WebElement saveAsItem = menuPanel.findElement(By.id("SaveVersionAs")); // 2-й способ по имени. WebElement saveAsItem2 = menuPanel.findElement(By.name("$UIControls_FilesControl_SaveAs"));

// Нажмём на элемент меню чтобы показать стандартный диалог сохранения файла. this.ElementClickHelper(saveAsItem); // Подождём появления окна. Thread.sleep(this.delayInterval);

// Демонстрация сохранения файла. this.ShowSaveFileDialog(fileVersionsWindow);

// Получаем кнопку закрытия диалога. WebElement closeButton = fileVersionsWindow.findElement(By.id("CloseButton")); // Нажимаем её и закрываем окно. this.ElementClickHelper(closeButton); }

В данном примере демонстрируется:

  • поиск окна версий файла;
  • поиск в списке версий элемента Печатный лист согласования.html по имени;
  • открытие контекстного меню файла;
  • выбора пункта Сохранить как.

Рассмотрим далее работу со стандартным системным диалогом сохранения файла.

/** * Демонстрация сохранения файла на рабочий стол при помощи стандартного диалога сохранения файла. * @param parentWindow - родительское окно. */ private void ShowSaveFileDialog(WebElement parentWindow) throws InterruptedException { // 1-й способ по имени. WebElement window = parentWindow.findElement(By.name("Сохранить как…")); // 2-й способ по имени класса. WebElement window2 = parentWindow.findElement(By.className("#32770"));

// Получим дерево навигации (в левой части диалога). WebElement navigationTree = window.findElement(By.id("100")); // Получим элемент "Рабочий стол". // Вначале получим элемент "Этот компьютер". WebElement myComputerItem = navigationTree.findElement(By.name("Этот компьютер")); // Убедимся, что элемент раскрыт. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ExpandCollapsePattern.Expand", myComputerItem, true); Thread.sleep(this.delayInterval);

// Находим целевой элемент "Рабочий стол". WebElement desktopItem = myComputerItem.findElement(By.name("Рабочий стол")); // Убедимся, что элемент видим. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", desktopItem);

// Выберем элемент. this.ElementClickHelper(desktopItem);

// Получаем кнопку "Сохранить". // 1-й способ по идентификатору. WebElement saveButton = window.findElement(By.id("1")); // 2-й способ по имени. WebElement saveButton2 = window.findElement(By.name("Сохранить"));

// Нажимаем на кнопку. this.ElementClickHelper(saveButton);

// Если файл с таким именем уже есть, // то выводится окно подтверждения сохранения файла. try { // Попробуем найти это окно. WebElement confirmWindow = window.findElement(By.xpath("*[@Name='Сохранить как…']")); // Получим кнопку "Да". WebElement yesButton = confirmWindow.findElement(By.id("6")); // Нажмём её. this.ElementClickHelper(yesButton);

} catch (Exception e) { // Если мы здесь, то окна нет. // Ничего делать не нужно. } }

В данном методе демонстрируется:

  • поиск окна диалога сохранения файла;
  • навигация к директории рабочего стола;
  • поиск и нажатие кнопки Сохранить диалога;
  • проверка окна подтверждения сохранения файла (если файл с таким именем уже есть);
  • нажатие кнопки Да в диалоге подтверждения сохранения файла.

Следующий метод демонстрирует регистрацию договора нажатием на плитку Зарегистрировать в левой панели.

/** * Демонстрация регистрации договора. */ private void ShowContractRegistration() throws InterruptedException { // Откроем левую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // 1-й вариант по идентификатору. WebElement openLeftPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:LeftSlider")); // 2-й вариант по имени. WebElement openLeftPanelSliderButton2 = this.mainWindow.findElement(By.name("LeftSlider"));

// Откроем боковую панель. openLeftPanelSliderButton.click();

Thread.sleep(this.delayInterval);

// Получим её содержимое. WebElement leftCommandPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём там плитку "Действия". // Получим плитку. // 1-й вариант по идентификатору. WebElement actionsTile = leftCommandPanel.findElement(By.id("ActionsGrouping")); // 2-й вариант по имени. WebElement actionsTile2 = leftCommandPanel.findElement(By.name("Действия"));

// Наводим курсор мыши на элемент, чтобы отобразить меню второго уровня. this.HoverMouse(actionsTile);

// Получим меню второго уровня. WebElement secondLevelPanel = leftCommandPanel.findElement(By.className("Popup"));

// Получим плитку "Зарегистрировать документ" из меню второго уровня. WebElement registerTile = secondLevelPanel.findElement(By.id("$KrButton_RegisterDocument"));

// Нажмём её. this.ElementClickHelper(registerTile);

// Получим окно диалога подтверждения запуска процесса. WebElement messageBox = this.mainWindow.findElement(By.id("TessaMessageBox")); // Получим кнопку "Да". WebElement yesButton = messageBox.findElement(By.id("YesButton")); // Нажимаем кнопку. this.ElementClickHelper(yesButton); }

Также в методе демонстрируется работа с диалогом подтверждения регистрации документа.

Рассмотрим далее операции по добавлению и редактированию файла.

/** * Демонстрация операций по добавлению и редактированию файла. * @param cardEditor - редактор карточки. * @param fileName - имя файла. */ private void ShowCardFileEditing(WebElement cardEditor, String fileName) throws InterruptedException { WebElement cardTab; // Получим вкладку с данными карточки. cardTab = this.GetCardTab(cardEditor); // Получим элемент управления со списком файлов. WebElement fileList = this.GetCardTabFileList(cardTab);

// Демонстрация добавления файла. this.ShowFileAddition(fileList, fileName);

// Получим список файлов. WebElement filesListBox = fileList.findElement(By.id("FileControlListBox")); // Найдём в нём только что добавленный файл. WebElement pdfSample = filesListBox.findElement(By.name(fileName)); // Нажимаем на нём правой клавишей мыши, чтобы показать контекстное меню. this.ElementRightClickHelper(pdfSample);

// Окно контекстного меню. WebElement menuPanel = this.mainWindow.findElement(By.className("Popup")); // Найдём в нём "Редактировать изображения". // 1-й способ по идентификатору. WebElement item = menuPanel.findElement(By.id("EditImages")); // 2-й способ по имени. WebElement item2 = menuPanel.findElement(By.name("$UIControls_FilesControl_EditImages"));

// Нажмём на элемент меню чтобы показать диалог со списком версий файла. this.ElementClickHelper(item); // Подождём появления окна. Thread.sleep(this.delayInterval * 8);

// Демонстрация редактирования PDF документа. this.ShowEditImagesDialog(); // Подождём закрытия окна. Thread.sleep(this.delayInterval * 8); }

В данном методе демонстрируется:

  • переход к главной вкладке карточки;
  • переход к элементу управления список файлов;
  • добавление указанного файла в карточку;
  • поиск добавленного файла в списке;
  • вызов контекстного меню файла нажатием на нём правой кнопки мыши;
  • поиска в контекстном меню элемента Редактировать изображения;
  • работа с редактором изображений.

Рассмотрим далее добавление нового файла в карточку.

/** * Демонстрация добавления файла. * @param fileList - элемент управления список файлов. * @param fileName - имя файла. */ private void ShowFileAddition(WebElement fileList, String fileName) throws InterruptedException { // Получаем кнопку меню. WebElement menuButton = fileList.findElement(By.id("MenuButton")); // Нажимаем на неё. this.ElementClickHelper(menuButton);

// Получим контекстное меню. WebElement menuPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём элемент "Загрузить файлы". // 1-й вариант по идентификатору. WebElement item = menuPanel.findElement(By.id("Upload")); // 2-й вариант по имени. WebElement item2 = menuPanel.findElement(By.name("$UIControls_FilesControl_UploadFiles"));

// Выберем элемент. this.ElementClickHelper(item); // Подождём открытия стандартного диалога открытия файлов. Thread.sleep(this.delayInterval);

// Демонстрация выбора файла. this.ShowOpenFileDialog(this.mainWindow, fileName); }

В методе демонстрируется:

  • поиск и нажатие на кнопку действий списка файлов;
  • выбор в контекстном меню пункта Загрузить файлы;
  • работа с диалогом выбора файла.

Далее демонстрируется работа со стандартным диалогом выбора файла.

/** * Демонстрация открытия файла с рабочего стола при помощи стандартного диалога выбора файла. * @param parentWindow - родительское окно. * @param fileName - имя файла. */ private void ShowOpenFileDialog(WebElement parentWindow, String fileName) throws InterruptedException { // 1-й способ по имени. WebElement window = parentWindow.findElement(By.name("Открытие")); // 2-й способ по имени класса. WebElement window2 = parentWindow.findElement(By.className("#32770"));

// Получим дерево навигации (в левой части диалога). WebElement navigationTree = window.findElement(By.id("100")); // Получим элемент "Рабочий стол". // Вначале получим элемент "Этот компьютер". WebElement myComputerItem = navigationTree.findElement(By.name("Этот компьютер")); // Убедимся, что элемент раскрыт. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: ExpandCollapsePattern.Expand", myComputerItem, true); Thread.sleep(this.delayInterval);

// Находим целевой элемент "Рабочий стол". WebElement desktopItem = myComputerItem.findElement(By.name("Рабочий стол")); // Убедимся, что элемент видим. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! // driver.executeScript("automation: ScrollItemPattern.ScrollIntoView", desktopItem);

// Выберем элемент. this.ElementClickHelper(desktopItem);

// Теперь выберем нужный файл. // Получим элемент управления со списком файлов. WebElement fileList = window.findElement(By.id("listview")); // Найдём в нём нужный нам файл. WebElement item = fileList.findElement(By.name(fileName)); // Выберем его. this.ElementClickHelper(item);

// Получаем кнопку "Открыть". // 1-й способ по идентификатору. WebElement openButton = window.findElement(By.id("1")); // 2-й способ по имени. WebElement openButton2 = window.findElement(By.name("Открыть"));

// Нажимаем на кнопку. this.ElementClickHelper(openButton2); }

В данном методе демонстрируется:

  • поиск окна выбора файла;
  • переход к директории Рабочий стол;
  • поиск файла с заданным именем в директории;
  • поиск и нажатие кнопки Открыть.

Следующий метод демонстрирует работу с диалогом редактирования документа.

/** * Демонстрация редактирования PDF документа. */ private void ShowEditImagesDialog() throws InterruptedException { // Находим окно редактирования документа. WebElement window = this.mainWindow.findElement(By.xpath("*[@Name='Редактирование документа' and @ClassName='Window']")); // Получаем основную рабочую область диалога. WebElement scanDialog = window.findElement(By.id("ScanDialog")); // Получаем список страниц. WebElement pagesList = scanDialog.findElement(By.id("PagesList")); // Получим вторую страниц. WebElement page = pagesList.findElement(By.id("02")); // Выбираем страницу. this.ElementClickHelper(page);

// Находим кнопку поворота страницы по часовой стрелке. WebElement rotateRightButton = scanDialog.findElement(By.id("RotateRightButton")); // Нажимаем на кнопку дважды. this.ElementClickHelper(rotateRightButton); this.ElementClickHelper(rotateRightButton);

// Находим список типов документов. WebElement documentTypes = scanDialog.findElement(By.id("DocumentTypes")); // Нажимаем на список, чтобы раскрыть его. this.ElementClickHelper(documentTypes);

// Находим элемент "формат документа PDF (со штампом)" // 1-й способ по идентификатору. WebElement item = documentTypes.findElement(By.id("WithStamp")); // 2-й способ по имени. WebElement item2 = documentTypes.findElement(By.name("формат документа PDF (со штампом)"));

// Выбираем элемент. this.ElementClickHelper(item);

// Находим кнопку "Сохранить и закрыть". // 1-й способ по идентификатору. WebElement saveAndCloseButton = scanDialog.findElement(By.id("SaveAndCloseButton")); // 2-й способ по имени. WebElement saveAndCloseButton2 = scanDialog.findElement(By.name("Сохранить и закрыть"));

// Нажимаем на кнопку. this.ElementClickHelper(saveAndCloseButton); }

В методе демонстрируется:

  • поиск окна редактирования;
  • поиск и выбор второй страницы документа;
  • поворот страницы по часовой стрелке на 180 градусов при помощи нажатия на соответствующую кнопку;
  • выбор из выпадающего списка элемента формат документа PDF (со штампом);
  • поиск и нажатие кнопки Сохранить и закрыть.

Рассмотрим далее работу с предварительным просмотром изменённого нами файла.

/** * Демонстрация открытия файла для предварительного просмотра. * @param cardEditor - редактор карточки. * @param fileName - имя файла. */ private void ShowFilePreviewActions(WebElement cardEditor, String fileName) throws InterruptedException { WebElement cardTab; // Получим вкладку с данными карточки. cardTab = this.GetCardTab(cardEditor); // Получим элемент управления со списком файлов. WebElement fileList = this.GetCardTabFileList(cardTab); // Найдём в нём список. WebElement filesListBox = fileList.findElement(By.id("FileControlListBox")); // В списке файлов найдём только что добавленный файл. WebElement pdfSample = filesListBox.findElement(By.name(fileName));

// Выбираем файл, для активации предварительного просмотра. this.ElementClickHelper(pdfSample); // Ждём некоторое время загрузки файла в предварительный просмотр. Thread.sleep(this.delayInterval * 4);

// Демонстрация работы с предварительным просмотром файлов. this.ShowPreviewActions(cardTab); }

Данный метод демонстрирует получение и навигацию к списку файлов, поиск в нём файла по имени и переход к предварительному просмотру файла.

Перейдём к рассмотрению действий, которые можно выполнить с файлом в режиме его предварительного просмотра.

/** * Демонстрация работы с предварительным просмотром файлов. * @param cardTab - содержимое текущей выбранной вкладки в карточке. */ private void ShowPreviewActions(WebElement cardTab) throws InterruptedException { // Получаем область содержимого выбранной вкладки. WebElement tabContent = cardTab.findElement(By.id("Content")); // Получаем область предварительного просмотра файлов. WebElement filePreview = tabContent.findElement(By.id("FilePreview")); // Получаем элемент управления предварительного просмотра. WebElement previewControl = filePreview.findElement(By.id("PreviewControl")); // Получаем текущий режим просмотра. WebElement currentView = previewControl.findElement(By.id("ScrollingView"));

// Получаем кнопку перехода к постраничному просмотру. WebElement pagingModeButton = currentView.findElement(By.id("PagingModeButton")); // Нажимаем на кнопку, чтобы изменить режим просмотра. this.ElementClickHelper(pagingModeButton);

// Получаем обновлённый режим просмотра. currentView = previewControl.findElement(By.id("PagingView"));

// Получаем кнопку перехода на следующую страницу. WebElement nextPageButton = currentView.findElement(By.id("NextPageButton")); // Нажимаем на кнопку для перехода. this.ElementClickHelper(nextPageButton);

// Получаем кнопку поворота страницы против часовой стрелки. WebElement rotateLeftButton = currentView.findElement(By.id("RotateLeftButton")); // Дважды нажимаем для возвращения страницы в исходный вид. this.ElementClickHelper(rotateLeftButton); this.ElementClickHelper(rotateLeftButton);

// Получаем кнопку "Вписать". WebElement fitToScreenButton = currentView.findElement(By.id("FitToScreenButton")); // Нажимаем на кнопку. this.ElementClickHelper(fitToScreenButton); }

В данном методе демонстрируется:

  • поиск элемента управления предварительного просмотра файла;
  • переход в режим постраничного просмотра при помощи нажатия на соответствующую кнопку;
  • переход ко второй странице документа при помощи кнопки Перейти к следующей странице;
  • поворот страницы против часовой стрелки на 180 градусов при помощи соответствующей кнопки;
  • поиск и нажатие кнопки вписать, чтобы убедиться что страница перевёрнута и штамп расположен внизу.

Далее продемонстрируем работу с обсуждениями. Основной метод приведён ниже.

/** * Демонстрация работы с обсуждениями. * @param cardEditor - редактор карточки. */ private void ShowForumsActions(WebElement cardEditor) throws InterruptedException { // Получим область вкладок карточки. WebElement cardTabs = cardEditor.findElement(By.id("CardTabs"));

// Получим вкладку с обсуждениями. // 1-й способ по идентификатору. WebElement forumsTab = cardTabs.findElement(By.id("Forum")); // 2-й способ по имени. WebElement forumsTab2 = cardTabs.findElement(By.name("$ForumTabs_Discussions"));

// Переходим на вкладку. this.ElementClickHelper(forumsTab);

// Демонстрация создания нового топика в обсуждениях. this.ShowForumNewTopicCreation(forumsTab);

// Демонстрация создания и отправки нового сообщения в обсуждениях. this.ShowForumSendMessage(forumsTab); Thread.sleep(this.delayInterval); }

В методе демонстрируется:

  • поиск и переход на вкладку Обсуждения;
  • создание нового обсуждения;
  • создание и отправка нового сообщения.

Рассмотрим создание нового обсуждения.

/** * Демонстрация создания нового обсуждения. * @param forumsTab - вкладка карточки "обсуждения". */ private void ShowForumNewTopicCreation(WebElement forumsTab) throws InterruptedException { // Получаем область содержимого выбранной вкладки. WebElement form = forumsTab.findElement(By.id("Form"));

// Получаем блок данных обсуждений. WebElement forumBlock = form.findElement(By.id("ControlsBlock:Topics")); // Получаем элемент управления отображения обсуждений. // 1-й способ по идентификатору. WebElement forumControl = forumBlock.findElement(By.id("Topics")); // 2-й способ по имени. WebElement forumControl2 = forumBlock.findElement(By.name("$Forum_Controls_Discussion"));

// Получаем форму отображения отсутствия обсуждений. WebElement emptyForum = forumControl.findElement(By.id("ForumEmptyView")); // Получаем кнопку "Добавить обсуждение". // 1-й способ по идентификатору. WebElement addTopicButton = emptyForum.findElement(By.id("AddTopicButton")); // 2-й способ по имени. WebElement addTopicButton2 = emptyForum.findElement(By.name("Добавить обсуждение"));

// Убеждаемся, что кнопка видна на экране. // Внимание!!! В стандартной версии Winium данная команда не поддерживается! driver.executeScript("automation: SetFocus", addTopicButton); Thread.sleep(this.delayInterval);

// Нажимаем на кнопку, чтобы добавить обсуждение. this.ElementClickHelper(addTopicButton);

// Демонстрация работы с диалогом добавления топика. this.ShowTopicAdditionDialog(); Thread.sleep(this.delayInterval); }

В методе демонстрируется:

  • поиск блока с данными обсуждений;
  • поиск и нажатие кнопки Добавить обсуждение;
  • работа с диалогом создания обсуждения.

Рассмотрим работу с диалогом создания обсуждения.

/** * Демонстрация работы с диалогом создания обсуждения. * @throws InterruptedException */ private void ShowTopicAdditionDialog() throws InterruptedException { // Получим окно добавления обсуждения. WebElement window = this.mainWindow.findElement(By.xpath("*[@ClassName='Window' and @Name='Добавить обсуждение']")); // Получим основную форму диалога. WebElement form = window.findElement(By.id("Form")); // Получим панель данных формы. WebElement cardPanel = form.findElement(By.id("CardPanel"));

// Получим блок основных настроек обсуждения. WebElement mainInfoBlock = cardPanel.findElement(By.id("AddTopicBlock")); // Получим элемент управления для ввода заголовка обсуждения. WebElement titleControl = mainInfoBlock.findElement(By.name("$Forum_Controls_Title")); // Получим текстовый редактор для ввода. WebElement valueEditor = titleControl.findElement(By.id("ValueEditor"));

// Введём называние обсуждения "Документ". valueEditor.sendKeys("Документ"); Thread.sleep(this.delayInterval);

// Получим кнопку "Сохранить". // 1-й способ по идентификатору. WebElement saveButton = window.findElement(By.id("$Forum_Dialog_Button_Save")); // 2-й способ по имени. WebElement saveButton2 = window.findElement(By.name("Сохранить"));

// Нажмём на кнопку, чтобы создать новое обсуждение. this.ElementClickHelper(saveButton); }

В методе демонстрируется:

  • поиск окна диалога добавления обсуждения;
  • ввод заголовка;
  • поиск и нажатие кнопки Сохранить для создания обсуждения.

Рассмотрим метод создания и отправки нового сообщения в обсуждение.

/** * Демонстрация создания нового сообщения в обсуждении. * @param forumsTab - вкладка карточки "обсуждения". */ private void ShowForumSendMessage(WebElement forumsTab) throws InterruptedException { // Получаем область содержимого выбранной вкладки. WebElement form = forumsTab.findElement(By.id("Form"));

// Получаем блок данных обсуждений. WebElement forumBlock = form.findElement(By.id("ControlsBlock:Topics")); // Получаем элемент управления отображения обсуждений. // 1-й способ по идентификатору. WebElement forumControl = forumBlock.findElement(By.id("Topics")); // 2-й способ по имени. WebElement forumControl2 = forumBlock.findElement(By.name("$Forum_Controls_Discussion"));

// Получаем форму текущего обсуждения. WebElement currentDiscussion = forumControl.findElement(By.className("TopicControlView")); // Получаем форму для ввода нового сообщения. WebElement messageForm = currentDiscussion.findElement(By.id("RichTextBox")); // Получаем текстовый редактор для ввода сообщения. WebElement valueEditor = messageForm.findElement(By.id("TextBox"));

// Вводим текст "Успешно зарегистрирован". valueEditor.sendKeys("Успешно зарегистрирован");

// Выделяем введённый текст. new Actions(this.driver) .sendKeys(Keys.CONTROL+"a"+Keys.CONTROL) .build() .perform();

// Получаем выпадающий список доступных размеров шрифта. WebElement fontSizes = messageForm.findElement(By.id("FontSizes")); // Раскрываем его. this.ElementClickHelper(fontSizes); // Получим его содержимое. WebElement fontSizesPanel = this.mainWindow.findElement(By.className("Popup")); // Получим элемент "18". WebElement item = fontSizesPanel.findElement(By.name("18")); // Установим его. this.ElementClickHelper(item);

// Получим кнопку цвета шрифта. WebElement foregroundColorButton = messageForm.findElement(By.id("ForegroundColorButton")); // Получим кнопку выбора цветов. WebElement showPaletteButton = foregroundColorButton.findElement(By.id("ShowPalette")); // Нажмём её для отображения палитры цветов. this.ElementClickHelper(showPaletteButton);

// Получим панель выбора цветов. WebElement colorPalettePanel = this.mainWindow.findElement(By.className("Popup")); // Получим основную форму палитры. WebElement colorPalette = colorPalettePanel.findElement(By.id("ColorPalette")); // Получим список доступных цветов. WebElement colors = colorPalette.findElement(By.id("Colors")); // Получим и выберем синий цвет. item = colors.findElement(By.name("#FF0D63A3")); this.ElementClickHelper(item);

// Получим кнопку "Отправить". // 1-й способ по идентификатору. WebElement sendButton = messageForm.findElement(By.id("SendButton")); // 2-й способ по имени. WebElement sendButton2 = messageForm.findElement(By.name("Отправить")); // Нажмём кнопку для отправки нашего сообщения. this.ElementClickHelper(sendButton); Thread.sleep(this.delayInterval);

// Получим кнопку "Назад к обсуждениям". WebElement backButton = currentDiscussion.findElement(By.id("BackToTopicsButton")); // Нажмём её. this.ElementClickHelper(backButton); }

В методе демонстрируются: - поиск элемента управления ввода текста сообщения;
- ввод текста сообщения и его выделение при помощи отправки сочетания клавиш Ctrl+A;
- изменение размера шрифта на 18-й;
- изменение цвета текста на синий;
- отправка получившегося сообщения нажатием на кнопку Отправить;
- возврат к списку обсуждений нажатием на кнопку Назад.

Рассмотрим метод изменения фонового изображения рабочего стола TessaClient на один из стандартных.

/** * Демонстрация изменения фона рабочего стола. * @param wallpaperId - идентификатор целевого изображения. */ private void ShowWallpaperChangeByTile(String wallpaperId) throws InterruptedException { // Откроем левую панель команд при помощи кнопок-слайдеров по бокам основной рабочей области. // Здесь также существует два варианта получения кнопки. // 1-й вариант по идентификатору. WebElement openPanelSliderButton = this.mainWindow.findElement(By.id("TessaPanels:RightSlider")); // 2-й вариант по имени. WebElement openPanelSliderButton2 = this.mainWindow.findElement(By.name("RightSlider"));

// Откроем боковую панель (наведением мыши). this.ElementClickHelper(openPanelSliderButton); // Проверим, что панель появилась. WebElement commandPanel = this.mainWindow.findElement(By.className("Popup"));

// Найдём там плитку "Фон". // Получим плитку. // 1-й вариант по идентификатору. WebElement wallpaperTile = commandPanel.findElement(By.id("SelectWallpaper")); // 2-й вариант по имени. WebElement wallpaperTile2 = commandPanel.findElement(By.name("Фон"));

// Наводим курсор мыши на элемент, чтобы отобразить меню второго уровня. this.HoverMouse(wallpaperTile);

// Получим меню второго уровня. WebElement secondLevelPanel = commandPanel.findElement(By.className("Popup"));

// Получим плитку с нужным нам фоном из меню второго уровня. WebElement targetWallpaperTile = secondLevelPanel.findElement(By.id(wallpaperId));

// Нажмём её. this.ElementClickHelper(targetWallpaperTile);

// Закрываем панель. this.ElementClickHelper(openPanelSliderButton); }

Главный метод

Теперь когда все сценарии работы с различными частями TessaClient рассмотрены, приведём главный метод, запускающий сценарии.

/** * Запустить тестирование. * @throws MalformedURLException */ public void Run() throws MalformedURLException, InterruptedException { // Инициализация драйвера, поиск главного окна приложения. this.Initialize(); // Изменение фонового рисунка приложения. this.ShowWallpaperChangeByTile("Wallpaper1"); // Демонстрация работы с рабочими местами. this.ShowUserWorkplaceOperations(); // Демонстрация работы с карточкой. this.ShowUserCardOperations(); // Изменение фонового рисунка приложения. this.ShowWallpaperChangeByTile("Wallpaper7"); // Завершение работы. this.Finalize(); }

Пример автоматизации на языке C#

Данный пример демонстрирует открытие вкладки с новым рабочим местом TessaClient.

Для работы сценария потребуются:

В этом примере, помимо работы с Winium будут продемонстрированы возможности по использованию автоматизации, например, для задач обучения пользователей в режиме живой демонстрации работы с приложением.

Вначале рассмотрим ряд вспомогательных классов, которые нужны для отображения подсказок.

Класс - помощник работы с диспетчером очереди сообщений для WPF

Рассмотрим класс - помощник по работе с диспетчером.

using System.Threading; using System.Threading.Tasks; using System.Windows.Threading;

namespace demo { /// <summary> /// Помощник создания и удаления диспетчера для работы WPF приложения. /// </summary> public static class DispatcherHelper { private static TaskCompletionSource dispatcherIsShutdown; private static Dispatcher dispatcher;

/// <summary> /// Диспетчер. /// Может быть <c>null</c>, если не запущен. /// </summary> public static Dispatcher Dispatcher => dispatcher;

/// <summary> /// Запускает диспетчер. /// </summary> public static void Run() { // Диспетчер уже запущен. if (dispatcher != null) { return; } dispatcherIsShutdown = new(); var thread = new Thread(() => { SynchronizationContext.SetSynchronizationContext( new DispatcherSynchronizationContext(dispatcher = Dispatcher.CurrentDispatcher));

try { Dispatcher.Run(); } finally { dispatcherIsShutdown.SetResult(); } });

thread.SetApartmentState(ApartmentState.STA); thread.Start();

SpinWait.SpinUntil(() => dispatcher != null); }

/// <summary> /// Асинхронно завершает диспетчер. /// </summary> /// <returns>Задача для ожидания.</returns> public static async Task ShutdownAsync() { if (dispatcher is null) { return; } await dispatcher.InvokeAsync(dispatcher.InvokeShutdown); await dispatcherIsShutdown.Task; dispatcher = null; } } }

Класс безрамочного окна для отображения подсказок

Рассмотрим XAML файл разметки окна TooltipWindow.xaml.

<Window x:Class="demo.TooltipWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:demo" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="TooltipWindow" AllowsTransparency="True" Background="Transparent" FontSize="14" ShowActivated="False" ShowInTaskbar="False" SizeToContent="WidthAndHeight" Topmost="True" WindowStyle="None" mc:Ignorable="d"> <Border Padding="15" Background="LightBlue" BorderBrush="Gray" BorderThickness="1" CornerRadius="10"> <Grid x:Name="MainLayout"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock x:Name="Message" Margin="0,5" Text="Some text..." TextAlignment="Center" TextWrapping="Wrap" /> <Grid x:Name="ButtonsPanel" Grid.Row="1" /> </Grid> </Border> </Window>

Теперь рассмотрим класс поддержки данного окна.

using System; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Threading;

namespace demo { /// <summary> /// Позиции указывающие где нужно расположить окно /// относительно целевого элемента. /// </summary> public enum Placements { /// <summary> /// Расположить окно слева от элемента. /// </summary> Left,

/// <summary> /// Расположить окно справа от элемента. /// </summary> Right,

/// <summary> /// Расположить окно над элементом. /// </summary> Top,

/// <summary> /// Расположить окно под элементом. /// </summary> Bottom,

/// <summary> /// Расположить окно в центре элемента. /// </summary> Center, };

/// <summary> /// Дескриптор кнопки окна. /// </summary> /// <param name="Text">Текст кнопки, должен быть не пустым.</param> /// <param name="Code">Код завершения, должен быть уникальным.</param> public record ButtonDescriptor(string Text, int Code);

/// <summary> /// Interaction logic for TooltipWindow.xaml /// </summary> public partial class TooltipWindow : Window { private TaskCompletionSource<int> windowClosedSource = new(); private DispatcherTimer timer;

/// <summary> /// Создаёт новое окно. /// </summary> public TooltipWindow() { InitializeComponent(); }

/// <summary> /// Показать окно с заданным текстом, в заданном положении относительно указанной области. /// </summary> /// <param name="text">Отображаемый текст.</param> /// <param name="targetRect">Целевой прямоугольник.</param> /// <param name="placement">Относительная позиция для отображения окна (по умолчанию - в центре).</param> /// <param name="showTime">Время отображения окна в миллисекундах.</param> /// <param name="buttonDescriptors">Дескрипторы кнопок окна.</param> /// <returns>Задача которая выполнится при закрытии окна.</returns> public static async Task<int> ShowAsync( string text, Rect targetRect, Placements placement = Placements.Center, int showTime = 0, ButtonDescriptor[] buttonDescriptors = null) { TooltipWindow window = await DispatcherHelper.Dispatcher.InvokeAsync(() => { var window = new TooltipWindow(); window.Message.Text = text; window.Loaded += (o, e) => { window.SetWindowPosition(placement, targetRect); if (window.timer != null) { window.timer.Start(); } }; if (showTime > 0) { window.timer = new(); window.timer.Interval = TimeSpan.FromMilliseconds(showTime); window.timer.Tick += (o, e) => { window.timer.Stop(); window.CloseHelper(); }; } if (buttonDescriptors != null && buttonDescriptors.Length > 0) { for (int i = 0; i < buttonDescriptors.Length; ++i) { window.ButtonsPanel.ColumnDefinitions.Add(new ColumnDefinition()); } for (int i = 0; i < buttonDescriptors.Length; ++i) { var buttonDescriptor = buttonDescriptors[i]; var button = new Button(); button.Content = buttonDescriptor.Text; button.Click += (o, e) => { window.CloseHelper(buttonDescriptor.Code); }; button.SetValue(Grid.ColumnProperty, i); button.Margin = new Thickness(1); window.ButtonsPanel.Children.Add(button); } } window.Show(); return window; }); return await window.windowClosedSource.Task; }

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) => this.CloseHelper();

private void SetWindowPosition(Placements placement, Rect targetRect) { double halfElementWidth = targetRect.Width * 0.5, halfElementHeight = targetRect.Height * 0.5; double cx = targetRect.X + halfElementWidth, cy = targetRect.Y + halfElementHeight; double halfWindowWidth = this.ActualWidth * 0.5, halfWindowHeight = this.ActualHeight * 0.5; switch (placement) { case Placements.Left: this.Left = cx - halfElementWidth - this.ActualWidth; this.Top = cy - halfWindowHeight; break; case Placements.Right: this.Left = cx + halfElementWidth; this.Top = cy - halfWindowHeight; break; case Placements.Top: this.Left = cx - halfWindowWidth; this.Top = cy - halfElementHeight - this.ActualHeight; break; case Placements.Bottom: this.Left = cx - halfWindowWidth; this.Top = cy + halfElementHeight; break; case Placements.Center: this.Left = cx - halfWindowWidth; this.Top = cy - halfWindowHeight; break; default: this.Left = cx + halfElementWidth; this.Top = cy + halfElementHeight; break; } }

private void CloseHelper(int resultCode = 0) { this.Close(); this.windowClosedSource.SetResult(resultCode); } } }

Главным методом, который необходимо использовать для отображения окна является ShowAsync.
Он позволяет отобразить окно с заданным текстом (аргумент text) относительно (аргумент placement по умолчанию в центре элемента) переданного положения элемента (аргумент targetRect).
По умолчанию закрытие окна производится нажатием на нём левой кнопки мыши.
Для автоматического закрытия окна, можно установить время отображения окна (аргумент showTime).
Также окно предусматривает использование в качестве диалогового окна, в этом случае нужно передать описания отображаемых кнопок (аргумент buttonDescriptors). Обратите внимание на то, что у каждой кнопки должно быть задано имя и уникальный код завершения диалога. Именно он будет возвращён при закрытии диалога при помощи кнопки. Необходимо также отметить, что если диалог был закрыт нажатием левой кнопки мыши в свободную область или по таймеру, то будет возвращён код 0.

Каркас класса для тестирования

Рассмотрим класс для тестирования. В нём приведены все необходимые импорты, базовые свойства и конструктор. Все методы, которые мы будем рассматривать, принадлежат данному классу и вы можете помещать их в приведённый ниже каркас для получения полной версии класса.

using System; using System.Threading; using System.Threading.Tasks; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Winium;

namespace demo { /// <summary> /// Класс для тестирования TessaClient. /// </summary> public class Tester { /// <summary> /// Интервал задержки в миллисекундах. /// </summary> private int delayInterval;

/// <summary> /// Интервал задержки отображения подсказки в миллисекундах. /// </summary> private int displayTooltipInterval;

/// <summary> /// Путь к запускаемому приложению. /// </summary> private readonly string applicationPath;

/// <summary> /// Драйвер Winium. /// </summary> private WiniumDriver driver;

/// <summary> /// Главное окно TessaClient. /// </summary> private IWebElement mainWindow;

/// <summary> /// Создаёт экземпляр класса для тестирования TessaClient. /// </summary> /// <param name="delay">Задержка в миллисекундах.</param> /// <param name="tooltipDelay">Задержка отображения окон с подсказками в миллисекундах.</param> /// <param name="applicationPath">Путь к запускаемому приложению (может быть пустым, если приложение уже запущено).</param> public Tester(int delay = 500, int tooltipDelay = 4000, string applicationPath = null) { this.delayInterval = delay; this.displayTooltipInterval = tooltipDelay; this.applicationPath = applicationPath ?? string.Empty; } } }

Метод инициализации

В методе инициализации выполняется:

  • настройка подключения к Winium.Desktop.Driver-у;
  • установка специфичных для Winium настроек, регламентирующих, необходимо ли подключаться к уже запущенному приложению, или же его требуется сначала запустить;
  • поиск главного окна приложения по части имени его заголовка.

/// <summary> /// Инициализация драйвера подключения и поиск главного окна приложения. /// </summary> private void Initialize() { this.driver = new WiniumDriver( new Uri("http://localhost:9999"), new DesktopOptions() { ApplicationPath = this.applicationPath, DebugConnectToRunningApp = string.IsNullOrEmpty(this.applicationPath) });

this.mainWindow = this.driver.FindElement(By.XPath("/*[@ClassName='Window' and starts-with(@Name, 'TessaClient')]")); }

Методы помощники

Вначале рассмотрим помощник наведения курсора мыши на переданный элемент.

/// <summary> /// Помощник наведения мыши на элемент. /// </summary> /// <param name="element">Элемент.</param> private async Task HoverMouseAsync(IWebElement element) { new Actions(this.driver) .MoveToElement(element) .Build() .Perform(); await Task.Delay(this.delayInterval); }

Далее помощник по нажатию левой кнопки мыши на элементе.

/// <summary> /// Помощник клика по элементу (наведение, задержка, клик, задержка). /// </summary> /// <param name="element">Элемент.</param> private async Task ElementClickAsync(IWebElement element) { await this.HoverMouseAsync(element); element.Click(); await Task.Delay(this.delayInterval); }

Рассмотрим помощник получения ограничивающего прямоугольника для элемента.

/// <summary> /// Помощник получения ограничивающего прямоугольника для элемента. /// </summary> /// <param name="element">Элемент.</param> /// <returns>Ограничивающий прямоугольник.</returns> private static Rect GetRect(IWebElement element) { int[] dims = element .GetAttribute("BoundingRectangle") .Split(",") .Select(s => int.Parse(s)) .ToArray(); return new Rect(dims[0], dims[1], dims[2], dims[3]); }

Рассмотрим помощник закрытия вкладки.

/// <summary> /// Демонстрация закрытия указанной вкладки. /// </summary> /// <param name="tab">Вкладка.</param> private async Task CloseTabAsync(IWebElement tab) { // Продемонстрируем как закрыть вкладку. // Для этого получим кнопку закрытия вкладки. // Но, поскольку кнопка скрыта, вначале, нужно навести курсор на вкладку. await TooltipWindow.ShowAsync( "Продемонстрируем как закрыть вкладку.\nДля этого получим кнопку закрытия вкладки.\nНо, поскольку кнопка скрыта, вначале, нужно навести курсор на вкладку.", GetRect(tab), Placements.Bottom, this.displayTooltipInterval); await this.HoverMouseAsync(tab); // Теперь находим кнопку, она должна появиться в дереве автоматизации. var tabCloseButton = tab.FindElement(By.Id("TabItem:CloseButton")); await TooltipWindow.ShowAsync( "Нажмём на появившуюся кнопку.", GetRect(tabCloseButton), Placements.Bottom, this.displayTooltipInterval); // И нажимаем на неё. await this.ElementClickAsync(tabCloseButton); }

Главный метод

В методе продемонстрированы возможности по использованию автоматизации для целей обучения. Перед выполнением действий, требующих внимания пользователя, на экране показывается соответствующая всплывающая подсказка.

В конце сценария пользователю задаётся вопрос о необходимости повторного показа, и в случае положительного ответа сценарий воспроизводится повторно.

/// <summary> /// Главный метод для тестирования. /// </summary> public async Task RunAsync() { try { // Инициализируем драйвер и находим главное окно приложения. this.Initialize();

await TooltipWindow.ShowAsync( "Сейчас мы подключились к основному окну TessaClient.", GetRect(this.mainWindow), showTime: this.displayTooltipInterval);

bool showAgain = false; do { // Находим кнопку открытия рабочих мест. var workplacesButton = this.mainWindow.FindElement(By.Name("WorkplacesButton")); await Task.Delay(this.delayInterval);

await TooltipWindow.ShowAsync( "Нажмём на кнопку, чтобы получить\nсписок доступных рабочих мест.", GetRect(workplacesButton), Placements.Bottom, this.displayTooltipInterval);

// Нажимаем на неё, чтобы открыть список доступных рабочих мест. await this.ElementClickAsync(workplacesButton);

// В открывшемся меню находим рабочее место "Пользователь". var openUserWorkplaceMenuElement = this.mainWindow.FindElement(By.Name("$WorkplacesUser"));

await TooltipWindow.ShowAsync( "Выберем рабочее место.", GetRect(openUserWorkplaceMenuElement), Placements.Right, this.displayTooltipInterval);

// Открываем его. await this.ElementClickAsync(openUserWorkplaceMenuElement);

// Находим открытое рабочее место. Т.к. их может быть много, находим последнее. var workspaceTab = this.mainWindow.FindElement( By.XPath("*[@AutomationId='TabControl']/*[@ClassName='TabItem' and @AutomationId='Workplace:c3d72683-f6c0-4766-a3d4-1fd9a7fe6827'][last()]"));

await TooltipWindow.ShowAsync( "Перейдём к вкладке\nоткрытого рабочего места.", GetRect(workspaceTab), Placements.Bottom, this.displayTooltipInterval);

// Переходим в рабочее место. // Внимание, это действие не обязательно, т.к. // при открытии нового рабочего места оно активируется автоматически. await this.ElementClickAsync(workspaceTab);

// Получим основное содержимое вкладки. IWebElement workspaceContent = workspaceTab.FindElement(By.Id("WorkplaceContent"));

await this.HoverMouseAsync(workspaceContent);

await TooltipWindow.ShowAsync( "Немного подождём эмулируя\nнекую работу пользователя.", GetRect(workspaceContent), Placements.Center, this.displayTooltipInterval);

// Закрываем только что открытую вкладку рабочего места. await this.CloseTabAsync(workspaceTab);

var descriptors = new[] { new ButtonDescriptor("Да", 0), new ButtonDescriptor("Нет", 1), }; showAgain = (await TooltipWindow.ShowAsync( "Хотите повторить сценарий?", GetRect(this.mainWindow), Placements.Center, buttonDescriptors: descriptors)) == 0; } while (showAgain); } finally { if (this.driver != null) { this.driver.Quit(); } } }

Рассмотрим как запустить главный метод сценария.

using System.Threading.Tasks;

namespace demo { class Program { static async Task Main(string[] args) { // Запустить и запомнить диспетчер. DispatcherHelper.Run(); try { // Создать и запустить сценарий с настройками по умолчанию. await new Tester().RunAsync(); } finally { // Остановить диспетчер. await DispatcherHelper.ShutdownAsync(); } } } }

В сценарии демонстрируется:

  • инициализация драйвера;
  • поиск главного окна приложения;
  • поиск и нажатие кнопки открытия рабочих мест;
  • выбор элемента с нужным рабочим местом;
  • поиск открытого рабочего места;
  • ожидание;
  • закрытие рабочего места;
  • использование системы подсказок (например, для повтора сценария).

Пример автоматизации на языке Python

Данный пример демонстрирует открытие вкладки с новым рабочим местом TessaClient.

Для работы сценария потребуются:

Внимание! Для работы примера необходим Python версии 3.7.

Каркас класса для тестирования

Рассмотрим класс для тестирования. В нём приведены все необходимые импорты, базовые свойства и конструктор. Все методы, которые мы будем рассматривать, принадлежат данному классу и вы можете помещать их в приведённый ниже каркас для получения полной версии класса.

import string from selenium import webdriver from selenium.webdriver import ActionChains import time

class Tester: """ Класс для тестирования TessaClient """ def __init__(self, delay: int = 1, applicationPath: string = "") -> None: """ Конструктор

Параметры --------- delay : int задержка в секундах applicationPath : string путь к приложению (пустой, если подключаемся к уже запущенному приложению) """ self.delayInterval = delay self.applicationPath = applicationPath

Методы инициализации и финализации

В методе инициализации выполняется:

  • настройка подключения к Selenium.WebDriver-у;
  • установка специфичных для Winium настроек, регламентирующих, необходимо ли подключаться к уже запущенному приложению, или же его требуется сначала запустить;
  • поиск главного окна приложения по части имени его заголовка.

def initialize(self) -> None: """ Инициализация драйвера подключения и поиск главного окна приложения. """ self.driver = webdriver.Remote( command_executor='http://localhost:9999', desired_capabilities={ "debugConnectToRunningApp": (not self.applicationPath), "app": self.applicationPath, }) # Если запускаем приложение выставляем задержку, чтобы оно запустилось. if self.applicationPath: time.sleep(self.delayInterval * 2) # Находим главное окно приложения. self.window = self.driver.find_element_by_xpath("/*[@ClassName='Window' and starts-with(@Name, 'TessaClient')]")

В методе финализации производится освобождение занятых ресурсов драйвера.

def finalize(self) -> None: """ Закрытие приложения, если мы запускали приложение. """ self.driver.quit()

Методы - помощники

Рассмотрим помощник закрытия вкладки.

def closeTab(self, tab) -> None: """ Демонстрация закрытия указанной вкладки.

Параметры --------- tab : WebElement вкладка """ # Теперь продемонстрируем как закрыть вкладку. # Для этого получим кнопку закрытия вкладки. # Но, поскольку кнопка скрыта, вначале, нужно навести курсор на вкладку. actions = ActionChains(self.driver) actions.move_to_element(tab) actions.perform()

# Теперь находим кнопку, она должна появиться в дереве автоматизации. closeButton = tab.find_element_by_id("TabItem:CloseButton")

time.sleep(self.delayInterval)

# И нажимаем на неё. closeButton.click()

Главный метод

def run(self) -> None: """ Главный метод для тестирования. """ # Инициализируем драйвер и находим главное окно приложения. self.initialize()

# Находим кнопку открытия рабочих мест. workplacesButton = self.window.find_element_by_name("WorkplacesButton") time.sleep(self.delayInterval) # Нажимаем на неё, чтобы открыть список доступных рабочих мест. workplacesButton.click() time.sleep(self.delayInterval) # В открывшемся меню находим рабочее место "Пользователь". openUserWorkplaceMenuElement = self.window.find_element_by_name("$WorkplacesUser") # Открываем его. openUserWorkplaceMenuElement.click() time.sleep(self.delayInterval)

# Находим открытое рабочее место. Т.к. их может быть много, находим последнее. workspaceTab = self.window.find_element_by_xpath("*[@AutomationId='TabControl']/*[@ClassName='TabItem' and @AutomationId='Workplace:c3d72683-f6c0-4766-a3d4-1fd9a7fe6827'][last()]") # Переходим в рабочее место. # Внимание, это действие не обязательно, т.к. # при открытии нового рабочего места оно активируется автоматически. workspaceTab.click() time.sleep(self.delayInterval)

# Закрываем только что открытую вкладку рабочего места. self.closeTab(workspaceTab)

# Завершаем работу. Закрываем приложение, если это необходимо. self.finalize()

В этом методе демонстрируется:

  • инициализация драйвера;
  • поиск главного окна приложения;
  • поиск и нажатие кнопки открытия рабочих мест;
  • выбор элемента с нужным рабочим местом;
  • поиск открытого рабочего места;
  • ожидание;
  • закрытие рабочего места.

Пример автоматизации на языке JavaScript

Данный пример демонстрирует открытие вкладки с новым рабочим местом TessaClient.

Для работы сценария потребуются:

Каркас класса для тестирования

Рассмотрим класс для тестирования. В нём приведены все необходимые импорты, базовые свойства и конструктор. Все методы, которые мы будем рассматривать, принадлежат данному классу и вы можете помещать их в приведённый ниже каркас для получения полной версии класса.

"use strict"; const { Builder, By, Key, until } = require("selenium-webdriver");

/** * Класс для тестирования TessaClient. */ class Tester { /** * Создаёт экземпляр класса для тестирования TessaClient. * @param {number} delay Задержка в миллисекундах. * @param {string} applicationPath Путь к запускаемому приложению (может быть пустым, если приложение уже запущено). */ constructor(delay = 500, applicationPath = "") { this.delayInterval = delay; this.applicationPath = applicationPath; } }

Метод инициализации

В методе инициализации выполняется:

  • настройка подключения к Selenium.WebDriver-у;
  • установка специфичных для Winium настроек, регламентирующих, необходимо ли подключаться к уже запущенному приложению, или же его требуется сначала запустить;
  • поиск главного окна приложения по части имени его заголовка.

/** * Инициализация драйвера подключения и поиск главного окна приложения. */ async initialize() { this.driver = await new Builder() .usingServer("http://localhost:9999") .withCapabilities({ app: this.applicationPath, debugConnectToRunningApp: !this.applicationPath, platformName: "Windows", deviceName: "WindowsPC", }) .forBrowser("windows") .build();

await sleep(this.delayInterval); this.window = await this.driver.findElement( By.xpath("/*[@ClassName='Window' and starts-with(@Name, 'TessaClient')]") ); }

Методы - помощники

Рассмотрим помощник закрытия вкладки.

/** * Демонстрация закрытия указанной вкладки. * @param {WebElement} tab Вкладка. */ async closeTab(tab) { // Продемонстрируем как закрыть вкладку. // Для этого получим кнопку закрытия вкладки. // Но, поскольку кнопка скрыта, вначале, нужно навести курсор на вкладку. const actions = this.driver.actions(); await actions.mouseMove(tab).perform(); // Теперь находим кнопку, она должна появиться в дереве автоматизации. const tabCloseButton = await tab.findElement(By.id("TabItem:CloseButton")); // И нажимаем на неё. await tabCloseButton.click(); }

Рассмотрим помощник ожидания заданное количество времени.

/** * Задержать выполнение потока на указанное время. * @param {number} ms Временная задержка в миллисекундах. * @returns асинхронная функция задержки выполнения. */ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }

Главный метод

/** * Главный метод для тестирования. */ async run() { try { // Инициализируем драйвер и находим главное окно приложения. await this.initialize();

// Находим кнопку открытия рабочих мест. const workplacesButton = await this.window.findElement( By.name("WorkplacesButton") ); await sleep(this.delayInterval); // Нажимаем на неё, чтобы открыть список доступных рабочих мест. await workplacesButton.click(); await sleep(this.delayInterval);

// В открывшемся меню находим рабочее место "Пользователь". const openUserWorkplaceMenuElement = await this.window.findElement( By.name("$WorkplacesUser") ); // Открываем его. await openUserWorkplaceMenuElement.click(); await sleep(this.delayInterval);

// Находим открытое рабочее место. Т.к. их может быть много, находим последнее. const workspaceTab = await this.window.findElement( By.xpath( "*[@AutomationId='TabControl']/*[@ClassName='TabItem' and @AutomationId='Workplace:c3d72683-f6c0-4766-a3d4-1fd9a7fe6827'][last()]" ) ); // Переходим в рабочее место. // Внимание, это действие не обязательно, т.к. // при открытии нового рабочего места оно активируется автоматически. await workspaceTab.click(); await sleep(this.delayInterval);

// Закрываем только что открытую вкладку рабочего места. await this.closeTab(workspaceTab); } finally { // Завершаем работу. Закрываем приложение, если это необходимо. if (this.driver) { await this.driver.quit(); } } }

В этом методе демонстрируется:

  • инициализация драйвера;
  • поиск главного окна приложения;
  • поиск и нажатие кнопки открытия рабочих мест;
  • выбор элемента с нужным рабочим местом;
  • поиск открытого рабочего места;
  • ожидание;
  • закрытие рабочего места.

Программа обучения пользователя работе с TessaClient

В данном разделе демонстрируется ещё одна область применения возможностей автоматизации TessaClient - обучение пользователя работе с системой, для чего был разработан каркасный проект программы обучения.

Note

Для работы с проектом необходим .NET 6.0.

Представим его основные возможности:

  • различные виды окон:
    • главное окно управления обучением с кнопками Меню и Выход;
    • информационное окно без указания элемента;
    • окно с вопросом к пользователю;
    • окно подсветки элементов пользовательского интерфейса;
    • информационное окно с указанием элемента;
    • окно выбора сценария обучения;
    • окно повторения последней операции;
  • автопостроение меню с выбором сценариев обучения исходя из сценариев, присутствующих в проекте;
  • различные варианты запуска сценариев:
    • до основного цикла обучения;
    • сценарий обучения;
    • после основного цикла обучения;
    • сценарий, исключенный из меню, но который можно программно запустить из другого сценария;
  • гибкие механизмы отслеживания выполнения действий с возможностью повторения неудачного действия;
  • гибкие механизмы построения окон с подсказками пользователю.

Краткая инструкция по работе с проектом

Для работы вам потребуется скачать Winium.Desktop.Driver версии не ниже 2.1.1 и распаковать архив.

Настройка для запуска

Все настройки программы обучения пользователя содержатся в файле app.json.

Настройка Описание Пример
DriverPath Полный путь к драйверу Winium, включая имя файла. Заполняется только если нужно запускать драйвер. При подключении к уже запущенному драйверу заполнять не требуется. Если вы не уверены, запущен драйвер или нет, заполните это поле "DriverPath": "..\\Winium.Desktop.Driver.2.1.1\\Winium.Desktop.Driver.exe",
ClientPath Полный путь к TessaClient, включая имя файла. Не заполняется только если требуется подключиться к уже запущенному TessaClient "ClientPath": "..\\..\\TessaClient.exe",
UserName Логин пользователя. Заполняется, если необходимо запустить TessaClient для указанного пользователя. Связан с ClientPath "UserName": "1111",
Password Пароль пользователя. Заполняется, если необходимо запустить TessaClient для указанного пользователя. Если совпадает с UserName, то может не заполняться. Связан с ClientPath и UserName "Password": "1111",
GeneralDelay Базовая задержка действий автоматизации в формате времени (по умолчанию 0.5 секунд). Меняя данную настройку, вы можете ускорять или замедлять действия, выполняемые автоматически "GeneralDelay": "00:00:00.5",
DialogDelay Базовая задержка отображения окна с подсказкой пользователю в формате времени (по умолчанию 1 секунда). Меняя данную настройку, вы можете ускорять или замедлять время отображения подсказок пользователю "DialogDelay": "00:00:01",
MaxInteractionDelay Максимальное время ожидания действия пользователя в формате времени (по умолчанию 5 минут). Меняя данную настройку, вы можете управлять временем ожидания выполнения пользователем требуемых действий "MaxInteractionDelay": "00:05:00"

Note

Все указанные здесь задержки (GeneralDelay, DialogDelay, MaxInteractionDelay) являются базовыми. В программе задаются коэффициенты (множители) для них. Поэтому изменяя значения здесь, можно повлиять на воспроизведение всего сценария.

Для некоторых параметров файла app.json определены соответствующие параметры командной строки.
Для всех аргументов используется стандартный, принятый в TESSA, синтаксис <Ключ>:<Значение>.
Возможно также использовать синтаксис <Ключ>=<Значение>.

Настройка Аргумент командной строки
DriverPath -d
ClientPath -c
UserName -u
Password -p

Структура проекта и разработка сценариев

Все сценарии должны располагаться в директории Guides и реализовывать интерфейс IGuide. Рекомендуется наследоваться от абстрактного базового класса Guide, который содержит базовую реализацию некоторых деталей интерфейса IGuide. Примеры сценариев вы также найдёте в указанной директории.

Рекомендуем при написании сценариев не менять никакой код снаружи директории Guides. При необходимости создавайте статические классы помощники и другие вспомогательные файлы в директории Guides и её дочерних директориях.

Note

Всё, что находится вне директории Guides, считается инфраструктурными компонентами и может быть заменено в следующей версии каркасного проекта. Чтобы защитить свои наработки от изменений при обновлении, располагайте их в пользовательской директории Giudes.

Рассмотрим содержимое проекта по директориям.

Корневая директория

В корневой директории содержатся классы общего назначения.

Класс Описание
IGuide Интерфейс, который необходимо реализовать в каждом сценарии. В противном случае ваш сценарий не будет запущен. Свойство Order задаёт порядок выполнения сценария и размещения его в меню. Не забудьте задать правильный порядок! Свойство Caption определяет имя пункта в меню
Guide Базовый абстрактный класс для всех сценариев. Наследуйте свои сценарии от него
GuideState Перечисление с основными вариантами запуска сценариев. Каждый сценарий должен указывать требуемый ему вариант запуска
GuideHelper Основной класс помощников для пользовательских сценариев
GuideProvider Провайдер для запуска сценариев. Отвечает за всю инфраструктурную информацию. Запускает драйвер и клиент, подключается к драйверу и находит главное окно клиента. Содержит информацию по задержкам операций
GuideDiscardException Ожидаемая операция не выполнена пользователем
DispatcherHelper Статический помощник по работе с UI потоком WPF. Вся работа с этим классом скрыта от файлов сценариев
Program Главный класс приложения. Не модифицируйте его
Options Содержит считанные из файла app.json настройки приложения. Вам не требуется работать с этим классом

Директория Extensions

Содержит классы расширений.

Класс Описание
GuideProviderExtensions Расширения для класса GuideProvider. Если нужно добавить новую функциональность для провайдера, размещайте её в одноимённом классе в папке Guides
WebElementExtensions Расширения для класса WebElement. Если нужно добавить новую функциональность для IWebElement, размещайте её в одноимённом классе в папке Guides
SystemParametersExtensions Расширения для класса SystemParameters. Класс содержит методы и свойства по работе с разрешением и размерами рабочих столов

Директория Windows

Содержит классы окон и классы помощники работы с ними. Далее описаны только те классы, которые вам нужно будет использовать в сценариях. Остальные классы являются служебными.

Класс Описание
Highlighter Помощник по отображению окна визуального выделения (подсветки) элемента или прямоугольной области. Одновременно может подсветить одну и более областей. Обратите внимание, что окно с подсветкой отображается поверх элемента, поэтому элемент будет терять фокус!
Placement Перечисление с основными положениями окна относительно элемента
ArrowPlacement Перечисление для определения положения указывающей стрелки в дополнении к базовому положению
ButtonDescriptor Запись, описывающая кнопку для отображения в окне с подсказкой в режиме диалога
TooltipOptionsBuilder Помощник по построению настроек отображения окна с подсказкой. Рекомендуется использовать этот построитель везде, где необходимо предоставить настройки отображения окна с подсказкой. Вручную создавать класс обычно не требуется, т.к. методы ShowTooltipAsync передают лямбда-выражение для конфигурирования экземпляра этого класса
Tooltip Помощник по отображению окна подсказки
StandardButtons Помощник, содержащий стандартные кнопки (“Готово”, “Да” и “Нет”). Размещайте дескрипторы собственных кнопок в одноимённом классе в папке Guides

Директория Guides

Содержит классы сценариев. Размещайте классы сценариев и любые вспомогательные классы здесь.

Класс Описание
IntroGuide Сценарий приветствия с обучением по типам подсказок
ClientWindowGuide Сценарий обучения работы с окном TessaClient
BackgroundChangeGuide Сценарий обучения по смене фона
MyPreferencesGuide Сценарий обучения работе с “Моими настройками”
ConclusionGuide Заключительный сценарий. Вызывается после завершения обучения

Сборка проекта для запуска на компьютере конечного пользователя

Данная программа предназначена для работы на компьютере конечного пользователя.
Для работы программы вне зависимости от наличия на компьютере конечного пользователя .NET 6.0, необходимо собрать программу с настройками, копирующими необходимые компоненты среды времени выполнения.

Обратите внимание, что нужно собирать программу для архитектур x64 и x86.

dotnet publish -c Release -r win-x64 --sc -o bin\publish\x64 dotnet publish -c Release -r win-x86 --sc -o bin\publish\x86

Краткое типовое руководство пользователя

Для работы с программой обучения необходимо выполнить следующие шаги.
Обратите внимание, что первые два шага нужно выполнять только при первом развёртывании или обновлении программы обучения. Для повторного запуска уже развёрнутой программы обучения необходимо начинать с шага 3 представленного ниже алгоритма.

  1. Скачать и распаковать архив cs-user-guide (выполняется разово при первой установке или при обновлении).
  2. Скачать и распаковать архив Winium.Desktop.Driver (выполняется разово при первой установке или при обновлении).
  3. Запустить Winium.Desktop.Driver.exe из папки в п.2.
  4. Через Tessa Applications запустить TessaClient. Минимальная поддерживаемая версия TESSA 3.6.0.9.
  5. Запустить cs-user-guide.exe из папки в п.1.
  6. Пройти сценарий, по завершению нажать кнопку Выход.
  7. Закрыть окна TessaClient и Winium.Desktop.Driver.

При прохождении обучения вы всегда можете вернуться в меню выбора сценария обучения, нажав в панели управления обучением кнопку Меню.

Каркасный проект

Ссылка для скачивания каркасного проекта программы обучения.

Back to top