使用 Windows 自帶的 MSAA 發現浏覽器視窗,自動執行 JavaScript 很簡單!
aardio 代碼示例:
import winex;
import winex.accObject;
import winex.key;
//周遊浏覽器視窗(相容 Chrome,Edge 等)
for hwnd,title in winex.each("Chrome_WidgetWin_1") {
//擷取 MSAA 接口對象
var accObject = winex.accObject.fromWindow(hwnd);
//查找文本框
var edit = accObject.find(
role = 0x2A;
name = "<Address and search bar>|<位址和搜尋欄>";
)
if(edit){
//擷取浏覽器位址欄内容
var url = edit.value();
//修改浏覽器位址欄内容
edit.setValue("javascript:alert(document.location.href)")
edit.takeFocus();
//背景發送按鍵消息
winex.key.click(hwnd,"ENTER");
thread.delay(1000);
}
}
運作一下看看效果:
所有 Windows 系統都自帶 MSAA,接口簡單,易于使用,生成的 EXE 程式體積也會很小。aardio 标準庫 winex.accObject 則對 MSAA 做了進一步封裝,用法就更簡單了。
視窗基礎知識
1、什麼是視窗
「視窗」是應用程式在螢幕上建立的一個顯示區域,通常用于接收并處理使用者操作,并顯示要輸出的内容。視窗上的文本框、按鈕、菜單這些也都是視窗。
我們一般将頂層獨立視窗稱為「窗體」,而窗體上的子視窗稱為「控件」。
2、什麼是視窗句柄
視窗句柄是一個用于唯一标準視窗的整數值。
其實很多系統資源,例如位圖、程序、線程都有唯一标準資源的句柄。
在 aardio 中所有句柄都存為指針類型,唯有視窗句柄是普通的數值類型。
3、無句柄視窗
無句柄視窗是指該視窗上的控件沒有建立子視窗,典型的例如網頁上的按鈕、文本框都沒有視窗句柄。MSAA 可用于操作無句柄視窗。
使用視窗探測器
請在 aardio 中打開 『工具 > 探測器 > 視窗探測器』:
拖動『視窗探測器』左下角的十字圖示到目标視窗上,就會顯示視窗資訊。
使用視窗探測器我們可以發現 Chrome, Edge 等浏覽器的網頁視窗類名都是 "Chrome_WidgetWin_1", 是以我們可以用下面的 aadio 代碼擷取所有打開的浏覽器視窗:
import winex;
for hwnd,title in winex.each("Chrome_WidgetWin_1") {
}
aardio 中 winex 名字空間的所有庫、函數都是用于操作外部程式視窗的。
winex.each() 用于周遊所有符合條件的視窗, winex.each() 的第一個參數可以指定視窗類名,這個類名支援模式比對文法( 類正規表達式,但更簡單,用法請參考文法文檔 )。
自視窗句柄擷取 MSAA 對象
自視窗句柄擷取 MSAA 對象,代碼很簡單:
import winex;
import winex.accObject;
//周遊浏覽器視窗(相容 Chrome,Edge 等)
for hwnd,title in winex.each("Chrome_WidgetWin_1") {
//擷取 MSAA 接口對象
var accObject = winex.accObject.fromWindow(hwnd);
}
使用 MSAA 探測工具
請在 aardio 中打開 winex.accObject 的文檔或源碼,搜尋“ACC對象浏覽工具” 并下載下傳該工具( inspect.exe )。
運作 inspect.exe ,點選下圖的『 Watch Cursor 』圖示:
也就是允許探測滑鼠指向的視窗。
然後将滑鼠移向浏覽器的位址欄,Inspect 找到了位址欄所在的 ACC 對象,并顯示了一堆資訊,我們重點關注這幾行:
Name: "Address and search bar"
Role: editable text (0x2A)
Name 是 ACC 對象的名稱。
Role 是 ACC 對象的角色,其實就是控件類型。
根據上面的資訊,我們修改代碼擷取浏覽器位址欄:
import winex;
import winex.accObject;
import console;
//周遊浏覽器視窗(相容 Chrome,Edge 等)
for hwnd,title in winex.each("Chrome_WidgetWin_1") {
//擷取 MSAA 接口對象
var accObject = winex.accObject.fromWindow(hwnd);
//查找位址欄
var edit = accObject.find(
role = 0x2A;
name = "<Address and search bar>|<位址和搜尋欄>";
)
//顯示位址欄的内容
if(edit) console.log( edit.value() )
}
console.pause();
在 aardio 中運作上面的代碼,我們幹淨利索地拿到了浏覽器位址欄的網址。
拿到一個 accObject 對象以後,可以調用 accObject.find() 函數繼續查找符合條件的子節點。而查找參數就是我們用 Inspect.exe 探測到的參數。
查找參數中,role, state 可以是文本,也可以是數值,一般建議用數值( 速度更快 )。
上面的 name 參數用到了模式比對:
name = "<Address and search bar>|<位址和搜尋欄>";
這個模式表達式中的 | 線是 “或” 的意思,而 < > 括号用于包含子串。如果目标 ACC 對象的 name 包含 "Address and search bar" 或者 "位址和搜尋欄" 都符合條件。
背景按鍵
aardio 标準庫 key,mouse 用于對前台視窗模拟按鍵滑鼠。
例如:
key.press("ENTER")
作用就是模拟按下Enter鍵。
如果我們改用 winex.key, winex.mouse 就可以直接向背景視窗發送按鍵或滑鼠消息。這樣的好處是不會幹擾使用者操作。
例如向浏覽器視窗發送Enter鍵消息:
import winex;
import winex.key;
//周遊浏覽器視窗(相容 Chrome,Edge 等)
for hwnd,title in winex.each("Chrome_WidgetWin_1") {
//背景發送按鍵消息
winex.key.click(hwnd,"ENTER");
thread.delay(1000);
}
鍵名
操作按鍵的函數都需要用到鍵名。
我們還可以直接運作 aardio 『 工具 > 滑鼠按鍵 > 按鍵指令生成器』
在『按鍵指令生成器』視窗内我們任意按鍵,就可以顯示對應的鍵名了。
聽說 UIA 更先進
有時候先進也是一種負擔,飛機比自行車先進,這不等于任何時候都要用飛機替代自行車。
當然,在 aardio 中調用 UIA 也是很簡單的,示例( 可獨立運作 ):
import process;
process.executeWaitInput("notepad.exe",io.getSpecial(0x25/*_CSIDL_SYSTEM*/,"drivers\etc\HOSTS"));
//導入 .Net 類
import System.Windows.Automation;
TreeScope = ::UIAutomationTypes.import("System.Windows.Automation.TreeScope");
//通路 .Net 類的靜态成員
Automation = System.Windows.Automation;
AutomationElement = Automation.AutomationElement;
RootElement = AutomationElement.RootElement;
//查找記事本視窗
var condNotepadClass = Automation.PropertyCondition(AutomationElement.ClassNameProperty,"Notepad")
var notepad = RootElement.FindFirst( TreeScope.Children, condNotepadClass)
//查找記事本的編輯框
var condEditClass = Automation.PropertyCondition(AutomationElement.ClassNameProperty,"Edit");
var editBox = notepad.FindFirst( TreeScope.Descendants, condEditClass);
if(!editBox){
//Windows 11
condEditClass = Automation.PropertyCondition(AutomationElement.ClassNameProperty,"RichEditD2DPT");
editBox = notepad.FindFirst( TreeScope.Descendants, condEditClass);
}
//擷取記事本内的文本
var textPattern = editBox.GetCurrentPattern(Automation.TextPattern.Pattern);
var text = textPattern.DocumentRange.GetText(50)
import win.dlg.message;
win.dlg.message().info(text + " ……")
然并卵,WebDriver 不香麼?
aardio 調用 WebDriver 就更簡單了,示例:
import chrome.driver;
var driver = chrome.driver();
//建立會話,打開chrome浏覽器,調用參數僅用于示範( 可以省略 )。
var browser = driver.startBrowser();
//打開網頁
browser.go("網址你猜")
//查找文本輸入框,傳回的DOM對象也可以使用ququerySelector繼續查找子節點
var ele = browser.querySelector("body").querySelector("#kw");
//在網頁輸入框輸入内容
ele.setValue( "ChromeDriver" )