Skip to content

Commit 090fb6e

Browse files
committed
MQE-1737: How to write good selectors
- First draft
1 parent cd1bda5 commit 090fb6e

File tree

1 file changed

+311
-0
lines changed

1 file changed

+311
-0
lines changed

docs/guides/selectors.md

+311
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# How To Write Good Selectors
2+
3+
Selectors are an atomic unit of test writing. They fit into the hierarchy like this: MFTF tests make use of action groups, which are made up of actions, which interact with page objects, which contain elements, which are specified by selectors. Because they are the building blocks, we must take care when writing them.
4+
5+
## What is a selector?
6+
7+
A "selector" is like an address to an element in the Document Object Model (DOM). It locates those elements and allows MFTF to interact with them. By element we mean things such as input fields, buttons, tables, divs, etc. By interact we mean actions such as click, fill field, etc.
8+
9+
Selectors live inside of MFTF page objects and are meant to be highly re-usable amongst all tests. They can be written in either CSS or XPath.
10+
11+
## Why are good selectors important?
12+
13+
Good selectors are important because they are the most re-used component of functional testing. They are the lowest building blocks of tests, the foundation. If they are unstable then everything else built on top of them will inherit that instability.
14+
15+
## How do I write good selectors?
16+
17+
We could cover this subject with an infinite amount of documentation and some lessons only come from experience. But this guide will explain some DOs and DONTs to help you along the way towards mastery.
18+
19+
### Inspecting the DOM
20+
21+
To write a selector you need to be able to see the DOM and find the element within it. Fortunately you don't have to look at the entire DOM every time. Nor do you have to read it from top to bottom. Instead you can make use of your browsers built in developer tools or go a step further and try out some popular browser extensions.
22+
23+
See these links for more information about built-in browser developer tools:
24+
* [Chrome Developer Tools](https://developers.google.com/web/tools/chrome-devtools/)
25+
* [Firefox Developer Tools](https://developer.mozilla.org/en-US/docs/Tools)
26+
27+
See these links for common browser addons that may offer advantages over browser developer tools:
28+
* [Live editor for CSS, Less & Sass - Magic CSS](https://chrome.google.com/webstore/detail/live-editor-for-css-less/ifhikkcafabcgolfjegfcgloomalapol?hl=en)
29+
* [XPath Helper](https://chrome.google.com/webstore/detail/xpath-helper/hgimnogjllphhhkhlmebbmlgjoejdpjl?hl=en)
30+
31+
### CSS vs XPath
32+
33+
There are many similarities and many differences between CSS and XPath. It is too much to cover in this guide alone. You should search out additional material on your own. In general:
34+
35+
* CSS is more stable, easier to read, and easier to maintain (typically).
36+
* XPath provides several powerful tools and it has been around the longest so it’s well documented.
37+
* XPath can be less stable and potentially unsupported by certain actions in Selenium.
38+
39+
### Priority
40+
41+
The best and most simple selector will always be `#some-id-here`. If only we were so lucky to have this every time. When writing selectors, you should prioritize looking for these starting points to build your selector from.
42+
43+
1. ID, Name, Class, or anything else that is unique to the element
44+
2. Complex CSS selectors
45+
3. XPath selectors
46+
4. If none of the above work for you, then the last resort is to ask a developer to add a unique ID or Class to the element you're trying to select.
47+
48+
Important: Notice we prefer the use of CSS selectors above XPath selectors when possible.
49+
50+
### Do's and Don'ts
51+
52+
Let's start with the Don'ts because they're more important.
53+
54+
#### Don't #1
55+
56+
DO NOT right click on an element in your browser developer tools and select "Copy selector" or "Copy XPath" and simply use that as your selector. These auto-generated selectors are prime examples of what not to do.
57+
58+
These are bad:
59+
60+
```
61+
#html-body > section > div > div > div > div
62+
```
63+
64+
```
65+
//*[@id='html-body']/section/div/div/div/div
66+
```
67+
68+
They both include unnecessary hierarchical details. As written, we're looking for a `div` inside of a `div` inside of a `div` inside of... you get the picture. But if an application developer adds another `div` parent tomorrow, even for stylistic reasons, then this selector will break. Furthermore, when reading it, it's not even clear what was the intended target in the first place.
69+
70+
#### Don't #2
71+
72+
DO NOT make your selectors too generic. If a selector is too generic, there is a high probability that it will match multiple elements on the page. Maybe not today. But probably tomorrow when the application under test changes.
73+
74+
These are bad:
75+
76+
```
77+
input[name*='firstname']
78+
```
79+
80+
The `*=` means `contains`. So the selector is saying find an input whose name contains the string "firstname". But if a future change adds a new element to the page whose name also contains "firstname", then this selector will now match two elements and that's bad.
81+
82+
```
83+
.add
84+
```
85+
86+
Similarly here, this will match any element which contains the class `.add`. This is brittle and susceptible to breaking when new elements/styles are added to the page.
87+
88+
#### Don't #3
89+
90+
DO NOT make your selectors too specific either. If a selector is too specific, there is a high probability that it will break due to even minor changes to the application under test.
91+
92+
These are bad:
93+
94+
```
95+
#container .dashboard-advanced-reports .dashboard-advanced-reports-description .dashboard-advanced-reports-title
96+
```
97+
98+
This selector is too brittle. It would break very easily if an application developer does something as simple as adding a parent container for style reasons.
99+
100+
```
101+
//*[@id='container']/*[@class='dashboard-advanced-reports']/*[@class='dashboard-advanced-reports-description']/*[@class='dashboard-advanced-reports-title']
102+
```
103+
104+
This is the same selector as above, but represented in XPath instead of CSS. It is brittle for the same reasons.
105+
106+
#### Do #1
107+
108+
You should think in terms of "isolation" when writing new selectors.
109+
110+
For example, let's say you have a login form that contains a username field, a password field, and a Sign In button. First isolate the parent element. Perhaps it's `#login-form`. Then target the child element under that parent element: `.sign-in-button` The result is `#login-form .sign-in-button`.
111+
112+
Thinking like this will reduce the amount of DOM that you need to worry about.
113+
114+
#### Do #2
115+
116+
If you need to interact with the parent element but it's too generic, and the internal contents are unique then you need to:
117+
118+
1. Target the unique internal contents first
119+
2. Then jump to the parent element using `::parent`
120+
121+
For example let's imagine you want to find a table row that contains the string "Jerry Seinfeld". You can use the following XPath selector:
122+
123+
```
124+
//div[contains(text(), 'Jerry Seinfeld')]/parent::td/parent::tr
125+
```
126+
127+
### CSS Examples
128+
129+
Examples of common HTML elements and the corresponding selector to find that element in the DOM:
130+
131+
Type|HTML|Selector
132+
---|---|---
133+
IDs|`<div id="idname"/>`|`#idname`
134+
Classes|`<div class="classname"/>`|`.classname`
135+
HTML Tags|`<div/>`|`div`
136+
HTML Tag & ID|`<div id="idname"/>`|`div#idname`
137+
HTML Tag & Class|`<div class="classname"/>`|`div.classname`
138+
ID & Class|`<div id="idname" class="classname"/>`|`#idname.classname`
139+
HTML Tag & ID & Class|`<div id="idname" class="classname"/>`|`div#idname.classname`
140+
141+
Examples of common CSS selector operators and their purpose:
142+
143+
Symbol|Name|Purpose|Selector
144+
---|---|---|---
145+
`*`|Universal Selector|Allows you to select ALL ELEMENTS on the Page. Wild Card.|`*`
146+
Whitespace|Descendant Combinator|Allows you to combine 2 or more selectors.|`#idname .classname`
147+
`>`|Child Combinator|Allows you to select the top-level elements THAT FOLLOWS another specified element.|`#idname > .classname`
148+
`+`|Adjacent Sibling Combinator|Allows you to select an element THAT FOLLOWS DIRECTLY AFTER another specified element.|`#idname + .classname`
149+
`~`|General Sibling Combinator|Allows you to select an element THAT FOLLOWS (directly or indirectly) another specified element.|`#idname ~ .classname`
150+
151+
Examples of CSS attribute operators and their purpose
152+
153+
Symbol|Purpose|Example
154+
---|---|---
155+
`=`|Returns all elements that CONTAIN the EXACT string in the value.|`[attribute='value']`
156+
`*=`|Returns all elements that CONTAINS the substring in the value.|`[attribute*='value']`
157+
`~=`|Returns all elements that CONTAINS the given words delimited by spaces in the value.|`[attribute~='value']`
158+
`$=`|Returns all elements that ENDS WITH the substring in the value.|`[attribute$='value']`
159+
`^=`|Returns all elements that BEGIN EXACTLY WITH the substring in the value.|`[attribute^='value']`
160+
`!=`|Returns all elements that either DOESN’T HAVE the given attribute or the value of the attribute is NOT EQUAL to the value.|`[attribute!='value']`
161+
162+
### XPath Examples
163+
164+
#### `/` vs `//`
165+
166+
The absolute XPath selector is a single forward slash `/`. It is used to provide a direct path to the element from the root element.
167+
168+
WARNING: The `/` selector is brittle and should only be used sparingly.
169+
170+
Here's an example of what NOT to do, but this demonstrates how the selector works:
171+
172+
```
173+
/html/body/div[2]/div/div[2]/div[1]/div[2]/form/div/input
174+
```
175+
176+
In the BAD example above we are specifying a very precise path to an input element in the DOM.
177+
178+
Similarly, the relative XPath selector is a double forward slash `//`. It is used to start searching for an element anywhere in the DOM.
179+
180+
Example:
181+
182+
```
183+
//div[@class=’form-group’]//input[@id='user-message']
184+
```
185+
186+
#### Parent Selectors
187+
188+
The parent selector (`..`) allows you to jump to the parent element.
189+
190+
Example #1:
191+
192+
Given this HTML:
193+
194+
```
195+
<tr>
196+
<td>
197+
<div>Unique Value</div>
198+
</td>
199+
</tr>
200+
```
201+
202+
We can locate the `<tr>` element with this selector:
203+
204+
```
205+
//*[text()='Unique Value']/../..
206+
```
207+
208+
Example #2:
209+
210+
Given this HTML:
211+
212+
```
213+
<tr>
214+
<td>
215+
<a href=“#”>Edit</a>
216+
</td>
217+
<td>
218+
<div>Unique Value</div>
219+
</td>
220+
</tr>
221+
```
222+
223+
We can locate the `<a>` element with this selector:
224+
225+
```
226+
//div[text()='Unique Value']/../..//a
227+
```
228+
229+
#### Attribute Selectors
230+
231+
Attribute selectors allow you to select an element that match a specific attribute value.
232+
233+
Examples:
234+
235+
Attribute|HTML|Selector
236+
---|---|---
237+
id|`<div id='idname'/>`|`//*[@id='idname']`
238+
class|`<div class='classname'/>`|`//*[@class='classname']`
239+
type|`<button type='submit'/>`|`//*[@type='submit']`
240+
value|`<input value='value'/>`|`//*[@value='value']`
241+
href|`<a href='https://google.com'/>`|`//*[@href='https://google.com']`
242+
src|`<img src='/img.png'/>`|`//*[@src='/img.png']`
243+
244+
#### `contains()` Selector
245+
246+
The `contains()` selector allows you to select an element that contains an attribute value search string.
247+
248+
Examples:
249+
250+
Attribute|HTML|Selector
251+
---|---|---
252+
`text()`|`<p>Hello World!</p>`|`[contains(text(), 'Hello')]`
253+
`@id`|`<div id='idname1234abcd'/>`|`[contains(@id, 'idname')]`
254+
`@class`|`<div class='classname1 classname2'/>`|`[contains(@class, 'classname1')]`
255+
`@name`|`<input name='inputname'/>`|`[contains(@name, 'name')]`
256+
`@value`|`<input value='value'/>`|`[contains(@value, 'value')]`
257+
`@href`|`<a href='https://google.com'/>`|`[contains(@href, 'google.com')]`
258+
259+
#### `text()` Selector
260+
261+
The `text()` selector allows you to select an element that contains a specific string.
262+
263+
Examples:
264+
265+
Type|HTML|Selector
266+
---|---|---
267+
Exact Match|`<p>Hello World!!</p>`|`//p[text()='Hello World!!']`
268+
Substring Match|`<p>Hello World!!</p>`|`//p[contains(text(), 'Hello')]`
269+
270+
#### `starts-with()` Selector
271+
272+
The `starts-with()` selector allows you to select an element whose attribute or text starts with a search string.
273+
274+
Examples:
275+
276+
Attribute|HTML|Selector
277+
---|---|---
278+
`@id`|`<div id='unique_id_abcd1234'/>`|`//*[starts-with(@id, 'unique_id')]`
279+
`@class`|`<div class='unique_class_abcd1234'/>`|`//*[starts-with(@class, 'unique_class')]`
280+
`@href`|`<a href='https://www.google.com/'/>`|`//a[starts-with(@href, 'https://')]`
281+
`text()`|`<p>Hello World!</p>`|`//p[starts-with(text(), 'Hello ')]`
282+
283+
#### `ends-with()` Selector
284+
285+
The `ends-with()` selector allows you to select an element whose attribute or text ends with a search string.
286+
287+
Examples:
288+
289+
Attribute|HTML|Selector
290+
---|---|---
291+
`@id`|`<div id='abcd1234_unique_id'/>`|`//*[ends-with(@id, 'unique_id')]`
292+
`@class`|`<div class='abcd1234_unique_class'/>`|`//*[ends-with(@class, 'unique_class')]`
293+
`@href`|`<a href='https://www.google.com'/>`|`//a[ends-with(@href, 'google.com')]`
294+
`text()`|`<p>Hello World!</p>`|`//p[ends-with(text(), 'World!')]`
295+
296+
### Translating Between CSS and XPath
297+
298+
Most of the time it is possible to translate from CSS to XPath and vice versa. Here are some examples:
299+
300+
Type|CSS|XPath
301+
---|---|---
302+
IDs|`#idname`|`//*[@id='idname']`
303+
Classes|`.classname`|`//*[@class='classname']`
304+
HTML Tags|`div`|`//div`
305+
HTML Tag & ID|`div#idname`|`//div[@id='idname']`
306+
HTML Tag & Class|`div.classname`|`//div[@class='classname']`
307+
Universal|`*`|`//*`
308+
Descendant|`#idname .classname`|`//*[@id='idname']//*[@class='classname']`
309+
Child|`#idname > .classname`|`//*[@id='idname']/*[@class='classname']`
310+
Adjacent Sibling|`#idname + .classname`|`//*[@id='idname']/following-sibling::*[@class='classname'][1]`
311+
General Sibling|`#idname ~ .classname`|`//*[@id='idname']/following-sibling::*[@class='classname']`

0 commit comments

Comments
 (0)