当活动窗口有错误消息框时,捕获屏幕失败

时间:2013-10-22 03:15:36

标签: perl winapi screenshot

我正在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来获取屏幕截图,我想知道它们可能因同样的问题而失败?任何提示?

1 个答案:

答案 0 :(得分:1)

我研究了Imager::Screenshot的源代码,发现屏幕截图失败的可能原因。

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

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

  1. 根据窗口处理程序GetDC使用hwnd获取显示设备上下文dc
  2. 使用CreateCompatibleDC获取设备上下文处理程序hdc
  3. 使用GetDIBits检索设备上下文的位,并将它们写入bmp文件
  4. 我的问题是,当被测试的软件崩溃并被杀死时,其窗口的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;
    }