/* * Copyright 2015-2017 the original author or authors. * * Licensed 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.glowroot.tests; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Method; import java.net.ServerSocket; import java.net.URL; import java.security.SecureRandom; import java.util.Properties; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.Session; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.BaseEncoding; import com.google.common.io.Files; import com.machinepublishers.jbrowserdriver.JBrowserDriver; import com.saucelabs.common.SauceOnDemandAuthentication; import com.saucelabs.common.SauceOnDemandSessionIdProvider; import com.saucelabs.junit.SauceOnDemandTestWatcher; import kr.motd.maven.os.Detector; import org.junit.rules.TestWatcher; import org.openqa.selenium.Dimension; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.remote.RemoteWebDriver; import org.rauschig.jarchivelib.ArchiveFormat; import org.rauschig.jarchivelib.Archiver; import org.rauschig.jarchivelib.ArchiverFactory; import org.rauschig.jarchivelib.CompressionType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.it.harness.Container; import org.glowroot.agent.it.harness.Containers; import org.glowroot.agent.it.harness.impl.JavaagentContainer; import org.glowroot.agent.it.harness.impl.LocalContainer; public class WebDriverSetup { protected static final boolean useCentral = Boolean.getBoolean("glowroot.internal.webdriver.useCentral"); private static final boolean USE_FIREFOX = false; private static final String GECKO_DRIVER_VERSION = "0.15.0"; private static final Logger logger = LoggerFactory.getLogger(WebDriverSetup.class); static { // shorter time so aggregates and gauges will be collected during BasicSmokeIT System.setProperty("glowroot.internal.rollup.0.intervalMillis", "1000"); System.setProperty("glowroot.internal.gaugeCollectionIntervalMillis", "1000"); } public static WebDriverSetup create() throws Exception { if (!SharedSetupRunListener.useSharedSetup()) { return createSetup(false); } WebDriverSetup sharedSetup = SharedSetupRunListener.getSharedSetup(); if (sharedSetup == null) { sharedSetup = createSetup(true); SharedSetupRunListener.setSharedSetup(sharedSetup); } return sharedSetup; } private final Container container; private final int uiPort; private final boolean shared; private WebDriver driver; private String remoteWebDriverSessionId; private WebDriverSetup(Container container, int uiPort, boolean shared, WebDriver driver) throws Exception { this.container = container; this.uiPort = uiPort; this.shared = shared; this.driver = driver; } public void close() throws Exception { close(false); } public void close(boolean evenIfShared) throws Exception { if (shared && !evenIfShared) { // this is the shared setup and will be closed at the end of the run return; } if (driver != null) { driver.quit(); } container.close(); if (useCentral) { Class<?> bootstrapClass = Class.forName("org.glowroot.central.Bootstrap"); Method mainMethod = bootstrapClass.getMethod("main", String[].class); mainMethod.invoke(null, (Object) new String[] {"stop"}); CassandraWrapper.stop(); } } public Container getContainer() { return container; } public int getUiPort() { return uiPort; } public WebDriver getDriver() { return driver; } public void beforeEachTest(String testName, ScreenshotOnExceptionRule screenshotOnExceptionRule) throws Exception { if (SauceLabs.useSauceLabs()) { // need separate webdriver instance per test in order to report each test separately in // saucelabs driver = SauceLabs.getWebDriver(testName); // need to capture sessionId since it is needed in sauceLabsTestWatcher, after // driver.quit() is called remoteWebDriverSessionId = ((RemoteWebDriver) driver).getSessionId().toString(); } else { screenshotOnExceptionRule.setDriver(driver); } } public void afterEachTest() throws Exception { if (SauceLabs.useSauceLabs()) { driver.quit(); } container.checkAndReset(); } public TestWatcher getSauceLabsTestWatcher() { if (!SauceLabs.useSauceLabs()) { return new TestWatcher() {}; } String sauceUsername = System.getenv("SAUCE_USERNAME"); String sauceAccessKey = System.getenv("SAUCE_ACCESS_KEY"); SauceOnDemandAuthentication authentication = new SauceOnDemandAuthentication(sauceUsername, sauceAccessKey); SauceOnDemandSessionIdProvider sessionIdProvider = new SauceOnDemandSessionIdProvider() { @Override public String getSessionId() { return remoteWebDriverSessionId; } }; return new SauceOnDemandTestWatcher(sessionIdProvider, authentication); } private static WebDriverSetup createSetup(boolean shared) throws Exception { int uiPort = getAvailablePort(); File testDir = Files.createTempDir(); Container container; if (useCentral) { CassandraWrapper.start(); Cluster cluster = Cluster.builder().addContactPoint("127.0.0.1").build(); Session session = cluster.newSession(); session.execute("create keyspace if not exists glowroot_unit_tests with replication =" + " { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }"); session.execute("use glowroot_unit_tests"); session.execute("drop table if exists agent"); session.execute("drop table if exists agent_rollup"); session.execute("drop table if exists user"); session.execute("drop table if exists role"); session.execute("drop table if exists central_config"); session.execute("drop table if exists config"); session.close(); cluster.close(); container = createCentralAndContainer(uiPort, testDir); } else { container = createContainer(uiPort, testDir); } if (SauceLabs.useSauceLabs()) { return new WebDriverSetup(container, uiPort, shared, null); } else { // single webdriver instance for much better performance WebDriver driver; if (USE_FIREFOX) { File geckoDriverExecutable = downloadGeckoDriverIfNeeded(); System.setProperty("webdriver.gecko.driver", geckoDriverExecutable.getAbsolutePath()); driver = new FirefoxDriver(); } else { driver = new JBrowserDriver(); } // 768 is bootstrap media query breakpoint for screen-sm-min // 992 is bootstrap media query breakpoint for screen-md-min // 1200 is bootstrap media query breakpoint for screen-lg-min driver.manage().window().setSize(new Dimension(1200, 800)); return new WebDriverSetup(container, uiPort, shared, driver); } } private static Container createContainer(int uiPort, File testDir) throws Exception { File adminFile = new File(testDir, "admin.json"); Files.write("{\"web\":{\"port\":" + uiPort + "}}", adminFile, Charsets.UTF_8); if (Containers.useJavaagent()) { return new JavaagentContainer(testDir, true, ImmutableList.of()); } else { return new LocalContainer(testDir, true, ImmutableMap.of()); } } private static Container createCentralAndContainer(int uiPort, File testDir) throws Exception { int grpcPort = getAvailablePort(); PrintWriter props = new PrintWriter("glowroot-central.properties"); props.println("cassandra.keyspace=glowroot_unit_tests"); byte[] bytes = new byte[16]; new SecureRandom().nextBytes(bytes); props.println("cassandra.symmetricEncryptionKey=" + BaseEncoding.base16().lowerCase().encode(bytes)); props.println("grpc.port=" + grpcPort); props.println("ui.port=" + uiPort); props.close(); Class<?> bootstrapClass = Class.forName("org.glowroot.central.Bootstrap"); Method mainMethod = bootstrapClass.getMethod("main", String[].class); mainMethod.invoke(null, (Object) new String[] {"start"}); if (Containers.useJavaagent()) { // -Xmx is to limit memory usage on travis-ci builds return new JavaagentContainer(testDir, false, ImmutableList .of("-Dglowroot.collector.address=localhost:" + grpcPort, "-Xmx64m")); } else { return new LocalContainer(testDir, false, ImmutableMap.of("glowroot.collector.address", "localhost:" + Integer.toString(grpcPort))); } } private static int getAvailablePort() throws Exception { if (SauceLabs.useSauceLabs()) { // glowroot must listen on one of the ports that sauce connect proxies // see https://saucelabs.com/docs/connect#localhost return 4000; } else { ServerSocket serverSocket = new ServerSocket(0); int port = serverSocket.getLocalPort(); serverSocket.close(); return port; } } private static File downloadGeckoDriverIfNeeded() throws IOException { MyDetector detector = new MyDetector(); Properties props = detector.detect(); String osDetectedName = props.getProperty("os.detected.name"); String osDetectedArch = props.getProperty("os.detected.arch"); int bits; if (osDetectedArch.endsWith("_64")) { bits = 64; } else if (osDetectedArch.endsWith("_32")) { bits = 32; } else { throw new IllegalStateException("Unexpected os.detected.arch: " + osDetectedArch); } String optionalExt; String downloadFilenameSuffix; String downloadFilenameExt; Archiver archiver; if (osDetectedName.equals("linux")) { optionalExt = ""; downloadFilenameSuffix = "linux" + bits; downloadFilenameExt = "tar.gz"; archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP); } else if (osDetectedName.equals("osx")) { optionalExt = ""; downloadFilenameSuffix = "macos"; downloadFilenameExt = "tar.gz"; archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP); } else if (osDetectedName.equals("windows")) { optionalExt = ".exe"; downloadFilenameSuffix = "win" + bits; downloadFilenameExt = "zip"; archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP); } else { throw new IllegalStateException( "Unsupported OS for running geckodriver: " + osDetectedName); } File targetDir = new File("target"); targetDir.mkdir(); File geckoDriverExecutable = new File(targetDir, "geckodriver-" + GECKO_DRIVER_VERSION + optionalExt); if (!geckoDriverExecutable.exists()) { downloadAndExtractGeckoDriver(targetDir, downloadFilenameSuffix, downloadFilenameExt, optionalExt, archiver); } return geckoDriverExecutable; } private static void downloadAndExtractGeckoDriver(File directory, String downloadFilenameSuffix, String downloadFilenameExt, String optionalExt, Archiver archiver) throws IOException { // using System.out to make sure user sees why there is a delay here System.out.print("Downloading Mozilla geckodriver " + GECKO_DRIVER_VERSION + " ..."); URL url = new URL("https://github.com/mozilla/geckodriver/releases/download/v" + GECKO_DRIVER_VERSION + "/geckodriver-v" + GECKO_DRIVER_VERSION + '-' + downloadFilenameSuffix + '.' + downloadFilenameExt); InputStream in = url.openStream(); File archiveFile = File.createTempFile("geckodriver-" + GECKO_DRIVER_VERSION + '-', '.' + downloadFilenameExt); Files.asByteSink(archiveFile).writeFrom(in); in.close(); archiver.extract(archiveFile, directory); Files.move(new File(directory, "geckodriver" + optionalExt), new File(directory, "geckodriver-" + GECKO_DRIVER_VERSION + optionalExt)); archiveFile.delete(); System.out.println(" OK"); } private static class MyDetector extends Detector { private Properties detect() { Properties props = new Properties(); super.detect(props, ImmutableList.<String>of()); return props; } @Override protected void log(String message) { logger.info(message); } @Override protected void logProperty(String name, String value) { if (logger.isInfoEnabled()) { logger.info(name + ": " + value); } } } }