1 package io.guixer.tools.monkey;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static com.google.common.collect.Lists.newArrayList;
5 import static io.guixer.tools.Locators.inferLocator;
6 import static org.apache.commons.lang3.StringUtils.isBlank;
7 import static org.apache.commons.lang3.StringUtils.substringAfterLast;
8
9 import java.io.IOException;
10 import java.util.List;
11 import java.util.Locale;
12
13 import javax.annotation.Nullable;
14
15 import org.apache.commons.lang3.ArrayUtils;
16 import org.apache.commons.lang3.NotImplementedException;
17 import org.apache.commons.lang3.RandomUtils;
18 import org.openqa.selenium.By;
19 import org.openqa.selenium.WebDriver;
20 import org.openqa.selenium.WebElement;
21
22 import io.guixer.tools.GuixerConsumer;
23 import io.guixer.tools.RunnerContext;
24 import io.guixer.types.AttributeScope;
25
26 public class MonkeySteps implements GuixerConsumer {
27
28 private final int stepCount;
29
30 public MonkeySteps(
31 final int stepCount
32 ) {
33
34 this.stepCount = stepCount;
35 }
36
37 @Override
38 public void accept(
39 final RunnerContext context
40 ) throws IOException {
41
42 checkNotNull(context, "context");
43
44 for (int stepIndex = 1; stepIndex <= stepCount; ++stepIndex) {
45
46 final WebDriver driver = context.getDriver();
47
48 final List<WebElement> anchors = driver.findElements(By.tagName("a"));
49
50 final List<WebElement> buttons = driver.findElements(By.tagName("button"));
51
52 final List<WebElement> inputs = driver.findElements(By.tagName("input"));
53
54 final List<Interactable> interactables = newArrayList();
55
56 for (final WebElement anchor : anchors) {
57
58 @Nullable
59 final Interactable interactable = Interactable.fromAnchor(driver, anchor);
60
61 if (interactable != null) {
62
63 interactables.add(interactable);
64 }
65 }
66
67 for (final WebElement button : buttons) {
68
69 @Nullable
70 final Interactable interactable = Interactable.fromButton(driver, button);
71
72 if (interactable != null) {
73
74 interactables.add(interactable);
75 }
76 }
77
78 for (final WebElement input : inputs) {
79
80 @Nullable
81 final Interactable interactable = Interactable.fromInput(driver, input);
82
83 if (interactable != null) {
84
85 interactables.add(interactable);
86 }
87 }
88
89 if (interactables.isEmpty()) {
90
91
92
93
94
95
96
97 context.intent("Monkey " + stepIndex + "/" + stepCount + ": No more interactable elements: We go back");
98
99 context.tag(AttributeScope.STEP, "no_more_interactables");
100
101 context.executeScript("history.back()");
102
103 context.sleep(2);
104
105 context.takeScreenshot();
106
107 } else {
108
109 final Interactable interactable = interactables.get(RandomUtils.nextInt(0, interactables.size()));
110
111 context.intent("Monkey " + stepIndex + "/" + stepCount + ": We click at random: " + interactable.label);
112
113 interactable.interact(context);
114
115 context.sleep(2);
116
117 context.takeScreenshot();
118 }
119 }
120 }
121
122 private static abstract class Interactable {
123
124 public final String label;
125
126 private Interactable(
127 final String label
128 ) {
129
130 this.label = checkNotNull(label, "label");
131 }
132
133 public abstract void interact(
134 RunnerContext context
135 ) throws IOException;
136
137 @Nullable
138 public static Interactable fromAnchor(
139 final WebDriver driver,
140 final WebElement anchor
141 ) {
142
143 if (!anchor.isDisplayed() || !anchor.isEnabled()) {
144
145 return null;
146 }
147
148 @Nullable
149 final String href = anchor.getAttribute("href");
150
151 if (href == null) {
152
153 return null;
154 }
155
156 final String label;
157
158 @Nullable
159 final String text = anchor.getText();
160
161 if (!isBlank(text)) {
162
163 label = text;
164
165 } else {
166
167 final List<WebElement> imgs = anchor.findElements(By.tagName("img"));
168
169 if (!imgs.isEmpty()) {
170
171 label = substringAfterLast(imgs.get(0).getAttribute("src"), "/");
172
173 } else {
174
175 throw new NotImplementedException("<a> without text or <img>, href: " + href + ": " + anchor);
176 }
177 }
178
179
180
181 return new Interactable(label) {
182
183 @Override
184 public void interact(
185 final RunnerContext context
186 ) throws IOException {
187
188 final String locator = inferLocator(driver, anchor);
189
190 context.click(locator);
191 }
192 };
193 }
194
195 @Nullable
196 public static Interactable fromButton(
197 final WebDriver driver,
198 final WebElement button
199 ) {
200
201 if (!button.isDisplayed() || !button.isEnabled()) {
202
203 return null;
204 }
205
206 final String label;
207
208 @Nullable
209 final String text = button.getText();
210
211 if (!isBlank(text)) {
212
213 label = text;
214
215 } else {
216
217 throw new NotImplementedException("<button> without text: " + button);
218 }
219
220
221
222 return new Interactable(label) {
223
224 @Override
225 public void interact(
226 final RunnerContext context
227 ) throws IOException {
228
229 final String locator = inferLocator(driver, button);
230
231 context.click(locator);
232 }
233 };
234 }
235
236 @Nullable
237 public static Interactable fromInput(
238 final WebDriver driver,
239 final WebElement input
240 ) {
241
242 if (!input.isDisplayed() || !input.isEnabled()) {
243
244 return null;
245 }
246
247 @Nullable
248 final String type = input.getAttribute("type");
249
250 if (type == null || !ArrayUtils.contains(new String[] {
251 "button",
252 "checkbox", "radio",
253 "submit"
254 }, type.toLowerCase(Locale.ENGLISH))) {
255
256 return null;
257 }
258
259 final String label;
260
261 @Nullable
262 final String value = input.getAttribute("value");
263
264 if (!isBlank(value)) {
265
266 label = value;
267
268 } else {
269
270 throw new NotImplementedException("<input> without value: " + input);
271 }
272
273
274
275 return new Interactable(label) {
276
277 @Override
278 public void interact(
279 final RunnerContext context
280 ) throws IOException {
281
282 final String locator = inferLocator(driver, input);
283
284 context.click(locator);
285 }
286 };
287 }
288 }
289 }