1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package dev.aherscu.qa.jgiven.webdriver;
18
19 import static com.google.common.base.Suppliers.*;
20 import static dev.aherscu.qa.jgiven.commons.utils.SessionName.*;
21 import static dev.aherscu.qa.testing.utils.EnumUtils.*;
22 import static dev.aherscu.qa.testing.utils.StringUtilsExtensions.*;
23 import static java.util.Collections.*;
24 import static java.util.Locale.*;
25 import static java.util.Objects.*;
26 import static java.util.stream.Collectors.*;
27
28 import java.util.*;
29 import java.util.concurrent.atomic.*;
30 import java.util.function.*;
31 import java.util.stream.*;
32
33 import org.apache.commons.configuration.*;
34 import org.openqa.selenium.Platform;
35 import org.openqa.selenium.chrome.*;
36
37 import com.google.common.collect.*;
38
39 import dev.aherscu.qa.jgiven.commons.utils.*;
40 import dev.aherscu.qa.testing.utils.*;
41 import dev.aherscu.qa.testing.utils.config.BaseConfiguration;
42 import lombok.*;
43 import lombok.extern.slf4j.*;
44
45
46
47
48 @Slf4j
49 public class WebDriverConfiguration extends BaseConfiguration {
50
51 enum DeviceType {
52 _WINDOWS, _IOS, _ANDROID, _WEB;
53
54 static DeviceType from(final String deviceType) {
55 return isBlank(deviceType)
56 ? _WEB
57 : fromString(DeviceType.class,
58 deviceType.toUpperCase(ROOT));
59 }
60
61 @Override
62 public String toString() {
63 return EnumUtils.toString(this).toLowerCase(ROOT);
64 }
65 }
66
67
68
69
70 private static final AtomicInteger nextRequiredCapabilitiesIndex =
71 new AtomicInteger(0);
72 private static final AtomicReference<DeviceType> theDeviceType =
73 new AtomicReference<>();
74 private final Supplier<List<DesiredCapabilitiesEx>> requiredCapabilities =
75 memoize(() -> unmodifiableList(loadRequiredCapabilities()
76 .peek(capabilities -> log.trace("loaded {}", capabilities))
77 .collect(toList())));
78
79
80
81
82
83
84
85
86
87
88
89 public WebDriverConfiguration(final Configuration... configurations) {
90 super(configurations);
91 if (theDeviceType.compareAndSet(null, deviceType())) {
92 log.info("initialized with {}", deviceType());
93 } else {
94 log.warn(
95 "attempting to re-initialize with {}; resetting next required capabilities index from {}",
96 deviceType(),
97 resetNextRequiredCapabilitiesIndex());
98 }
99 }
100
101
102
103
104
105
106
107
108
109
110
111
112
113 static int resetNextRequiredCapabilitiesIndex(final int value) {
114 val oldValue = nextRequiredCapabilitiesIndex.get();
115 nextRequiredCapabilitiesIndex.set(value);
116 return oldValue;
117 }
118
119
120
121
122
123
124
125
126
127
128 static int resetNextRequiredCapabilitiesIndex() {
129 return resetNextRequiredCapabilitiesIndex(0);
130 }
131
132
133
134
135
136
137
138 @SneakyThrows
139 public DesiredCapabilitiesEx capabilities(final DeviceType deviceType) {
140 return capabilitiesFor(provider() + deviceType.toString());
141 }
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 @SneakyThrows
158 public DesiredCapabilitiesEx capabilities() {
159 return nextRequiredCapabilities(deviceType())
160 .orElse(capabilities(deviceType()));
161 }
162
163
164
165
166
167
168
169
170 @SuppressWarnings("serial")
171 @SneakyThrows
172 public DesiredCapabilitiesEx capabilitiesFor(final String prefix) {
173 log.trace("building capabilities for {}", prefix);
174 return new DesiredCapabilitiesEx() {
175 {
176 setCapability("sauce:name", generateFromCurrentThreadAndTime());
177 entrySet()
178 .stream()
179 .map(entry -> new AbstractMap.SimpleImmutableEntry<>(
180 entry.getKey().toString(),
181 entry.getValue().toString()))
182 .filter(entry -> entry.getKey()
183 .startsWith(prefix + DOT))
184 .peek(capabilitiesEntry -> log.trace(
185 "adding capabilities entry {}",
186 capabilitiesEntry))
187 .forEach(capabilitiesEntry -> setCapability(
188 substringAfter(capabilitiesEntry.getKey(),
189 prefix + DOT),
190 capabilitiesEntry.getValue()));
191 }
192 };
193 }
194
195
196
197
198
199 public DeviceType deviceType() {
200 return DeviceType.from(getString("device.type", EMPTY));
201 }
202
203
204
205
206 public String provider() {
207 return getString("provider");
208 }
209
210
211
212
213
214
215 public List<DesiredCapabilitiesEx> requiredCapabilities(
216 final DeviceType deviceType) {
217 return requiredCapabilities.get()
218 .stream()
219 .filter(capabilities -> DeviceType
220 .from(requireNonNull(capabilities
221 .getCapability("-x:type"),
222 "internal error, no type capability")
223 .toString())
224 .equals(deviceType))
225 .peek(capabilities -> log.trace("found required capabilities {}",
226 capabilities))
227 .collect(toList());
228 }
229
230
231
232
233 public List<DesiredCapabilitiesEx> requiredCapabilities() {
234 return requiredCapabilities(deviceType());
235 }
236
237 private Stream<DesiredCapabilitiesEx> loadRequiredCapabilities() {
238 return groupsOf("required.capability")
239 .map(requiredCapabilitiesGroup -> new DesiredCapabilitiesEx(
240 capabilitiesFor(provider()
241 + requiredCapabilitiesGroup.get("type")))
242 .with(requiredCapabilitiesGroup
243 .entrySet()
244 .stream()
245 .peek(e -> log.trace("adding capabilities entry {}", e))
246 .map(e -> Maps.immutableEntry(
247 "type".equals(e.getKey()) ? "-x:type" : e.getKey(),
248 e.getValue()))));
249 }
250
251
252
253
254
255
256
257 private Optional<DesiredCapabilitiesEx> nextRequiredCapabilities(
258 final DeviceType deviceType) {
259 val capabilities = requiredCapabilities(deviceType);
260
261
262
263
264 return capabilities.isEmpty()
265 ? Optional.empty()
266 : Optional.of(capabilities
267 .get(nextRequiredCapabilitiesIndex
268 .getAndUpdate(
269 currentIndex -> currentIndex < capabilities.size() - 1
270 ? currentIndex + 1
271 : 0))
272
273
274
275
276 .with("sauce:name", generateFromCurrentThreadAndTime()));
277 }
278 }