19. 通过 xpath 定位

我们今天将讨论 XPath 策略。 这也是一种先进且有效的定位策略(cssSelectors也是!)。 虽然有效,但有时可能会造成混淆。 因此,让我们深入研究如何理解我们经常被误解的朋友,并一劳永逸地提出好的代码。

当所有希望都丧失了时,拯救自己的唯一机会就是转向 XPath 策略。 因为在大多数情况下,我们将测试现有的东西并且无法修改。 我们并不总是能够控制页面以添加一些 ID,从而使自动化成为一项更简单的任务。 因此,请停止咆哮并认真对待!

XPath (XML 路径语言):根据 w3schools 的说法,XPath 是一种“路径式”语言,用于标识和浏览 XML 文档中的各种元素和属性。因此,XPath 提供了用于定位 HTML 文档中任何元素的语法。

注意:

如果到目前为止,您从未接触过 XPath,请在继续进行操作之前先了解 XPath 术语。 特别是,节点和这些节点之间的关系即父级,子级,同级,祖先,后代。

如果您已经知道这些术语,并且希望略微刷一下,请参考下图。 礼貌: w3schools

xpath DOM

现在是当今的主要任务:通过 XPath 策略定位元素!在这篇文章中,我们将研究以下技术:

  1. 捷径
  2. 绝对 XPath 和相对 XPath
  3. 使用标签和属性
  4. 使用两个条件
  5. 使用contains()
  6. 查找多个元素

1. 捷径:

是否想以简单的方式在网页上找到任何元素的 XPath? 做完了。是否希望几乎每次都处于完美的工作状态? 也做完了。 马上完成所有这些操作呢? 它让您涵盖了摇滚明星! 您只需要“Firebug”! 它可以作为 Firefox 浏览器的附加组件使用。

  1. 点击 Firebug 图标或按“F12”。
  2. 检查其 XPath 是必需的元素。
  3. 相应的代码将在 Firebug 面板的“HTML”部分中突出显示。
  4. 右键点击突出显示的代码,然后选择“复制 XPath”
  5. 瞧! 您已将准备好的烘焙 XPath 复制到剪贴板!

xpath firebug

如果您希望获得有关 Firebug 的详细信息,请在处查看

2. 现在对于长短:

为了从头提出 XPath,我们首先需要了解可用的两种 Xpath。 它们是绝对 XPath 和相对 XPath。

绝对 XPath 相对 XPath
它以单个正斜杠(/)开头。 它以双正斜杠(//)开头。
/指示 XPath 引擎参考根节点搜索元素。 //指示 XPath 引擎在 DOM 结构中的任何位置搜索匹配的元素。
与相对的 XPath 相比,元素标识更快。 由于仅指定了部分路径,因此需要花费更多时间来标识元素。
即使对 HTML DOM 结构进行了最细微的更改(例如添加标签或删除标签),绝对 XPath 也会失败。 相对 XPath 较短,更改的可能性较小,从而使其更可靠。
例如,/html/head/body/div[2]/form/input 例如//input[@name=”username”]

3. 使用标签和属性:

可以使用其 HTML 标签,其属性(例如 ID,名称,类,标题,值,hrefsrc等)及其相应的值来定位特定的 Web 元素。

  • 语法driver.findElement(By.xpath("//tag_name[@attribute='value']"));
  • 解释 :标识 XPath 指向的元素。 “//”标识指定的节点,“@”符号用于选择与给定值匹配的指定属性。

示例 :让我们在 Gmail 帐户注册页面上找到名字文本框。

  • 右键点击“名字”文本框,然后选择检查元素,以获取相应的 HTML 代码,如下所示:
    <input value="" name="FirstName" id="FirstName" spellcheck="false" 
    class="form-error" aria-invalid="true" type="text">
    
  • 我们可以看到“input”标签具有一个“name”属性,其值为“FirstName”。
  • 代码: (可以使用以下任一选项)
    driver.findElement(By.xpath("//input[@name='FirstName']"));
    driver.findElement(By.xpath("//input[@id='FirstName']"));
    driver.findElement(By.xpath("//input[@class='form-error']"));
    
  • 如果您希望使用绝对路径
    driver.findElement(By.xpath("/html/body/div[1]/div[2]/div/div[1]/div/form/div[1]/fieldset/label[1]/input"));
    

表单标签有许多子div标签。 在我们的情况下,我们希望选择第一个div标签。 可以将其指定为“div[1]”。 这些方括号[]中的数字表示要选择的确切同级。

4. 使用两个条件

如果多个标签具有相同的属性值怎么办? 或者,如果您希望仅在元素与指定条件都匹配时才定位它,该怎么办?

  • 语法driver.findElement(By.xpath("//tag_name[@attribute1='value1'][@attribute2='value2']"))
  • 说明 :标识具有指定tag_name的元素,这些元素的属性与给定值匹配。

示例 :让我们在 Gmail 帐户注册页面上找到“确认密码”文本框。

  • 右键点击“创建密码”和“确认密码”文本框,然后选择检查元素以获取相应的 HTML 代码,如下所示:
    <input name="Passwd" id="Passwd" type="password">
    <input name="PasswdAgain" id="PasswdAgain" type="password">
    
  • 请注意,两个文本框的“class”属性值均相同,但“id”和“name”的值均不同。 因此,为了找到“确认密码”文本框,让我们同时提及其“class”和“id”值。
  • 代码:
    driver.findElement(By.xpath("//input[@type='password'][@id='PasswdAgain']"));
    

5. 使用contains():

如今,大多数属性值(例如“id”,“src”,“href”等)都是使用恒定的前缀或后缀动态生成的。

想象一个网页每天都有变化的图像。 其src可以是“Image_random-generated-key_date.jpg”。 在这种情况下,我们可以通过 XPath 使用在其src属性中包含“Image”值的“img”标记定位图像。 因此,通过指定属性的部分值,我们可以找到元素。

  • 语法driver.findElement(By.xpath("//tag_name[contains(@attribute, 'value')]]"))
  • 说明 :标识具有指定tag_name的元素,该元素的属性与给定的部分值相匹配。

示例 :让我们在 Gmail 帐户注册页面上找到“下一步”按钮。

  • 右键单击按钮并检查元素以获取相应的 HTML 代码,如下所示:
    driver.findElement(By.xpath("//div[contains(@class,'button')]/input"));
    
  • 注意,该 XPath 标识div元素,该div元素包含带有部分值buttonclass属性(<div class="form-element nextstep-button">),然后找到其子“input”标签(即“提交”按钮)。

6. 查找多个元素

您可能会遇到希望查找具有特定类或名称的所有元素并对它们执行某些操作的情况。 星号(*)符号可助我们一臂之力!

示例 :让我们在 Gmail 帐户注册页面上找到所有类值为“goog-inline-block”的元素。

  • driver.findElements(By.xpath("//*[contains(@class,'goog-inline-block')]"));
    
  • 这将找到在其“class”属性中包含值“goog-inline-block”的所有标签。
  • 明确指出要使用“findElements”,以便将所有已标识的 Web 元素添加到列表中。 如果使用“findElement”,它将仅返回所标识的第一个元素。

让我们来看一个测试案例,该案例实现了迄今为止本文中涵盖的所有技术:

  1. 打开 Firefox 浏览器。
  2. 导航到 Google 帐户创建页面
  3. 使用绝对 XPath 找到“名字”文本框
  4. 输入“testFirst”作为名字
  5. 使用标签和“id”属性找到“姓氏”文本框(当然是相对的 XPath!)
  6. 输入“testLast”作为姓氏
  7. 使用两个条件(类型和 ID 属性)找到“确认密码”文本框
  8. 输入“Pass1234!”作为确认密码
  9. 使用星号将所有包含在其“class”属性中值“goog-inline-block”的元素定位
  10. 将找到的元素总数打印到控制台
  11. 将第一个标识的元素的标题值打印到控制台
  12. 使用contains()找到“下一步”按钮
  13. 将其“name”属性的值打印到控制台
  14. 验证 Eclipse IDE 控制台的输出屏幕和 JUnit 窗格是否成功
package com.blog.junitTests;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
public class ElementLocatorTest4 {
    //Declaring variables
    private WebDriver driver; 
    private String baseUrl;
    @Before
    public void setUp() throws Exception{
        // Selenium version3 beta releases require system property set up
        System.setProperty("webdriver.gecko.driver", "E:\\ Softwares\\Selenium\\geckodriver-v0.10.0-win64\\geckodriver.exe");
        // Create a new instance for the class FirefoxDriver
        // that implements WebDriver interface
        driver = new FirefoxDriver();
        // Implicit wait for 5 seconds
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        // Assign the URL to be invoked to a String variable
        baseUrl = "https://accounts.google.com/SignUp";
    }
    @Test
    public void testPageTitle() throws Exception{
        // Open baseUrl in Firefox browser window
        driver.get(baseUrl);
        // Locate 'First Name' text box by absolute XPath
        // assign it to a variable of type WebElement  
        WebElement firstName = driver.findElement(By.xpath("/html/body/div[1]/div[2]/div/div[1]"
            + "/div/form/div[1]/fieldset/label[1]/input"));
        // Clear the default placeholder or any value present
        firstName.clear();
        // Enter/type the value to the text box
        firstName.sendKeys("testFirst");
        // Locate 'Last Name' text box by relative XPath: using tag and id attribute
        WebElement lastName = driver.findElement(By.xpath("//input[@id='LastName']"));
        lastName.clear();
        lastName.sendKeys("testLast");
        // Locate 'Confirm your password' text box by XPath: using two conditions   
        WebElement confirmPwd = driver.findElement(By.xpath("//input[@type='password'][@id='PasswdAgain']"));
        confirmPwd.clear();
        confirmPwd.sendKeys("Pass1234!");
        //Locate all elements with class 'goog-inline-block' by relative XPath: using asterisk symbol
        List<WebElement> dropdowns = driver.findElements(By.xpath("//*[contains(@class,'goog-inline-block')]"));
        // Prints to the console, the total number of elements located 
        System.out.println("Total elements containing the class 'goog-inline-block' are= " + dropdowns.size());  
        // Prints first identified element's title value to console
        System.out.println("Value of the dropdown's 'title' attribute = " + dropdowns.get(0).getAttribute("title"));
        // Locate 'Next step' button by XPath: contains() and child element
        WebElement submitBtn = driver.findElement(By.xpath("//div[contains(@class,'button')]/input"));
        // Prints submitBtn's 'name' attribute's value to the console 
        System.out.println("Value of the button's 'name' attribute = " + submitBtn.getAttribute("name"));
    }
     @After
      public void tearDown() throws Exception{
        // Close the Firefox browser
        driver.close();
    }
}

执行结果:(这段代码将作为本文讨论的每种技术的一部分进行解释。)

在 JUnit 窗口中,绿色条显示测试用例已成功执行。 控制台窗口显示没有任何错误。 它还显示带有星号的 Web 元素总数,以及下拉菜单和按钮的属性值。

xpath console output

下图显示了成功执行测试脚本后获得的 Firefox 输出。

xpath firefox output

7. 使用text()

通过提供与网页上显示的文本完全相同的 文本,这是一种轻松定位元素的方法。

注意:

当心! 即使错误地包含空格,您也可能遇到“ElementNotFound”异常。 在代码中提供的文本必须与可见文本完全匹配,这一点非常重要,我的意思是从字面上看!

示例 :让我们找到“Facebook 注册”页面上的“忘记帐户?”链接。

  • 右键点击“忘记帐户?”链接,然后选择检查元素以获取相应的 HTML 代码,如下所示:
    <a href="https://www.facebook.com/recover/initiate?lwv=111" 
    data-testid="forgot_account_link">Forgot account?</a>
    
  • 让我们通过提供出现在 Facebook 页面上的文本来找到此链接。
    driver.findElement(By.xpath("//a[text()='Forgot account?']"));
    
  • 如果要查找包含部分文本的所有元素,可以将contains()text()技术结合使用。 可以使用findElements方法在列表中获取所有元素。
  • 让我们尝试在同一示例中同时实现contains()text()。 由于我们只有一个链接与该文本,因此将使用findElement方法。
    driver.findElement(By.xpath("//*[contains(text(),'Forgot')]"));
    

8. 使用starts-with()

通过指定属性的部分值(前缀),可以使用starts-with()查找元素。 当页面重新加载时属性值动态更改时,此功能非常有用。

示例 :让我们在“Facebook 注册”页面上找到“新密码”文本框。

  • 右键点击“新密码”文本框,然后选择检查元素以获取相应的 HTML 代码:
    <input class="inputtext _58mg _5dba _2ph-" data-type="text" 
    name="reg_passwd__" aria-required="1" placeholder="" id="u_0_d" 
    aria-label="New password" type="password">
    
  • 让我们通过提供type属性的部分前缀值“通过”来找到此文本框。 请注意,“登录”部分中的密码字段还具有type作为password
    <input class="inputtext" name="pass" id="pass" tabindex="2" type="password">
    
  • 因此,第二个元素必须位于我们的案例中。
    driver.findElement(By.xpath("(//input[starts-with(@type,'pass')])[2]"));
    

9. 使用 XPath 轴

XPath 轴定义在当前节点浏览 DOM 的树形结构时要考虑的相对于当前节点的节点集或方向。下表(礼貌性表示: w3schools )显示了所有 13 个可用的 XPath 轴及其结果。

轴名称 结果
祖先 选择当前节点的所有祖先(父,祖父级等)
祖先或自己 选择当前节点的所有祖先(父,祖父级等)和当前节点本身
属性 选择当前节点的所有属性
子项 选择当前节点的所有子节点
后代 选择当前节点的所有后代(子代,孙代等)
后代或自己 选择当前节点的所有后代(子代,孙代等)和当前节点本身
之后 选择当前节点的结束标记之后的文档中的所有内容
之后的同级 选择当前节点之后的所有同级
命名空间 选择当前节点的所有名称空间节点
父项 选择当前节点的父节点
之前 选择出现在文档中当前节点之前的所有节点,但祖先,属性节点和名称空间节点除外
之前的同级 选择当前节点之前的所有同级
自己 选择当前节点

9.1. 父轴

选择当前节点的父级。

  • 示例 :让我们找到位于 Facebook Sign Up 页面左上方的 Facebook 徽标的锚标记。
  • 在检查元素后:
    <a href="https://www.facebook.com/" title="Go to Facebook Home">
       <i class="fb_logo img sp_euCDsy2vhU4 sx_af4dba">
           Facebook
       </i>
    </a>
    
  • 代码:
    driver.findElement(By.xpath("//i[@class='fb_logo']/parent::a"));
    
  • 将找到具有hreftitle属性的父节点“a”。

9.2. 祖先轴

选择当前节点的所有祖先(父代,祖父级等)。

  • 示例 :让我们在“Facebook 注册”页面上找到文字“生日”。
    driver.findElement(By.xpath("//select[@id='month']/ancestor::div[@class='_5k_5']/preceding-sibling::div"));
    
  • 带有 ID 的“select”标签,选择了“month”。 转到类“_5k_5”的祖先div标签。 然后到其前一个带有“div”标签的同级节点,其文本为“Birthday”。
  • 选择该特定示例以显示可以组合多个轴以获得所需的结果。

9.3. 子轴

选择当前节点的所有子节点

  • 示例 :让我们找到“Facebook 注册”页面上的“登录”按钮。
     <label class="uiButton uiButtonConfirm" for="u_0_q" id="loginbutton"></label>
    
  • 代码:
    driver.findElement(By.xpath("//label[@id='loginbutton']/child::input"));
    
  • 标识为“loginbutton”的标签的子节点-标识为“u_0_q”的输入标签。

9.4. 后代轴

选择当前节点的所有后代(子代,孙代等)。

  • 示例 :让我们在“Facebook 注册”页面上找到“名字”文本框。
    <input id=”u_0_1″ class=”inputtext _58mg _5dba _2ph-” data-type=”text” name=”firstname” aria-required=”1″ placeholder=”” aria-label=”First name” type=”text”> </div>
    
  • 代码:
    driver.findElement(By.xpath("//div[contains(@class,'uiStickyPlaceholderInput')]/descendant::input"));
    
  • 类别为uiStickyPlaceholderInputdiv标签的后代节点-ID 为u_o_1的输入标签已找到。

9.5. 同级轴

选择当前节点之后的所有同级

  • 示例 :让我们在“Facebook 注册”页面页脚部分中找到“登录”链接。
    <td class="_51m- hLeft plm">
       <a href="/r.php" title="Sign Up for Facebook">Sign Up</a>
    </td>
    <td class="_51m- hLeft plm">
       <a href="/login/" title="Log into Facebook">Log In</a>
    </td>
    
  • 代码:
    driver.findElement(By.xpath("//td[@class='_51m- hLeft plm']/following-sibling::td/child::a"));
    
  • 以下td标签的同级类_51m-hLeft plm是另一个td标签,其子对象是带有标题“登录 Facebook”的锚标签。
  • 将后继同级和子级轴组合在一起,以在页脚部分中找到“登录”超链接。

9.6. 上一个同级轴

选择当前节点之前的所有同级

  • 示例 :让我们找到“Facebook 注册”页面上的“女性”单选按钮。
    <span class="_5k_2 _5dba">
       <input id="u_0_g" name="sex" value="1" type="radio">
       <label class="_58mt" for="u_0_g">Female</label>
    </span>
    
  • 代码:
    driver.findElement(By.xpath("//label[@class='_58mt']/preceding-sibling::input"));
    
  • “标签”标签的前面同级是“无线电”类型的input标签。 这样就找到了所需的单选按钮。
  • 这样,就涵盖了一些常用的 XPath 轴类型。

我们在 BrainBell 方面一直处于停滞状态。 那为什么要延迟呢? 开始了,

BrainBell注意! 注意您的大脑状态。

  • 症状 :没有任何东西被注册,开始浏览文章或忘记您刚刚阅读的内容。
  • 诊断 :您的大脑超负荷。
  • 补救 :休息一下! 但是记得很快回来😉

我将根据我的经验个人建议 Pomodoro 技术 。 它非常有效。 试一试!

概览

让我们来看一个测试案例,该案例实现了迄今为止本文中涵盖的所有技术,

  1. 打开 Firefox 浏览器。
  2. 导航到 www.facebook.com
  3. 使用text()找到“忘记帐户?”链接
  4. 将链接文本打印到控制台
  5. 使用starts-with()找到“新密码”文本框
  6. 输入值“test1234!
  7. 使用子轴找到“登录”按钮
  8. 将值属性打印到控制台
  9. 使用父轴找到 Facebook 徽标
  10. 将其title属性的值打印到控制台
  11. 在页脚部分的“兄弟姐妹”轴中找到“登录”链接
  12. 将其title属性的值打印到控制台
  13. 使用上一个同级轴找到“女性”单选按钮
  14. 点击单选按钮
  15. 使用祖先轴找到文本“生日”
  16. 将其文本打印到控制台
  17. 使用后代轴找到“名字”文本框
  18. 输入值“首先测试”
  19. 验证 Eclipse IDE 控制台的输出屏幕和 JUnit 窗格是否成功
package com.blog.junitTests;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
public class ElementLocatorTest4b {
    // Declaring variables
    private WebDriver driver;
    private String baseUrl;
    @Before
    public void setUp() throws Exception {
        // Selenium version 3 beta releases require system property set up
        System.setProperty("webdriver.gecko.driver", "E:\\Softwares\\Selenium\\geckodriver-v0.10.0-win64\\geckodriver.exe");
        // Create a new instance for the class FirefoxDriver
        // that implements WebDriver interface
        driver = new FirefoxDriver();
        // Implicit wait for 5 seconds
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        // Assign the URL to be invoked to a String variable
        baseUrl = "https://www.facebook.com/";
    }
    @Test
    public void testPageTitle() throws Exception {
        // Open baseUrl in Firefox browser window
        driver.get(baseUrl);
        // Locate 'Forgot Account' link using XPath: text()
        WebElement forgotAccLink = driver.findElement(By.xpath("//*[contains(text(),'Forgot')]"));
        // Prints the link text to the console
        System.out.println("Link text: " + forgotAccLink.getText());
        // Locate 'New password' text box by XPath: using starts-with()
        WebElement passwordNew = driver.findElement(By.xpath("(//input[starts-with(@type,'pass')])[2]"));
        // Clear the default placeholder or any value present
        passwordNew.clear();
        // Enter/type the value to the text box
        passwordNew.sendKeys("test1234!");
        // Locate 'Log In' button using XPath: child axis
        WebElement logIn = driver.findElement(By.xpath("//label[@id='loginbutton']/child::input"));
        // Prints 'value' attribute to console
        System.out.println("Child axis: " + logIn.getAttribute("value"));
        // Locate 'Facebook' logo using XPath: parent axis
        WebElement fbLogo = driver.findElement(By.xpath("//i[contains(@class,'fb_logo')]/parent::a"));
        // Prints 'title' attribute's value to console
        System.out.println("Parent axis: " + fbLogo.getAttribute("title"));
        // Locate 'Log In' link in footer section using XPath: following-sibling axis
        WebElement loginFooter = driver.findElement(By.xpath("//td[@class='_51m- hLeft plm']/following-sibling::td/child::a"));
        //Prints 'title' attribute's value to console
        System.out.println("Following-sibling: " + loginFooter.getAttribute("title"));
        // Locate 'female' radio button using XPath: preceding-sibling axis
        WebElement femaleRadioBtn = driver.findElement(By.xpath("//label[@class='_58mt']/preceding-sibling::input"));
        // Click the radio button
        femaleRadioBtn.click();
        // Locate 'Birthday' text using XPath: ancestor axis
        WebElement birthday = driver.findElement(By.xpath("//select[@id='month']/ancestor::div[@class='_5k_5']/preceding-sibling::div"));
        //Prints text to console
        System.out.println("Ancestor axis: " + birthday.getText());
        // Locate 'first name' test box using XPath: descendant axis
        WebElement firstName = driver.findElement(By.xpath("//div[contains(@class,'uiStickyPlaceholderInput')]/descendant::input"));
        firstName.clear();
        firstName.sendKeys("test first");
    }
    @After
    public void tearDown() throws Exception {
        // Close the Firefox browser
        driver.close();
    }
}

执行结果:(这段代码将作为本文讨论的每种技术的一部分进行解释。)

在 JUnit 窗口中,绿色条显示测试用例已成功执行。 控制台窗口显示没有任何错误。 它还可以按预期显示所有打印结果。

XPath JUnit Console

下图显示了成功执行测试脚本后获得的 Firefox 输出。

XPath Firefox output

下一节:今天锦上添花:“一种验证所选择的定位器策略是否唯一标识被测网络元素的捷径”。