t.me/atinfo_chat Telegram группа по автоматизации тестирования

[Solved] Как заполнить структуру COMBOBOXINFO с использованием JNA

Доброго времени суток, уважаемые коллеги.

Прошу помощи по вопросу, над которым бьюсь уже второй день.
Пишу тестирующее приложение, выполняющее функциональные тесты над десктоп приложением. Со временем, надеюсь, данные наработки превратятся в полноценный фреймоврк - обертку над Win32 API, если будет время довести его до ума. Для доступа к Win32 функциям использую JNA (java native access) либу.
Столкнулся со следующей задачей, которая оказалась на практике довольно-таки нетривиальной, - нужно разобрать комбобокс на ListBox, Array ListItem’ов и т.д., а выполнить в Win32 это можно следующими способами:

  • вызовом BOOL GetComboBoxInfo(In HWND hwndCombo, Out PCOMBOBOXINFO pcbi)

  • вызовом BOOL SendMessageA(In hWnd, CB_GETCOMBOBOXINFO, 0, Out PCCOMBOBOXINFO pcbi)

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

Ниже отрывки из кода

Описание структуры COMBOBOXINFO ( http://msdn.microsoft.com/en-us/library/bb775798(v=vs.85).aspx ):
COMBOBOXINFO.java

public class COMBOBOXINFO extends Structure
{
public int    cbSize;
public RECT   rcItem;
public RECT   rcButton;
public int    stateButton;
public HWND   hwndCombo;
public HWND   hwndItem;
public HWND   hwndList;

/**
 * Constructs new instance
 */
public COMBOBOXINFO()
{
	super();
	cbSize = size();
}

public List<?> getFieldsList()
{
	return getFields(true);
}

@Override
protected List<?> getFieldOrder() 
{
	return Arrays.asList(new String[] {"cbSize", "hwndCombo", "hwndItem", "hwndList", "rcButton", "rcItem", "stateButton"} );
}

}

Класс WindowsHelper - слой-обертка над Win32 API функциями (вернее, его кусок)

public class WindowHelper 
{
    private static final int CB_GETCOMBOBOXINFO = 0x0164;

    private interface User32Ex extends User32
    {  
	
	User32Ex INSTANCE = (User32Ex)Native.loadLibrary("user32.dll",     User32Ex.class, W32APIOptions.DEFAULT_OPTIONS);

	boolean SendMessageA(HWND hWnd, int msg, int wParam, COMBOBOXINFO cbi);
	
	boolean GetComboBoxInfo(HWND hWnd, COMBOBOXINFO cbi);
    }
    /**
     * 
     * Defines extended interface with additional methods
     * for calling corresponding Win32 functions
     */
     private static final User32Ex _user32Instance = User32Ex.INSTANCE;
    ....................................................

}    

Делаю вызовы следующим способом:

some_function(HWND hWnd /* хендл окна комбобокса */)
{
     COMBOBOXINFO cbi = new COMBOBOXINFO();
     boolean res = _user32Instance.GetComboBoxInfo(hWnd, cbi);
     или
     boolean res = _user32Instance.SendMessageA(hWnd, CB_GETCOMBOBOXINFO, 0, cbi);
}

И проблема в следующем: хотя и передается правильный хендл окна ComboBox, в обоих случаях функции возвращают false и, соответственно, поля структуры не заполнены.

Может кто-то уже сталкивался с подобной проблемой или библиотекой JNA по-больше сможет направить на путь истинный. Копия вопроса на stackoverflow http://stackoverflow.com/questions/25388066/populating-comboboinfo-structure-by-using-jna , но пока без результата :frowning:
Thanks in advance.

Начать надо с GetLastError (в комментах http://msdn.microsoft.com/ru-ru/library/windows/desktop/ms644950(v=vs.85).aspx правильно пишут, что хорошо бы ещё предварительно очистить очередь ошибок). Возможно, WinAPI вовсю ругается на ваши параметры, а вы и не подозреваете.

Я уже редко пользую WinAPI, но что-то мне такое припоминается, что функции, оканчивающиеся на A, относятся больше к Windows 95/98, а на линейки NT-систем правильнее использовать W (мне-то в С# вообще думать об этом не надо, сама система даёт то, что нужно). С другой стороны, статья упоминает только Windows 2000 как старейшую поддерживаемую систему, так что A и W давно уже могут работать одинаково.

А хэндл контрола вы откуда берёте? Ваш код его возвращает или каким-то тулом получаете? Во втором случае может быть ситуация, что вы даёте правильный хэндл, но ваше приложении не имеет достаточно прав (например, тестируемое приложение элевейтится, а ваш код - нет).

спасибо за наводку, про GetLastError как-то вылетело - безусловно необходимо смотреть, что она говорит.
По поводу A и W - это ANSI и wide (Unicode) версии функций.
Хендл контрола возвращает мой код путем вызова EnumChildWindows и нахождения в списке дочерних окон того, которое мне нужно - здесь без проблем - значение хендла совпадает с тем, что индицирует spy, что дает мне уверенность что я все-таки передаю правильный хендл контрола.

@apetrovskiy, спасибо большое. с вашей помощью разобрался и получилось то, как вы и предполагали:
GetLastError сказала 5 (ERROR_ACCESS_DENIED) и, соответственно, проблема была в элевейте. Запуск тестирующего приложения под правами администратора решил проблему.
Так зациклился на одном участке кода, что перестал обращать внимание на простые решения и пути, лежащие рядом :smile:

Пожалуйста, не мог пройти мимо такого героического порыва написать аналог AutoIt/UI Automation/etc/etc.

А зачем вам всё это делать из джавы (кроме прокачки скилла)?
Джава же позиционируется как “написано тут, будет работать там и там, а если повезёт, и везде”.
А портировать Win32 обёртки, кроме как на другую версию винды, некуда.

И другое соображение: вот вы окучите стандартные контролы Win32, потом вам надо будет окучить дотнетовские стандартные контролы. Потом devexpress, infragistics, telerik, etc, etc, Т.е. проделать всю ту работу, что проделала команда Майкла Бернстейна в мелкософте в 2006-м году. На самом деле, у них уже даже две UI Automation, одна идёт с .NET, другая - на Nuget’е.

Понятно, что вызывать .NET из Java невыгодно (а писать сразу на C++/C# вы, вероятно, не хотите).
Но есть же ещё MSAA, да и UI Automation для C++. Уже всё (многое: стандартное и часть не стандартного) обёрнуто. Из минусов - работает только в юзерской сессии, потому скринридеры, магнифайеры и т.д. предусматриваются только для юзеров, но не для сервисов.
Особенно интересен в плане переносимости MSAA: работал аж с Windows 95. Считается legacy, но используется до сих пор.

@apetrovskiy
Джава была выбрана как раз по причине возможности использовать библиотеку JNA, имеющая очень полезную для меня возможность - имитировать, так сказать, настоящий user input.
Приведу маленький пример из практики: у меня в тестируемом приложении есть диалог, который вызывается по нажатию определенной кнопки, вернее даже не вызывается, а при клике на кнопку он просто показывается, т.к. всегда существует на экране, но просто невидим. Как оказалось на практике, с помощью SetCursorPos, а затем посылкой кнопке SendMessage c WM_LBUTTONDOWN(UP) c запакованными координатами курсора, диалог не показывается. Посылать скрытому диалогу WM_SHOWWINDOW тоже некорректно для тестов.
С помощью этой jna-либы возможно симулировать INPUT, который имитирует движения и нажатия мышки, или, например, ввод текста якобы с клавиатуры, а не посылкой WM_SETTEXT со строкой, которую нужно, например, насетить в эдит-бокс. T.e. я делаю скажем MouseMove, затем MouseDown и MouseUp в середине кнопки - курсор передвигается на кнопку, кликает и искомый диалог видим.
Второй профит от такой возможности имитировать user input - это возможность реально симитировать drag’n’drop, что необходимо для моих тестов т.к. в тестируемом приложении одной из главных возможностей использовать UI - это drag’n’drop.
По всей видимости, эта либа тоже является оберткой над всеми этими сообщениями и т.д. (в ее коде пока не лазил), но довольно грамотно сделанная, что показали тесты над моим тестируемом приложении.
И, к сожалению, мне не хватило бы знаний так обернуть мессаджы для имитации юзер-инпут.
На данный момент я не знаю, например, .NET библиотек с такой возможностью и не знаю имеет ли такую возможность UIAtomation (кстати, который тоже рассматривался для использования в виде фреймворка TestStack White)
По поводу окучивания всех стандартных контролов, devexpress, telerik и т.д. пока речи не идет и, возможно, идти не будет (с моей стороны, по крайней мере точно ближайшее время :smile: ) Пока есть несколько оберток только для нужных мне контролов (для более удобного их использования).
Сейчас у меня стоит задача ( моя личная в том числе :smile: ) сделать некий слой, который сможет работать с кастомными контролами, основанными на стандартных, т.е. например возможность заюзать не только Button, а еще какой-нибудь ButtonCustomVasyaPetyaCorporation :smile: + имитация “реальных” нажатий-передвижений мыши\клавиатуры на этих контролах.
По поводу С++ - не рассматриваю как язык для написания таких тестов в принципе. (да и он сейчас, так сказать, немного не такой, которым я баловался, учась в институте несколько лет назад :smile: - новые стандарты и т.д.)
Да, еще по поводу окучивания контролов и добавления возможностей, хочу затем все это выложить на гите, чтобы желающие могли принять участие и опытные люди могли бы поисправлять мои ошибки и улучшить код.
Пока, к сожалению, не могу этого сделать.

Есть неплохой фреймворк для имитации ввода: InputSimulator. Я его использую как дополнительный вариант ввода в моём фреймворке. В применении достаточно прост и удобен.

UI Automation умеет дёргать некоторые события контролов, они не всегда соответствуют тем, чего ожидает программер. К примеру, программер задаёт обработку изменения данных в текстбоксе через TextChanged (почему? да просто потому, что при двойном клике на контрол студия предлагает это событие:)) А UI Automation через ValuePattern активирует, скорее, ModifiedChanged. Обычно это вызывает недовольство программеров - они же честно сделали дабл-клик на контроле и написали код в сгенерённом для них методе (мозг не включается для такой низменной вещи, как написание UI).

Также авторы UI Automation надеются, что кто-нибудь (опытный тимлид, начальство, тестировщики, кастомеры, жажда всё знать и всё делать хорошо) надавит на разработчика в плане использования Accessibility пропертей контролов (та же AccessibilityRole). Ни разу не работал с такими разработчиками, которые до многонедельной серии пинков и увещеваний, сами начинают внедрять Accessibillity в свои продукты.
Так что юзерский инпут (тот же InputSimulator) иногда остаётся наилучшим выбором - только надо контрол на передний план переводить. И не забывать о том, что некоторые драйверы (Vmware Pointing Device, например) не дают работать некоторым функциям, к примеру, SetCursorPos.