В этой статье я рассмотрю случай, когда стандартные методы TestComplete WaitProcess и WaitProperty не подходят из-за особенностей архитектуры приложения и необходимо найти другой способ определения, когда тестируемое приложение запустилось и готово к работе.
Наше тестируемое приложение запускается немного специфично: окно приложения становится доступным пользователю, однако после этого в течение ещё некоторого времени оно подгружает данные с сервера, при этом обновляя значения элементов управления. В зависимости от текущей скорости сети и нагрузки на сервер базы данных, такое подгружение данных может занимать от 10 секунд до нескольких минут. Если в это время пытаться получать значения свойств элементов с помощью TestComplete-овских стандартных методов типа WaitProperty, иногда это приводит к тому, что тестируемое приложение попросту крешится. Как же дожидаться, пока приложение выполнит все фоновые операции и будет готово к работе?
Самый простой способ — дождаться, пока потребляемое процессом процессорное время не снизится до нуля. Для этого мы можем в цикле проверять значение свойства CPUUsage нужного нам процесса. Однако даже простое обращение к объекту процесса (Sys.Process(…)) иногда приводит к крешу приложения, поэтому необходимо использовать другой подход: проверять CPUUsage извне TestComplete, т.е. используя возможности операционной системы.
Для этого можно воспользоваться объектом WMI (Windows Management Instrumentation) — технологии для централизованного управления и слежения за работой различных частей компьютерной инфраструктуры под управлением Windows. Мы напишем 2 функции: первая будет ожидать появления процесса с помощью WMI-объекта Win32_Process, вторая с помощью объекта Win32_PerfRawData_PerfProc_Process будет дожидаться, пока используемое процессом процессорное время не снизится до нуля. Причем для второй функции нам недостаточно просто дождаться, пока CPU Usage станет равным нулю, нужно чтобы процесс бездействовал в течение некоторого времени.
Вот как будет выглядеть первая функция, ожидающая появления процесса:
function waitProcessStart(procName, maxWait){
maxWait = maxWait || 10000;
var wmiObj, dataset;
var timer = HISUtils.StopWatch;
timer.Start();
var message = 'Waiting ' + maxWait/1000 + ' seconds for process ' + procName + ' to appear';
Log.Message(message);
do {
wmiObj = GetObject("WinMgmts:{impersonationLevel=impersonate}!\\\\localhost\\root\\cimv2");
dataset = wmiObj.ExecQuery("Select * from Win32_Process WHERE Name='" + procName + ".exe'");
if(timer.Split()> maxWait) {
Log.Warning("Maximum timeout reached, process didn't start");
timer.Stop();
return false;
}
if(dataset.Count> 1) {
Log.Message('Only 1 existing process is supported, found: ' + dataset.Count);
timer.Stop();
return false;
}
else if(dataset.Count == 1) {
Log.Message('Process ' + procName + ' appeared');
break;
}
else { /* no process found */
var secondsLeft = maxWait/1000 - Math.round(timer.Split()/1000);
Delay(1000, message + ' (' + secondsLeft + ' seconds left)');
}
}
while(true);
timer.Stop();
return true;
}
Здесь мы передаём в качестве параметра имя процесса (без расширения) и максимальный таймаут ожидания. Для удобства в Индикаторе отображается оставшееся время до окончания ожидания:

Теперь напишем функцию, ожидающую, пока процесс прекратит любые действия:
function waitProcessIdle(procName, maxWait, numberOfRetries){
maxWait = maxWait || 10000;
numberOfRetries = numberOfRetries || 3;
var tries = numberOfRetries;
var cpuUsage, wmiObj, dataset;
var timer = HISUtils.StopWatch;
timer.Start();
var message = 'Waiting for process ' + procName + ' to idle';
Log.Message(message);
do {
if(tries == 0) {
timer.Stop();
return true;
}
wmiObj = GetObject("WinMgmts:{impersonationLevel=impersonate}!\\\\localhost\\root\\cimv2");
dataset = wmiObj.ExecQuery("SELECT * FROM Win32_PerfRawData_PerfProc_Process WHERE Name='" + procName + "'");
var procs = new Enumerator(dataset);
procs.moveFirst();
var item = procs.item();
var p1 = procs.item().PercentProcessorTime;
var n1 = procs.item().Timestamp_Sys100NS;
var secondsLeft = maxWait/1000 - Math.round(timer.Split()/1000);
Delay(1000, message + ' (' + tries + ' retries and ' + secondsLeft + ' seconds left)');
dataset = wmiObj.ExecQuery("SELECT * FROM Win32_PerfRawData_PerfProc_Process WHERE Name='" + procName + "'");
procs = new Enumerator(dataset);
procs.moveFirst();
item = procs.item();
var p2 = procs.item().PercentProcessorTime;
var n2 = procs.item().Timestamp_Sys100NS;
var p = p2 - p1;
var n = n2 - n1;
cpuUsage = 100*p/n.toFixed(2);
if(cpuUsage == 0) {
tries--;
continue;
}
if(cpuUsage> 0) {
tries = numberOfRetries;
}
if(timer.Split()> maxWait) {
Log.Warning('Maximum timeout reached');
timer.Stop();
return false;
}
}
while(true);
}
В этой функции есть дополнительный параметр numberOfRetries — количество попыток проверки бездействия процесса, которые должны успешно завершиться подряд. Для удобства эта функция выводит в Индикатор оставшееся время и количество оставшихся попыток:

Чтобы проверить работоспособность кода, достаточно запустить вот такую функцию:
function Test1(){
Log.Message(waitProcessStart('notepad'));
Log.Message(waitProcessIdle('notepad'));
}
После её запуска откройте Блокнот и начните набирать в нём любой текст. Пока вы это делаете, TestComplete будет выводить в Индикатор информацию о состоянии ожидания. Если вы закончите ввод, то примерно через 3 секунды функция остановится.
Ссылки по теме:
http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.wmi/2005-09/msg00156.html
http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/WMI/User~Sample~Code+Get~CPU~Usage~of~a~Process.txt
https://www.petri.com/powershell-problem-solver-process-cpu-utilization
https://docs.microsoft.com/en-us/scripting/javascript/reference/enumerator-object-javascript