/* * Copyright 2014-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.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.base.Stopwatch; import com.google.common.collect.Sets; import com.machinepublishers.jbrowserdriver.JBrowserDriver; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.Request; import com.ning.http.client.Response; import io.netty.handler.codec.http.QueryStringDecoder; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.Keys; import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.glowroot.tests.jvm.JvmSidebar; import org.glowroot.tests.util.Utils; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; public class BasicSmokeIT extends WebDriverIT { @BeforeClass public static void setUp() throws Exception { AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); Request request = asyncHttpClient .prepareGet("http://localhost:" + getUiPort() + "/backend/config/transaction?agent-id=" + agentId) .build(); Response response = asyncHttpClient.executeRequest(request).get(); JsonNode responseNode = new ObjectMapper().readTree(response.getResponseBody()); String version = responseNode.get("version").asText(); request = asyncHttpClient .preparePost("http://localhost:" + getUiPort() + "/backend/config/transaction?agent-id=" + agentId) .setBody("{\"slowThresholdMillis\":0,\"profilingIntervalMillis\":10," + "\"captureThreadStats\":false,\"version\":\"" + version + "\"}") .build(); int statusCode = asyncHttpClient.executeRequest(request).get().getStatusCode(); if (statusCode != 200) { asyncHttpClient.close(); throw new AssertionError("Unexpected status code: " + statusCode); } for (int i = 0; i < 3; i++) { container.executeNoExpectedTrace(JdbcServlet.class); container.executeNoExpectedTrace(ErrorServlet.class); } // wait until above transactions are reported in UI Stopwatch stopwatch = Stopwatch.createStarted(); Set<String> transactionNames = Sets.newHashSet(); while (stopwatch.elapsed(SECONDS) < 30) { long from = System.currentTimeMillis() - HOURS.toMillis(2); long to = from + HOURS.toMillis(4); request = asyncHttpClient .prepareGet("http://localhost:" + getUiPort() + "/backend/transaction/summaries?agent-rollup-id=" + agentId + "&transaction-type=Web&from=" + from + "&to=" + to + "&sort-order=total-time&limit=10") .build(); response = asyncHttpClient.executeRequest(request).get(); responseNode = new ObjectMapper().readTree(response.getResponseBody()); for (JsonNode transactionNode : responseNode.get("transactions")) { transactionNames.add(transactionNode.get("transactionName").asText()); } if (transactionNames.contains("/jdbcservlet") && transactionNames.contains("/errorservlet")) { break; } Thread.sleep(10); } asyncHttpClient.close(); if (!transactionNames.contains("/jdbcservlet") || !transactionNames.contains("/errorservlet")) { throw new AssertionError("Timed out waiting for /jdbcservlet and /errorservlet to both" + " show up in sidebar"); } Executors.newSingleThreadExecutor().submit(new Callable<Void>() { @Override public Void call() throws Exception { container.executeNoExpectedTrace(SleepServlet.class); return null; } }); } @AfterClass public static void tearDown() throws Exception { container.interruptAppUnderTest(); } @Test public void shouldCheckTransactionPages() throws Exception { App app = app(); GlobalNavbar globalNavbar = globalNavbar(); app.open(); // hitting F5 is just to test 304 responses Utils.withWait(driver, By.partialLinkText("Response time")).sendKeys(Keys.F5); Utils.withWait(driver, By.xpath("//a[@gt-display='All Web Transactions'][contains(., '%')]")); driver.findElement(By.xpath("//button[@title='By percent of total time']")).click(); driver.findElement(By.linkText("By average time")).click(); Utils.withWait(driver, By.xpath("//a[@gt-display='All Web Transactions'][contains(., 'ms')]")); driver.findElement(By.xpath("//button[@title='By average time']")).click(); driver.findElement(By.linkText("By throughput (per min)")).click(); Utils.withWait(driver, By.xpath("//a[@gt-display='All Web Transactions'][contains(., '/min')]")); driver.findElement(By.xpath("//button[@title='By throughput (per min)']")).click(); driver.findElement(By.linkText("By percent of total time")).click(); Utils.withWait(driver, By.xpath("//a[@gt-display='All Web Transactions'][contains(., '%')]")); Utils.withWait(driver, By.partialLinkText("percentiles")).click(); Utils.withWait(driver, By.partialLinkText("throughput")).click(); Utils.withWait(driver, By.partialLinkText("Slow traces")).click(); Utils.withWait(driver, By.partialLinkText("Queries")).click(); Utils.withWait(driver, By.partialLinkText("Continuous profiling")).click(); Utils.withWait(driver, By.xpath("//input[@ng-model='filter']")).sendKeys("JdbcServlet"); Utils.withWait(driver, By.xpath("//button[@ng-click='refresh()']")).click(); new WebDriverWait(driver, 30).until(ExpectedConditions .textToBePresentInElementLocated(By.className("gt-profile"), "JdbcServlet")); Utils.withWait(driver, By.linkText("View flame graph (experimental)")).click(); // give flame graph a chance to render (only for visual when running locally) Thread.sleep(1000); globalNavbar.getTransactionsLink().click(); Utils.withWait(driver, By.partialLinkText("/jdbcservlet")).click(); Utils.withWait(driver, By.partialLinkText("percentiles")).click(); Utils.withWait(driver, By.partialLinkText("Slow traces")).click(); Utils.withWait(driver, By.partialLinkText("Queries")).click(); Utils.withWait(driver, By.partialLinkText("Continuous profiling")).click(); Utils.withWait(driver, By.linkText("View flame graph (experimental)")).click(); } @Test public void shouldCheckNonActiveTraceModalPages() throws Exception { App app = app(); app.open(); Utils.withWait(driver, By.partialLinkText("Slow traces")).click(); String url = "http://localhost:" + getUiPort() + "/backend/transaction/points" + "?transaction-type=Web" + "&from=0" + "&to=" + Long.MAX_VALUE + "&headline-comparator=begins" + "&headline=" + "&error-message-comparator=begins" + "&error-message=" + "&user-comparator=begins" + "&user=" + "&attribute-name=" + "&attribute-value-comparator=begins" + "&attribute-value=" + "&limit=500" + "&agent-rollup-id=" + agentId; AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); Request request = asyncHttpClient .prepareGet(url) .build(); Response response = asyncHttpClient.executeRequest(request).get(); JsonNode responseNode = new ObjectMapper().readTree(response.getResponseBody()); ArrayNode pointsNode = (ArrayNode) responseNode.get("normalPoints"); String traceId = ((ArrayNode) pointsNode.get(0)).get(3).asText(); if (WebDriverSetup.useCentral) { driver.get(app.getBaseUrl() + "/transaction/traces?agent-id=" + agentId + "&transaction-type=Web&modal-agent-id=" + agentId + "&modal-trace-id=" + traceId); } else { driver.get(app.getBaseUrl() + "/transaction/traces?transaction-type=Web&modal-trace-id=" + traceId); } asyncHttpClient.close(); clickAroundInTraceModal(traceId, false); } @Test public void shouldCheckActiveTraceModalPages() throws Exception { App app = app(); GlobalNavbar globalNavbar = globalNavbar(); JvmSidebar jvmSidebar = new JvmSidebar(driver); app.open(); globalNavbar.getJvmLink().click(); jvmSidebar.getThreadDumpLink().click(); WebElement viewTraceLink = Utils.withWait(driver, By.linkText("view trace")); String href = viewTraceLink.getAttribute("href"); String traceId = new QueryStringDecoder(href).parameters().get("modal-trace-id").get(0); viewTraceLink.click(); clickAroundInTraceModal(traceId, true); } @Test public void shouldCheckErrorsPages() throws Exception { App app = app(); GlobalNavbar globalNavbar = globalNavbar(); app.open(); globalNavbar.getErrorsLink().click(); Utils.withWait(driver, By.xpath("//a[@gt-display='All Web Transactions'][not(contains(., '%'))]")); driver.findElement(By.xpath("//button[@title='By error count']")).click(); driver.findElement(By.linkText("By error rate")).click(); Utils.withWait(driver, By.xpath("//a[@gt-display='All Web Transactions'][contains(., '%')]")); driver.findElement(By.xpath("//button[@title='By error rate']")).click(); driver.findElement(By.linkText("By error count")).click(); Utils.withWait(driver, By.xpath("//a[@gt-display='All Web Transactions'][not(contains(., '%'))]")); Utils.withWait(driver, By.xpath("//input[@ng-model='filter']")).sendKeys("xyz"); Utils.withWait(driver, By.xpath("//button[@ng-click='refresh()']")).click(); Utils.withWait(driver, By.partialLinkText("Error traces")).click(); globalNavbar.getErrorsLink().click(); try { Utils.withWait(driver, By.partialLinkText("/errorservlet")).click(); } catch (StaleElementReferenceException e) { // this happens occassionally during travis-ci builds now that sidebar refresh is // delayed by 100 ms Utils.withWait(driver, By.partialLinkText("/errorservlet")).click(); } Utils.withWait(driver, By.partialLinkText("Error traces")).click(); } @Test public void shouldCheckJvmPages() throws Exception { App app = app(); GlobalNavbar globalNavbar = globalNavbar(); JvmSidebar jvmSidebar = new JvmSidebar(driver); app.open(); globalNavbar.getJvmLink().click(); // sleep for a second to give time for jvm gauges page to make 2 requests // (first to get gauge list and then to get gauge points for default selected gauges) Thread.sleep(1000); jvmSidebar.getEnvironmentLink().click(); jvmSidebar.getThreadDumpLink().click(); // jstack view is not accessible via jvm sidebar currently app.open("/jvm/jstack"); jvmSidebar.getHeapDumpLink().click(); if (!WebDriverSetup.useCentral) { // heap dump is somehow causing cassandra connection to be lost on travis-ci: // // com.datastax.driver.core.exceptions.NoHostAvailableException: All host(s) tried for // query failed (tried: /127.0.0.1:9042 // (com.datastax.driver.core.exceptions.ConnectionException: [/127.0.0.1] Write attempt // on defunct connection)) Utils.withWait(driver, By.xpath("//button[normalize-space()='Heap dump']")).click(); Utils.withWait(driver, By.xpath("//button[normalize-space()='Yes']")).click(); String heapDumpFileName = Utils .withWait(driver, By.xpath("//div[@ng-show='heapDumpResponse']//table//tr[1]/td[2]")) .getText(); if (!new File(heapDumpFileName).delete()) { throw new IOException("Could not delete heap dump file: " + heapDumpFileName); } } Utils.withWait(driver, By.xpath("//button[normalize-space()='Check disk space']")).click(); Utils.withWait(driver, By.xpath("//div[@ng-show='availableDiskSpaceBytes !== undefined']")); jvmSidebar.getHeapHistogramLink().click(); jvmSidebar.getMBeanTreeLink().click(); List<WebElement> elements = new WebDriverWait(driver, 30).until(ExpectedConditions .visibilityOfAllElementsLocatedBy(By.className("gt-mbean-unexpanded-content"))); for (WebElement element : elements) { element.click(); } // test the refresh of opened items driver.navigate().refresh(); if (!(driver instanceof JBrowserDriver)) { // need to go back to top of page b/c sidebar links need to be viewable before they can // be clicked in chrome and safari drivers ((JavascriptExecutor) driver).executeScript("scroll(0, 0)"); } // jvm capabilities is not accessible via config sidebar currently app.open("/jvm/capabilities"); } @Test public void shouldCheckLogPage() throws Exception { AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); Request request = asyncHttpClient .prepareGet("http://localhost:" + getUiPort() + "/log") .setFollowRedirects(true) .build(); Response response = asyncHttpClient.executeRequest(request).get(); assertThat(response.getStatusCode()).isEqualTo(200); asyncHttpClient.close(); } private void clickAroundInTraceModal(String traceId, boolean active) throws Exception { Utils.withWait(driver, By.className("gt-entries-toggle")).click(); Utils.withWait(driver, By.xpath("//div[starts-with(normalize-space(.),'jdbc execution:')]")); Utils.withWait(driver, By.className("gt-main-thread-profile-toggle")).click(); // wait for profile to open Thread.sleep(1000); // "click download", verify no error String download; String urlSuffix = active ? "&check-live-traces=true" : ""; if (WebDriverSetup.useCentral) { download = "http://localhost:" + getUiPort() + "/export/trace?agent-id=" + agentId + "&trace-id=" + traceId + urlSuffix; } else { download = "http://localhost:" + getUiPort() + "/export/trace?trace-id=" + traceId + urlSuffix; } AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); Request request = asyncHttpClient .prepareGet(download) .build(); Response response = asyncHttpClient.executeRequest(request).get(); assertThat(response.getStatusCode()).isEqualTo(200); asyncHttpClient.close(); } }