1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.example.demo.qunit;
21
22 import org.apache.commons.lang3.time.DurationFormatUtils;
23 import org.junit.jupiter.api.AfterAll;
24 import org.junit.jupiter.api.Assertions;
25 import org.junit.jupiter.api.BeforeAll;
26 import org.junit.jupiter.params.provider.Arguments;
27 import org.openqa.selenium.By;
28 import org.openqa.selenium.NoSuchElementException;
29 import org.openqa.selenium.WebDriver;
30 import org.openqa.selenium.WebElement;
31 import org.openqa.selenium.chrome.ChromeOptions;
32 import org.openqa.selenium.remote.RemoteWebDriver;
33 import org.openqa.selenium.support.ui.ExpectedConditions;
34 import org.openqa.selenium.support.ui.FluentWait;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import java.io.IOException;
39 import java.io.UnsupportedEncodingException;
40 import java.lang.invoke.MethodHandles;
41 import java.net.HttpURLConnection;
42 import java.net.InetAddress;
43 import java.net.MalformedURLException;
44 import java.net.URL;
45 import java.net.URLEncoder;
46 import java.net.UnknownHostException;
47 import java.nio.file.Files;
48 import java.nio.file.Path;
49 import java.nio.file.Paths;
50 import java.time.Duration;
51 import java.time.LocalTime;
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.LinkedList;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.stream.Collectors;
58 import java.util.stream.Stream;
59
60 abstract class SeleniumBase {
61
62 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
63
64 private static WebDriver chromeDriver;
65 private static List<String> serverUrls = new ArrayList<>();
66 private static Map<String, String> ignores = new HashMap<>();
67
68 @BeforeAll
69 static void setUp() {
70 ignores.put(":8083/tobago-example-demo-myfaces-2.3",
71 "MyFaces 2.3 don't work with Tomcat 8.5 and openjdk10");
72 ignores.put("tobago-example-demo-mojarra-2.0",
73 "Ajax events don't work with Mojarra 2.0: https://issues.apache.org/jira/browse/TOBAGO-1589");
74 ignores.put("tobago-example-demo-mojarra-2.3",
75 "Currently Tobago demo don't run with Mojarra 2.3 on Tomcat 8.5");
76
77 ignores.put("content/40-test/6000-event/event.xhtml",
78 "Focus/blur event can only be fired if the browser window is in foreground."
79 + " This cannot be guaranteed in selenium tests."
80 + " event.test.js contain focus/blur events");
81
82 final String tobago1910 = "TreeSelect: Single selection nodes are not deselected correctly with mojarra: "
83 + "https://issues.apache.org/jira/browse/TOBAGO-1910";
84 ignores.put("tobago-example-demo-mojarra-2.1/content/20-component/090-tree/01-select/tree-select.xhtml",
85 tobago1910);
86 ignores.put("tobago-example-demo-mojarra-2.2/content/20-component/090-tree/01-select/tree-select.xhtml",
87 tobago1910);
88 }
89
90 @AfterAll
91 static void tearDown() {
92 if (chromeDriver != null) {
93 chromeDriver.quit();
94 }
95 }
96
97 enum Browser {
98 chrome
99
100 }
101
102 static List<String> getServerUrls() throws UnknownHostException, MalformedURLException {
103 if (serverUrls.size() <= 0) {
104 final String hostAddress = InetAddress.getLocalHost().getHostAddress();
105
106 List<String> ports = new ArrayList<>();
107 ports.add("8082");
108 ports.add("8083");
109
110 List<String> contextPaths = new ArrayList<>();
111 contextPaths.add("tobago-example-demo");
112 contextPaths.add("tobago-example-demo-myfaces-2.1");
113 contextPaths.add("tobago-example-demo-myfaces-2.2");
114 contextPaths.add("tobago-example-demo-myfaces-2.3");
115 contextPaths.add("tobago-example-demo-mojarra-2.0");
116 contextPaths.add("tobago-example-demo-mojarra-2.1");
117 contextPaths.add("tobago-example-demo-mojarra-2.2");
118 contextPaths.add("tobago-example-demo-mojarra-2.3");
119
120 for (String port : ports) {
121 for (String contextPath : contextPaths) {
122 String url = "http://" + hostAddress + ":" + port + "/" + contextPath;
123 final int status = getStatus(url);
124 if (status == 200) {
125 serverUrls.add(url);
126 } else {
127 LOG.warn("\n⚠️ IGNORED: Tests for " + url + ":\n Server status: " + status);
128 }
129 }
130 }
131 }
132
133 return serverUrls;
134 }
135
136 private static int getStatus(String url) throws MalformedURLException {
137 URL siteURL = new URL(url);
138 try {
139 HttpURLConnection connection = (HttpURLConnection) siteURL.openConnection();
140 connection.setRequestMethod("GET");
141 connection.connect();
142 return connection.getResponseCode();
143 } catch (IOException e) {
144 return -1;
145 }
146 }
147
148 static List<String> getStandardTestPaths() throws IOException {
149 return Files.walk(Paths.get("src/main/webapp/content/"))
150 .filter(Files::isRegularFile)
151 .map(Path::toString)
152 .filter(s -> s.endsWith(".test.js"))
153 .map(s -> s.substring("src/main/webapp/".length()))
154 .sorted()
155 .map(s -> s.substring(0, s.length() - 8) + ".xhtml")
156 .collect(Collectors.toList());
157 }
158
159 boolean isIgnored(final String serverUrl, final String path) {
160 final String url = serverUrl + "/" + path;
161 for (String key : ignores.keySet()) {
162 if (url.contains(key)) {
163 return true;
164 }
165 }
166 return false;
167 }
168
169 void logIgnoreMessage(final String serverUrl, final String path) {
170 final String url = serverUrl + "/" + path;
171 for (final Map.Entry<String, String> ignore : ignores.entrySet()) {
172 if (url.contains(ignore.getKey())) {
173 final String message = ignore.getValue();
174 LOG.info("\n⚠️ IGNORED: Test for " + url + ":\n" + message);
175 return;
176 }
177 }
178 }
179
180 void setupWebDriver(final Browser browser, final String serverUrl, final String path, final boolean accessTest)
181 throws MalformedURLException, UnsupportedEncodingException {
182 if (Browser.chrome.equals(browser)
183 && (chromeDriver == null) || ((RemoteWebDriver) chromeDriver).getSessionId() == null) {
184 chromeDriver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), new ChromeOptions());
185 }
186
187 final String base = path.substring(0, path.length() - 6);
188 final String url = serverUrl + "/test.xhtml?base="
189 + URLEncoder.encode(base, "UTF-8") + (accessTest ? "&accessTest=true" : "");
190 getWebDriver(browser).get(url);
191 }
192
193 WebDriver getWebDriver(Browser browser) {
194 if (Browser.chrome.equals(browser)) {
195 return chromeDriver;
196 } else {
197 return null;
198 }
199 }
200
201 static Stream<Arguments> getArguments(final List<String> paths) throws MalformedURLException, UnknownHostException {
202 final LocalTime startTime = LocalTime.now();
203 final int testSize = Browser.values().length * getServerUrls().size() * paths.size();
204
205 List<Arguments> arguments = new LinkedList<>();
206
207 int testNo = 1;
208 for (String serverUrl : getServerUrls()) {
209 for (String path : paths) {
210 for (Browser browser : Browser.values()) {
211 arguments.add(Arguments.of(browser, serverUrl, path, startTime, testSize, testNo));
212 testNo++;
213 }
214 }
215 }
216
217 return arguments.stream();
218 }
219
220 String getTimeLeft(final LocalTime startTime, final int testSize, final int testNo) {
221 final LocalTime now = LocalTime.now();
222 final Duration completeWaitTime = Duration.between(startTime, now).dividedBy(testNo).multipliedBy(testSize);
223 final LocalTime endTime = LocalTime.from(startTime).plus(completeWaitTime);
224 final Duration timeLeft = Duration.between(LocalTime.now(), endTime);
225
226 if (timeLeft.toHours() > 0) {
227 return DurationFormatUtils.formatDuration(timeLeft.toMillis(), "H'h' m'm' s's'");
228 } else if (timeLeft.toMinutes() > 0) {
229 return DurationFormatUtils.formatDuration(timeLeft.toMillis(), "m'm' s's'");
230 } else if (timeLeft.toMillis() >= 0) {
231 return DurationFormatUtils.formatDuration(timeLeft.toMillis(), "s's'");
232 } else {
233 return "---";
234 }
235 }
236
237
238
239
240
241
242
243 WebElement waitForQUnitBanner(final WebDriver webDriver) {
244 final FluentWait<WebDriver> fluentWait = new FluentWait<>(webDriver)
245 .withTimeout(Duration.ofSeconds(90))
246 .pollingEvery(Duration.ofSeconds(1))
247 .ignoring(NoSuchElementException.class);
248
249 WebElement qunitBanner = fluentWait.until(driver -> driver.findElement(By.id("qunit-banner")));
250 fluentWait.until(ExpectedConditions.attributeToBeNotEmpty(qunitBanner, "class"));
251
252 return qunitBanner;
253 }
254
255 void parseQUnitResults(final Browser browser, final String serverUrl, final String path) {
256 final WebDriver webDriver = getWebDriver(browser);
257 WebElement qunitBanner;
258 try {
259 qunitBanner = waitForQUnitBanner(webDriver);
260 } catch (Exception e) {
261 qunitBanner = webDriver.findElement(By.id("qunit-banner"));
262 }
263
264 WebElement qunitTestResult = webDriver.findElement(By.id("qunit-testresult"));
265 WebElement qunitTests = webDriver.findElement(By.id("qunit-tests"));
266
267 final List<WebElement> testCases = qunitTests.findElements(By.xpath("li"));
268 Assertions.assertTrue(testCases.size() > 0, "There must be at least one test case.");
269
270 final boolean testFailed = !qunitBanner.getAttribute("class").equals("qunit-pass");
271
272 int testCaseCount = 1;
273 final StringBuilder stringBuilder = new StringBuilder();
274 stringBuilder.append(qunitTestResult.getAttribute("textContent"));
275 stringBuilder.append("\n");
276
277 if (testFailed) {
278 for (final WebElement testCase : testCases) {
279 final String testName = getText(testCase, "test-name");
280 final String testStatus = testCase.getAttribute("class").toUpperCase();
281
282 stringBuilder.append(testCaseCount++);
283 stringBuilder.append(". ");
284 stringBuilder.append(testStatus);
285 stringBuilder.append(": ");
286 stringBuilder.append(testName);
287 stringBuilder.append(" (");
288 stringBuilder.append(getText(testCase, "runtime"));
289 stringBuilder.append(")\n");
290
291 final WebElement assertList = testCase.findElement(By.className("qunit-assert-list"));
292 final List<WebElement> asserts = assertList.findElements(By.tagName("li"));
293 int assertCount = 1;
294 for (final WebElement assertion : asserts) {
295 final String assertStatus = assertion.getAttribute("class");
296
297 stringBuilder.append("- ");
298 if (assertCount <= 9) {
299 stringBuilder.append("0");
300 }
301 stringBuilder.append(assertCount++);
302 stringBuilder.append(". ");
303 stringBuilder.append(assertStatus);
304 stringBuilder.append(": ");
305 stringBuilder.append(getText(assertion, "test-message"));
306 stringBuilder.append(getText(assertion, "runtime"));
307 stringBuilder.append("\n");
308
309 final String assertExpected = getText(assertion, "test-expected");
310 if (!"null".equals(assertExpected)) {
311 stringBuilder.append("-- ");
312 stringBuilder.append(assertExpected);
313 stringBuilder.append("\n");
314 }
315 final String assertResult = getText(assertion, "test-actual");
316 if (!"null".equals(assertResult)) {
317 stringBuilder.append("-- ");
318 stringBuilder.append(assertResult);
319 stringBuilder.append("\n");
320 }
321 final String assertSource = getText(assertion, "test-source");
322 if (!"null".equals(assertSource)) {
323 stringBuilder.append("-- ");
324 stringBuilder.append(assertSource);
325 stringBuilder.append("\n");
326 }
327 }
328
329 stringBuilder.append(getText(testCase, "qunit-source"));
330 stringBuilder.append("\n\n");
331 }
332 }
333
334 final String url = serverUrl + "/" + path;
335 if (testFailed) {
336 final String message = "\n❌ FAILED: Test with '" + browser + "' for " + url + "\n" + stringBuilder.toString();
337 LOG.warn(message);
338 Assertions.fail(message);
339 } else {
340 final String message = "\n✅ PASSED: Test with '" + browser + "' for " + url + "\n" + stringBuilder.toString();
341 LOG.info(message);
342 Assertions.assertTrue(true, message);
343 }
344 }
345
346 private String getText(final WebElement webElement, final String className) {
347 final List<WebElement> elements = webElement.findElements(By.className(className));
348 if (elements.size() > 0) {
349 return elements.get(0).getAttribute("textContent");
350 } else {
351 return "null";
352 }
353 }
354 }