http://www.bkjia.com/webzh/974312.html
企业中做自动化测试,会用到Selenium,它确实是一个强大,免费,而便捷的自动化测试框架。但有时候我们会遇到一些特别的浏览器输入控件,他们不是正常的Html input元素,因此Selenium无法获取到。这时候我们需要用本地化操作进行模拟。当然,这种技术也不仅可以用来做测试,还可以....你懂的。切入正题。
问题/任务描述: 实现向支付宝密码控件自动输入密码
解决方案:使用JNA提供的Native代理,调用Windows系统函数,通过SendMessage函数发送字符,实现输入。
具体步骤:(1)找到控件窗口的句柄(可以使用Spy++),总结如何找到的,这是一个自定义的算法.因为支付宝的控件类名是随机的乱码.
(2)循环发送密码的每一个字符。
上代码:
package org.tbworks.auto_alipay;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser.WNDENUMPROC;
import com.sun.jna.win32.W32APIOptions;
/**
* @author tangb Note: className means the class name of window (frame), you can
* get it by spy++
*/
public class AlipayEditInputTyper {
// load the user32.dll
final protected static User324J user32 = (User324J) Native.loadLibrary(
"user32", User324J.class, W32APIOptions.DEFAULT_OPTIONS);
/**
* @author tangb
* You can not modify a final variable in the anonymous class, but you can modify its member if the member was not final.
*/
private static final class finalHandleContainer
{
private HWND handle;
}
// -----------------------privates-------------------------------
/**
* get browser frame's window handle by class name and window title
*
* @param browserClassName
* : browser window's class name, you can get it by spy++
* @param browserTitle
* @return
*/
private static HWND getAlipayWindowHandle(String browserClassName,
String browserTitle) {
HWND deskTopHandle = user32.GetDesktopWindow();
char[] winClass = new char[100];
user32.GetClassName(deskTopHandle, winClass, 100);
//System.out.println(winClass);
if( deskTopHandle == null )
throw new RuntimeException("Can not find the desktop's window handle!");
return getSpecifiedWindowHandle(deskTopHandle,browserClassName,browserTitle);
}
/** search the children windows of the father window specified by father-handle and find the child window with specified className and substring of title
* @param fatherHandle handle of the father window
* @param className className of the child window
* @param title title of the child window
* @return
*/
private static HWND getSpecifiedWindowHandle(HWND fatherHandle, final String className,final String title)
{
final finalHandleContainer alipayWindowHandleContainer = new finalHandleContainer();
boolean result = user32.EnumChildWindows(fatherHandle, new WNDENUMPROC() {
@Override
public boolean callback(HWND hWnd, Pointer data) {
// TODO Auto-generated method stub
char[] winClass = new char[100];
char[] winText = new char[200];
user32.GetClassName(hWnd, winClass, 100);
user32.GetWindowText(hWnd, winText, 200);
System.out.println("Class Name:"+Native.toString(winClass)+" Title:"+Native.toString(winText));
if(user32.IsWindowVisible(hWnd) && Native.toString(winClass).equals(className) && Native.toString(winText).contains(title))
{
alipayWindowHandleContainer.handle = hWnd;
return false;//false means stop
}
return true;
}
}, Pointer.NULL);
return alipayWindowHandleContainer.handle;
}
/** get all children's class-names and handles under specified father windows, and store them in a map.
* @param fatherHandle the handle of father window
* @return a map stores every class name and its window handles, e.g., key="CLASSONE", value="12312"
*/
private static Map<String,List<HWND>> getChildWindowClassHandleMap(HWND fatherHandle)
{
final finalHandleContainer alipayWindowHandleContainer = new finalHandleContainer();
final Map<String,List<HWND>> resultMap = new HashMap<String,List<HWND>>();
boolean result = user32.EnumChildWindows(fatherHandle, new WNDENUMPROC() {
@Override
public boolean callback(HWND hWnd, Pointer data) {
// TODO Auto-generated method stub
char[] winClass = new char[100];
user32.GetClassName(hWnd, winClass, 100);
if(user32.IsWindowVisible(hWnd))
{
String tempClassName = Native.toString(winClass);
if(resultMap.containsKey(tempClassName))
{
resultMap.get(tempClassName).add(hWnd);
}
else
{
List<HWND> tempHWNDList = new ArrayList<HWND>();
tempHWNDList.add(hWnd);
resultMap.put(tempClassName, tempHWNDList);
}
}
return true;
}
}, Pointer.NULL);
return resultMap;
}
/** In term of alipay password edit control, its class name changes very time. And I come up with one solution: the window class name with only one instance may be the alipay edit, others are not definitely. But times always change, so maybe this is not a permanently valid way! Cautions!
* Get several potential alipay edit window. As sending messages to all the sub windows are not efficient, so it is necessary to shrink the range.
* @param browserWindowHandle
* @return
*/
private static List<HWND> getPotentialAlipayEditHandle(HWND browserWindowHandle) {
Map<String,List<HWND>> candidates = getChildWindowClassHandleMap(browserWindowHandle);
List<HWND> resultList = new ArrayList<HWND>();
for(Iterator it =candidates.entrySet().iterator();it.hasNext();)
{
Map.Entry<String, List<HWND>> next = (Map.Entry<String, List<HWND>>) it.next();
List<HWND> tempList = next.getValue();
if(tempList.size()==1)
{
resultList.add(tempList.get(0));
}
}
return resultList;
}
// -----------------------publics--------------------------------
/**
* Set password to all potential alipay password edit controls embedded in the browser page
*
* @param browserClassName
* : browser's class name. e.g., MozillaWindowClass for firefox
* @param browserTitleOrSubTitle
* : browser frame's title, or you can just specified a sub-title
* which can mark the alipay window uniquely. E.g., frame's title
* is "123456-scs**0s90", you can just utilize "234" if it is ok.
* @param className
* : alipay edit frame's class name.
* @param title
* : alipay edit frame's title.
* @param password
* : the password.
* @return
* @throws InterruptedException
*/
public static boolean setPassword(String browserClassName,
String browserTitleOrSubTitle,String password) throws InterruptedException {
HWND browserWindowHandle = getAlipayWindowHandle(browserClassName,
browserTitleOrSubTitle);
System.out.println("------------------------------------------------------------------------------------------------------");
List<HWND> list = getPotentialAlipayEditHandle(browserWindowHandle);
for (char c : password.toCharArray()) {
TimeUnit.MILLISECONDS.sleep(500);
for(HWND handle:list)
user32.SendMessage(handle, MESSAGE.WM_CHAR.toInt(), (byte) c, 0);
}
return true;
}
}
提一下JNA访问的原理:JNA封装了操作系统的dll,自然也包括Windows的User32.dll,JNA通过java动态代理,获取你要的功能(传入的接口),然后就可以在java中调用了。JNA的包,可以通过Maven获取。
以下是测试代码:
package org.tbworks.auto_alipay;
public class TestTyper {
public static void main(String[] args) throws InterruptedException {
// firefox
AlipayEditInputTyper.setPassword("MozillaWindowClass", "支付宝", "mypassword");
// chrome
AlipayEditInputTyper.setPassword("Chrome_WidgetWin_1", "支付宝", "mypassword");
}
}
这里测试了2个浏览器,仅仅实现了输入功能。成功运行后,会看到字符鬼魅般的一个个输入到密码控件中。
源码下载:https://github.com/tbwork/alipay_edit_typer.git

微信公众号: 架构师日常笔记 欢迎关注!
浙公网安备 33010602011771号