使用Win32::OLE自动化Windows应用程序
初涉网络
我在1994年左右在Lotus Development第一次接触互联网。Mosaic的演示给我留下了深刻的印象。几个月后,在马萨诸塞州剑桥的一家名为Dataware的小公司工作,让我更接近网络革命。
1995年,我在美国在线找到了一份工作,在那里我为他们的浏览器团队工作。他们最初拥有自己的浏览器,是从CMGI购买的。在当时,这相当酷,因为它包括了标签框架功能,比Gecko引擎早了大约八年。
在那里,Pete Deschanes使用Microsoft Visual Test编写了一个工具,来自动化美国在线嵌入的网页浏览器。我对他使用的某些捕获浏览器事件的函数特别感兴趣。
后来,我搬到了马萨诸塞州切尔姆斯福德的一家公司,该公司使用OLE自动化将Microsoft Word文档转换为传真文档。Rob Murtha向我解释了很多关于OLE自动化的知识,并介绍了我使用Perl和Java。
登陆
2002年,互联网泡沫破裂,我最终被困在富达投资公司的岸边,成为他们众多投资网页团队之一的手动测试员。经过几周的手动测试后,我准备自动化我的几个任务。
有一个问题。有很多可用的Winrunner许可证,但我不能使用它们,因为我不是他们的自动化团队的一部分。实际上,我因为过于坚持要求使用一个许可证而受到了批评,几乎失去了我的工作。然后我决定编写自己的自动化工具。
我有C、C++、Java和Perl的经验。我决定从一个脚本语言开始,只是为了让原型动起来。我还认为有一个真正的开源脚本语言来编写代码会很有趣,而不是一些工程师专门为网页自动化开发的。
我的过程包括
- 提出一个问题。
- 做一些研究。
- 写一些代码。
开始
我需要做的第一件事是看看我是否能从Perl启动IExplore.exe
。我知道我不想简单地用system("C:\\Program Files\\Internet Explorer\\IExplore.exe");
启动进程。是的,我可以通过这种方式启动IE并使其运行,但我无法对其进行任何有用的操作,除了将其结束。
我注意到Active Perl中有一个名为Win32::OLE
的有趣模块。我打开了OLE.pm文件,开始阅读注释。
这样的注释看起来很有希望
此模块提供从Perl到OLE自动化的接口。OLE自动化带来了类似于VisualBasic的脚本功能,并提供了强大的扩展性和从Perl脚本控制许多Win32应用程序的能力。
这个看起来也不错
MessageLoop()
类方法将运行标准的Windows消息循环,直到调用QuitMessageLoop()
类方法。它用于等待OLE事件。
牢记信仰是希望之物的实质,是不见之物的证据,我开始使用Active Perl的Win32::OLE
编写一个简单的自动化模块,用于Internet Explorer。
回到Win32::OLE
文档中,我找到了如何通过COM对象启动IE的方法。我将一些用于Excel和Word的示例翻译过来,最后得到这个
$IE = Win32::OLE->new("InternetExplorer.Application")
|| die "Could not start Internet Explorer.Application\n";
这很好,但我的电脑屏幕上没有显示任何内容。我能听到硬盘发出像启动应用程序一样的声音,但我看不到Internet Explorer。我决定在Google上搜索一些示例。网上的信息非常稀少,但我找到了将可见属性设置为1的方法
$IE->{visible} = 1;
这次,当我在Internet Explorer中看到一个空白屏幕时,我知道开始了。我想,在我之前的努力中,我无法在我的机器上看到许多IE进程,所以我用任务管理器杀死了它们。
接下来,我启动了一个名为OLEVIEW.exe
的免费Microsoft工具。这给我显示了机器上注册的所有自动化对象的树形视图。有数百个。我找到了名为Internet Explorer(版本1.0)的项,并展开树以查找方法。IWebBrowser2
看起来很有趣,所以我点击它并选择“查看类型信息”按钮。弹出了一个新窗口,其中列出了方法。一切看起来都越来越好。
我点击了一个名为Navigate
的方法,然后看到
[id(0x00000068), helpstring("Navigates to a URL or file.")].
void Navigate(
[in] BSTR URL,
[in, optional] VARIANT* Flags,
[in, optional] VARIANT* TargetFrameName,
[in, optional] VARIANT* PostData,
[in, optional] VARIANT* Headers);
我决定试试看
$IE->Navigate("http://www.google.com");
现在,我已经用我的新自动化工具导航到了第一个网站。
在旅途中
我知道我接近了墙。通过Google和新闻组,我看到很多人在这个游戏的这个阶段转向了。回想起Pete Deschanes的Visual Test Tool,我相信如果我能捕获Internet Explorer的事件,我就能取得更大的进展。我回到OLE.pm文档,阅读更多关于事件的内容。
=item Win32::OLE->WithEvents(OBJECT[, HANDLER[, INTERFACE]])
This class method enables and disables the firing of events by
the specified OBJECT.
如果我能理解OBJECT
、HANDLER
和INTERFACE
代表什么,我相信我能够获取到事件。我做了一些猜测。
对象:这将是由Win32::OLE->new()
返回的内容。每个人都知道你使用new
运算符实例化对象。
处理程序:我继续阅读OLE.pm
Win32::OLE->WithEvents()
的HANDLER
参数可以是CODE引用或包名。在第一种情况下,所有事件都将调用这个特定的函数。该函数的前两个参数将是对象本身和事件名称。其余参数将是特定于事件的。Win32::OLE->WithEvents($Obj, \&Event);
现在,我明白了WithEvents
将会告诉Internet Explorer在IE触发事件时调用我的Perl处理程序。我必须像这样给WithEvents
调用一个子例程引用
\&Event
接口名称将会是什么?我回到OLEVIEWER,查看了接口文件夹。看起来DwebBrowserEvents2
将提供我所需要的东西。
这是我得到的结果
Win32::OLE->WithEvents($IE,\&Event,"DWebBrowserEvents2");
现在,我只需要为IE编写一个事件子程序。
sub Event {
my ($Obj,$Event,@Args) = @_;
print "Event triggered: '$Event'\n";
}
我注意到还有一个名为@Args
的第三个参数,并假设这是为了捕获每个事件的未知参数。
我该如何阻塞我的代码等待事件发生?我回到Win32::OLE的注释
MessageLoop()
类方法将运行标准的Windows消息循环,直到调用QuitMessageLoop()
类方法。它用于等待OLE事件。
我添加了这段代码,只是为了看看我是否能够捕获IE事件
Win32::OLE->MessageLoop();
我已经到达了山顶
当我看到这些事件从我九行代码中涌现而出时,那是一个很特别的时刻
Event triggered: CommandStateChange
Event triggered: OnVisible
Event triggered: PropertyChange
Event triggered: BeforeNavigate2
Event triggered: DownloadBegin
Event triggered: StatusTextChange
Event triggered: ProgressChange
Event triggered: FileDownload
Event triggered: DownloadComplete
Event triggered: TitleChange
Event triggered: NavigateComplete2
Event triggered: OnQuit
Internet Explorer正在触发事件,其COM对象正在调用我的Perl事件子程序。
从这里,我继续在新新闻组中搜索。我找到了一句话,有人提到从DocumentComplete
事件中获取DOM。我知道这是一个关键,但如何使用Perl获取这个引用呢?
我学习了DOM的相关知识。我从工作中的同事那里借了一本书。 微软有自己的DOM版本,称为DHTML,我偶然发现了他们的网页。阅读了这段文档一段时间后,我发现DOM可以提供我实现自动化工具所需的一切。我需要的只是参考资料。
寻找DOM
我把事件子例程分解成几个部分。我想为每个触发的事件做不同的事情。具体来说,我想在DocumentComplete
事件触发时尝试获取DOM的引用。我的想法是,如果我从@Args数组中移除第一个元素,我就能找到我需要的引用。我重写了事件子例程
sub Event {
my ($Obj,$Event,@Args) = @_;
print " Event triggered: $Event\n";
if ($Event eq "DocumentComplete") {
$IEObject = shift @Args;
print "Here is my reference: $IEObject\n";
}
}
这会打印出来
Here is the Event: DocumentComplete
Here is my reference: Win32::OLE=HASH(0x1a524fc)
这看起来越来越好。
我得到了一个引用,但这是否是刚刚加载的页面的DHTML?唯一的方法是测试一下:我能用它来发起一个DHTML调用吗?我在微软DHTML参考资料页上寻找一个可以告诉我我得到了引用的属性。《code>URL看起来不错,所以我试了这段代码
print "URL: " . $IEObject->URL . "\n";
这没有给我任何东西。我回到OLEVIEWER,找到了这个有趣的方法
[id(0x000000cb), propget, helpstring("Returns the active
Document automation object, if any.")]
IDispatch* Document();
Active document听起来不错,所以我试了
print "URL: " . $IEObject->Document->URL . "\n";
这给了我
URL: http://www.google.com/
太棒了!我的下一步是找到一种方法来跳出我所在的MessageLoop()
。
Win32::OLE->QuitMessageLoop();
回家
我的最后一步是用我的DHTML引用做更有用的事情。是时候编写一个子例程,将文本输入到编辑框中。这样就能证明我的概念。
其他自动化工具需要在运行自动化之前制作GUI映射(Mercury Winrunner)或包含文件(Segue Silk)的每个页面。我希望有一种方法,它可以直接查看页面上已有的代码,并利用正则表达式的力量动态选择控件。
我在微软DHTML API文档中找到了以下示例中的所有方法和属性。首先,我需要给我的子例程起一个名字。SetEditBox
看起来很容易理解。
sub SetEditBox {
}
接下来,我需要传入两个参数。第一个将是控件的名称,第二个将设置文本。
sub SetEditBox {
my ($name,$value) = @_;
我必须从DOM的引用开始,从文档对象开始
sub SetEditBox {
my ($name,$value) = @_;
$IEDocument = $IEObject->{Document};
为了节省时间迭代,我假设编辑框只会出现在表单内部。我使用了名为forms
的集合来返回页面上的所有表单。
$forms = $IEDocument->forms;
现在轮到迭代了。
for ($i = 0; $i < $forms->length; $i++) {
}
首先,我需要表单集合中的每个元素。
$form = $forms->item($i);
在这个迭代中,我想找到具有编辑框名称的表单的特定元素。
if (defined($form->elements($name))) {
}
在这个if语句中,我想将编辑框的值设置为传递给子例程的值。
$form->elements($name)->{value} = $value;
然后是时候离开这个地方,以免浪费时间去继续迭代。
return;
这是最终的初始子例程。
sub SetEditBox {
my ($name, $value) = @_;
my $IEDocument = $IEObject->{Document};
my $forms = $IEDocument->forms;
for (my $i = 0; $i < $forms->length; $i++) {
my $form = $forms->item($i);
if (defined($form->elements($name))) {
$form->elements($name)->{value} = $value;
}
return;
}
}
这是与第一个原始概念版本非常相似的东西SAMIE
use Win32::OLE qw(EVENTS);
my $URL = "http://samie.sf.net/simpleform.html";
my $IE = Win32::OLE->new("InternetExplorer.Application")
|| die "Could not start Internet Explorer.Application\n";
Win32::OLE->WithEvents($IE,\&Event,"DWebBrowserEvents2");
$IE->{visible} = 1;
$IE->Navigate($URL);
Win32::OLE->MessageLoop();
SetEditBox("name","samie");
sub Event {
my ($Obj,$Event,@Args) = @_;
print "Here is the Event: $Event\n";
if ($Event eq "DocumentComplete") {
$IEObject = shift @Args;
print "Here is my reference: $IEObject\n";
print "URL: " . $IEObject->Document->URL . "\n";
Win32::OLE->QuitMessageLoop();
}
}
sub SetEditBox {
my ($name, $value) = @_;
my $IEDocument = $IEObject->{Document};
my $forms = $IEDocument->forms;
for (my $i = 0; $i < $forms->length; $i++) {
my $form = $forms->item($i);
if (defined($form->elements($name))) {
$form->elements($name)->{value} = $value;
}
return;
}
}
这让我笑了,向Larry Wall致敬,我想我可以用大约三十行Perl实现一个价值3000美元每座的自动化工具的基本概念。更多信息请查看SAMIE主页。
标签
反馈
这篇文章有什么问题?请通过在GitHub上打开一个问题或拉取请求来帮助我们