【问题标题】:CefSharp webpage element clickCefSharp 网页元素点击
【发布时间】:2018-08-03 22:21:26
【问题描述】:

我正在尝试对某些页面元素(如 btn 或链接)进行简单的点击。

我编写了 2 个函数,用于通过 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);
        }
    }

详情:

使用的点击代码:

_browser.ClickCss("#upload-container a");

再一次:相同的 js 代码在浏览器开发控制台中完美运行,但由于某种原因在 CEF 中无法运行。

顺便说一下,我已经在 Chrome 中测试过 JS 代码。所以 WebEngine 在这两种情况下都是一样的。

PS:我也可以模拟将某些特定文件拖放到某些特定 Web 元素。但是我没有找到任何关于这个的信息,不是针对Cef,不是针对Js,不是针对JQuery... =(

【问题讨论】:

  • 尝试一个更简单的例子
  • @amaitland 但相同的代码在浏览器中工作。我没有在浏览器中收到“弹出窗口被阻止”消息。 OpenFileDialog 已在 Chrome 和 Firefox 中以相同的代码和相同的按钮成功打开。
  • @amaitland 关于更简单的示例(只是一个链接)在 Cef 中运行良好。我在我的问题中写了这个:)
  • CEF 也有开发工具,你可以用它们来调试。如果您尝试打开文件对话框,那么我相信您遇到了安全限制,请查看 devtools 中显示的消息。

标签: c# click cefsharp


【解决方案1】:

问题在于 JS 代码的安全限制。

问题的解决方法是:

  1. 使用 JS 代码获取按钮/链接的坐标
  2. 使用 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 issues I had 提供了灵感。当JavaScript 不能时,CefSharp 可以节省时间。
  • 您是如何获得 SendMouseClickEvent 的正确坐标的?
  • 那个坐标来自元素对吗?我想知道鼠标点击中相同函数调用的真/假是什么意思?是按进去又放出来的吗?
【解决方案2】:

上面的答案没问题。我只是想补充一些我认为非常重要的信息......


获取 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 - 到目前为止,获取 DOM 元素在 CefSharp 上的位置 的最佳方法是执行自定义 JavaScript,然后将结果解析回来。这就是上面代码第一段的目的。它通过元素的 ID(在加载的网页的 HTML 结构中定义)获取元素,然后获取其相对于视口的位置,然后将其返回给 C# 代码。 回到 C#,我们使用 Newtonsoft 的 JObject.Parse 方法对其进行解析。

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

3 - 查看点击是否真正被执行以及具体执行位置的一个技巧是将其从MouseButtonType.Left 更改为MouseButtonType.Right。这样做,如果网页没有限制,你就能看到“鼠标右键菜单”。如果您在外部单击,您可能会在操作系统上看到“鼠标右键菜单”(此处为 Windows,我可以看到我使用此技巧在组件外部单击) .

4 - 有时(特别是反爬虫环境)还需要模拟鼠标移动。我使用方法SendMouseMoveEvent 来实现,如第一行所示。

5 - 如果您的组件中加载了一个非常高的页面,并且您需要单击组件边界之外的按钮,则需要滚动网页。您可以像上面的示例代码那样执行一些 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);

参数中最大的部分是不言自明的,但这里最重要的部分是第 3 和第 4 个参数,分别是“X 滚动多少”和“Y 滚动多少”。要向下滚动,只需使用负值,向上滚动,使用正值。在上面的代码中,它不会水平滚动(零像素)(x 轴)和垂直向下滚动 100 像素(y 轴)。

您可以在loop 中使用此代码来滚动您需要多少。我将它与 JavaScript 代码结合使用来检索按钮的位置,检测它是否在视口上,或者我是否需要再次发送滚动事件。

【讨论】:

  • 使用 JSON 来表示 x,y 坐标似乎有点矫枉过正,而且会产生不必要的开销。只需在 JavaScript 中的数组中返回两个值,在 .Net 中,您将通过简单的转换获得相应的数组。混合 await 和 ContinueWith 有点笨拙,从代码可读性的角度来看,使用一个或另一个会更好。
  • @amaitland 感谢您的修改。关于“使用 JSON 是一种矫枉过正”,是的,但它保持一切整洁,如果出于任何原因我更改 JS 中的参数顺序或需要返回一些其他值,我不需要更改其余代码。我认为有一个很好的方法来保持数据组织的处理开销太小了。如果我使用一个简单的数组,我将无法命名属性,而只会依赖数组中元素的顺序,这可能是维护的噩梦......
  • @amaitland 关于“混合等待和继续”你是完全正确的。这是糟糕的设计,感谢您指出它。我重写了它,并认为它现在好多了。让我知道你的想法。
  • 如果你想命名属性,然后在 JavaScript 中返回一个简单的对象,你会在 .Net 中得到一个带有键值对的字典。 Await 用法现在更容易阅读:+1:
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多