1

我正在编写一个perl在 Windows 平台上使用的软件测试框架,它通过调用被测软件来运行测试用例。如果测试用例失败,框架将捕获屏幕,以便我们获得有关失败的更多信息。

一开始我用了一个小程序,叫做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 ($@);

这次它在软件崩溃案例发生时给出了黑屏截图(全黑图像)。还是不行。

然后我发现当出现崩溃和错误消息框时,但软件还没有被杀死所以测试框架仍然挂起,使用上面任何解决方案的小脚本都可以捕获屏幕截图。似乎他们只是在被测软件被杀死的那一刻失败了。

由于这 3 种方法都使用 Win32 API 来获取屏幕截图,我想知道它们是否会因为同样的问题而失败?有什么提示吗?

4

1 回答 1

1

研究了一下源码Imager::Screenshot,找到了截图失败的可能原因。

首先,如果我使用-dperl 选项来调试截图脚本,当被测软件崩溃并在超时后被杀死时,截图工作。所以我想截图失败应该是特定情况下的一个极端情况。

然后我阅读了源代码Imager::Screenshot。基本上,它是一个调用用 Win32 API 编写的 XS 扩展的 perl 模块。处理流程基本如下:

  1. GetDC根据窗口处理程序使用hwnd获取显示设备上下文dc
  2. 用于CreateCompatibleDC获取设备上下文处理程序hdc
  3. 用于GetDIBits检索设备上下文的位,并将它们写入 bmp 文件

我的问题是当被测软件崩溃并被杀死时,hwnd它的窗口会立即失效,但它仍然被传递给以GetDC获取显示设备上下文,因此结果也无效(bmp文件memset对所有0开头,所以是黑色截图)


现在我注意到根本原因是无效的hwnd,我想出了一个解决方法:在杀死被测软件之前截取屏幕截图。我用Proc::BackgroundWin32::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;
}
于 2013-10-29T07:55:20.473 回答