我正在Windows平台上使用perl
编写一个软件测试框架,它通过调用被测软件来运行测试用例。如果测试用例失败,框架将捕获屏幕,以便我们可以获得有关失败的更多信息。
起初我使用了一个名为boxcutter-fs.exe
的小程序。所以我需要的是在测试用例失败时调用这个程序:
system("boxcutter-fs.exe screenshot.png");
print "Failed: $?" if ($?);
当框架处理正常故障时,它运行良好,并给我正确的故障屏幕截图。但我注意到,当软件崩溃时(活动窗口上会出现一个错误消息框,并且超时后被测试的软件将被终止),boxcutter-fs.exe
退出代码1,并且没有获得任何屏幕截图
然后我转向其他解决方案。我尝试过的第一个选择是Win32::GuiTest
:
eval {
SendKeys('{PRTSCR}');
my $screen = Win32::Clipboard::GetBitmap() or die "No image captured: $!\n";
open BITMAP, "> screenshot.bmp" or die "Couldn't open bitmap file: $!\n";
binmode BITMAP;
print BITMAP $screen;
close BITMAP;
};
print "$@" if ($@);
同样的结果。除非发生软件崩溃情况,否则这很有效。该计划报告No image captured
所以我认为Win32::Clipboard::GetBitmap
没有在剪贴板中得到任何东西。
最后一个解决方案是Imager::Screenshot
:
eval {
my $img = screenshot(hwnd => 'active');
$img->write(file => 'screenshot.bmp', type => 'bmp' )
or die "Failed: ", $img->{ERRSTR} , "\n";
};
print "$@" if ($@);
这次在发生软件崩溃的情况下,它给出了黑屏截图(全黑图像)。仍然无效。
然后我发现当崩溃和错误消息框出现,但软件没有被杀死所以测试框架仍然悬挂,使用上面任何解决方案的小脚本可以捕获屏幕截图。在被测软件被杀的那一刻,它们似乎只是失败了。
由于这三种方法都使用Win32 API来获取屏幕截图,我想知道它们可能因同样的问题而失败?任何提示?
答案 0 :(得分:1)
我研究了Imager::Screenshot
的源代码,发现屏幕截图失败的可能原因。
首先,如果我使用perl的-d
选项来调试屏幕截图脚本,当被测软件崩溃并在超时后被杀死时,屏幕截图有效。所以我认为在特定情况下屏幕截图失败应该是一个极端情况。
然后我读了Imager::Screenshot
的源代码。基本上,它是一个perl模块,调用用Win32 API编写的XS扩展。处理流程基本如下:
GetDC
使用hwnd
获取显示设备上下文dc
CreateCompatibleDC
获取设备上下文处理程序hdc
GetDIBits
检索设备上下文的位,并将它们写入bmp文件我的问题是,当被测试的软件崩溃并被杀死时,其窗口的hwnd
会立即失效,但它仍然传递给GetDC
以获取显示设备上下文,因此结果也是无效的(开始时bmp文件为memset
到全0,所以这是一个黑色截图)
现在我注意到根本原因是无效的hwnd
,我想出了一个解决方法:在杀死被测软件之前先截取屏幕截图。我使用了Proc::Background
和Win32::GuiTest
。关键是要确保软件GUI设置为前台窗口:
sub captureWindow {
my ($pid, $screenshot_name) = @_;
for my $hwnd (&findHwnd($pid)) {
if (Win32::GuiTest::SetActiveWindow($hwnd) && Win32::GuiTest::SetForegroundWindow($hwnd)) {
system("boxcutter-fs.exe $screenshot_name");
# send ALT+TAB key so the script was set back to foreground window
Win32::GuiTest::SendKeys("%{TAB}");
last;
}
}
}
sub findHwnd {
my ($target_pid) = @_;
my @target_hwnd;
EnumWindows(
Win32::API::Callback->new(sub {
my ($hwnd, $target_pid) = @_;
my $pid = 0xffffffff;
my $tid = GetWindowThreadProcessId($hwnd, $pid);
$pid = unpack 'L', $pid;
if ($target_pid == $pid) {
push @target_hwnd, $hwnd;
}
return 1;
}, 'NN', 'I'),
$target_pid,
);
return @target_hwnd;
}
sub monitorTestProcess {
my ($cmd, $timeout) = @_;
my $rs;
my $proc = Proc::Background->new($cmd);
my $pid = $proc->pid;
select(undef, undef, undef, 0.5);
&captureWindow($pid, "screenshot_begin.png");
my $timeCount = 0;
while ($proc->alive) {
if ($timeCount >= $timeout) {
&captureWindow($pid, "screenshot_timeout.png");
$proc->die;
last;
}
select(undef, undef, undef, 1);
$timeCount++;
}
return $rs;
}