blob: d025d937cf20edf61837752d3c7d5959cab5e2de [file] [log] [blame]
/*
* 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";
}
}
}