content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.instrumentation.vaadin;
  
  import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
  import static org.awaitility.Awaitility.await;
  
  import com.vaadin.flow.server.Version;
  import com.vaadin.flow.spring.annotation.EnableVaadin;
  import io.opentelemetry.api.trace.SpanKind;
  import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
  import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest;
  import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
  import io.opentelemetry.sdk.trace.data.SpanData;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.time.Duration;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import org.junit.jupiter.api.AfterAll;
  import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.extension.RegisterExtension;
  import org.openqa.selenium.By;
  import org.openqa.selenium.chrome.ChromeOptions;
  import org.openqa.selenium.remote.RemoteWebDriver;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.SpringBootApplication;
  import org.springframework.context.ConfigurableApplicationContext;
  import org.testcontainers.Testcontainers;
  import org.testcontainers.containers.BrowserWebDriverContainer;
  import org.testcontainers.containers.output.Slf4jLogConsumer;
  
  public abstract class AbstractVaadinTest
      extends AbstractHttpServerUsingTest<ConfigurableApplicationContext> {
  
    private static final Logger logger = LoggerFactory.getLogger(AbstractVaadinTest.class);
  
    @RegisterExtension
    static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
  
    private BrowserWebDriverContainer<?> browser;
  
    @SpringBootApplication
    @EnableVaadin("test.vaadin")
    static class TestApplication {
      public TestApplication() {}
  
      static ConfigurableApplicationContext start(int port, String contextPath) {
        SpringApplication app = new SpringApplication(TestApplication.class);
        Map<String, Object> properties = new HashMap<>();
        properties.put("server.port", port);
        properties.put("server.servlet.contextPath", contextPath);
        properties.put("server.error.include-message", "always");
        app.setDefaultProperties(properties);
        return app.run();
      }
    }
  
    @BeforeAll
    protected void setup() throws URISyntaxException {
      startServer();
  
      Testcontainers.exposeHostPorts(port);
  
      browser =
          new BrowserWebDriverContainer<>()
              .withCapabilities(new ChromeOptions())
              .withLogConsumer(new Slf4jLogConsumer(logger));
      browser.start();
  
      address = new URI("http://host.testcontainers.internal:" + port + getContextPath() + "/");
    }
  
    @AfterAll
    void cleanup() {
      cleanupServer();
      if (browser != null) {
        browser.stop();
      }
    }
  
    protected void prepareVaadinBaseDir(File baseDir) {}
  
    protected static void copyClasspathResource(String resource, Path destination) {
      if (!Files.exists(destination)) {
        try (InputStream inputStream = AbstractVaadinTest.class.getResourceAsStream(resource)) {
          Files.copy(inputStream, destination);
        } catch (IOException e) {
          throw new IllegalStateException(e);
        }
      }
    }
  
    @Override
    protected ConfigurableApplicationContext setupServer() {
      File baseDir = new File("build/vaadin-" + Version.getFullVersion());
      baseDir.mkdirs();
      prepareVaadinBaseDir(baseDir);
  
      // set directory for files generated by vaadin development mode
      // by default these go to project root
      System.setProperty("vaadin.project.basedir", baseDir.getAbsolutePath());
      return TestApplication.start(port, getContextPath());
    }
  
    @Override
    protected void stopServer(ConfigurableApplicationContext ctx) {
      ctx.close();
    }
  
    @Override
    public String getContextPath() {
      return "/xyz";
    }
  
    private void waitForStart(RemoteWebDriver driver) {
      // In development mode ui javascript is compiled when application starts
      // this involves downloading and installing npm and a bunch of packages
      // and running webpack. Wait until all of this is done before starting test.
      driver.manage().timeouts().implicitlyWait(Duration.ofMinutes(3));
      driver.get(address.resolve("main").toString());
      // wait for page to load
      driver.findElement(By.id("main.label"));
      // clear traces so test would start from clean state
      testing.clearData();
  
      driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));
    }
  
    private RemoteWebDriver getWebDriver() {
      return new RemoteWebDriver(browser.getSeleniumAddress(), new ChromeOptions(), false);
    }
  
    abstract void assertFirstRequest();
  
    private void assertButtonClick() {
      await()
          .untilAsserted(
              () -> {
                List<List<SpanData>> traces = testing.waitForTraces(1);
                assertThat(traces.get(0))
                    .satisfies(
                        spans -> {
                          assertThat(spans.get(0))
                              .hasName("POST " + getContextPath() + "/main")
                              .hasNoParent()
                              .hasKind(SpanKind.SERVER);
                          assertThat(spans.get(1))
                              .hasName("SpringVaadinServletService.handleRequest")
                              .hasParent(spans.get(0))
                              .hasKind(SpanKind.INTERNAL);
                          // we don't assert all the handler spans as these vary between
                          // vaadin versions
                          assertThat(spans.get(spans.size() - 2))
                              .hasName("UidlRequestHandler.handleRequest")
                              .hasParent(spans.get(1))
                              .hasKind(SpanKind.INTERNAL);
                          assertThat(spans.get(spans.size() - 1))
                              .hasName("EventRpcHandler.handle/click")
                              .hasParent(spans.get(spans.size() - 2))
                              .hasKind(SpanKind.INTERNAL);
                        });
              });
    }
  
    @Test
    public void navigateFromMainToOtherView() {
      RemoteWebDriver driver = getWebDriver();
      waitForStart(driver);
  
      // fetch the test page
      driver.get(address.resolve("main").toString());
  
      // wait for page to load
      assertThat(driver.findElement(By.id("main.label")).getText()).isEqualTo("Main view");
      assertFirstRequest();
  
      testing.clearData();
  
      // click a button to trigger calling java code in MainView
      driver.findElement(By.id("main.button")).click();
  
      // wait for page to load
      assertThat(driver.findElement(By.id("other.label")).getText()).isEqualTo("Other view");
      assertButtonClick();
  
      driver.close();
    }
  }
  