© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
S. ShuklaThe Protractor Handbookhttps://doi.org/10.1007/978-1-4842-7289-3_2

2. Selenium-Inherited Web Locators

Shashank Shukla1  
(1)
Mandla, Madhya Pradesh, India
 
Now that your framework is installed and your first test case is running successfully, let’s proceed to the next step. This chapter explains how to locate the elements of a web page so that they can be interacted with. You will also learn about different locator strategies that Protractor inherits from Selenium WebDriver (webdriver.By) library, including the following.
  • Taking a screenshot of an identified locator

  • ID, class, and name locators

  • tagName locator

  • linkText and partialLinkText locators

  • CSS and XPath locators

  • JavaScript locators

Web pages are written in HyperText Markup Language (HTML for short). Cascading Style Sheets (CSS) beautify those pages. JavaScript brings the pages to life by giving them functionality. Any web page is a mix of those three foundational technologies. Note that Angular is not considered a substitute for XML/HTML. It is a development framework that gives more control over how data is dynamically manipulated and presented to the user on a web page. Applications and web pages built on Angular must use other technologies. Hence, you should know which operation should be applied to which element to interact or perform a function on it. And for that to happen, it is necessary to uniquely identify the element first. Since Angular offers enhancements to the DOM, there are some Angular-specific elements that are more easily located by Protractor than any other test framework’s locator strategies.

I hope you now understand that good locator strategies are the foundation of any automation project. With the help of a good locator strategy, you can uniquely identify the element with which you need to interact with among numerous other elements present on a web page. A good test script needs a uniquely identifiable element that remains unchanged throughout the developmental increments of the product to make the test script robust. Let’s now look at some locator strategies.

Tag Name

Let’s take a slight detour to learn how to take a screenshot of a located element. The ability to take screenshots lets you accurately validate your results after successfully identifying elements.

Consider the web page shown in Figure 2-1. On the right side, you see Chrome DevTools docked on the web page. Chrome DevTools can inspect an element. Open it using Ctrl+Shift+I while you are on a web page.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig1_HTML.jpg

If you open any web page in your browser and glance at the DOM, you find elements that do not have attributes like ID, class, or name readily available—these include elements like the <h1> tag, <td> tag, or <tr> tag. In these cases, you can use a tagName locator strategy. I captured the <h3> tag in Figure 2-1 because it’s the only <h3> tag on the entire web site DOM, so I was guaranteed to get a unique element (i.e., heading 3).

Locating the element is not enough. You must also validate that the located element is correct and what you intended. Hence, let’s capture its screenshot. First, create a folder named screenshot in your project’s root directory. Then add the code in Listing 2-1 to the spec.js file inside the describe block.

Syntax

element(by.tagName('tagName'))
it('Should capture Element(tagName) Screenshot', function () {
    browser.get('http://juliemr.github.io/protractor-demo/');
    let heading = element(by.tagName('h3'))
    heading.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/heading.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-1

Locate an Element by Its Tagname and Capturing Its Screenshot

The first part of Listing 2-1 locates the element by its tag name and stores it in a heading variable. It then captures a screenshot by using Protractor’s browser takeScreenshot API, which uses the Node.js file system (fs) library. This library interacts with your computer’s file system by creating read and write streams to upload and download files to and from your computer to Node.js. Do not forget to “require” the fs library by adding it to the first line of code.

Output

Figure 2-2 shows the screenshot is saved in the screenshot folder.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig2_HTML.jpg
Figure 2-2

Screenshot of h3 tag content saved on hard disk

Note that if the tag is a button (e.g., something like <button>Login</button>), you can locate it by using the following customized locator strategy provided by Protractor.
element(by.buttonText('Log in')) or element(by.partialButtonText('Log'))
Table 2-1 lists differences between the syntax of traditional Java Selenium and the Protractor API in finding elements.
Table 2-1

Syntax to Find Elements: Traditional Java Selenium vs. Protractor API

Java Selenium

Protractor Customization

driver.findElement(By.LocatorStrategy("LocatorValue"));

element(by.LocatorStrategy("LocatorValue");

driver.findElements(By.LocatorStrategy("LocatorValue"));

element.all(by.LocatorStrategy("LocatorValue");

Depending on your Internet or CPU speed, the screenshot may not be captured, come out blurry, or be blank. To help with this, you can add a 10-second delay to the execution with browser.sleep(10000) just before taking the screenshot. This also gives you more time to slow down and observe the automated execution.

Next, let’s look at other Selenium WebDriver inherited locator strategies in Protractor.

IDs

Refer to the element highlighted in Figure 2-3. gobutton is the Go button element ID that you want to capture. According to the World Wide Web Consortium (W3C), each element in the web page should have a unique ID. Although most dev teams don’t follow this practice, they provide unique IDs to the most important elements, making this locator popular and highly reliable. Comment out (xit) from all previous it blocks before running the code in Listing 2-2; otherwise, you see multiple results after execution.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig3_HTML.jpg
Figure 2-3

Locating button by its ID

Syntax

element(by.id('<idtext>'))
it('Should capture Element(id) Screenshot', function () {
    browser.get('http://juliemr.github.io/protractor-demo/');
    let button = element(by.id('gobutton'))
    button.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/button.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-2

Finding an Element by ID

Output

In the console terminal window shown in Figure 2-4, **. represents two test cases that were skipped and one test case that passed. The test case execution time is 2.5 seconds. The later chapters provide an in-depth discussion of Protractor logs.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig4_HTML.jpg
Figure 2-4

Screenshot of Go! button on the right VS Code window using id locator strategy

className

Figure 2-3 shows that the Go! HTML element that forms the Go button has another attribute. It is a class attribute with the value “btn”. The className selector selects elements with a specific class attribute. Multiple elements in the HTML are grouped as a class to achieve consistency in formatting. Figure 2-3 is almost the same as Figure 2-2; the one difference is the className locator strategy is used in Listing 2-3 instead of the ID locator strategy. Figure 2-5 shows that since the screenshot was saved, it implies that the element was first successfully located through the className selector. The screenshot functionality is an example; however, you can do anything with this element once it is located, such as get its text, send its text, or click it. You see other useful implementations in upcoming sections.

Syntax

element(by.className('<className>'))
it('Should capture Element(class) Screenshot', function () {
    browser.get('http://juliemr.github.io/protractor-demo/');
    let btn = element(by.className('btn'))
    btn.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/btn_class.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
Listing 2-3

Finding an Element by Class

Output

../images/511436_1_En_2_Chapter/511436_1_En_2_Fig5_HTML.jpg
Figure 2-5

Screenshot of Go! button on the right VS Code window using className locator strategy

Name Attribute

You can only find the name attribute with following elements: <a>, <applet>, <button>, <form>, <frame>, <iframe>, <img>, <input>, <map>, <meta>, <object>, <param>, <select>, and <textarea>. You cannot find this attribute with <span> or <div>.

You can use this locator strategy to handle former elements as shown in Listing 2-4. Although it’s rare to find the name attribute in an Angular web site, Figure 2-6 shows this attribute in the “Can’t access your account?​” link that is successfully located in Figure 2-7.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig6_HTML.jpg
Figure 2-6

Microsoft login page built on Angular as one of its techstacks

Syntax

element(by.name('<name>'))
it('Should capture Element(name) Screenshot', function () {
    browser.get('https://login.microsoftonline.com/');
    browser.sleep(5000)
    let cantLogin = element(by.name('cannotAccessAccount'))
    cantLogin.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/cantLogin.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-4

Finding an Element by Its Name Attribute

Output

../images/511436_1_En_2_Chapter/511436_1_En_2_Fig7_HTML.jpg
Figure 2-7

Screenshot of element using name locator strategy

Link Text

Hyperlinks are rendered on a web page with an anchor (<a>) tag and accompanied by a link and text. If the anchor tag doesn’t have a unique ID or name (see Figure 2-8), you can use the link text locator strategy from Listing 2-5 to fetch the result, as shown in Figure 2-9.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig8_HTML.jpg
Figure 2-8

Locating the element with its text Sign up

Syntax

element(by.linkText('<linktext>'))
it('Should capture Element(linkText) Screenshot', function () {
    browser.get('https://angular.realworld.io/');
    let signup = element(by.linkText('Sign up'))
    signup.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/signup.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-5

Finding an Element Using Link Text

Output

../images/511436_1_En_2_Chapter/511436_1_En_2_Fig9_HTML.jpg
Figure 2-9

The output here is a link identified by LinkText locator

Note

Listing 2-5 will make more sense in the real-world applications once you use the click operation on an element in the upcoming chapters.

If two links have the same text, this method only accesses the first one when you use element(by.linkText) instead of element.all(by.linkText). To avoid confusion, bear this in mind when using link text as your locator strategy. It is advisable to use a different locator strategy for a more robust test script in scenarios like this.

Partial Link Text

If your text is very long , or you are confident it has a unique subtext that you can leverage (see Figure 2-10), use Global instead of Global Feed to uniquely identify the element through a partial link text locator strategy as shown in Listing 2-6. This ensures that your locators are not long and are easy to manage and more flexible when making any future changes to the link text and get you the saame result as show in Figure 2-11.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig10_HTML.jpg
Figure 2-10

Locating the element with its partial text i.e only Global

Syntax

element(by.partialLinkText('<partiallinktext>'))
it('Should capture Element(partialLinkText) Screenshot', function () {
    browser.get('https://angular.realworld.io/');
    let globalFeed = element(by.partialLinkText('Global'))
    globalFeed.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/globalFeed.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-6

Finding an Element Using Partial Link Text

Output

../images/511436_1_En_2_Chapter/511436_1_En_2_Fig11_HTML.jpg
Figure 2-11

Global feed is captured as output

Note

If the developers ever need/tech businesses to rename an element, the partialLinkText locator remains unimpacted by the change. To avoid common mistakes, use either partialLinkText or linkText for testing. Keep in mind that both are case-sensitive.

CSS Selector

If you cannot find elements with general locators like ID, class, and name, use a CSS selector. Observe the element highlighted in Figure 2-12. It is a form with the form-inline ng-pristine ng-valid class. Listing 2-7 creates the CSS using the syntax shown in Figure 2-13. CSS selectors are a specific pattern through which you can uniquely locate an element in the DOM. CSS selectors select HTML elements according to their ID, class, type, or a combination of them to get a unique element, as shown in Figure 2-13.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig12_HTML.jpg
Figure 2-12

Form element

Syntax

element(by.css('tag[attribute="value"]'))
it('Should capture Element(CSS) Screenshot', function () {
    browser.get('http://juliemr.github.io/protractor-demo/');
    let form = element(by.css('form[class="form-inline ng-pristine ng-valid"]'))
    form.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/form.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-7

Creating CSS Using Syntax

Output

../images/511436_1_En_2_Chapter/511436_1_En_2_Fig13_HTML.jpg
Figure 2-13

Form element and everything inside it is captured as output

Note

cssContainingText is another CSS-based locator strategy. Protractor customizes it. Fetch the Go! button in Figure 2-12 using the following locator from Listing 2-7.
element(by.cssContainingText('.btn', 'Go!'))

On rare occasions when dealing with shadow elements, you can use the by.deepCss('value') locator.

XPath

XPath is short for XML Path Language, which navigates through the DOM of a web page. Developers use either CSS selectors or XPath locator strategies. It depends on which one is more familiar and comfortable or the strategy used by the organization for test automation.

Syntax

As you can see in Figure 2-14, XPath syntax starts with a double slash.
Xpath=//tagname[@attribute='value']
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig14_HTML.jpg
Figure 2-14

XPath syntax

// selects the current node. Tag names are any tag enclosed with angle brackets (<>), like div, form, p, or a. The @ symbol is a select attribute, like id, class, and ng-model. And value is the value of that attribute. Listing 2-8 fetches the Go! button, as depicted in Figure 2-15.
it('Should capture Element(xPath) Screenshot', function () {
    browser.get('http://juliemr.github.io/protractor-demo/');
    let btn = element(by.xpath('//button[@id="gobutton"]'))
    btn.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/btn_xpath.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-8

Finding an Element Through XPath

Output

../images/511436_1_En_2_Chapter/511436_1_En_2_Fig15_HTML.jpg
Figure 2-15

Go! button identified by XPath

Note

These days, CSS selectors are given preference over XPath because they are considered to be faster in the long run, especially if there are thousands of test scripts to run in a continuous integration environment. This is debatable. So as a best practice, you can ask the developers to add an ID to elements as much as possible, so you don’t have to rely on XPath and CSS selectors all the time.

Go to www.w3schools.com/xml/xpath_syntax.asp for more information on how to use XPath.

Though it is always recommended that you should know how to use your own XPath syntax, there are some good online utilities, like the SelectorsHub add-on, that integrate with your browser. You can leverage it to find a selector (see Figure 2-16).
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig16_HTML.jpg
Figure 2-16

SelectorsHub add-on window in Chrome developer tool

JS Function

Protractor offers the flexibility of using vanilla JavaScript with a web-native API to fetch an element on a web page and successfully return it. Figure 2-17 highlights the Protractor logo. Listing 2-9 shows the unique way that this selector strategy can be integrated with Protractor and its result in Figure 2-18.
../images/511436_1_En_2_Chapter/511436_1_En_2_Fig17_HTML.jpg
Figure 2-17

Protractor logo from www.​protractortest.​org

Syntax

document.querySelectorAll('.class or #id');
it('Should capture Element(JS) Screenshot', function () {
    browser.get('https://www.protractortest.org/');
    var img = element(by.js(function () {
        return document.querySelectorAll('.protractor-logo');
    }));
    img.takeScreenshot().then(function (element) {
        let stream = fs.createWriteStream('./screenshots/JS_logo.png');
        stream.write(new Buffer.from(element, 'base64'));
        stream.end();
    })
})
Listing 2-9

Finding an Element Through JavaScript

Output

../images/511436_1_En_2_Chapter/511436_1_En_2_Fig18_HTML.jpg
Figure 2-18

Protractor logo captured by JS document query locator

The difference between WebDriver commands and JavaScript commands is when WebDriver does a click, it attempts as best as it can to simulate what happens when a real user uses the browser. Suppose the first element is a login <button> and the second element is transparent and completely covers the first element. When you tell WebDriver to click the first element, it simulates the click, but the second element (i.e., <div>) receives the click first. Why? Because the <div> element completely covers <button>, and if a user were to click <button>, then <div> would get the click event first. Whether or not the <button> element would eventually get the click event depends on how that transparent <div> handles the event. In this case, WebDriver’s behavior is the same as when a real user tries to click a <button> element.

JavaScript’s button.click() method does not reproduce what happens when a real user clicks <button>. JavaScript sends the click event directly to the <button> element, and the transparent <div> element doesn’t get in its way.

You should make an informed decision on which option you choose as your locator strategy.

Summary

This concludes the discussion of non-Angular locator strategies. In this chapter, you learned different strategies inherited from the Selenium API to uniquely locate elements. The next chapter looks we look at additional locator strategies provided by Protractor to specifically handle Angular elements on a web page.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.27.244