View Javadoc
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  			// "#abc"
25  			//
26  			return By.id(id); // TODO We should check for duplicate @ids...
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  			// e.g.
47  			//
48  			// Selenium: (href)
49  			// http://192.168.0.18:8080/uploads/FDa8SZinwm/revue/BrowseToUploadsTest/test/1681061187259/4?showAttributes=yes
50  			//
51  			// DOM: (hrefFromJs)
52  			// ./uploads/FDa8SZinwm/revue/BrowseToUploadsTest/test/1681061187259/4?showAttributes=yes
53  
54  			final String hrefFromJs = dom.getHrefFromJs(element);
55  
56  			// "//a[@href = 'abc']"
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  				// "//a[@href = 'abc' and normalize-space() = 'def']"
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  					// "//table[A]/tbody/tr[B]/td[C]//a[@href = 'abc' and normalize-space() =
89  					// 'def']"
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 					// "//table[A]/tbody/tr[B]/td[C]//a[D][@href = 'abc' and normalize-space() =
106 					// 'def']"
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 				// "//table[A]/tbody/tr[B]/td[C]//a[@href = 'abc']"
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 			// "//button[normalize-space() = 'def']"
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 				// "//table[A]/tbody/tr[B]/td[C]//button[normalize-space() = 'def']"
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 			// We already found the context with closestId
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) + "]/"); // e.g. "td[3]/"
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 			// We already found the context with closestId
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) + "]/"); // e.g. "tr[3]/"
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 			// We already found the context with closestId
346 			//
347 			return;
348 		}
349 
350 		final String tagName = parent.getTagName();
351 
352 		if (!"table".equals(tagName)) {
353 
354 			sb.insert(0, tagName + "/"); // e.g. "tbody/"
355 
356 			extractTableContext(sb, dom, parent);
357 
358 			return;
359 		}
360 
361 		sb.insert(0, tagName + "/"); // "table/"
362 	}
363 }