3.5. Синхронизация выполнения скриптов

Назад: 3.4 Использование именования (Namespaces) и псевдонимов (Aliases) Содержание Дальше: 3.6 Использование хранилищ (Stores) и контрольных точек

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

Например, вы можете записать скрипт, в котором при нажатии на какую-то кнопку выполняется выборка данных из базы с небольшим количеством записей. После того, как выборка сделана, появляется окно с результатами поиска. Допустим, что на выборку было потрачено 5 секунд. После этого этот же скрипт запускается на другой базе, с гораздо большим количеством записей, и процесс выборки занимает 20 секунд. TestComplete не может знать о том, что используется другая база, и через 5 секунд, не дождавшись появления окна (как это произошло при записи), он сообщит об ошибке и прекратит выполнение скрипта (или попытается продолжить выполнение скрипта дальше, в зависимости от настроек TestComplete, однако так как окно не появилось, работать с ним некак, а это, в свою очередь, вызовет новое сообщение об ошибке).

Возможна и другая ситуация. Предположим, что у вас в окне есть десяток элементов управления с почти одинаковыми свойствами (например, 10 кнопок без текста), однако только одна из них доступна (Enabled, т.е. может быть нажата). Мы ничего не знаем о том, какая именно из этих 10ти кнопок доступна, но нажать нам надо именно на нее. При первом запуске приложения (и записи скрипта) может быть доступна первая кнопка, а при следующем запуске – пятая или седьмая. Каким образом нам выбрать именно ту кнопку, которая нужна нам в момент выполнения скрипта?

Для решения подобных проблем в TestComplete есть несколько методов:

  • Wait… методы окон и элементов управления (WaitWindow, WaitVCLWindow, WaitWinFormsObject, WaitVBObject и т.п.). Обратите внимание, что методы Wait существуют для любых объектов (например, объекту Panel соответствует методWaitPanel, объекту Button – метод WaitButton и т.д.)
  • Wait… методы для свойств и дочерних элементов окон (WaitChild, WaitProperty)
  • Методы для поиска объектов (FindChild, FindAll, FindAllChildren, FindId)

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

Теперь рассмотрим несколько примеров использования этих методов на примере тестирования Калькулятора. Для начала запишем скрипт, который выбирает в Калькуляторе пункт меню Help – About и затем закрывает окно About.

function Test1()
{
  var  calc;
  calc = Sys.Process(“calc”);
  calc.Window(“SciCalc”, “Calculator Plus”).MainMenu.Click(“Help|About Calculator Plus”);
  calc.Window(“#32770″, “About Calculator Plus”).Close();
}

Теперь предположим, что окно About появляется не мгновенно, а может появиться как через секунду, так и через 5 или 7 секунд и нам необходимо как-то ждать его появления. Для этого мы воспользуемся методом WaitWindow.

function Test1()
{
  var  calc;
  calc = Sys.Process(“calc”);
  calc.Window(“SciCalc”, “Calculator Plus”).MainMenu.Click(“Help|About Calculator Plus”);
 
  if( !calc.WaitWindow(“#32770″, “About Calculator Plus”, -1, 10000).Exists)
  {
    Log.Error(“Окно About не открылось!”);
  }
  else
  {
    calc.Window(“#32770″, “About Calculator Plus”).Close();
  }
}

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

Объекту Window мы передавали 2 параметра: класс окна (“#32770”) и его заголовок (“About Calculator Plus”). В метод WaitWindow, в дополнение к этим двум параметрам, мы передаем еще два: порядковый номер окна (или его позиция, так называемый Z-порядок) и таймаут в миллисекундах (то есть время ожидания окна). В нашем случае порядковый номер равен -1 (если номер окна меньше единицы, то он просто игнорируется TestComplete-ом), а время ожидания равно 10 тысячам миллисекунд, т.е. 10ти секундам.

Метод WaitWindow возвращает объект, аналогичный объекту Window. У этого объекта есть свойства и методы, которые можно использовать, и в данном случае мы как раз воспользовались одним из свойств – Exists, который возвращает true, если объект существует, и false в противном случае.

Таким образом, если в течение 10ти секунд окно About не появится, то отработает первый блок IFа, в котором выдается сообщение об ошибке, а если окно появится – то отработает второй блок IFа и окно About будет закрыто.

Обратите внимание на некоторые особенности, которые обычно вызывают сложности у начинающих:

  1. Метод WaitWindow возвращает объект, а не проверяет, существует ли окно. То есть, следующий код будет неправильным:
    if( !calc.WaitWindow(“#32770″, “About Calculator Plus”, -1, 10000) )
    Даже если окно не существует, метод WaitWindow все равно вернет объект, у которого необходимо проверить свойство Exists
  2. Вместо метода WaitWindow для проверки существования окна можно использовать и объект Window (if( !calc.Window(“#32770″, “About Calculator Plus”).Exists)). Однако в этом случае если окно не откроется, TestComplete выдаст в лог сообщение об ошибке (“Window not found”), что не всегда бывает удобно, если необходимо только проверить наличие окна и в зависимости от результата выполнить разные действия
  3. Не используйте метод WaitWindow для объектов другого типа. Для каждого типа элементов управления есть соответствующие методы Wait (например, WaitWinFormsObject для .NET элементов)
  4. Хотя вы и можете использовать объекты, которые возвращают методы Wait, для дальнейшей работы, на практике их лучше использовать только для проверки существования. Например, следующий код вполне работоспособен:

var obj = calc.WaitWindow(“#32770″, “About Calculator Plus”, -1, 10000);
if( obj.Exists)
  {
       obj.Activate();
obj.Close();
  }
Вместо этого лучше заново инициализировать объект и дальше с ним работать. То есть пример выше лучше переписать так:

if (calc.WaitWindow(“#32770″, “About Calculator Plus”, -1, 10000).Exists)
{
var obj = calc.Window(“#32770″, “About Calculator Plus”);
obj.Activate();
obj.Close();
}

При использовании первого варианта TestComplete может иногда «терять» объект, возвращенный методом Wait и выводить в лог сообщение об ошибке (Window not found). Подобное поведение, собственно говоря, не является нормальным, однако выявить его причину практически невозможно, а потому приходится просто мириться с этой особенностью.

Объект Window по умолчанию ждет появления окна в течение 10ти секунд. Чтобы изменить этот параметр, необходимо щелкнуть правой кнопкой мыши на имени проекта и выбрать пункт меню Edit – Properties, затем в открывшейся панели выбрать элемент Playback и установить необходимый таймаут в поле Auto-wait timeout, ms.

Теперь рассмотрим следующий пункт раздела синхронизации: методы WaitChild и WaitProperty. Метод WaitChild используется для ожидания появления какого-либо дочернего объекта у того элемента управления, для которого он вызывается.

Метод WaitChild принимает 2 параметра:

  • имя объекта, который следует ждать (его можно скопировать из Object Browser-a, выделив объект в дереве объектов и скопировав значение поля Name в правой части)
  • таймаут в миллисекундах, сколько ожидать объект

В качестве примера возьмем все тот же Калькулятор и напишем скрипт, который будет бесконечно ожидать появления окна About. Для того, чтобы проверить правильность работы скрипта, запустите этот пример и TestComplete будет работать, постоянно ожидая появления окна About. Чтобы прекратить выполнение скрипта, выберите в Калькуляторе пункт Help – About.

function TestWaitChild()
{
  var  calc;
  calc = Sys.Process(“calc”);
  calc.Window(“SciCalc”, “Calculator Plus”).Activate();
  while(true)
  {
    if(calc.WaitChild(“Window(\”#32770\”, \”About Calculator Plus\”, 1)”, 1).Exists)
    {
      Log.Message(“Окно About открылось!”);
      break;
    }
    aqUtils.Delay(100);
  }
}

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

Кроме того, если вы делаете подобный цикл, в нем рекомендуется вставлять задержку (мы воспользовались для этого методом Delay объекта Utils), чтобы не загружать процессор на 100% и не мешать выполнению других программ.

В имени объекта можно использовать символы групповой автозамены (? для одного символа и * для нескольких символов). Теперь строку ожидания объекта можно существенно упростить:

calc.WaitChild(“*About Calculator Plus*”, 1).Exists

Однако при использовании подобных приемов нужно быть осторожным, иначе можно слишком сильно упростить имя ожидаемого объекта и дождаться совсем другого элемента управления. Например, может оказаться так, что в окне будет кнопка с надписью «Help» и при ее нажатии будет открываться окно «Help». В таком случае нельзя будет просто сократить имя так, как мы сделали в нашем примере, иначе скрипт может «найти» кнопку вместо окна.

Если вы используете в скриптах NameMapping и Aliases, вам понадобится использовать  методы WaitNamedChild и WaitAliasChild соответственно.

Метод WaitProperty по принципу работы похож на WaitChild, однако вместо ожидания появления объекта он ожидает, пока какое-то конкретное свойство объекта не станет равным заданному значению. Например, если у нас есть невидимый объект (т.е. его можно увидеть в Object Browser-e, но не в приложении), то можно ожидать, пока его свойство VisibleOnScreen не станет равным True. Или можно ждать, пока в текстовом поле не появится какой-то текст. Вот пример скрипта, который ожидает появления значения «123» в поле Калькулятора. Как и в предыдущем примере, чтобы прекратить выполнение скрипта, просто введите в Калькуляторе цифры 123 и TestComplete прекратит работу.

function TestWaitProperty()
{
  var  calc;
  calc = Sys.Process(“calc”);
  calc.Window(“SciCalc”, “Calculator Plus”).Activate();
  var edit = calc.Window(“SciCalc”, “Calculator Plus”, 1).Window(“Edit”, “”, 1);
  while(true)
  {
    if(edit.WaitProperty(“wText”, “123. “))
    {
      Log.Message(“Текст ‘123’ введен!”);
      break;
    }
    aqUtils.Delay(100);
  }
}

И последняя группа функций, использующихся для синхронизации скриптов, функции Find… . В некотором роде эти методы самые сложные в использовании, так как работа с ними осуществляется по-разному в разных языках программирования. Это связано с тем, что методы Find… работают с массивами, а в языках VBScript и Jscript используются разные типы массивов.
Принцип работы всех этих методов одинаков: вы передаете два массива (один с именами свойств, второй со значениями этих свойств), а также в некоторых случаях дополнительные параметры, а TestComplete по этим наборам свойств и значений находит соответствующие объекты в приложении.

Сначала рассмотрим простой пример. Допустим, нам нужно в Калькуляторе найти кнопку C (Clear), которая обнуляет текстовое поле результатов вычисления.
  

Проблема в том, что у нас две таких кнопки: вторая кнопка с текстом C находится в ряде кнопок внизу, которые предназначены для шестнадцатеричных вычислений. Так как в данный момент вторая кнопка C недоступна (disabled), мы можем найти нужную нам кнопку C (Clear) по двум свойствам: текст = “C” и свойство Enabled = true.

Вот как поиск будет выглядеть на языке VBScript:

Sub TestFindChild
 
  Dim PropArray, ValuesArray, btn, wnd
 
  PropArray = Array(“WndCaption”, “Enabled”)
  ValuesArray = Array(“C”, True)

  Set wnd = Sys.Process(“calc”).Window(“SciCalc”, “Calculator Plus”)
  Set btn = wnd.FindChild(PropArray, ValuesArray, 5)
 
  wnd.Activate
 
  If btn.Exists Then
    btn.Click
  Else
    Log.Error “Кнопка ‘C’ не найдена!”
  End If
 
End Sub

Как видите, ничего сложного. Если хотите проверить, будет ли эта процедура работать когда кнопки С действительно нету, просто замените во втором массиве ValuesArray значение C на F (кнока с заголовком F в калькуляторе только одна и она недоступна) и снова запустите скрипт.

В случае использования языка DelphiScript код будет таким же простым, так как массивы в DelphiScript аналогичны массивам VBScript. Однако в случае использования языка Jscript (а также C++Script и C#Script) нам придется сначала конвертировать массивы Jscript в формат VBArray. Для этого воспользуемся функцией ConvertJScriptArray, которую можно найти в справочной системе TestComplete:

function ConvertJScriptArray(JScriptArray)
{
  // Uses the Dictionary object to convert a JScript array
  var objDict = Sys[“OleObject”](“Scripting.Dictionary”);
  objDict[“RemoveAll”]();
  for (var i in JScriptArray)
    objDict[“Add”](i, JScriptArray[i]);
  return objDict[“Items”]();
}

Теперь напишем JScript функцию, которая, как и пример выше, будет искать доступную снопку С и кликать по ней:
function TestFindChild()
{
  var  wnd;
  wnd = Sys.Process(“calc”).Window(“SciCalc”, “Calculator Plus”);
  wnd.Activate()
 
  var PropArray = ConvertJScriptArray(new Array(“WndCaption”, “Enabled”));
  var ValuesArray = ConvertJScriptArray(new Array(“C”, true));

  var btn = wnd.FindChild(PropArray, ValuesArray);
 
  if(btn.Exists)
    btn.Click();
  else
    Log.Error(“Кнопка С не найдена!”);
}

Метод FindChild предназначен для поиска одного объекта и возвращает первый найденный объект, который удовлетворяет условиям поиска (свойствам и их значениям). Если же нужно найти несколько объектов, то для этого можно использовать методы FindAll и FindAllChildren. Они практически идентичны, разница лишь в том, что метод FindAllChildren перебирает только дочерние объекты, а метод FindAll проверяет также тот объект, для которого вызван метод. Методы FindAll и FindAllChildren также работают с массивами типа VBArray и возвращают именно такой массив. Для преобразования массива типа VBArray в массив типа JScript используется метод toArray.

В качестве примера работы с методом FindAll мы найдем все кнопки в Калькуляторе, заголовок которых начинается с символа C, а затем выведем в отчет заголовки всех кнопок, которые удовлетворяют этому условию.

function TestFindAll()
{
  var  wnd, i;
  wnd = Sys.Process(“calc”).Window(“SciCalc”, “Calculator Plus”);
  wnd.Activate()
 
  var PropArray = ConvertJScriptArray(new Array(“WndCaption”));
  var ValuesArray = ConvertJScriptArray(new Array(“C*”));

  var res = wnd.FindAll(PropArray, ValuesArray).toArray();
 
  for (i in res)
  {
    Log.Message(res[i].WndCaption);
  }
}
Результат работы функции:
  

Как видите, кроме кнопок C, CE и cos в результат вывелся и сам Калькулятор (первый результат Calculator Plus), так как его заголовок тоже начинается с символа C. Если заменить в нашем примере метод FindAll на FindAllChildren, то в результат выведутся только кнопки, так как само окно проверяться не будет.

Последний метод, используемый для поиска элементов управления, метод FindId, используется для нахождения контрола по его уникальному идентификатору (id). Мы предоставим читателям самим опробовать работу с ним, так как он очень простой по сравнению с остальными методами Find…

И еще несколько слов по использованию методов Find… . Кроме параметров-массивов, в которых передаются имена и значения свойств искомых объектов, у этих методов есть и другие параметры:

  • Depth (глубина поиска). Этот параметр может оказаться важным в том случае, если при создании проекта вы решили использовать модель объектов Tree. При использовании этой модели иерархия элементов управления в Object Browser-e может быть очень сложной и при поиске объектов необходимо указывать «глубину» поиска, т.е. поиск будет проводиться не только в объекте, для которого вызван метод, но и в его дочерних объектах. С помощью параметра Depth можно указать глубину поиска по иерархии. По умолчанию Depth=1. Чтобы искать во всех дочерних объектах, необходимо установить значение Depth очень большим, точно превышающим реальную глубину вложенности элементов (например, 10000).
  • Refresh (обновление). По умолчанию TestComplete работает с кешированной версией иерархии объектов, которая иногда может отличаться от реальной иерархии (например, после определенных действий добавились или удалились некоторые элементы управления). Если установить параметр Refresh=true (как установлено по умолчанию), то в случае, если искомый объект не найден в кешированной версии приложения, TestComplete обновит ее и осуществит поиск еще раз.

Многие приложения имеют очень сложную иерархию (гораздо сложнее, чем это кажется конечному пользователю) с большим количеством элементов управления (в том числе невидимых). В таких случаях поиск может производиться долго, поэтому всегда ищите компромисс между быстрой работой скриптов (когда методы Find не используются) и качеством их работы. Используйте методы Find только там, где это действительно необходимо и оправдано.

Назад: 3.4 Использование именования (Namespaces) и псевдонимов (Aliases) Содержание Дальше: 3.6 Использование хранилищ (Stores) и контрольных точек