使用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.

如果我能理解OBJECTHANDLERINTERFACE代表什么,我相信我能够获取到事件。我做了一些猜测。

对象:这将是由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上打开一个问题或拉取请求来帮助我们