如何使用CSS选择器在Selenium中查找非根元素的直接子元素?

3

我试图重复使用下面的<table>找到的WebElement来搜索它的后代和直接后代:

<html>
    ...
    <table id="tbl">
        <tbody>
            <tr>
                <td>
                    <div>foo</div>
                </td>
            </tr>
            ...
            <tr><td><button class="btnDefault"/></td></tr>
        </tbody>
    </table>
    ...
</html>

这个表格可能包含嵌套的表格,也可能没有,我不想浏览整个表格,我更愿意使用保证可以实现我的目标的更具体的选择器。而且我需要的一些元素没有合理的ID,我希望使用以下选择器来查找它们:

#tbl1 > tbody > tr:first-child > td:first-child > div
#tbl1 > tbody > tr > td > button.btnDefault

为了让代码更简洁,可能也更快,我想缓存<table>元素。
var table = driver.FindElement(By.Id("tbl"));
var div = table.FindElement(By.CssSelector("> tbody > tr:first-child > td:first-child > div"));
var button = table.FindElement(By.CssSelector("> tbody > tr > td > button.btnDefault"));

第二个和第三个查询中,这将被视为无效的CSS选择器。这是正确的,但标准的CSS语法并不适用于从特定范围进行搜索。是否有某种构造可以在此处用作查询的根?

4个回答

6

一种选择是使用伪类:scope来引用当前元素。

这个伪类出现在Selectors Level 4 specification中,目前被Firefox和Chrome支持。但是有些浏览器可能不支持它。

var table = driver.FindElement(By.Id("tbl"));
var div = table.FindElement(By.CssSelector(":scope > tbody > tr:first-child > td:first-child > div"));
var button = table.FindElement(By.CssSelector(":scope > tbody > tr > td > button.btnDefault"));

请注意,使用像“直接子元素”这样的强依赖通常会导致脆弱的选择器,从而增加测试维护的成本。


这正是我在寻找的,谢谢!不幸的是,这个代码库是一个遗留的怪物,我宁愿接受容易出错的测试,也不愿没有任何测试。 - undefined

0
当你定义页面类时,为什么不使用以下方式:
public IWebDriver WebDriver { get; set; }

[FindsBy(How = How.ClassName, Using = "btnDefault")] 
private IWebElement button { get; set; }

当你实例化这个类时,你将会得到一个作为WebElement的按钮。

我真的不确定这样做是否能完全解决我的问题,除了可能稍微提高一点清晰度之外。(可能并不会,因为有多个这样的按钮,并且我正在测试一个涉及多步骤的工作流程,其中包括一个也是多步骤的 AJAX 弹出窗口。) - undefined

0
如果你有多个具有相同类名的按钮。当我建议识别按钮所在的单元格时。也许以下代码可以起作用:
public IWebDriver WebDriver { get; set; }

IWebElement tableElement = WebDriver.FindElement(By.Id(tableName));
            IWebElement tbodyElement = tableElement.FindElement(By.TagName("tbody"));
            List<IWebElement> rows = new List<IWebElement>(tbodyElement.FindElements(By.TagName("tr")));
            foreach (var row in rows)
            {
                if (row.GetAttribute("row") != "-1" && (!string.IsNullOrWhiteSpace(row.GetAttribute("row"))))
                {
                    List<IWebElement> cells = new List<IWebElement>(row.FindElements(By.TagName("td")));
                    if (cells.Count != 0)
                    {
                        if (cells[0].Text == columnName) //Identify the cell, may use other attribute
                        {
                            IWebElement btn = cells[0].FindElement(By.ClassName("btnDefault")) as IWebElement;
                            return btn;

                        }
                    }
                }
            }

这似乎相当于查询 table tbody tr td,所以比我已经有的还要不具体。 - undefined

0
因为你是从表格元素调用FindElement方法,所以这应该可以工作:
var div = table.FindElement(By.CssSelector("tbody > tr:first-child > td:first-child > div"));
var button = table.FindElement(By.CssSelector("tbody > tr > td > button.btnDefault"));

这将在开头删除“>”。


这只是修复了语法问题,但不能保证匹配的 tbody 是表格的直接子元素。话虽如此,我现在意识到这是无意义的,因为 FindElement 应该总是抓取第一个找到的元素,当从一个 table 搜索时,它将是相应的 tbody - undefined
啊,我明白问题了。我尝试过对缓存的 'table#tbl > tbody' 进行微小的更改,换成 'div' 可能会起作用,但是对于 'button' 来说效果不大。这并不能改善性能,但是为了减少重复,你可以在页面上创建一个方法来处理与这些元素的交互(假设你正在使用页面对象模型)。 - undefined

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