【问题标题】:How to automate an IE webapp that pops a modal HTML dialog?如何自动化弹出模态 HTML 对话框的 IE webapp?
【发布时间】:2013-04-10 00:02:09
【问题描述】:

[再次修订为清楚起见]

我有一个与网站交互的 C++ 程序。该网站是特定于 IE 的,我的程序也是如此。

我正在以普通方式连接到正在运行的 IE 实例(进程外——参见代码)。获得IWebBrowser2 后,我就可以轻松获得IHTMLDocument2 并与各个IHTMLElement 对象交互、填写字段并单击按钮。

但如果网页有调用window.showModalDialog 的javascript,我就会陷入困境:我需要与弹出窗口中的HTML 元素进行交互,就像其他页面一样;但我似乎无法得到它的IWebBrowser2

弹出窗口总是标题为“网页对话框”,是一个Internet Explorer_TridentDlgFrame 类型的窗口,其中包含Internet Explorer_Server。但是我无法像普通 IE 实例那样从Internet Explorer_Server 窗口获取 IWebBrowser2。

我可以得到IHTMLDocument2Ptr,但是当我尝试得到IWebBrowser2 时,我得到了HRESULTE_NOINTERFACE

代码是相当标准的东西,如果它是一个“普通”的 IE 窗口就可以正常工作

IHTMLDocument2Ptr pDoc;
LRESULT lRes;

/* hWndChild is an instance of class "Internet Explorer_Server" */

UINT nMsg = ::RegisterWindowMessage( "WM_HTML_GETOBJECT" );
::SendMessageTimeout( hWndChild, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, 
    (DWORD*)&lRes );

LPFNOBJECTFROMLRESULT pfObjectFromLresult = 
    (LPFNOBJECTFROMLRESULT)::GetProcAddress( hInst, "ObjectFromLresult" );
if ( pfObjectFromLresult != NULL )
{
    HRESULT hr;
    hr = (*pfObjectFromLresult)( lRes, IID_IHTMLDocument, 0, (void**)&pDoc );
    if ( SUCCEEDED(hr) ) {
        IServiceProvider *pService;
        hr = pDoc->QueryInterface(IID_IServiceProvider, (void **) &pService);
        if ( SUCCEEDED(hr) )
        {
            hr = pService->QueryService(SID_SWebBrowserApp,
                IID_IWebBrowser2, (void **) &pBrowser);

            // This is where the problem occurs:
            // hr == E_NOINTERFACE
         }
    }
}

如果重要的话,这是 VistaIE8。 (我强调这一点是因为这两个都在我的代码库中引入了重大更改,这些更改在 XP/IE7 上运行良好。)

再一次,我的目标是获取每个IHTMLElement 并与之交互。我无权访问我正在自动化的应用程序的源代码。

我正在考虑盲目地将击键发送到Internet Explorer_Server 窗口,但我不想这样做。

编辑添加:

有人建议获取子窗口并向它们发送消息,但我很确定这不适用于Internet Explorer_Server;根据 Spy++,没有任何子窗口。 (这不是 IE 特有的。Java 小程序似乎也没有子窗口。)

更新

在 cmets 中,Simon Maurer 说上面的代码对他有用,为了确保没有错别字,他非常慷慨地在 pastebin 上发布了一个完整的独立应用程序。当我使用他的代码时,它在同一个地方以同样的方式失败,我意识到他认为我想连接到底层页面,而不是弹出窗口。所以我编辑了上面的文字以消除这种歧义。

【问题讨论】:

  • 有什么例外?当您调用pDoc->QueryInterface 时,pDoc 看起来有效吗?
  • @NateHekman:我已经大幅修改了这个问题。
  • 你能确认 c++ 应用程序是进程外的吗?什么是“网页对话框”?是不是脚本调用showModalDialog时弹出的IE窗口?
  • @SimonMourier - 我已经修改了这个问题。 是的,它肯定是在进程之外。 (该应用程序的早期版本在进程中工作,但在 IE8 上停止工作。),我假设该应用程序正在调用 showModalDialog。
  • 仅供参考,我已经在 Windows 8 上使用 IE 10 测试了这段代码,它可以工作,即使显示了模式对话框。

标签: c++ internet-explorer browser-automation iwebbrowser2


【解决方案1】:

如果您只想要IHTMLElement,我不知道您为什么要获得IServiceProviderIWebBrowser2。您可以通过调用IHTMLDocumentget_all() 方法来获取它们。

这段代码 sn-p 向您展示了它是如何工作的:

#include <Windows.h>
#include <mshtml.h>
#include <Exdisp.h>
#include <atlbase.h>
#include <SHLGUID.h>
#include <oleacc.h>
#include <comdef.h>
#include <tchar.h>

HRESULT EnumElements(HINSTANCE hOleAccInst, HWND child)
{
    HRESULT hr;

    UINT nMsg = ::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));
    LRESULT lRes = 0;
    ::SendMessageTimeout(child, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (PDWORD)&lRes);

    LPFNOBJECTFROMLRESULT pfObjectFromLresult = (LPFNOBJECTFROMLRESULT)::GetProcAddress(hOleAccInst, "ObjectFromLresult");
    if (pfObjectFromLresult == NULL)
        return S_FALSE;

    CComPtr<IHTMLDocument2> spDoc;
    hr = (*pfObjectFromLresult)(lRes, IID_IHTMLDocument2, 0, (void**)&spDoc);
    if (FAILED(hr)) return hr;

    CComPtr<IHTMLElementCollection> spElementCollection;
    hr = spDoc->get_all(&spElementCollection);
    if (FAILED(hr)) return hr;

    CComBSTR url;
    spDoc->get_URL(&url);
    printf("URL: %ws\n", url);

    long lElementCount;
    hr = spElementCollection->get_length(&lElementCount);
    if (FAILED(hr)) return hr;
    printf("Number of elements: %d", lElementCount);

    VARIANT vIndex; vIndex.vt = VT_I4;
    VARIANT vSubIndex; vSubIndex.vt = VT_I4; vSubIndex.lVal = 0;
    for (vIndex.lVal = 0; vIndex.lVal < lElementCount; vIndex.lVal++)
    {
        CComPtr<IDispatch> spDispatchElement;
        if (FAILED(spElementCollection->item(vIndex, vSubIndex, &spDispatchElement)))
            continue;
        CComPtr<IHTMLElement> spElement;
        if (FAILED(spDispatchElement->QueryInterface(IID_IHTMLElement, (void**)&spElement)))
            continue;
        CComBSTR tagName;
        if (SUCCEEDED(spElement->get_tagName(&tagName)))
        {
            printf("%ws\n", tagName);
        }
    }
    return S_OK;
}

int _tmain(int argc, _TCHAR* argv[])
{
    ::CoInitialize(NULL);
    HINSTANCE hInst = ::LoadLibrary(_T("OLEACC.DLL"));
    if (hInst != NULL)
    {
        HRESULT hr = EnumElements(hInst, (HWND)0x000F05E4);    // Handle to Internet Explorer_Server determined with Spy++ :)
        ::FreeLibrary(hInst);
    }
    ::CoUninitialize();
    return 0;
}

以上代码适用于两者:普通窗口或模式窗口,只需将正确的HWND 传递给SendMessageTimeout 函数即可。

警告我在这个例子中使用了一个硬编码的HWND值,如果你想测试它,你应该启动一个IE实例并使用Internet Explorer_Server获取Internet Explorer_Server窗口的HWND间谍++。

我还建议您使用CComPtr 以避免内存泄漏。

【讨论】:

  • 我会试试这个。您使用的是哪个版本的 IE?
  • YES 这似乎行得通!确实,在这个应用程序中我只需要spElement,从那里我可以导出其他所有内容。
猜你喜欢
  • 1970-01-01
  • 2014-06-10
  • 2015-08-06
  • 2017-10-14
  • 1970-01-01
  • 1970-01-01
  • 2019-12-13
  • 1970-01-01
  • 2018-02-21
相关资源
最近更新 更多