首页 > 其他 > 详细

【机器人系列】支付宝支付控件输入框模拟输入

时间:2015-03-26 17:48:51      阅读:308      评论:0      收藏:0      [点我收藏+]

企业中做自动化测试,会用到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

【机器人系列】支付宝支付控件输入框模拟输入

原文:http://blog.csdn.net/tbwood/article/details/44649109

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!