1 package io.guixer.tools.dom;
2
3 import static io.guixer.tools.Locators.addQuotesForXPath;
4 import static org.apache.commons.lang3.StringUtils.isBlank;
5 import static org.apache.commons.lang3.StringUtils.normalizeSpace;
6
7 import javax.annotation.Nullable;
8
9 import org.apache.commons.lang3.NotImplementedException;
10 import org.openqa.selenium.By;
11
12 public abstract class DomFinder {
13
14 public static By inferBy(
15 final Dom dom,
16 final DomElement element
17 ) {
18
19 @Nullable
20 final String id = element.getAttribute("id");
21
22 if (!isBlank(id)) {
23
24
25
26 return By.id(id);
27 }
28
29 final String tagName = element.getTagName();
30
31 @Nullable
32 final String href = element.getAttribute("href");
33
34 @Nullable
35 final String text = element.getText();
36
37 @Nullable
38 final String closestId = findClosestId(dom, element);
39
40 final String xpathPrefix = (closestId != null)
41 ? "//*[@id = '" + closestId + "']" //
42 : "";
43
44 if ("a".equals(tagName) && !isBlank(href)) {
45
46
47
48
49
50
51
52
53
54 final String hrefFromJs = dom.getHrefFromJs(element);
55
56
57
58 final String xpath1 = xpathPrefix + "//a[@href = '" + hrefFromJs + "']";
59
60 final int count1 = dom.findElements(By.xpath(xpath1)).size();
61
62 if (count1 == 1) {
63
64 return By.xpath(xpath1);
65 }
66
67 if (!isBlank(text)) {
68
69
70
71 final String xpath2 = xpathPrefix + "//a[@href = '" + hrefFromJs + "'" //
72 + " and normalize-space() = "
73 + addQuotesForXPath(normalizeSpace(text)) + "]";
74
75 final int count2 = dom.findElements(By.xpath(xpath2)).size();
76
77 if (count2 == 1) {
78
79 return By.xpath(xpath2);
80 }
81
82 final StringBuilder sb = new StringBuilder();
83
84 extractThOrTdContext(sb, dom, element);
85
86 if (!sb.isEmpty()) {
87
88
89
90
91 final String xpath3 = xpathPrefix + "//" + sb.toString() //
92 + "a[@href = '" + hrefFromJs + "'"
93 + " and normalize-space() = "
94 + addQuotesForXPath(normalizeSpace(text)) + "]";
95
96 final int count3 = dom.findElements(By.xpath(xpath3)).size();
97
98 if (count3 == 1) {
99
100 return By.xpath(xpath3);
101 }
102
103 final int aPos = dom.calcPreviousSiblingCount(element, tagName) + 1;
104
105
106
107
108 final String xpath4 = xpathPrefix + "//" + sb.toString() //
109 + "a[" + aPos + "][@href = '" + hrefFromJs + "'"
110 + " and normalize-space() = "
111 + addQuotesForXPath(normalizeSpace(text)) + "]";
112
113 final int count4 = dom.findElements(By.xpath(xpath4)).size();
114
115 if (count4 == 1) {
116
117 return By.xpath(xpath4);
118 }
119
120 throw new NotImplementedException("href: " + href
121 + ", hrefFromJs: " + hrefFromJs
122 + ", count1: " + count1
123 + ", xpath1: " + xpath1
124 + ", count2: " + count2
125 + ", xpath2: " + xpath2
126 + ", count3: " + count3
127 + ", xpath3: " + xpath3
128 + ", count4: " + count4
129 + ", xpath4: " + xpath4);
130 }
131
132 throw new NotImplementedException("href: " + href
133 + ", hrefFromJs: " + hrefFromJs
134 + ", count1: " + count1
135 + ", xpath1: " + xpath1
136 + ", count2: " + count2
137 + ", xpath2: " + xpath2);
138 }
139
140 final StringBuilder sb = new StringBuilder();
141
142 extractThOrTdContext(sb, dom, element);
143
144 if (!sb.isEmpty()) {
145
146
147
148 final String xpath2 = xpathPrefix + "//" + sb.toString() //
149 + "a[@href = '" + hrefFromJs + "']";
150
151 final int count2 = dom.findElements(By.xpath(xpath2)).size();
152
153 if (count2 == 1) {
154
155 return By.xpath(xpath2);
156 }
157
158 throw new NotImplementedException("href: " + href
159 + ", hrefFromJs: " + hrefFromJs
160 + ", count1: " + count1
161 + ", xpath1: " + xpath1
162 + ", count2: " + count2
163 + ", xpath2: " + xpath2);
164 }
165
166 throw new NotImplementedException("href: " + href
167 + ", hrefFromJs: " + hrefFromJs
168 + ", count1: " + count1
169 + ", xpath1: " + xpath1);
170 }
171
172 if ("button".equals(tagName) && !isBlank(text)) {
173
174
175
176 final String xpath1 = xpathPrefix + "//button[normalize-space() = " //
177 + addQuotesForXPath(normalizeSpace(text)) + "]";
178
179 final int count1 = dom.findElements(By.xpath(xpath1)).size();
180
181 if (count1 == 1) {
182
183 return By.xpath(xpath1);
184 }
185
186 final StringBuilder sb = new StringBuilder();
187
188 extractThOrTdContext(sb, dom, element);
189
190 if (!sb.isEmpty()) {
191
192
193
194 final String xpath2 = xpathPrefix + "//" + sb.toString() //
195 + "button[normalize-space() = "
196 + addQuotesForXPath(normalizeSpace(text)) + "]";
197
198 final int count2 = dom.findElements(By.xpath(xpath2)).size();
199
200 if (count2 == 1) {
201
202 return By.xpath(xpath2);
203 }
204
205 throw new NotImplementedException("text: " + text
206 + ", count1: " + count1
207 + ", xpath1: " + xpath1
208 + ", count2: " + count2
209 + ", xpath2: " + xpath2);
210 }
211
212 throw new NotImplementedException("text: " + text
213 + ", count1: " + count1
214 + ", xpath1: " + xpath1);
215 }
216
217 throw new NotImplementedException("element: " + element
218 + ", href: " + href
219 + ", text: " + element.getText());
220 }
221
222 @Nullable
223 private static String findClosestId(
224 final Dom dom,
225 final DomElement element
226 ) {
227
228 final DomElement parent = dom.getParentElement(element);
229
230 if (parent == null) {
231
232 return null;
233 }
234
235 @Nullable
236 final String id = element.getAttribute("id");
237
238 if (!isBlank(id)) {
239
240 return id;
241 }
242
243 return findClosestId(dom, parent);
244 }
245
246 @Nullable
247 private static void extractThOrTdContext(
248 final StringBuilder sb,
249 final Dom dom,
250 final DomElement element
251 ) {
252
253 final DomElement parent = dom.getParentElement(element);
254
255 if (parent == null) {
256 return;
257 }
258
259 @Nullable
260 final String parentId = parent.getAttribute("id");
261
262 if (!isBlank(parentId)) {
263
264
265
266 return;
267 }
268
269 final String tagName = parent.getTagName();
270
271 if (!"th".equals(tagName) && "!td".equals(tagName)) {
272
273 if (sb.isEmpty()) {
274
275 sb.append("/");
276 }
277
278 extractThOrTdContext(sb, dom, parent);
279
280 return;
281 }
282
283 final int previousSiblingCount = dom.calcPreviousSiblingCount(parent, tagName);
284
285 sb.insert(0, tagName + "[" + (previousSiblingCount + 1) + "]/");
286
287 extractTrContext(sb, dom, parent);
288 }
289
290 @Nullable
291 private static void extractTrContext(
292 final StringBuilder sb,
293 final Dom dom,
294 final DomElement element
295 ) {
296
297 final DomElement parent = dom.getParentElement(element);
298
299 if (parent == null) {
300 return;
301 }
302
303 @Nullable
304 final String parentId = parent.getAttribute("id");
305
306 if (!isBlank(parentId)) {
307
308
309
310 return;
311 }
312
313 final String tagName = parent.getTagName();
314
315 if (!"tr".equals(tagName)) {
316
317 throw new IllegalStateException("th/td parent should be tr, but was: " + tagName + " (" + sb + ")");
318 }
319
320 final int previousSiblingCount = dom.calcPreviousSiblingCount(parent, "tr");
321
322 sb.insert(0, tagName + "[" + (previousSiblingCount + 1) + "]/");
323
324 extractTableContext(sb, dom, parent);
325 }
326
327 @Nullable
328 private static void extractTableContext(
329 final StringBuilder sb,
330 final Dom dom,
331 final DomElement element
332 ) {
333
334 final DomElement parent = dom.getParentElement(element);
335
336 if (parent == null) {
337 return;
338 }
339
340 @Nullable
341 final String parentId = parent.getAttribute("id");
342
343 if (!isBlank(parentId)) {
344
345
346
347 return;
348 }
349
350 final String tagName = parent.getTagName();
351
352 if (!"table".equals(tagName)) {
353
354 sb.insert(0, tagName + "/");
355
356 extractTableContext(sb, dom, parent);
357
358 return;
359 }
360
361 sb.insert(0, tagName + "/");
362 }
363 }