From 6acfcd51cbc1cbc1ae84b21a5de3f17e6c042035 Mon Sep 17 00:00:00 2001 From: Anna Koskinen Date: Fri, 6 Sep 2024 21:11:28 +0300 Subject: [PATCH] chore: DevelopmentServerLaunder & WebDriverManager for integration tests Also update the IT tests to use new deployment paths, convert to using direct imports to Assert methods, and switch to catching ElementNotInteractableException for added compatibility (ElementNotVisibleException extends it). --- vaadin-testbench-integration-tests/pom.xml | 193 +++++-- .../launcher/ApplicationRunnerServlet.java | 486 ++++++++++++++++++ .../CustomDeploymentConfiguration.java | 31 ++ .../launcher/DevelopmentServerLauncher.java | 285 ++++++++++ .../vaadin/launcher/VaadinStaticFiles.java | 19 + .../java/com/vaadin/testUI/TestServlet.java | 23 - .../com/vaadin/testUI/TestUIProvider.java | 39 -- .../resources/com/vaadin/launcher/keystore | Bin 0 -> 1367 bytes .../tests/elements/AbstractTB3Test.java | 47 +- .../elements/ElementScreenCompareIT.java | 13 +- .../tests/elements/ExecuteJavascriptIT.java | 9 +- .../elements/PrivateTB3Configuration.java | 60 +++ .../tests/elements/ScrollIntoViewIT.java | 42 +- 13 files changed, 1090 insertions(+), 157 deletions(-) create mode 100644 vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/ApplicationRunnerServlet.java create mode 100644 vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/CustomDeploymentConfiguration.java create mode 100644 vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/DevelopmentServerLauncher.java create mode 100644 vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/VaadinStaticFiles.java delete mode 100644 vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestServlet.java delete mode 100644 vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestUIProvider.java create mode 100644 vaadin-testbench-integration-tests/src/main/resources/com/vaadin/launcher/keystore diff --git a/vaadin-testbench-integration-tests/pom.xml b/vaadin-testbench-integration-tests/pom.xml index 95ba85b54..90b416612 100644 --- a/vaadin-testbench-integration-tests/pom.xml +++ b/vaadin-testbench-integration-tests/pom.xml @@ -17,7 +17,8 @@ https://oss.sonatype.org/content/repositories/vaadin-snapshots/ 8.0-SNAPSHOT - 9.3.12.v20160915 + 9.4.53.v20231009 + 1.7.36 @@ -91,10 +92,66 @@ + + org.eclipse.jetty + jetty-maven-plugin + + + 8080 + + -1 + 8081 + 5 + foo + + - + + org.eclipse.jetty + jetty-server + ${jetty.version} + provided + + + org.eclipse.jetty + jetty-servlets + ${jetty.version} + provided + + + org.eclipse.jetty.websocket + websocket-server + ${jetty.version} + provided + + + org.eclipse.jetty + jetty-webapp + ${jetty.version} + provided + + + org.eclipse.jetty + jetty-util + ${jetty.version} + provided + + + org.eclipse.jetty + jetty-proxy + ${jetty.version} + provided + + + org.eclipse.jetty + jetty-annotations + ${jetty.version} + provided + + + com.vaadin vaadin-server @@ -134,39 +191,58 @@ com.vaadin - vaadin-compatibility-server - ${vaadin.version} - - - com.vaadin - vaadin-compatibility-client - ${vaadin.version} - provided - - - com.vaadin - vaadin-compatibility-client-compiled - ${vaadin.version} - - - com.vaadin - vaadin-compatibility-shared - ${vaadin.version} - - - com.vaadin - vaadin-compatibility-themes - ${vaadin.version} - + vaadin-compatibility-server + ${vaadin.version} + + + com.vaadin + vaadin-compatibility-client + ${vaadin.version} + provided + + + com.vaadin + vaadin-compatibility-client-compiled + ${vaadin.version} + + + com.vaadin + vaadin-compatibility-shared + ${vaadin.version} + + + com.vaadin + vaadin-compatibility-themes + ${vaadin.version} + com.vaadin vaadin-testbench-core ${project.version} test + + + io.github.bonigarcia + webdrivermanager + 5.7.0 + test + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + - + verify-only-phantom @@ -191,36 +267,39 @@ - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty.version} - - 10 - foo - 9999 - - - - - start-jetty - pre-integration-test - - start - - - - stop-jetty - post-integration-test - - stop - - - - - + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + 10 + foo + 9999 + jar,war + + + + + start-jetty + pre-integration-test + + start + + + + stop-jetty + post-integration-test + + stop + + + + + + diff --git a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/ApplicationRunnerServlet.java b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/ApplicationRunnerServlet.java new file mode 100644 index 000000000..f6f90956e --- /dev/null +++ b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/ApplicationRunnerServlet.java @@ -0,0 +1,486 @@ +/* + * Vaadin TestBench Addon + * + * Copyright (C) 2012-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.launcher; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.vaadin.launcher.CustomDeploymentConfiguration.Conf; +import com.vaadin.server.DefaultDeploymentConfiguration; +import com.vaadin.server.DeploymentConfiguration; +import com.vaadin.server.ServiceException; +import com.vaadin.server.SystemMessages; +import com.vaadin.server.SystemMessagesProvider; +import com.vaadin.server.UIClassSelectionEvent; +import com.vaadin.server.UICreateEvent; +import com.vaadin.server.UIProvider; +import com.vaadin.server.VaadinRequest; +import com.vaadin.server.VaadinService; +import com.vaadin.server.VaadinServlet; +import com.vaadin.server.VaadinServletRequest; +import com.vaadin.server.VaadinServletService; +import com.vaadin.server.VaadinSession; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.ui.UI; +import com.vaadin.util.CurrentInstance; + +@WebServlet(value = "/run/*", asyncSupported = true, initParams = { + @WebInitParam(name = "heartbeatInterval", value = "10"), + @WebInitParam(name = "widgetset", value = "com.vaadin.v7.Vaadin7WidgetSet") }) +public class ApplicationRunnerServlet extends VaadinServlet { + + private static class ApplicationRunnerRedirectException + extends RuntimeException { + private final String target; + + public ApplicationRunnerRedirectException(String target) { + this.target = target; + } + + public String getTarget() { + return target; + } + + public static String extractRedirectTarget(ServletException e) { + Throwable cause = e.getCause(); + if (cause instanceof ServiceException) { + ServiceException se = (ServiceException) cause; + Throwable serviceCause = se.getCause(); + if (serviceCause instanceof ApplicationRunnerRedirectException) { + ApplicationRunnerRedirectException redirect = (ApplicationRunnerRedirectException) serviceCause; + return redirect.getTarget(); + } + } + + return null; + } + } + + public static String CUSTOM_SYSTEM_MESSAGES_PROPERTY = "custom-" + + SystemMessages.class.getName(); + + /** + * The name of the application class currently used. Only valid within one + * request. + */ + private LinkedHashSet defaultPackages = new LinkedHashSet<>(); + + private transient final ThreadLocal request = new ThreadLocal<>(); + + @Override + protected void servletInitialized() throws ServletException { + super.servletInitialized(); + getService().addSessionInitListener(event -> onVaadinSessionStarted( + event.getRequest(), event.getSession())); + } + + @Override + protected void service(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + this.request.set(request); + try { + super.service(request, response); + } catch (ServletException e) { + String redirectTarget = ApplicationRunnerRedirectException + .extractRedirectTarget(e); + if (redirectTarget != null) { + response.sendRedirect(redirectTarget + "?restartApplication"); + } else { + // Not the exception we were looking for, just rethrow + throw e; + } + } finally { + this.request.set(null); + } + } + + @Override + protected URL getApplicationUrl(HttpServletRequest request) + throws MalformedURLException { + @SuppressWarnings("deprecation") + URL url = super.getApplicationUrl(request); + + String path = url.toString(); + path += getApplicationRunnerApplicationClassName(request); + path += "/"; + + return new URL(path); + } + + protected void onVaadinSessionStarted(VaadinRequest request, + VaadinSession session) throws ServiceException { + try { + final Class classToRun = getClassToRun(); + if (UI.class.isAssignableFrom(classToRun)) { + session.addUIProvider( + new ApplicationRunnerUIProvider(classToRun)); + } else if (UIProvider.class.isAssignableFrom(classToRun)) { + session.addUIProvider((UIProvider) classToRun + .getDeclaredConstructor().newInstance()); + } else { + throw new ServiceException(classToRun.getCanonicalName() + + " is neither an Application nor a UI"); + } + } catch (IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException + | IllegalAccessException | InstantiationException e) { + throw new ServiceException(e); + } catch (final ClassNotFoundException e) { + throw new ServiceException(new InstantiationException( + "Failed to load application class: " + + getApplicationRunnerApplicationClassName( + (VaadinServletRequest) request))); + } + } + + private String getApplicationRunnerApplicationClassName( + HttpServletRequest request) { + return getApplicationClassName(request); + } + + private static final class ProxyDeploymentConfiguration + implements InvocationHandler, Serializable { + private final DeploymentConfiguration originalConfiguration; + + private ProxyDeploymentConfiguration( + DeploymentConfiguration originalConfiguration) { + this.originalConfiguration = originalConfiguration; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if (method.getDeclaringClass() == DeploymentConfiguration.class) { + // Find the configuration instance to delegate to + DeploymentConfiguration configuration = findDeploymentConfiguration( + originalConfiguration); + + return method.invoke(configuration, args); + } else { + return method.invoke(proxy, args); + } + } + } + + private static final class ApplicationRunnerUIProvider extends UIProvider { + private final Class classToRun; + + private ApplicationRunnerUIProvider(Class classToRun) { + this.classToRun = classToRun; + } + + @Override + @SuppressWarnings("unchecked") + public Class getUIClass(UIClassSelectionEvent event) { + return (Class) classToRun; + } + + @Override + public UI createInstance(UICreateEvent event) { + event.getRequest().setAttribute(ApplicationConstants.UI_ROOT_PATH, + "/" + event.getUIClass().getName()); + return super.createInstance(event); + } + } + + /** + * Parses application class name, e.g. + * {@code com.vaadin.testUI.ElementQueryUI} + * + * @param request + * @return application class name as a String + */ + private static String getApplicationClassName(HttpServletRequest request) { + // If request URL would be e.g. + // http://localhost:8080/vaadin/run/com.vaadin.testUI.ElementQueryUI + // then + // + // context = vaadin + // runner servlet = run + // Vaadin application = com.vaadin.testUI.ElementQueryUI + // + final String[] urlParts = request.getRequestURI().split("\\/"); + String applicationClassname = null; + String contextPath = request.getContextPath(); + if (urlParts[1].equals(contextPath.replaceAll("\\/", ""))) { + // class name comes after web context and runner application + // applicationURI = "/" + context + "/" + runner + "/" + // + applicationClassname; + if (urlParts.length == 3) { + throw new ApplicationRunnerRedirectException( + findLastModifiedApplication()); + } else { + applicationClassname = urlParts[3]; + } + } else { + // no context + // applicationURI = "/" + runner + "/" + applicationClassname; + if (urlParts.length == 2) { + throw new ApplicationRunnerRedirectException( + findLastModifiedApplication()); + } else { + applicationClassname = urlParts[2]; + } + } + return applicationClassname; + } + + private static String findLastModifiedApplication() { + String lastModifiedClassName = null; + + File uitestDir = new File("src/main/java"); + if (uitestDir.isDirectory()) { + LinkedList stack = new LinkedList<>(); + stack.add(uitestDir); + + long lastModifiedTimestamp = Long.MIN_VALUE; + while (!stack.isEmpty()) { + File file = stack.pop(); + if (file.isDirectory()) { + stack.addAll(Arrays.asList(file.listFiles())); + } else if (file.getName().endsWith(".java")) { + if (lastModifiedTimestamp < file.lastModified()) { + String className = file.getPath() + .substring(uitestDir.getPath().length() + 1) + .replace(File.separatorChar, '.'); + className = className.substring(0, + className.length() - ".java".length()); + if (isSupportedClass(className)) { + lastModifiedTimestamp = file.lastModified(); + lastModifiedClassName = className; + } + } + } + } + } + + if (lastModifiedClassName == null) { + throw new IllegalArgumentException("No application specified"); + } else { + return lastModifiedClassName; + } + } + + private static boolean isSupportedClass(String className) { + try { + Class type = Class.forName(className, false, + ApplicationRunnerServlet.class.getClassLoader()); + + if (UI.class.isAssignableFrom(type)) { + return true; + } else if (UIProvider.class.isAssignableFrom(type)) { + return true; + } + + return false; + } catch (Exception e) { + return false; + } + } + + private Class getClassToRun() throws ClassNotFoundException { + Class appClass = null; + + String baseName = getApplicationRunnerApplicationClassName( + request.get()); + try { + appClass = getClass().getClassLoader().loadClass(baseName); + return appClass; + } catch (Exception e) { + // + for (String pkg : defaultPackages) { + try { + appClass = getClass().getClassLoader() + .loadClass(pkg + "." + baseName); + } catch (ClassNotFoundException ee) { + // Ignore as this is expected for many packages + } catch (Exception e2) { + getLogger().log(Level.FINE, + "Failed to find application class " + pkg + "." + + baseName, + e2); + } + if (appClass != null) { + return appClass; + } + } + + } + + throw new ClassNotFoundException(baseName); + } + + private Logger getLogger() { + return Logger.getLogger(ApplicationRunnerServlet.class.getName()); + } + + @Override + protected DeploymentConfiguration createDeploymentConfiguration( + Properties initParameters) { + // Get the original configuration from the super class + final DeploymentConfiguration originalConfiguration = super.createDeploymentConfiguration( + initParameters); + + // And then create a proxy instance that delegates to the original + // configuration or a customized version + return (DeploymentConfiguration) Proxy.newProxyInstance( + DeploymentConfiguration.class.getClassLoader(), + new Class[] { DeploymentConfiguration.class }, + new ProxyDeploymentConfiguration(originalConfiguration)); + } + + @Override + protected VaadinServletService createServletService( + DeploymentConfiguration deploymentConfiguration) + throws ServiceException { + VaadinServletService service = super.createServletService( + deploymentConfiguration); + final SystemMessagesProvider provider = service + .getSystemMessagesProvider(); + service.setSystemMessagesProvider(systemMessagesInfo -> { + if (systemMessagesInfo.getRequest() == null) { + return provider.getSystemMessages(systemMessagesInfo); + } + Object messages = systemMessagesInfo.getRequest() + .getAttribute(CUSTOM_SYSTEM_MESSAGES_PROPERTY); + if (messages instanceof SystemMessages) { + return (SystemMessages) messages; + } + return provider.getSystemMessages(systemMessagesInfo); + }); + return service; + } + + private static DeploymentConfiguration findDeploymentConfiguration( + DeploymentConfiguration originalConfiguration) throws Exception { + // First level of cache + DeploymentConfiguration configuration = CurrentInstance + .get(DeploymentConfiguration.class); + + if (configuration == null) { + // Not in cache, try to find a VaadinSession to get it from + VaadinSession session = VaadinSession.getCurrent(); + + if (session == null) { + /* + * There's no current session, request or response when serving + * static resources, but there's still the current request + * maintained by AppliationRunnerServlet, and there's most + * likely also a HttpSession containing a VaadinSession for that + * request. + */ + + HttpServletRequest currentRequest = VaadinServletService + .getCurrentServletRequest(); + if (currentRequest != null) { + HttpSession httpSession = currentRequest.getSession(false); + if (httpSession != null) { + Map, CurrentInstance> oldCurrent = CurrentInstance + .setCurrent((VaadinSession) null); + try { + VaadinServletService service = (VaadinServletService) VaadinService + .getCurrent(); + session = service.findVaadinSession( + new VaadinServletRequest(currentRequest, + service)); + } finally { + /* + * Clear some state set by findVaadinSession to + * avoid accidentally depending on it when coding on + * e.g. static request handling. + */ + CurrentInstance.restoreInstances(oldCurrent); + currentRequest.removeAttribute( + VaadinSession.class.getName()); + } + } + } + } + + if (session != null) { + String name = ApplicationRunnerServlet.class.getName() + + ".deploymentConfiguration"; + try { + session.lock(); + configuration = (DeploymentConfiguration) session + .getAttribute(name); + + if (configuration == null) { + ApplicationRunnerServlet servlet = (ApplicationRunnerServlet) VaadinServlet + .getCurrent(); + Class classToRun; + try { + classToRun = servlet.getClassToRun(); + } catch (ClassNotFoundException e) { + /* + * This happens e.g. if the UI class defined in the + * URL is not found or if this servlet just serves + * static resources while there's some other servlet + * that serves the UI (e.g. when using /run-push/). + */ + return originalConfiguration; + } + + CustomDeploymentConfiguration customDeploymentConfiguration = classToRun + .getAnnotation( + CustomDeploymentConfiguration.class); + if (customDeploymentConfiguration != null) { + Properties initParameters = new Properties( + originalConfiguration.getInitParameters()); + + for (Conf entry : customDeploymentConfiguration + .value()) { + initParameters.put(entry.name(), entry.value()); + } + + configuration = new DefaultDeploymentConfiguration( + servlet.getClass(), initParameters); + } else { + configuration = originalConfiguration; + } + + session.setAttribute(name, configuration); + } + } finally { + session.unlock(); + } + + CurrentInstance.set(DeploymentConfiguration.class, + configuration); + + } else { + configuration = originalConfiguration; + } + } + return configuration; + } +} diff --git a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/CustomDeploymentConfiguration.java b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/CustomDeploymentConfiguration.java new file mode 100644 index 000000000..b2a43768d --- /dev/null +++ b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/CustomDeploymentConfiguration.java @@ -0,0 +1,31 @@ +/* + * Vaadin TestBench Addon + * + * Copyright (C) 2012-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.launcher; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(value = ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface CustomDeploymentConfiguration { + + @Target(value = ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public @interface Conf { + public String name(); + + public String value(); + } + + public CustomDeploymentConfiguration.Conf[] value(); +} diff --git a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/DevelopmentServerLauncher.java b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/DevelopmentServerLauncher.java new file mode 100644 index 000000000..5543d6db1 --- /dev/null +++ b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/DevelopmentServerLauncher.java @@ -0,0 +1,285 @@ +/* + * Vaadin TestBench Addon + * + * Copyright (C) 2012-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.launcher; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.log.JavaUtilLog; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.WebAppContext; + +/** + * Class for running Jetty servlet container within Eclipse project. + */ +public class DevelopmentServerLauncher { + + private static final String KEYSTORE = "src/main/resources/com/vaadin/launcher/keystore"; + private static final int serverPort = 8080; + + /** + * Main function for running Jetty. + * + * Command line Arguments are passed through to Jetty, see runServer method + * for options. + * + * @param args + * command line arguments + */ + public static void main(String[] args) { + System.setProperty("java.awt.headless", "true"); + System.setProperty("org.eclipse.jetty.util.log.class", + JavaUtilLog.class.getName()); + + assertAssertionsEnabled(); + + // + // Pass-through of arguments for Jetty + final Map serverArgs = parseArguments(args); + if (!serverArgs.containsKey("shutdownPort")) { + serverArgs.put("shutdownPort", "8081"); + } + + int port = Integer.parseInt(serverArgs.get("shutdownPort")); + if (port > 0) { + try { + // Try to notify another instance that it's time to close + Socket socket = new Socket((String) null, port); + // Wait until the other instance says it has closed + socket.getInputStream().read(); + // Then tidy up + socket.close(); + } catch (IOException e) { + // Ignore if port is not open + } + } + + // Start Jetty + System.out.println("Starting Jetty servlet container."); + try { + runServer(serverArgs, "Development Server Mode"); + } catch (Exception e) { + // NOP exception already on console by jetty + } + } + + private static void assertAssertionsEnabled() { + try { + assert false; + + System.err.println("You should run " + + DevelopmentServerLauncher.class.getSimpleName() + + " with assertions enabled. Add -ea as a VM argument."); + } catch (AssertionError e) { + // All is fine + } + } + + /** + * Run the server with specified arguments. + * + * @param serverArgs + * @return + * @throws Exception + * @throws Exception + */ + protected static String runServer(Map serverArgs, + String mode) throws Exception { + + // Assign default values for some arguments + assignDefault(serverArgs, "webroot", "src/main/webapp"); + assignDefault(serverArgs, "httpPort", "" + serverPort); + assignDefault(serverArgs, "context", ""); + + int port = serverPort; + try { + port = Integer.parseInt(serverArgs.get("httpPort")); + } catch (NumberFormatException e) { + // keep default value for port + } + + // Add help for System.out + System.out.println("-------------------------------------------------\n" + + "Starting Vaadin in " + mode + ".\n" + + "Running in http://localhost:" + port + + "\n-------------------------------------------------\n"); + + final Server server = new Server(); + + // Enable annotation scanning for webapps + Configuration.ClassList classlist = Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + + final ServerConnector connector = new ServerConnector(server); + connector.setPort(port); + if (serverArgs.containsKey("withssl")) { + @SuppressWarnings("deprecation") + SslContextFactory sslFact = new SslContextFactory(); + sslFact.setTrustStorePath(KEYSTORE); + sslFact.setTrustStorePassword("password"); + sslFact.setKeyStorePath(KEYSTORE); + sslFact.setKeyManagerPassword("password"); + sslFact.setKeyStorePassword("password"); + + ServerConnector sslConnector = new ServerConnector(server, sslFact); + sslConnector.setPort(8444); + + server.setConnectors(new Connector[] { connector, sslConnector }); + } else { + server.setConnectors(new Connector[] { connector }); + } + + final WebAppContext webappcontext = new WebAppContext(); + webappcontext.setContextPath(serverArgs.get("context")); + webappcontext.setWar(serverArgs.get("webroot")); + + // Make webapp use embedded classloader + webappcontext.setParentLoaderPriority(true); + webappcontext.setAttribute( + "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", + ".*/classes/.*"); + + server.setHandler(webappcontext); + + try { + server.start(); + + if (serverArgs.containsKey("shutdownPort")) { + int shutdownPort = Integer + .parseInt(serverArgs.get("shutdownPort")); + final ServerSocket serverSocket = new ServerSocket(shutdownPort, + 1, InetAddress.getByName("127.0.0.1")); + new Thread() { + @Override + public void run() { + try { + System.out.println( + "Waiting for shutdown signal on port " + + serverSocket.getLocalPort()); + // Start waiting for a close signal + Socket accept = serverSocket.accept(); + // First stop listening to the port + serverSocket.close(); + + // Start a thread that kills the JVM if + // server.stop() doesn't have any effect + Thread interruptThread = new Thread() { + @Override + public void run() { + try { + Thread.sleep(5000); + if (!server.isStopped()) { + System.out.println( + "Jetty still running. Closing JVM."); + dumpThreadStacks(); + System.exit(-1); + } + } catch (InterruptedException e) { + // Interrupted if server.stop() was + // successful + } + } + }; + interruptThread.setDaemon(true); + interruptThread.start(); + + // Then stop the jetty server + server.stop(); + + interruptThread.interrupt(); + + // Send a byte to tell the other process that it can + // start jetty + OutputStream outputStream = accept + .getOutputStream(); + outputStream.write(0); + outputStream.flush(); + // Finally close the socket + accept.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + }.start(); + + } + server.join(); + } catch (Exception e) { + server.stop(); + throw e; + } + + return "http://localhost:" + port + serverArgs.get("context"); + } + + /** + * Assign default value for given key. + * + * @param map + * @param key + * @param value + */ + private static void assignDefault(Map map, String key, + String value) { + if (!map.containsKey(key)) { + map.put(key, value); + } + } + + /** + * Parse all command line arguments into a map. + * + * Arguments format "key=value" are put into map. + * + * @param args + * command line arguments + * @return map of arguments in key value pairs. + */ + protected static Map parseArguments(String[] args) { + final Map map = new HashMap<>(); + for (String arg : args) { + final int d = arg.indexOf("="); + if (d > 0 && d < arg.length() && arg.startsWith("--")) { + final String name = arg.substring(2, d); + final String value = arg.substring(d + 1); + map.put(name, value); + } + } + return map; + } + + private static void dumpThreadStacks() { + for (Entry entry : Thread + .getAllStackTraces().entrySet()) { + Thread thread = entry.getKey(); + StackTraceElement[] stackTraceElements = entry.getValue(); + + System.out.println(thread.getName() + " - " + thread.getState()); + for (StackTraceElement stackTraceElement : stackTraceElements) { + System.out.println(" at " + stackTraceElement); + } + System.out.println(); + } + } +} diff --git a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/VaadinStaticFiles.java b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/VaadinStaticFiles.java new file mode 100644 index 000000000..e36e9af5e --- /dev/null +++ b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/launcher/VaadinStaticFiles.java @@ -0,0 +1,19 @@ +/* + * Vaadin TestBench Addon + * + * Copyright (C) 2012-2024 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.launcher; + +import javax.servlet.annotation.WebServlet; + +import com.vaadin.server.VaadinServlet; + +@WebServlet(value = "/VAADIN/*", asyncSupported = false) +public class VaadinStaticFiles extends VaadinServlet { +} diff --git a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestServlet.java b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestServlet.java deleted file mode 100644 index b09e29670..000000000 --- a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestServlet.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Vaadin TestBench Addon - * - * Copyright (C) 2012-2024 Vaadin Ltd - * - * This program is available under Vaadin Commercial License and Service Terms. - * - * See for the full - * license. - */ -package com.vaadin.testUI; - -import javax.servlet.annotation.WebInitParam; -import javax.servlet.annotation.WebServlet; - -import com.vaadin.server.VaadinServlet; - -@WebServlet(value = "/*", asyncSupported = true, initParams = { - @WebInitParam(name = "heartbeatInterval", value = "10"), - @WebInitParam(name = "widgetset", value = "com.vaadin.v7.Vaadin7WidgetSet"), - @WebInitParam(name = "UIProvider", value = "com.vaadin.testUI.TestUIProvider") }) -public class TestServlet extends VaadinServlet { -} diff --git a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestUIProvider.java b/vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestUIProvider.java deleted file mode 100644 index 57fac839d..000000000 --- a/vaadin-testbench-integration-tests/src/main/java/com/vaadin/testUI/TestUIProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Vaadin TestBench Addon - * - * Copyright (C) 2012-2024 Vaadin Ltd - * - * This program is available under Vaadin Commercial License and Service Terms. - * - * See for the full - * license. - */ -package com.vaadin.testUI; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.vaadin.server.UIClassSelectionEvent; -import com.vaadin.server.UIProvider; -import com.vaadin.ui.UI; - -public class TestUIProvider extends UIProvider { - private static Logger logger = Logger.getLogger(TestUIProvider.class - .getName()); - - @Override - public Class getUIClass(UIClassSelectionEvent event) { - String name = (event.getRequest()).getPathInfo(); - if (name.startsWith("/")) { - name = name.substring(1); - } - try { - String className = "com.vaadin.testUI." + name; - return Class.forName(className).asSubclass(UI.class); - } catch (ClassNotFoundException e) { - logger.log(Level.SEVERE, "Could not find UI " + name, e); - } - return null; - } - -} \ No newline at end of file diff --git a/vaadin-testbench-integration-tests/src/main/resources/com/vaadin/launcher/keystore b/vaadin-testbench-integration-tests/src/main/resources/com/vaadin/launcher/keystore new file mode 100644 index 0000000000000000000000000000000000000000..1314185e2a50c207d1e49be85a18fde905b31287 GIT binary patch literal 1367 zcmezO_TO6u1_mY|W&~r_tkjZ{N+3_~kw~ElP-dq=6Vp}$J~l3GHbxdkEha%mMpg!v zCZ>g_dS$){NbKHdb-?k_k)mCWT(T@iD)pWApVa;>F;stcqHq3!XBYEVe0m#jmfOy> z=8R>dhWPy*XYL=4lulT$pW#--bN-j|{|xRw(_7~UrR&sJ?5n)#C31P<`u^B<%U;X5 z$1kO1Ja}%KOM#cE+;hR(+m4FJ2)?spYvia`oXJ#TbWSAu%tYO2nHB4d z`1sma{QvcZPmV$GO-lAshpOr6bHf8>`RFVDRC#n^si3jdAKUvIB4hoP6s%0H@rCYA zHSk=PALX!Pwz6@}eebxfyXW3XyMAQvLC4zaeMW|t7p~q|cc-`8JcUy*WAo&4_l5+! zSXi{GQkH+Q{1SQUNv`?Z%_?C;yIzhhz{ZWg?&w&@w)QelRG zmU~(DPTo1$o%z>~Jp6CBv^ar%Uy!KhkEbS|Y&b4G|5K@^CG`HlFD;f?aS3c0*UKlJ zidcIswsL-s?0vt*Sx23pq^yitGvmX@-6@Zqj>WP$_dGasw7k~%i0f94_dRvKJZ0y- zjvFt#X($s2) zuM^nU-cCOyb#OxLw24<3Yu?@5dHMkNtFtK=IT*K{U6nWAy+*h(zu}Y{qsW;Jk$bNG zf4Yuc%G~|+xq0)n8a6&(e@w(c<+Q!TrK1xo?4Po%)lELBwnw=c<-`$oOd&CKYzJ!N8Mwo2^Wqo zU|yKrI`8XFmA^Vq2Iy<8F~?Ymidtz`9*-9J`d zkF{K+S#thw*V~lJ@W{-sGvDsHdQe$p#+#Mz#HU)nOZG`Ik5Zea!1Sj)@V#4;>>Vz? zBMnnS5Bu(sTOM(tzCiaY@AXCbx?%4f&onx(n!GecQs#T3&b$b=y@5Isb8m__yI8w) z?hR%7#6Tbex$*SIaNz!Dpr!OnKC>S(l6-kIKpy zk3^rJs$$Rl+VasSwRO|pRn$4IR*)_CJ9}i??Suziwd!4OUwjhotXQ7?UN`kXwcM&r ZSy~FKWSgh%eB%EhRkQc^{Z!_=j{wTiB!K_` literal 0 HcmV?d00001 diff --git a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/AbstractTB3Test.java b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/AbstractTB3Test.java index bcc3026be..680a8b7ab 100644 --- a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/AbstractTB3Test.java +++ b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/AbstractTB3Test.java @@ -31,6 +31,7 @@ import org.openqa.selenium.interactions.Mouse; import org.openqa.selenium.interactions.internal.Coordinates; import org.openqa.selenium.internal.Locatable; +import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.support.ui.ExpectedCondition; @@ -41,14 +42,16 @@ import com.google.gson.JsonSyntaxException; import com.vaadin.server.LegacyApplication; import com.vaadin.server.UIProvider; +import com.vaadin.testbench.Parameters; import com.vaadin.testbench.TestBenchDriverProxy; import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.parallel.Browser; import com.vaadin.testbench.parallel.BrowserUtil; import com.vaadin.testbench.parallel.ParallelTest; -import com.vaadin.testbench.parallel.setup.SetupDriver; import com.vaadin.ui.UI; +import io.github.bonigarcia.wdm.WebDriverManager; + /** * Base class for TestBench 3+ tests. All TB3+ tests in the project should * extend this class. @@ -101,16 +104,18 @@ public abstract class AbstractTB3Test extends ParallelTest { @Override @Before public void setup() throws Exception { - // override local driver behaviour, so we can easily specify local - // PhantomJS - // with a system property - if (getBooleanProperty("localPhantom")) { - WebDriver driver = new SetupDriver() - .setupLocalDriver(Browser.PHANTOMJS); - setDriver(driver); - } else { - super.setup(); + if (getRunLocallyBrowser() != null) { + setupWithWebDriverManager(getRunLocallyBrowser()); + } else if (Parameters.isLocalWebDriverUsed()) { + DesiredCapabilities desiredCapabilities = getDesiredCapabilities(); + String browserName = desiredCapabilities.getBrowserName() + .toUpperCase(); + if ("MICROSOFTEDGE".equals(browserName)) { + browserName = "EDGE"; + } + setupWithWebDriverManager(Browser.valueOf(browserName)); } + super.setup(); int w = SCREENSHOT_WIDTH; int h = SCREENSHOT_HEIGHT; @@ -127,6 +132,22 @@ public void setup() throws Exception { } } + private void setupWithWebDriverManager(Browser runLocallyBrowser) { + switch (runLocallyBrowser) { + case CHROME: + WebDriverManager.chromedriver().setup(); + break; + case FIREFOX: + WebDriverManager.firefoxdriver().setup(); + break; + case EDGE: + WebDriverManager.edgedriver().setup(); + break; + default: + break; + } + }; + protected boolean getBooleanProperty(String key) { return Boolean.parseBoolean(System.getProperty(key)); } @@ -567,12 +588,12 @@ protected final void setPush(boolean push) { * @return The path to the given UI class */ private String getDeploymentPath(Class uiClass) { - String runPath = ""; + String runPath = "/run"; if (UI.class.isAssignableFrom(uiClass)) { - return runPath + "/" + uiClass.getSimpleName() + return runPath + "/" + uiClass.getCanonicalName() + (isDebug() ? "?debug" : ""); } else if (LegacyApplication.class.isAssignableFrom(uiClass)) { - return runPath + "/" + uiClass.getSimpleName() + return runPath + "/" + uiClass.getCanonicalName() + "?restartApplication" + (isDebug() ? "&debug" : ""); } else { throw new IllegalArgumentException( diff --git a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ElementScreenCompareIT.java b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ElementScreenCompareIT.java index e2ceb21e7..3a02f6b4d 100644 --- a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ElementScreenCompareIT.java +++ b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ElementScreenCompareIT.java @@ -10,7 +10,8 @@ */ package com.vaadin.tests.elements; -import org.junit.Assert; +import static org.junit.Assert.assertTrue; + import org.junit.Test; import com.vaadin.testUI.ElementQueryUI; @@ -20,8 +21,10 @@ public class ElementScreenCompareIT extends MultiBrowserTest { @Override - protected String getDeploymentPath() { - return "/" + ElementQueryUI.class.getSimpleName(); + protected Class getUIClass() { + // intentionally overriding this method instead of getDeploymentPath() + // in order to test different ways of determining the deployment path + return ElementQueryUI.class; } @Test @@ -30,10 +33,10 @@ public void elementCompareScreen() throws Exception { TestBenchElement button4 = (TestBenchElement) findElements( By.className("v-button")).get(4); - Assert.assertTrue(button4.compareScreen("button4")); + assertTrue(button4.compareScreen("button4")); TestBenchElement layout = (TestBenchElement) button4 .findElement(By.xpath("../..")); - Assert.assertTrue(layout.compareScreen("layout")); + assertTrue(layout.compareScreen("layout")); } } diff --git a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ExecuteJavascriptIT.java b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ExecuteJavascriptIT.java index 065ea57c2..53485926f 100644 --- a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ExecuteJavascriptIT.java +++ b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ExecuteJavascriptIT.java @@ -10,7 +10,8 @@ */ package com.vaadin.tests.elements; -import org.junit.Assert; +import static org.junit.Assert.assertEquals; + import org.junit.Test; import com.vaadin.testUI.ElementQueryUI; @@ -21,7 +22,9 @@ public class ExecuteJavascriptIT extends MultiBrowserTest { @Override protected String getDeploymentPath() { - return "/" + ElementQueryUI.class.getSimpleName(); + // intentionally overriding this method instead of getUIClass() in order + // to test different ways of determining the deployment path + return "/run/" + ElementQueryUI.class.getCanonicalName(); } @Test @@ -33,6 +36,6 @@ public void elementCompareScreen() throws Exception { Long offsetTop = (Long) executeScript("return arguments[0].offsetTop", button); - Assert.assertEquals(Long.valueOf(0), offsetTop); + assertEquals(Long.valueOf(0), offsetTop); } } diff --git a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/PrivateTB3Configuration.java b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/PrivateTB3Configuration.java index 3272f60d7..17eb75a63 100644 --- a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/PrivateTB3Configuration.java +++ b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/PrivateTB3Configuration.java @@ -17,10 +17,15 @@ import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; +import java.util.Locale; import java.util.Properties; +import org.openqa.selenium.remote.DesiredCapabilities; + import com.vaadin.testbench.annotations.BrowserFactory; +import com.vaadin.testbench.annotations.RunLocally; import com.vaadin.testbench.annotations.RunOnHub; +import com.vaadin.testbench.parallel.Browser; /** * Provides values for parameters which depend on where the test is run. @@ -34,6 +39,8 @@ @RunOnHub("tb3-hub.intra.itmill.com") public abstract class PrivateTB3Configuration extends AbstractTB3Test { private static final String HOSTNAME_PROPERTY = "deployment.hostname"; + private static final String RUN_LOCALLY_PROPERTY = "com.vaadin.testbench.runLocally"; + private static final String ALLOW_RUN_LOCALLY_PROPERTY = "com.vaadin.testbench.allowRunLocally"; private static final String PORT_PROPERTY = "deployment.port"; public static final String CHROME_PATH_PROPERTY = "chrome.path"; public static final String FIREFOX_PATH_PROPERTY = "firefox.path"; @@ -54,6 +61,37 @@ public abstract class PrivateTB3Configuration extends AbstractTB3Test { throw new RuntimeException(e); } } + + if (properties.containsKey(RUN_LOCALLY_PROPERTY)) { + System.setProperty("useLocalWebDriver", "true"); + DesiredCapabilities localBrowser = getRunLocallyCapabilities(); + System.setProperty("browsers.include", + localBrowser.getBrowserName() + localBrowser.getVersion()); + } + } + + protected static DesiredCapabilities getRunLocallyCapabilities() { + VaadinBrowserFactory factory = new VaadinBrowserFactory(); + + try { + if (properties.containsKey(RUN_LOCALLY_PROPERTY)) { + // RunLocally defined in propeties file + return factory.create(Browser + .valueOf(properties.getProperty(RUN_LOCALLY_PROPERTY) + .toUpperCase(Locale.ROOT))); + } else if (System.getProperties().containsKey("browsers.include")) { + // Use first included browser as the run locally browser. + String property = System.getProperty("browsers.include"); + String firstBrowser = property.split(",")[0]; + + return factory.create(Browser.valueOf(firstBrowser + .replaceAll("[0-9]+$", "").toUpperCase(Locale.ROOT))); + } + } catch (Exception e) { + System.err.println(e.getMessage()); + System.err.println("Falling back to FireFox"); + } + return factory.create(Browser.FIREFOX); } public static String getProperty(String name) { @@ -72,6 +110,20 @@ protected String getDeploymentHostname() { return getConfiguredDeploymentHostname(); } + protected boolean isRunLocally() { + if (properties.containsKey(RUN_LOCALLY_PROPERTY)) { + return true; + } + + if (properties.containsKey(ALLOW_RUN_LOCALLY_PROPERTY) + && properties.get(ALLOW_RUN_LOCALLY_PROPERTY).equals("true") + && getClass().getAnnotation(RunLocally.class) != null) { + return true; + } + + return false; + } + /** * Gets the hostname that tests are configured to use. * @@ -87,6 +139,14 @@ public static String getConfiguredDeploymentHostname() { return hostName; } + @Override + protected String getBaseURL() { + if (isRunLocally()) { + return "http://localhost:8080"; + } + return super.getBaseURL(); + } + @Override protected int getDeploymentPort() { return getConfiguredDeploymentPort(); diff --git a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ScrollIntoViewIT.java b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ScrollIntoViewIT.java index 2ce50e27c..085104748 100644 --- a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ScrollIntoViewIT.java +++ b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/elements/ScrollIntoViewIT.java @@ -10,12 +10,17 @@ */ package com.vaadin.tests.elements; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.util.List; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.openqa.selenium.ElementNotVisibleException; +import org.openqa.selenium.ElementNotInteractableException; import org.openqa.selenium.remote.DesiredCapabilities; import com.vaadin.testbench.By; @@ -27,46 +32,49 @@ public class ScrollIntoViewIT extends MultiBrowserTest { @Before public void setUp() { + // intentionally not overriding either getDeploymentPath() or + // getUIClass() but relying on a matching UI class name in order to test + // different ways of determining the deployment path + openTestURL(); lastButton = (TestBenchElement) findElements(By.className("v-button")) .get(29); initialY = lastButton.getLocation().getY(); - } @Test public void getText_scrollIntoViewEnabled_ElementIsScrolled() { - Assert.assertFalse(lastButton.isDisplayed()); - Assert.assertEquals("Button 29", lastButton.getText()); - Assert.assertTrue(lastButton.isDisplayed()); - Assert.assertNotEquals(initialY, lastButton.getLocation().getY()); + assertFalse(lastButton.isDisplayed()); + assertEquals("Button 29", lastButton.getText()); + assertTrue(lastButton.isDisplayed()); + assertNotEquals(initialY, lastButton.getLocation().getY()); } @Test public void click_scrollIntoViewEnabled_ElementIsScrolled() { - Assert.assertFalse(lastButton.isDisplayed()); + assertFalse(lastButton.isDisplayed()); lastButton.click(); - Assert.assertTrue(lastButton.isDisplayed()); - Assert.assertNotEquals(initialY, lastButton.getLocation().getY()); + assertTrue(lastButton.isDisplayed()); + assertNotEquals(initialY, lastButton.getLocation().getY()); } @Test public void getText_scrollIntoViewDisabled_ElementIsNotScrolled() { - Assert.assertFalse(lastButton.isDisplayed()); + assertFalse(lastButton.isDisplayed()); getCommandExecutor().setAutoScrollIntoView(false); - Assert.assertNotEquals("Button 29", lastButton.getText()); - Assert.assertEquals(initialY, lastButton.getLocation().getY()); + assertNotEquals("Button 29", lastButton.getText()); + assertEquals(initialY, lastButton.getLocation().getY()); } @Test public void click_scrollIntoViewDisabled_ElementIsNotScrolled() { - Assert.assertFalse(lastButton.isDisplayed()); + assertFalse(lastButton.isDisplayed()); getCommandExecutor().setAutoScrollIntoView(false); try { lastButton.click(); - Assert.fail("Expected an ElementNotVisibleException to be thrown"); - } catch (ElementNotVisibleException anElementNotVisibleException) { - Assert.assertEquals(initialY, lastButton.getLocation().getY()); + fail("Expected an ElementNotInteractableException to be thrown"); + } catch (ElementNotInteractableException e) { + assertEquals(initialY, lastButton.getLocation().getY()); } }