65.9K
CodeProject 正在变化。 阅读更多。
Home

高效操作 WebDriver 元素

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2014年6月13日

CPOL

2分钟阅读

viewsIcon

14571

本技巧总结了如何高效地使用 WebDriver 读取/写入网页元素。

引言

最近,我使用 WebDriver 对一个单页应用程序进行了一些自动化测试,并开发了自己的框架以避免重复代码。 即使尝试将字符串输入到 AJAX 自动建议文本输入框、从使用 'optiongrp' 的下拉列表中选择一个选项,或访问表格中的单元格,都具有相当大的挑战性。

作为我过去工作的总结,也是展示我的框架的第一步,我想介绍一些常见 HTML 元素的基本操作。

向文本框输入文本

当大家都知道 "SendKey(string text)" 应该有效时,这可能看起来过于简单。 然而,在大多数情况下,如果文本框 (input, textArea) 未清除,SendKey 会将文本附加到现有文本上。

因此,常用的做法是在调用 SendKey() 之前先清除它。

IWebElement element = theWebDriver.findElement(By.Id("textId"));
...
element.Clear();
element.SendKey("text to be input");

另一种选择是,首先使用 "Ctrl+A" 选择现有的文本,然后再进行实际输入。

public static readonly string Control = Convert.ToString
(Convert.ToChar(0xE009, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
public static readonly string Tab = Convert.ToString(Convert.ToChar
(0xE004, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
public static readonly string Control_a = Control + "a";

IWebElement element = theWebDriver.findElement(By.Id("textId"));
...
element.SendKey(Control_a);
element.SendKey("text to be input");

然而,这对于由 Angular 等技术支持的自动完成文本框来说是不够的,当我执行以下操作时,会弹出一个警告对话框。 再次考虑在这种情况下的人工操作,可以通过向该字段发送一个 TAB 键来解决问题。 因此,对我来说,首选方法是

public static readonly string Tab = Convert.ToString
(Convert.ToChar(0xE004, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
...


element.SendKey(Control_a);
?element.SendKey("text to be input" + Tab);

从下拉列表中选择一个选项

Selenium 建议从下拉列表中选择一个选项的方法是

SelectElement select = new SelectElement(driver.FindElement(By.TagName("select")));
select.DeselectAll();
select.SelectByText(Edam");

但是,当使用 "optiongrp" 包含多个选项时,它不起作用。 一种简单的解决方法是像在浏览器中一样使用 SendKeys() 操作。

IWebElement element = theWebDriver.findElement(By.Id("selectId"));
...
element.SendKey("Edam" + Tab);

这样,我们只能通过文本选择选项,而不是通过其值,但这可以通过在调用 "SendKey()" 之前匹配值来完成,并且对于大多数情况来说应该足够了。

访问表格

作为表格行/单元格的集合,访问表格的特定单元格比较麻烦,以下讨论仅限于行数相同的表格。

基本上,有 3 个步骤可以确定表格的结构

  1. 获取所有行(或标签名为 "tr" 的子元素);
  2. 解析第一行,查看它是否包含标题(标签名为 "th" 的元素),并存储标题名称;
  3. 保留实际的行号和列号,以便进行后续操作。
public int RowCount { get; private set; }
public int ColumnCount { get; private set; }
public List<string> Headers { get; private set; }
public bool WithHeaders { get; private set; }

ReadOnlyCollection<IWebElement> rows = driver.FindElements(By.CssSelector("tr"));
IWebElement firstRow = rows[0];
var headers = firstRow.FindElements(By.CssSelecor("th")).ToList();
RowCount = rows.Count();
if (headers.Count() != 0)
{
    WithHeaders = true;
    ColumnCount = headers.Count();
    Headers = headers.Select(x => x.Text).ToList();
}
else
{
    RowCount += 1;
    WithHeaders = false;
    Headers = null;
    ColumnCount = rows.FirstOrDefault().FindElements(By.CssSelector("td")).Count();
}

然后,可以有多种方法来访问所需的单元格/行,如下面的示例所示。

  1. 通过 rowIndex columnIndex 访问单元格
    IWebElement table = driver.FindElement(By.Id("table"));
    
    public IWebElement cellOf(int rowIndex, int columnIndex)
    {
        if (columnIndex < 0 || columnIndex >= ColumnCount)
            throw new Exception("Column Index ranges from 0 to " + (ColumnCount-1));
        if (rowIndex < 0 || rowIndex >= RowCount)
            throw new Exception("Row Index ranges from 0 to " + (RowCount-1));
        if (rowIndex == 0 && !WithHeaders)
            throw new Exception("No headers (rowIndex=0) defined for this table.");
    
        String css = string.Format("tr:nth-of-type({0}) {1}:nth-of-type({2})", rowIndex + 1,
                            rowIndex == 0 ? "th" : "td",
                            columnIndex);
        return table.FindElement(By.CssSelector(css));
    }
  2. 通过 rowIndex 和标题名称访问单元格
    IWebElement table = driver.FindElement(By.Id("table"));
    
    public IWebElement cellOf(int rowIndex, string headername)
    { 
        int columnIndex = Headers.FindIndex(s => s.Contains(headerKeyword));
        return this[rowIndex, columnIndex];
    }
  3. 更具体地说,返回包含特定值在标题下的行
    IWebElement table = driver.FindElement(By.Id("table"));
    
    public IWebElement rowOf(string headerKeyword, Func<string, bool> predicate)
    {
        int columnIndex = Headers.FindIndex(s => s.Contains(headerKeyword));
        var allRows = table.FindElements(By.CssSelector("tr"));
        if (WithHeader)
            allRows.Skip(1);
    
        foreach( var row in allRows)
        {
            var td = row.FindElement(By.CssSelector(string.Format("td:nth-of-type({0})", columnIndex + 1)));
            if (predicate(td.Text))
                return row;
        }
        return null;
    }

看点

在本技巧中,我们讨论了一些非常详细的程序,用于操作一些正在工作但未记录的常见 HTML 元素。

© . All rights reserved.