| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.myfaces.tobago.example.demo.qunit; |
| |
| import org.apache.commons.lang3.time.DurationFormatUtils; |
| import org.junit.jupiter.api.AfterAll; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.BeforeAll; |
| import org.junit.jupiter.params.provider.Arguments; |
| import org.openqa.selenium.By; |
| import org.openqa.selenium.NoSuchElementException; |
| import org.openqa.selenium.WebDriver; |
| import org.openqa.selenium.WebElement; |
| import org.openqa.selenium.chrome.ChromeOptions; |
| import org.openqa.selenium.remote.RemoteWebDriver; |
| import org.openqa.selenium.support.ui.ExpectedConditions; |
| import org.openqa.selenium.support.ui.FluentWait; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.lang.invoke.MethodHandles; |
| import java.net.HttpURLConnection; |
| import java.net.InetAddress; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLEncoder; |
| import java.net.UnknownHostException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.time.Duration; |
| import java.time.LocalTime; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| abstract class SeleniumBase { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| private static WebDriver chromeDriver; |
| private static List<String> serverUrls = new ArrayList<>(); |
| private static Map<String, String> ignores = new HashMap<>(); |
| |
| @BeforeAll |
| static void setUp() { |
| ignores.put(":8083/tobago-example-demo-myfaces-2.3", |
| "MyFaces 2.3 don't work with Tomcat 8.5 and openjdk10"); |
| ignores.put("tobago-example-demo-mojarra-2.0", |
| "Ajax events don't work with Mojarra 2.0: https://issues.apache.org/jira/browse/TOBAGO-1589"); |
| ignores.put("tobago-example-demo-mojarra-2.3", |
| "Currently Tobago demo don't run with Mojarra 2.3 on Tomcat 8.5"); |
| |
| ignores.put("content/40-test/6000-event/event.xhtml", |
| "Focus/blur event can only be fired if the browser window is in foreground." |
| + " This cannot be guaranteed in selenium tests." |
| + " event.test.js contain focus/blur events"); |
| |
| final String tobago1910 = "TreeSelect: Single selection nodes are not deselected correctly with mojarra: " |
| + "https://issues.apache.org/jira/browse/TOBAGO-1910"; |
| ignores.put("tobago-example-demo-mojarra-2.1/content/20-component/090-tree/01-select/tree-select.xhtml", |
| tobago1910); |
| ignores.put("tobago-example-demo-mojarra-2.2/content/20-component/090-tree/01-select/tree-select.xhtml", |
| tobago1910); |
| } |
| |
| @AfterAll |
| static void tearDown() { |
| if (chromeDriver != null) { |
| chromeDriver.quit(); |
| } |
| } |
| |
| enum Browser { |
| chrome |
| //, firefox // TODO implement firefox |
| } |
| |
| static List<String> getServerUrls() throws UnknownHostException, MalformedURLException { |
| if (serverUrls.size() <= 0) { |
| final String hostAddress = InetAddress.getLocalHost().getHostAddress(); |
| |
| List<String> ports = new ArrayList<>(); |
| ports.add("8082"); // Tomcat JRE 8 |
| ports.add("8083"); // Tomcat JRE 10 |
| |
| List<String> contextPaths = new ArrayList<>(); |
| contextPaths.add("tobago-example-demo"); // MyFaces 2.0 |
| contextPaths.add("tobago-example-demo-myfaces-2.1"); |
| contextPaths.add("tobago-example-demo-myfaces-2.2"); |
| contextPaths.add("tobago-example-demo-myfaces-2.3"); |
| contextPaths.add("tobago-example-demo-mojarra-2.0"); |
| contextPaths.add("tobago-example-demo-mojarra-2.1"); |
| contextPaths.add("tobago-example-demo-mojarra-2.2"); |
| contextPaths.add("tobago-example-demo-mojarra-2.3"); |
| |
| for (String port : ports) { |
| for (String contextPath : contextPaths) { |
| String url = "http://" + hostAddress + ":" + port + "/" + contextPath; |
| final int status = getStatus(url); |
| if (status == 200) { |
| serverUrls.add(url); |
| } else { |
| LOG.warn("\n⚠️ IGNORED: Tests for " + url + ":\n Server status: " + status); |
| } |
| } |
| } |
| } |
| |
| return serverUrls; |
| } |
| |
| private static int getStatus(String url) throws MalformedURLException { |
| URL siteURL = new URL(url); |
| try { |
| HttpURLConnection connection = (HttpURLConnection) siteURL.openConnection(); |
| connection.setRequestMethod("GET"); |
| connection.connect(); |
| return connection.getResponseCode(); |
| } catch (IOException e) { |
| return -1; |
| } |
| } |
| |
| static List<String> getStandardTestPaths() throws IOException { |
| return Files.walk(Paths.get("src/main/webapp/content/")) |
| .filter(Files::isRegularFile) |
| .map(Path::toString) |
| .filter(s -> s.endsWith(".test.js")) |
| .map(s -> s.substring("src/main/webapp/".length())) |
| .sorted() |
| .map(s -> s.substring(0, s.length() - 8) + ".xhtml") |
| .collect(Collectors.toList()); |
| } |
| |
| boolean isIgnored(final String serverUrl, final String path) { |
| final String url = serverUrl + "/" + path; |
| for (String key : ignores.keySet()) { |
| if (url.contains(key)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void logIgnoreMessage(final String serverUrl, final String path) { |
| final String url = serverUrl + "/" + path; |
| for (final Map.Entry<String, String> ignore : ignores.entrySet()) { |
| if (url.contains(ignore.getKey())) { |
| final String message = ignore.getValue(); |
| LOG.info("\n⚠️ IGNORED: Test for " + url + ":\n" + message); |
| return; |
| } |
| } |
| } |
| |
| void setupWebDriver(final Browser browser, final String serverUrl, final String path, final boolean accessTest) |
| throws MalformedURLException, UnsupportedEncodingException { |
| if (Browser.chrome.equals(browser) |
| && (chromeDriver == null) || ((RemoteWebDriver) chromeDriver).getSessionId() == null) { |
| chromeDriver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), new ChromeOptions()); |
| } |
| |
| final String base = path.substring(0, path.length() - 6); |
| final String url = serverUrl + "/test.xhtml?base=" |
| + URLEncoder.encode(base, "UTF-8") + (accessTest ? "&accessTest=true" : ""); |
| getWebDriver(browser).get(url); |
| } |
| |
| WebDriver getWebDriver(Browser browser) { |
| if (Browser.chrome.equals(browser)) { |
| return chromeDriver; |
| } else { |
| return null; |
| } |
| } |
| |
| static Stream<Arguments> getArguments(final List<String> paths) throws MalformedURLException, UnknownHostException { |
| final LocalTime startTime = LocalTime.now(); |
| final int testSize = Browser.values().length * getServerUrls().size() * paths.size(); |
| |
| List<Arguments> arguments = new LinkedList<>(); |
| |
| int testNo = 1; |
| for (String serverUrl : getServerUrls()) { |
| for (String path : paths) { |
| for (Browser browser : Browser.values()) { |
| arguments.add(Arguments.of(browser, serverUrl, path, startTime, testSize, testNo)); |
| testNo++; |
| } |
| } |
| } |
| |
| return arguments.stream(); |
| } |
| |
| String getTimeLeft(final LocalTime startTime, final int testSize, final int testNo) { |
| final LocalTime now = LocalTime.now(); |
| final Duration completeWaitTime = Duration.between(startTime, now).dividedBy(testNo).multipliedBy(testSize); |
| final LocalTime endTime = LocalTime.from(startTime).plus(completeWaitTime); |
| final Duration timeLeft = Duration.between(LocalTime.now(), endTime); |
| |
| if (timeLeft.toHours() > 0) { |
| return DurationFormatUtils.formatDuration(timeLeft.toMillis(), "H'h' m'm' s's'"); |
| } else if (timeLeft.toMinutes() > 0) { |
| return DurationFormatUtils.formatDuration(timeLeft.toMillis(), "m'm' s's'"); |
| } else if (timeLeft.toMillis() >= 0) { |
| return DurationFormatUtils.formatDuration(timeLeft.toMillis(), "s's'"); |
| } else { |
| return "---"; |
| } |
| } |
| |
| /** |
| * Wait for the qunit-banner web element and return it. |
| * If the web element is available, the execution of qunit test should be done and it is safe to parse the results. |
| * |
| * @return qunit-banner web element |
| */ |
| WebElement waitForQUnitBanner(final WebDriver webDriver) { |
| final FluentWait<WebDriver> fluentWait = new FluentWait<>(webDriver) |
| .withTimeout(Duration.ofSeconds(90)) |
| .pollingEvery(Duration.ofSeconds(1)) |
| .ignoring(NoSuchElementException.class); |
| |
| WebElement qunitBanner = fluentWait.until(driver -> driver.findElement(By.id("qunit-banner"))); |
| fluentWait.until(ExpectedConditions.attributeToBeNotEmpty(qunitBanner, "class")); |
| |
| return qunitBanner; |
| } |
| |
| void parseQUnitResults(final Browser browser, final String serverUrl, final String path) { |
| final WebDriver webDriver = getWebDriver(browser); |
| WebElement qunitBanner; |
| try { |
| qunitBanner = waitForQUnitBanner(webDriver); |
| } catch (Exception e) { |
| qunitBanner = webDriver.findElement(By.id("qunit-banner")); |
| } |
| |
| WebElement qunitTestResult = webDriver.findElement(By.id("qunit-testresult")); |
| WebElement qunitTests = webDriver.findElement(By.id("qunit-tests")); |
| |
| final List<WebElement> testCases = qunitTests.findElements(By.xpath("li")); |
| Assertions.assertTrue(testCases.size() > 0, "There must be at least one test case."); |
| |
| final boolean testFailed = !qunitBanner.getAttribute("class").equals("qunit-pass"); |
| |
| int testCaseCount = 1; |
| final StringBuilder stringBuilder = new StringBuilder(); |
| stringBuilder.append(qunitTestResult.getAttribute("textContent")); |
| stringBuilder.append("\n"); |
| |
| if (testFailed) { |
| for (final WebElement testCase : testCases) { |
| final String testName = getText(testCase, "test-name"); |
| final String testStatus = testCase.getAttribute("class").toUpperCase(); |
| |
| stringBuilder.append(testCaseCount++); |
| stringBuilder.append(". "); |
| stringBuilder.append(testStatus); |
| stringBuilder.append(": "); |
| stringBuilder.append(testName); |
| stringBuilder.append(" ("); |
| stringBuilder.append(getText(testCase, "runtime")); |
| stringBuilder.append(")\n"); |
| |
| final WebElement assertList = testCase.findElement(By.className("qunit-assert-list")); |
| final List<WebElement> asserts = assertList.findElements(By.tagName("li")); |
| int assertCount = 1; |
| for (final WebElement assertion : asserts) { |
| final String assertStatus = assertion.getAttribute("class"); |
| |
| stringBuilder.append("- "); |
| if (assertCount <= 9) { |
| stringBuilder.append("0"); |
| } |
| stringBuilder.append(assertCount++); |
| stringBuilder.append(". "); |
| stringBuilder.append(assertStatus); |
| stringBuilder.append(": "); |
| stringBuilder.append(getText(assertion, "test-message")); |
| stringBuilder.append(getText(assertion, "runtime")); |
| stringBuilder.append("\n"); |
| |
| final String assertExpected = getText(assertion, "test-expected"); |
| if (!"null".equals(assertExpected)) { |
| stringBuilder.append("-- "); |
| stringBuilder.append(assertExpected); |
| stringBuilder.append("\n"); |
| } |
| final String assertResult = getText(assertion, "test-actual"); |
| if (!"null".equals(assertResult)) { |
| stringBuilder.append("-- "); |
| stringBuilder.append(assertResult); |
| stringBuilder.append("\n"); |
| } |
| final String assertSource = getText(assertion, "test-source"); |
| if (!"null".equals(assertSource)) { |
| stringBuilder.append("-- "); |
| stringBuilder.append(assertSource); |
| stringBuilder.append("\n"); |
| } |
| } |
| |
| stringBuilder.append(getText(testCase, "qunit-source")); |
| stringBuilder.append("\n\n"); |
| } |
| } |
| |
| final String url = serverUrl + "/" + path; |
| if (testFailed) { |
| final String message = "\n❌ FAILED: Test with '" + browser + "' for " + url + "\n" + stringBuilder.toString(); |
| LOG.warn(message); |
| Assertions.fail(message); |
| } else { |
| final String message = "\n✅ PASSED: Test with '" + browser + "' for " + url + "\n" + stringBuilder.toString(); |
| LOG.info(message); |
| Assertions.assertTrue(true, message); |
| } |
| } |
| |
| private String getText(final WebElement webElement, final String className) { |
| final List<WebElement> elements = webElement.findElements(By.className(className)); |
| if (elements.size() > 0) { |
| return elements.get(0).getAttribute("textContent"); |
| } else { |
| return "null"; |
| } |
| } |
| } |