CefSharp网页元素点击

5
我正在尝试对一些页面元素(如按钮或链接)进行简单的点击。
我编写了两个函数,用于通过XPath和CSS选择器进行点击。
这两个函数在浏览器的开发者控制台中完美运行,但在CEF中部分功能无法正常工作。
  • 代码可以在开发者控制台和 CEF 中成功点击简单链接
  • 代码可以在开发者控制台中成功点击精确按钮,但是在CEF中却不能点击。因为某种原因它会被忽略...
这是怎么回事?Js代码完全相同!...
    public void Click(string xpath)
    {
        var js = "document.evaluate(\"" + xpath + "\", document, null, XPathResult.ANY_TYPE, null).iterateNext().click();";

        EvaluateJavascript(js);
    }

    public void ClickCss(string css)
    {
        var js = "document.querySelector('"+ css + "').click()";

        EvaluateJavascript(js);
    }


    public async Task EvaluateJavascript(string script)
    {
        JavascriptResponse javascriptResponse = await Browser.GetMainFrame().EvaluateScriptAsync(script);

        if (!javascriptResponse.Success)
        {
            throw new JavascriptException(javascriptResponse.Message);
        }
    }

细节: enter image description here enter image description here 使用的点击代码:
_browser.ClickCss("#upload-container a");

再试一次:同样的JS代码在浏览器开发控制台完美运行,但在CEF中出现问题。

顺便说一下,我已经在Chrome中测试了JS代码。所以WebEngine在两种情况下是相同的。

PS:对我来说还可以模拟将某个特定文件拖放到某个特定的 Web 元素上。但是我没有找到任何关于 Cef、Js 或 JQuery 的相关信息...=(


可能 https://blog.mariusschulz.com/2016/05/31/programmatically-opening-a-file-dialog-with-javascript 与编程有关。 - amaitland
尝试一个更简单的例子。 - amaitland
@amaitland提供的简单示例(只有一个链接)在Cef中很有效。我已经在我的问题中写了这个 :) - Andrew_STOP_RU_WAR_IN_UA
最简单的选项是将焦点设置到元素并发送一个回车键按下事件,您可能可以很容易地适应 https://gist.github.com/jankurianski/5b56b9e36526606bcf175747c592e1c8#file-sendkeyeventexample-cs-L37。 - amaitland
@amaitland 谢谢你的想法,但是...由于某些原因,这个按钮无法在页面上聚焦。即使我尝试手动聚焦,它也无法聚焦。:/ 即使没有任何代码,也是如此。我还尝试了代码,但它仍然无法工作。但是这个想法很棒! :) - Andrew_STOP_RU_WAR_IN_UA
显示剩余3条评论
2个回答

8
问题出在JS代码的安全限制上。
解决方案如下:
  1. Get coordinates of a button/link with JS code
  2. Simulate click action on it with CEF:

    public void MouseClick(int x, int y)
    {
        Browser.GetBrowser().GetHost().SendMouseClickEvent(x, y, MouseButtonType.Left, false, 1, CefEventFlags.None);
        Thread.Sleep(15);
        Browser.GetBrowser().GetHost().SendMouseClickEvent(x, y, MouseButtonType.Left, true, 1, CefEventFlags.None);
    }
    

非常感激,它为我解决了KeyEvent问题(具体信息请见:https://dev59.com/4qLia4cB1Zd3GeqPp9_D),同时也成为我的灵感来源。当`JavaScript`无法胜任时,`CefSharp`挺身而出,扮演英雄拯救世界。 - Janis S.
5
你是如何获取SendMouseClickEvent的正确坐标的? - Konrad
这些坐标是来自于该元素的吗?我想知道在鼠标点击事件中,同一函数调用中的true/false是什么意思?是指按下和释放吗? - gumuruh
@gumuruh mouseUp http://cefsharp.github.io/api/57.0.0/html/M_CefSharp_IBrowserHost_SendMouseClickEvent.htm@gumuruh 鼠标松开 http://cefsharp.github.io/api/57.0.0/html/M_CefSharp_IBrowserHost_SendMouseClickEvent.htm - Andrew_STOP_RU_WAR_IN_UA
我花了三天时间才得出这个答案,之前不知道JS限制了从代码中触发文件选择器等操作。 - Nestor Mancilla

7

上面的答案可以正常工作。我只想补充一些我认为非常重要的信息...


获取DOM元素的位置,并模拟鼠标点击(和移动):

// get button's position
string jsonString = null;
var jsReponse = await chromiumWebBrowser1.EvaluateScriptAsync(
@"(function () {
    var bnt = document.getElementById('pnnext');
    bnt.focus();
    var bntRect = bnt.getBoundingClientRect();
    return JSON.stringify({ x: bntRect.left, y: bntRect.top });
})();"
);
if (jsReponse.Success && jsReponse.Result != null)
    jsonString = (string)jsReponse.Result;

// send mouse click event
if (jsonString != null)
{
    var jsonObject = JObject.Parse(jsonString);
    var xPosition = (int)jsonObject["x"] + 1;  // add +1 pixel to the click position
    var yPosition = (int)jsonObject["y"] + 1;  // add +1 pixel to the click position

    var host = chromiumWebBrowser1.GetBrowser().GetHost();
    host.SendMouseMoveEvent(xPosition, yPosition, false, CefEventFlags.None);
    Thread.Sleep(50);
    host.SendMouseClickEvent(xPosition, yPosition, MouseButtonType.Left, false, 1, CefEventFlags.None);
    Thread.Sleep(50);
    host.SendMouseClickEvent(xPosition, yPosition, MouseButtonType.Left, true, 1, CefEventFlags.None);
}

1 - 目前获取CefSharp DOM元素位置的最佳方法是执行自定义JavaScript并解析返回结果。这就是上述代码段的第一部分的目的。它通过ID获取元素(在加载的网页的HTML结构中定义),然后获取其相对于视口的位置,最后将其返回到C#代码。

回到C#,我们使用Newtonsoft的JObject.Parse方法进行解析。

2 - 单击发生在指定的xy坐标处,但非常重要的是要知道这些坐标是相对于浏览器组件窗口(视口)的。因此,如果您的Web浏览器组件只有100px高,并且您将单击命令发送到y坐标作为150px,则单击将发生但超出Web浏览器,因此不在Web页面上。

3 - 看是否真正执行了单击操作以及确切位置的一个技巧是将其从MouseButtonType.Left更改为MouseButtonType.Right。如果没有Web页面上的限制,则这样做,您将能够看到“鼠标右键菜单”。如果您正在点击外部,则很有可能会在操作系统上看到“鼠标右键菜单”(在这里是Windows,我用这个技巧发现我点击了组件外部)

4 - 有时候(尤其是在反网络爬虫环境中),还需要模拟鼠标移动。我使用第一行所示的SendMouseMoveEvent方法来执行此操作。

5 - 如果您的组件中加载了非常高的页面,并且需要单击超出组件边界的按钮,则需要滚动Web页面。您可以通过执行一些JavaScript代码(如上例所示)或通过以下代码执行此操作:


模拟鼠标滚轮(鼠标滚轮事件):

// send scroll command
// (mouse position X, mouse position Y, how much to scroll X, how much to scroll Y, events)
chromiumWebBrowser1.GetBrowser().GetHost().SendMouseWheelEvent(10, 10, 0, -100, CefEventFlags.None); 
Thread.Sleep(300);

大部分参数都是易于理解的,但这里最重要的是第三和第四个参数,分别是“要滚动多少像素X轴”和“要滚动多少像素Y轴”。向下滚动时,请使用负值,向上滚动请使用正值。在上面的代码中,水平方向(x轴)不滚动(零像素),垂直方向(y轴)向下滚动了100像素。

您可以将此代码放在一个循环内,以滚动所需的距离。我将其与JavaScript代码结合使用,以检索按钮的位置,以便检测它是否在视口上,或者是否需要再次发送滚动事件。


1
使用JSON表示x,y坐标似乎有些过度,而且具有不必要的开销。在JavaScript中只需返回两个值的数组,在.Net中,您将获得一个相应的数组,只需进行简单的转换即可。混合使用await和ContinueWith有点笨拙,从代码可读性的角度来看,使用其中之一会更好。 - amaitland
1
@amaitland感谢您的修订。关于“使用JSON有些过度”,是的,但它可以使一切井然有序,如果由于任何原因我需要在JS中更改参数顺序或返回其他值,我就不需要更改其余代码。我认为这种方式对于保持数据组织良好来说处理开销太小了。如果我使用简单的数组,我将无法命名属性,并且只能依赖数组中元素的顺序,这可能会成为维护的噩梦... - Vitox
@amaitland 关于“混合使用await和ContinueWith”的问题,你是完全正确的。这是一个糟糕的设计,感谢你指出来。我已经重写了它,并且认为现在更好了。请告诉我你的想法。 - Vitox
如果你想给属性命名,在JavaScript中返回一个简单的对象,你将得到一个带有键值对的字典在.Net中。现在使用await更容易阅读 :+1: - amaitland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接