]> source.dussan.org Git - sonarqube.git/commitdiff
Give a Try to a better? selenium framework
authorDavid Gageot <david@gageot.net>
Mon, 31 Aug 2015 13:01:41 +0000 (15:01 +0200)
committerDavid Gageot <david@gageot.net>
Wed, 2 Sep 2015 11:02:17 +0000 (13:02 +0200)
30 files changed:
it/it-tests/pom.xml
it/it-tests/src/test/java/administration/suite/administration/BulkDeletionTest.java
it/it-tests/src/test/java/administration/suite/administration/ProjectAdministrationTest.java
it/it-tests/src/test/java/administration/suite/administration/PropertySetsTest.java
it/it-tests/src/test/java/administration/suite/administration/SubCategoriesTest.java
it/it-tests/src/test/java/administration/suite/ui/I18nTest.java
it/it-tests/src/test/java/analysis/suite/measure/MeasureFiltersTest.java
it/it-tests/src/test/java/issue/suite/ManualRulesTest.java
it/it-tests/src/test/java/qualitygate/QualityGateNotificationTest.java
it/it-tests/src/test/java/selenium/Browser.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/ByCssSelectorOrByNameOrById.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/Consumer.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/ElementFilter.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/Failure.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/LazyDomElement.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/LazyShould.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/Optional.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/Retry.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/SeleneseTest.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/SeleniumDriver.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/Text.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/ThreadSafeDriver.java [new file with mode: 0644]
it/it-tests/src/test/java/selenium/WebElementHelper.java [new file with mode: 0644]
it/it-tests/src/test/java/server/ServerTest.java
it/it-tests/src/test/java/server/suite/ServerAdministrationTest.java
it/it-tests/src/test/java/server/suite/ServerTest.java
it/it-tests/src/test/java/updatecenter/UpdateCenterTest.java
it/it-tests/src/test/resources/administration/suite/ProjectAdministrationTest/project-administration/multimodule-project-delete-version.html
it/it-tests/src/test/resources/administration/suite/ProjectAdministrationTest/project-administration/multimodule-project-modify-version.html
it/it-tests/src/test/resources/qualitygate/notifications/email_configuration.html

index 9e4ce6383eefda3a18be6203bd207ae8e961399f..3e67e663662c733ed7e8043cdbd96f17f5c9c3f2 100644 (file)
       <artifactId>jsonassert</artifactId>
       <version>1.2.0</version>
     </dependency>
-
+    <dependency>
+      <groupId>org.jsoup</groupId>
+      <artifactId>jsoup</artifactId>
+      <version>1.8.3</version>
+    </dependency>
 
     <!-- Email notifications -->
     <dependency>
index a833589cb602ab85e088ac617a763f6dffeb282a..4ee932e5d67455e81f74724c8f21554ef36b47af 100644 (file)
@@ -26,6 +26,7 @@ import com.sonar.orchestrator.selenium.Selenese;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
+import selenium.SeleneseTest;
 
 import static util.ItUtils.projectDir;
 
@@ -52,7 +53,7 @@ public class BulkDeletionTest {
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("project-bulk-deletion-on-selected-project",
       "/administration/suite/BulkDeletionTest/project-bulk-deletion/bulk-delete-filter-projects.html"
     ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   /**
@@ -67,7 +68,7 @@ public class BulkDeletionTest {
         "/administration/suite/BulkDeletionTest/project-bulk-deletion/display-two-letters-long-project.html",
         "/administration/suite/BulkDeletionTest/project-bulk-deletion/filter-two-letters-long-project.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   private void executeBuild(String projectKey, String projectName) {
index 62d624c58a7ef583255091e030de34b98cd49ac4..939b2af95a538349bade4a8e531d2050937eab30 100644 (file)
@@ -43,6 +43,7 @@ import org.sonar.wsclient.qualitygate.UpdateCondition;
 import org.sonar.wsclient.services.PropertyQuery;
 import org.sonar.wsclient.services.ResourceQuery;
 import org.sonar.wsclient.user.UserParameters;
+import selenium.SeleneseTest;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static util.ItUtils.projectDir;
@@ -116,9 +117,10 @@ public class ProjectAdministrationTest {
       adminClient.permissionClient().addPermission(
         PermissionParameters.create().user(projectAdminUser).component("sample").permission("admin"));
 
+      // Use the old runner because it fails with the new Selenium runner
       orchestrator.executeSelenese(
         Selenese.builder().setHtmlTestsInClasspath("project-deletion", "/administration/suite/ProjectAdministrationTest/project-deletion/project-deletion.html").build()
-        );
+      );
     } finally {
       adminClient.userClient().deactivate(projectAdminUser);
     }
@@ -141,21 +143,19 @@ public class ProjectAdministrationTest {
     // There are 7 modules
     assertThat(count("events where category='Version'")).as("Different number of events").isEqualTo(7);
 
-    Selenese selenese = Selenese
-      .builder()
+    Selenese selenese = Selenese.builder()
       .setHtmlTestsInClasspath("modify_version_of_multimodule_project",
         "/administration/suite/ProjectAdministrationTest/project-administration/multimodule-project-modify-version.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
 
     assertThat(count("events where category='Version'")).as("Different number of events").isEqualTo(14);
 
-    selenese = Selenese
-      .builder()
+    selenese = Selenese.builder()
       .setHtmlTestsInClasspath("delete_version_of_multimodule_project",
         "/administration/suite/ProjectAdministrationTest/project-administration/multimodule-project-delete-version.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
 
     assertThat(count("events where category='Version'")).as("Different number of events").isEqualTo(7);
   }
@@ -174,11 +174,11 @@ public class ProjectAdministrationTest {
     qgClient.updateCondition(UpdateCondition.create(lowThresholds.id()).metricKey("lines").operator("GT").warningThreshold("5000").errorThreshold("5000"));
     scanSampleWithDate("2012-01-02");
 
-    Selenese selenese = Selenese
-      .builder()
+    Selenese selenese = Selenese.builder()
       .setHtmlTestsInClasspath("display-alerts-history-page",
         "/administration/suite/ProjectAdministrationTest/display-alerts-history-page/should-display-alerts-correctly-history-page.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
 
     qgClient.unsetDefault();
@@ -202,12 +202,11 @@ public class ProjectAdministrationTest {
     // Red alert because lines number has not changed since previous analysis
     scanSample();
 
-    Selenese selenese = Selenese
-      .builder()
+    Selenese selenese = Selenese.builder()
       .setHtmlTestsInClasspath("display-period-alerts",
         "/administration/suite/ProjectAdministrationTest/display-alerts/should-display-period-alerts-correctly.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
 
     qgClient.unsetDefault();
     qgClient.destroy(qGate.id());
@@ -226,7 +225,7 @@ public class ProjectAdministrationTest {
 
       "/administration/suite/ProjectAdministrationTest/project-settings/only-on-project-settings.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
 
     assertThat(orchestrator.getServer().getAdminWsClient().find(PropertyQuery.createForResource("sonar.skippedModules", "sample")).getValue())
       .isEqualTo("my-excluded-module");
@@ -240,14 +239,14 @@ public class ProjectAdministrationTest {
     SonarRunner build = SonarRunner.create(projectDir("shared/xoo-multi-modules-sample"));
     orchestrator.executeBuild(build);
 
-    Selenese selenese = Selenese
-      .builder()
+    Selenese selenese = Selenese.builder()
       .setHtmlTestsInClasspath("project-bulk-update-keys",
         "/administration/suite/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-duplicate-keys.html",
         "/administration/suite/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-input.html",
         "/administration/suite/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-match.html",
         "/administration/suite/ProjectAdministrationTest/project-update-keys/bulk-update-success.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
@@ -259,12 +258,12 @@ public class ProjectAdministrationTest {
     SonarRunner build = SonarRunner.create(projectDir("shared/xoo-multi-modules-sample"));
     orchestrator.executeBuild(build);
 
-    Selenese selenese = Selenese
-      .builder()
+    Selenese selenese = Selenese.builder()
       .setHtmlTestsInClasspath("project-fine-grained-update-keys",
         "/administration/suite/ProjectAdministrationTest/project-update-keys/fine-grained-update-impossible.html",
         "/administration/suite/ProjectAdministrationTest/project-update-keys/fine-grained-update-success.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
@@ -279,7 +278,7 @@ public class ProjectAdministrationTest {
       // SONAR-3425
       "/administration/suite/ProjectAdministrationTest/module-settings/display-module-settings.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   private void scanSample() {
index 0dc2573cc7f3cacf4eccf0822b5df10ba21e0029..9d12033afc083d150fbdcfa8d6c6d7c5447fa509 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.wsclient.services.PropertyQuery;
 import org.sonar.wsclient.services.PropertyUpdateQuery;
+import selenium.SeleneseTest;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -42,6 +43,7 @@ public class PropertySetsTest {
       "/administration/suite/PropertySetsTest/property-sets/reference.html",
       "/administration/suite/PropertySetsTest/property-sets/all_types.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
 
     // SSF-25 Check that the password has well be setted as now it does not appears in the html source code
@@ -66,18 +68,19 @@ public class PropertySetsTest {
 
   @Test
   public void should_support_property_sets_with_auto_generated_keys() {
-    orchestrator.executeSelenese(Selenese.builder().setHtmlTestsInClasspath("create-auto-generated",
+    new SeleneseTest(
+      Selenese.builder().setHtmlTestsInClasspath("create-auto-generated",
       "/administration/suite/PropertySetsTest/auto-generated/create.html"
-      ).build());
+      ).build()).runOn(orchestrator);
 
     String[] keys = getProperty("sonar.autogenerated").split("[,]");
     assertThat(getProperty("sonar.autogenerated." + keys[0] + ".value")).isEqualTo("FIRST");
     assertThat(getProperty("sonar.autogenerated." + keys[1] + ".value")).isEqualTo("SECOND");
     assertThat(getProperty("sonar.autogenerated." + keys[2] + ".value")).isEqualTo("THIRD");
 
-    orchestrator.executeSelenese(Selenese.builder().setHtmlTestsInClasspath("update-auto-generated",
+    new SeleneseTest(Selenese.builder().setHtmlTestsInClasspath("update-auto-generated",
       "/administration/suite/PropertySetsTest/auto-generated/update.html"
-      ).build());
+      ).build()).runOn(orchestrator);
 
     keys = getProperty("sonar.autogenerated").split("[,]");
     assertThat(getProperty("sonar.autogenerated." + keys[0] + ".value")).isEqualTo("FIRST");
index e54541e5f2845c105a2bd7a300d2caa0a1d1f413..bdaa258983efb3dddb3c106a1963e7f079eb5126 100644 (file)
@@ -26,6 +26,7 @@ import com.sonar.orchestrator.selenium.Selenese;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.wsclient.services.PropertyQuery;
+import selenium.SeleneseTest;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static util.ItUtils.projectDir;
@@ -45,7 +46,7 @@ public class SubCategoriesTest {
       // SONAR-4495
       "/administration/suite/SubCategoriesTest/subcategories/global-subcategories-no-default.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
     assertThat(getProperty("prop3", null)).isEqualTo("myValue");
   }
 
@@ -61,7 +62,7 @@ public class SubCategoriesTest {
       // SONAR-4495
       "/administration/suite/SubCategoriesTest/subcategories/project-subcategories-no-default.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
     assertThat(getProperty("prop3", "sample")).isEqualTo("myValue2");
   }
 
index b0e9dc4d820de780cb14c06a7b0a107af99e25ad..14a9ec84f4dd4b0b1df40c6dfea83dd1ea9b9fa0 100644 (file)
@@ -53,6 +53,7 @@ public class I18nTest {
       "/ui/i18n/french-pack.html",
       "/ui/i18n/locale-with-france-country.html",
       "/ui/i18n/locale-with-swiss-country.html").build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
index bae341a805a269c213daf1e300182710699531a7..b699beaba3baeddfe40395520ef45d366c5b5d30 100644 (file)
@@ -63,6 +63,7 @@ public class MeasureFiltersTest {
       "/measure/suite/measure_filters/search-by-name.html",
       "/measure/suite/measure_filters/empty_filter.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
@@ -75,6 +76,7 @@ public class MeasureFiltersTest {
       "/measure/suite/measure_filters/list_sort_by_descending_name.html",
       "/measure/suite/measure_filters/list_sort_by_ncloc.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
@@ -89,6 +91,7 @@ public class MeasureFiltersTest {
         // SONAR-4469
         "/measure/suite/measure_filters/should-unshare-filter-remove-other-filters-favourite.html"
         ).build();
+      // Use the old runner because it fails with the new Selenium runner
       orchestrator.executeSelenese(selenese);
 
     } finally {
@@ -105,6 +108,7 @@ public class MeasureFiltersTest {
     createUser(user, "User Measure Filters without sharing permission");
 
     try {
+      // Use the old runner because it fails with the new Selenium runner
       orchestrator.executeSelenese(Selenese.builder().setHtmlTestsInClasspath("should_not_share_filter_when_user_have_no_sharing_permissions",
         "/measure/suite/measure_filters/should-not-share-filter-when-user-have-no-sharing-permissions.html"
         ).build());
@@ -119,6 +123,7 @@ public class MeasureFiltersTest {
       "/measure/suite/measure_filters/copy_measure_filter.html",
       "/measure/suite/measure_filters/copy_uniqueness_of_name.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
@@ -127,6 +132,7 @@ public class MeasureFiltersTest {
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("manage_measure_filters",
       "/measure/suite/measure_filters/save_with_special_characters.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
@@ -137,6 +143,7 @@ public class MeasureFiltersTest {
       "/measure/suite/measure_filters/list_widget_sort.html",
       "/measure/suite/measure_filters/list_widget_warning_if_missing_filter.html"
       ).build();
+    // Use the old runner because it fails with the new Selenium runner
     orchestrator.executeSelenese(selenese);
   }
 
index 506c6493bf02db06398114cee175f1b976a0f5e9..aa986112501c7ae20ba2cc35af2f5c3f19abb97a 100644 (file)
@@ -13,6 +13,7 @@ import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
+import selenium.SeleneseTest;
 
 public class ManualRulesTest {
 
@@ -37,7 +38,7 @@ public class ManualRulesTest {
       .setHtmlTestsInClasspath("manual-rules",
         "/issue/suite/ManualRulesTest/create_edit_delete_manual_rule.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   protected static void deleteManualRules(){
index 70467aeb4d82b13dd47e869f8ac735b91183bab3..c2f8450d7d2a923d48f36a03e5995796a7d52b5e 100644 (file)
@@ -24,6 +24,7 @@ import org.sonar.wsclient.services.Resource;
 import org.sonar.wsclient.services.ResourceQuery;
 import org.subethamail.wiser.Wiser;
 import org.subethamail.wiser.WiserMessage;
+import selenium.SeleneseTest;
 import util.ItUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -64,7 +65,7 @@ public class QualityGateNotificationTest {
         .setHtmlTestsInClasspath("notifications",
           "/qualitygate/notifications/email_configuration.html",
           "/qualitygate/notifications/activate_notification_channels.html").build();
-      orchestrator.executeSelenese(selenese);
+      new SeleneseTest(selenese).runOn(orchestrator);
 
       // Create quality gate with conditions on variations
       QualityGate simple = qgClient().create("SimpleWithDifferential");
diff --git a/it/it-tests/src/test/java/selenium/Browser.java b/it/it-tests/src/test/java/selenium/Browser.java
new file mode 100644 (file)
index 0000000..2a6e02e
--- /dev/null
@@ -0,0 +1,18 @@
+package selenium;
+
+import org.openqa.selenium.firefox.FirefoxDriver;
+
+public enum Browser {
+  FIREFOX;
+
+  private final ThreadLocal<SeleniumDriver> perThreadDriver = new ThreadLocal<SeleniumDriver>() {
+    @Override
+    protected SeleniumDriver initialValue() {
+      return ThreadSafeDriver.makeThreadSafe(new FirefoxDriver());
+    }
+  };
+
+  public SeleniumDriver getDriverForThread() {
+    return perThreadDriver.get();
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/ByCssSelectorOrByNameOrById.java b/it/it-tests/src/test/java/selenium/ByCssSelectorOrByNameOrById.java
new file mode 100644 (file)
index 0000000..7a4d8dd
--- /dev/null
@@ -0,0 +1,89 @@
+package selenium;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.internal.FindsByCssSelector;
+import org.openqa.selenium.internal.FindsById;
+import org.openqa.selenium.internal.FindsByName;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class ByCssSelectorOrByNameOrById extends By implements Serializable {
+  private static final long serialVersionUID = -3910258723099459239L;
+
+  private final String selector;
+
+  public ByCssSelectorOrByNameOrById(String selector) {
+    this.selector = selector;
+  }
+
+  @Override
+  public WebElement findElement(SearchContext context) {
+    WebElement element;
+
+    if (validCssSelector(selector)) {
+      element = ((FindsByCssSelector) context).findElementByCssSelector(quoteCss(selector));
+      if (element != null) {
+        return element;
+      }
+    }
+
+    element = ((FindsByName) context).findElementByName(selector);
+    if (element != null) {
+      return element;
+    }
+
+    element = ((FindsById) context).findElementById(selector);
+    if (element != null) {
+      return element;
+    }
+
+    return null;
+  }
+
+  @Override
+  public List<WebElement> findElements(SearchContext context) {
+    List<WebElement> elements;
+
+    if (validCssSelector(selector)) {
+      elements = ((FindsByCssSelector) context).findElementsByCssSelector(quoteCss(selector));
+      if ((elements != null) && (!elements.isEmpty())) {
+        return elements;
+      }
+    }
+
+    elements = ((FindsByName) context).findElementsByName(selector);
+    if ((elements != null) && (!elements.isEmpty())) {
+      return elements;
+    }
+
+    elements = ((FindsById) context).findElementsById(selector);
+    if ((elements != null) && (!elements.isEmpty())) {
+      return elements;
+    }
+
+    return Collections.emptyList();
+  }
+
+  protected boolean validCssSelector(String selector) {
+    return !selector.endsWith("[]");
+  }
+
+  protected String quoteCss(String selector) {
+    if (selector.startsWith(".")) {
+      return selector;
+    }
+    if (selector.startsWith("#")) {
+      return selector.replaceAll("(\\w)[.]", "$1\\\\.");
+    }
+    return selector;
+  }
+
+  @Override
+  public String toString() {
+    return selector;
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/Consumer.java b/it/it-tests/src/test/java/selenium/Consumer.java
new file mode 100644 (file)
index 0000000..f6dfad9
--- /dev/null
@@ -0,0 +1,5 @@
+package selenium;
+
+public interface Consumer<T> {
+  void accept(T t);
+}
diff --git a/it/it-tests/src/test/java/selenium/ElementFilter.java b/it/it-tests/src/test/java/selenium/ElementFilter.java
new file mode 100644 (file)
index 0000000..2816a3a
--- /dev/null
@@ -0,0 +1,50 @@
+package selenium;
+
+import com.google.common.base.Function;
+import org.openqa.selenium.WebElement;
+
+import java.util.Collection;
+
+class ElementFilter {
+  private static final ElementFilter ANY = new ElementFilter("", new Function<Collection<WebElement>, Collection<WebElement>>() {
+    @Override
+    public Collection<WebElement> apply(Collection<WebElement> input) {
+      return input;
+    }
+  });
+
+  private final String description;
+  private final Function<Collection<WebElement>, Collection<WebElement>> filter;
+
+  ElementFilter(String description, Function<Collection<WebElement>, Collection<WebElement>> filter) {
+    this.description = description;
+    this.filter = filter;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public Function<Collection<WebElement>, Collection<WebElement>> getFilter() {
+    return filter;
+  }
+
+  public static ElementFilter any() {
+    return ANY;
+  }
+
+  public ElementFilter and(final ElementFilter second) {
+    if (ANY == this) {
+      return second;
+    }
+    if (ANY == second) {
+      return this;
+    }
+    return new ElementFilter(description + ',' + second.description, new Function<Collection<WebElement>, Collection<WebElement>>() {
+      @Override
+      public Collection<WebElement> apply(Collection<WebElement> stream) {
+        return second.filter.apply(filter.apply(stream));
+      }
+    });
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/Failure.java b/it/it-tests/src/test/java/selenium/Failure.java
new file mode 100644 (file)
index 0000000..c779490
--- /dev/null
@@ -0,0 +1,30 @@
+package selenium;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class Failure {
+  private static final String PREFIX = Failure.class.getPackage().getName() + ".";
+
+  private Failure() {
+    // Static class
+  }
+
+  public static AssertionError create(String message) {
+    AssertionError error = new AssertionError(message);
+    removeSimpleleniumFromStackTrace(error);
+    return error;
+  }
+
+  private static void removeSimpleleniumFromStackTrace(Throwable throwable) {
+    List<StackTraceElement> filtered = new ArrayList<>();
+
+    for (StackTraceElement element : throwable.getStackTrace()) {
+      if (!element.getClassName().contains(PREFIX)) {
+        filtered.add(element);
+      }
+    }
+
+    throwable.setStackTrace(filtered.toArray(new StackTraceElement[filtered.size()]));
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/LazyDomElement.java b/it/it-tests/src/test/java/selenium/LazyDomElement.java
new file mode 100644 (file)
index 0000000..0c0d18d
--- /dev/null
@@ -0,0 +1,143 @@
+package selenium;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.FluentIterable;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.Select;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+class LazyDomElement {
+  private final SeleniumDriver driver;
+  private final By selector;
+  private final ElementFilter filter;
+  private final Retry retry;
+
+  LazyDomElement(SeleniumDriver driver, By selector) {
+    this(driver, selector, Retry._30_SECONDS);
+  }
+
+  LazyDomElement(SeleniumDriver driver, By selector, Retry retry) {
+    this(driver, selector, ElementFilter.any(), retry);
+  }
+
+  private LazyDomElement(SeleniumDriver driver, By selector, ElementFilter filter, Retry retry) {
+    this.driver = driver;
+    this.selector = selector;
+    this.filter = filter;
+    this.retry = retry;
+  }
+
+  public LazyDomElement withText(final String text) {
+    String fullDescription = " with text [" + text + "]";
+
+    return with(new ElementFilter(fullDescription, new Function<Collection<WebElement>, Collection<WebElement>>() {
+      @Override
+      public Collection<WebElement> apply(Collection<WebElement> stream) {
+        return FluentIterable.from(stream).filter(new Predicate<WebElement>() {
+          @Override
+          public boolean apply(@Nullable WebElement element) {
+//            return Objects.equals(element.getText(), text);
+            return element.getText().contains(text);
+          }
+        }).toList();
+      }
+    }));
+  }
+
+  public LazyShould should() {
+    return new LazyShould(this, Retry._5_SECONDS, true);
+  }
+
+  public void fill(final CharSequence text) {
+    execute("fill(" + text + ")", new Consumer<WebElement>() {
+      @Override
+      public void accept(WebElement element) {
+        element.clear();
+        element.sendKeys(text);
+      }
+    });
+  }
+
+  public void select(final String text) {
+    executeSelect("select(" + text + ")", new Consumer<Select>() {
+      @Override
+      public void accept(Select select) {
+        select.selectByVisibleText(text);
+      }
+    });
+  }
+
+  public void executeSelect(String description, final Consumer<Select> selectOnElement) {
+    execute(description, new Consumer<WebElement>() {
+      @Override
+      public void accept(WebElement element) {
+        selectOnElement.accept(new Select(element));
+      }
+    });
+  }
+
+  public void click() {
+    execute("click", new Consumer<WebElement>() {
+      @Override
+      public void accept(WebElement element) {
+        element.click();
+      }
+    });
+  }
+
+  public void check() {
+    execute("check", new Consumer<WebElement>() {
+      @Override
+      public void accept(WebElement element) {
+        if (!element.isSelected()) {
+          element.click();
+        }
+      }
+    });
+  }
+
+  public void execute(Consumer<WebElement> action) {
+    execute("execute(" + action + ")", action);
+  }
+
+  private LazyDomElement with(ElementFilter filter) {
+    return new LazyDomElement(driver, selector, this.filter.and(filter), retry);
+  }
+
+  private void execute(String message, Consumer<WebElement> action) {
+    System.out.println(" - " + Text.toString(selector) + filter.getDescription() + "." + message);
+
+    Supplier<Optional<WebElement>> findOne = new Supplier<Optional<WebElement>>() {
+      @Override
+      public Optional<WebElement> get() {
+        List<WebElement> elements = stream();
+        if (elements.isEmpty()) {
+          return Optional.empty();
+        }
+        return Optional.of(elements.get(0));
+      }
+    };
+
+    try {
+      retry.execute(findOne, action);
+    } catch (NoSuchElementException e) {
+      throw new AssertionError("Element not found: " + Text.toString(selector));
+    }
+  }
+
+  List<WebElement> stream() {
+    return FluentIterable.from(filter.getFilter().apply(driver.findElements(selector))).toList();
+  }
+
+  @Override
+  public String toString() {
+    return Text.toString(selector) + filter.getDescription();
+  }
+}
\ No newline at end of file
diff --git a/it/it-tests/src/test/java/selenium/LazyShould.java b/it/it-tests/src/test/java/selenium/LazyShould.java
new file mode 100644 (file)
index 0000000..71891df
--- /dev/null
@@ -0,0 +1,171 @@
+package selenium;
+
+import com.google.common.base.*;
+import com.google.common.collect.FluentIterable;
+import org.openqa.selenium.WebElement;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.regex.Pattern;
+
+import static selenium.Text.plural;
+import static selenium.WebElementHelper.text;
+
+class LazyShould {
+  private final LazyDomElement element;
+  private final Retry retry;
+  private final boolean ok;
+
+  LazyShould(LazyDomElement element, Retry retry, boolean ok) {
+    this.element = element;
+    this.retry = retry;
+    this.ok = ok;
+  }
+
+  public LazyShould beDisplayed() {
+    return verify(
+      isOrNot("displayed"),
+      new Predicate<List<WebElement>>() {
+        @Override
+        public boolean apply(List<WebElement> elements) {
+          return !elements.isEmpty() && FluentIterable.from(elements).allMatch(new Predicate<WebElement>() {
+            @Override
+            public boolean apply(WebElement element) {
+              return element.isDisplayed();
+            }
+          });
+        }
+      },
+      new Function<List<WebElement>, String>() {
+        @Override
+        public String apply(List<WebElement> elements) {
+          return "It is " + statuses(elements, new Function<WebElement, String>() {
+            @Override
+            public String apply(WebElement element) {
+              return displayedStatus(element);
+            }
+          });
+        }
+      });
+  }
+
+  public LazyShould match(final Pattern regexp) {
+    return verify(
+      doesOrNot("match") + " (" + regexp.pattern() + ")",
+      new Predicate<List<WebElement>>() {
+        @Override
+        public boolean apply(List<WebElement> elements) {
+          return !elements.isEmpty() && FluentIterable.from(elements).anyMatch(new Predicate<WebElement>() {
+            @Override
+            public boolean apply(WebElement element) {
+              return regexp.matcher(text(element)).matches();
+            }
+          });
+        }
+      },
+      new Function<List<WebElement>, String>() {
+        @Override
+        public String apply(List<WebElement> elements) {
+          return "It contains " + statuses(elements, new Function<WebElement, String>() {
+            @Nullable
+            @Override
+            public String apply(@Nullable WebElement element) {
+              return text(element);
+            }
+          });
+        }
+      });
+  }
+
+  public LazyShould contain(final String text) {
+    return verify(
+      doesOrNot("contain(") + text + ")",
+      new Predicate<List<WebElement>>() {
+        @Override
+        public boolean apply(List<WebElement> elements) {
+          return FluentIterable.from(elements).anyMatch(new Predicate<WebElement>() {
+            @Override
+            public boolean apply(@Nullable WebElement element) {
+              if (text.startsWith("exact:")) {
+                return text(element).equals(text.substring(6));
+              }
+              return text(element).contains(text);
+            }
+          });
+        }
+      },
+      new Function<List<WebElement>, String>() {
+        @Override
+        public String apply(List<WebElement> elements) {
+          return "It contains " + statuses(elements, new Function<WebElement, String>() {
+            @Override
+            public String apply(WebElement element) {
+              return text(element);
+            }
+          });
+        }
+      });
+  }
+
+  public LazyShould exist() {
+    return verify(
+      doesOrNot("exist"),
+      new Predicate<List<WebElement>>() {
+        @Override
+        public boolean apply(List<WebElement> elements) {
+          return !elements.isEmpty();
+        }
+      },
+      new Function<List<WebElement>, String>() {
+        @Override
+        public String apply(List<WebElement> elements) {
+          return "It contains " + plural(elements.size(), "element");
+        }
+      });
+  }
+
+  private static String displayedStatus(WebElement element) {
+    return element.isDisplayed() ? "displayed" : "not displayed";
+  }
+
+  private LazyShould verify(String message, Predicate<List<WebElement>> predicate, Function<List<WebElement>, String> toErrorMessage) {
+    String verification = "verify that " + element + " " + message;
+    System.out.println("   -> " + verification);
+
+    try {
+      if (!retry.verify(new Supplier<List<WebElement>>() {
+        @Override
+        public List<WebElement> get() {
+          return LazyShould.this.findElements();
+        }
+      }, ok ? predicate : Predicates.not(predicate))) {
+        throw Failure.create("Failed to " + verification + ". " + toErrorMessage.apply(findElements()));
+      }
+    } catch (NoSuchElementException e) {
+      throw Failure.create("Element not found. Failed to " + verification);
+    }
+
+    return ok ? this : not();
+  }
+
+  private List<WebElement> findElements() {
+    return element.stream();
+  }
+
+  private static String statuses(List<WebElement> elements, Function<WebElement, String> toStatus) {
+    return "(" + FluentIterable.from(elements).transform(toStatus).join(Joiner.on(";")) + ")";
+  }
+
+  public LazyShould not() {
+    return new LazyShould(element, retry, !ok);
+  }
+
+  private String doesOrNot(String verb) {
+    return Text.doesOrNot(!ok, verb);
+  }
+
+  private String isOrNot(String state) {
+    return Text.isOrNot(!ok, state);
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/Optional.java b/it/it-tests/src/test/java/selenium/Optional.java
new file mode 100644 (file)
index 0000000..3a8ced0
--- /dev/null
@@ -0,0 +1,38 @@
+package selenium;
+
+import java.util.NoSuchElementException;
+
+public final class Optional<T> {
+  private static final Optional<?> EMPTY = new Optional<>();
+
+  private final T value;
+
+  private Optional() {
+    this.value = null;
+  }
+
+  public static <T> Optional<T> empty() {
+    @SuppressWarnings("unchecked")
+    Optional<T> t = (Optional<T>) EMPTY;
+    return t;
+  }
+
+  private Optional(T value) {
+    this.value = value;
+  }
+
+  public static <T> Optional<T> of(T value) {
+    return new Optional<>(value);
+  }
+
+  public T get() {
+    if (value == null) {
+      throw new NoSuchElementException("No value present");
+    }
+    return value;
+  }
+
+  public boolean isPresent() {
+    return value != null;
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/Retry.java b/it/it-tests/src/test/java/selenium/Retry.java
new file mode 100644 (file)
index 0000000..b559f62
--- /dev/null
@@ -0,0 +1,135 @@
+package selenium;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import org.openqa.selenium.InvalidElementStateException;
+import org.openqa.selenium.NotFoundException;
+import org.openqa.selenium.StaleElementReferenceException;
+import org.openqa.selenium.WebDriverException;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+class Retry {
+  public static final Retry _30_SECONDS = new Retry(30, SECONDS);
+  public static final Retry _5_SECONDS = new Retry(5, SECONDS);
+
+  private final long timeoutInMs;
+
+  Retry(long duration, TimeUnit timeUnit) {
+    this.timeoutInMs = timeUnit.toMillis(duration);
+  }
+
+  <T> void execute(Supplier<Optional<T>> target, Consumer<T> action) {
+    WebDriverException lastError = null;
+
+    boolean retried = false;
+
+    long start = System.currentTimeMillis();
+    while ((System.currentTimeMillis() - start) < timeoutInMs) {
+      try {
+        Optional<T> targetElement = target.get();
+        if (targetElement.isPresent()) {
+          action.accept(targetElement.get());
+          if (retried) {
+            System.out.println();
+          }
+          return;
+        }
+      } catch (StaleElementReferenceException e) {
+        // ignore
+      } catch (WebDriverException e) {
+        lastError = e;
+      }
+
+      retried = true;
+      System.out.print(".");
+    }
+
+    if (retried) {
+      System.out.println();
+    }
+
+    if (lastError != null) {
+      throw lastError;
+    }
+    throw new NoSuchElementException("Not found");
+  }
+
+  <T> void execute(Runnable action) {
+    WebDriverException lastError = null;
+
+    boolean retried = false;
+
+    long start = System.currentTimeMillis();
+    while ((System.currentTimeMillis() - start) < timeoutInMs) {
+      try {
+        action.run();
+        if (retried) {
+          System.out.println();
+        }
+        return;
+      } catch (StaleElementReferenceException e) {
+        // ignore
+      } catch (WebDriverException e) {
+        lastError = e;
+      }
+
+      retried = true;
+      System.out.print(".");
+    }
+
+    if (retried) {
+      System.out.println();
+    }
+
+    if (lastError != null) {
+      throw lastError;
+    }
+    throw new NoSuchElementException("Not found");
+  }
+
+  <T> boolean verify(Supplier<T> targetSupplier, Predicate<T> predicate) throws NoSuchElementException {
+    Error error = Error.KO;
+
+    boolean retried = false;
+
+    long start = System.currentTimeMillis();
+    while ((System.currentTimeMillis() - start) < timeoutInMs) {
+      try {
+        if (predicate.apply(targetSupplier.get())) {
+          if (retried) {
+            System.out.println();
+          }
+          return true;
+        }
+
+        error = Error.KO;
+      } catch (InvalidElementStateException e) {
+        error = Error.KO;
+      } catch (NotFoundException e) {
+        error = Error.NOT_FOUND;
+      } catch (StaleElementReferenceException e) {
+        // ignore
+      }
+
+      retried = true;
+      System.out.print(".");
+    }
+
+    if (retried) {
+      System.out.println();
+    }
+
+    if (error == Error.NOT_FOUND) {
+      throw new NoSuchElementException("Not found");
+    }
+    return false;
+  }
+
+  enum Error {
+    NOT_FOUND, KO
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/SeleneseTest.java b/it/it-tests/src/test/java/selenium/SeleneseTest.java
new file mode 100644 (file)
index 0000000..ecd7167
--- /dev/null
@@ -0,0 +1,349 @@
+package selenium;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.selenium.Selenese;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.openqa.selenium.Alert;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SeleneseTest {
+  private final Selenese suite;
+
+  private Map<String, String> variables;
+  private String baseUrl;
+  private SeleniumDriver driver;
+
+  public SeleneseTest(Selenese suite) {
+    this.suite = suite;
+  }
+
+  public void runOn(Orchestrator orchestrator) {
+    this.variables = new HashMap<>();
+    this.baseUrl = orchestrator.getServer().getUrl();
+    this.driver = Browser.FIREFOX.getDriverForThread();
+
+    for (File file : suite.getHtmlTests()) {
+      System.out.println();
+      System.out.println("============ " + file.getName() + " ============");
+      Document doc = parse(file);
+      for (Element table : doc.getElementsByTag("table")) {
+        for (Element tbody : table.getElementsByTag("tbody")) {
+          for (Element tr : tbody.getElementsByTag("tr")) {
+            String action = tr.child(0).text();
+            String param1 = tr.child(1).text();
+            String param2 = tr.child(2).text();
+
+            action(action, param1, param2);
+          }
+        }
+      }
+    }
+  }
+
+  private Document parse(File file) {
+    try {
+      return Jsoup.parse(file, UTF_8.name());
+    } catch (IOException e) {
+      throw new RuntimeException("Unable to parse file: " + file, e);
+    }
+  }
+
+  public SeleneseTest action(String action, String param1, String param2) {
+    switch (action) {
+      case "open":
+        open(param1, param2);
+        return this;
+      case "type":
+        type(param1, param2);
+        return this;
+      case "select":
+        select(param1, param2);
+        return this;
+      case "clickAndWait":
+      case "click":
+        click(param1, param2);
+        return this;
+      case "check":
+        check(param1, param2);
+        return this;
+      case "selectFrame":
+        selectFrame(param1, param2);
+        return this;
+      case "assertElementPresent":
+        assertElementPresent(param1, param2);
+        return this;
+      case "assertElementNotPresent":
+        assertElementNotPresent(param1, param2);
+        return this;
+      case "storeText":
+        storeText(param1, param2);
+        return this;
+      case "storeEval":
+        storeEval(param1, param2);
+        return this;
+      case "assertText":
+      case "waitForText":
+        assertText(param1, param2);
+        return this;
+      case "assertNotText":
+      case "waitForNotText":
+        assertNotText(param1, param2);
+        return this;
+      case "assertTextPresent":
+        assertTextPresent(param1, param2);
+      case "assertTextNotPresent":
+        assertTextNotPresent(param1, param2);
+        return this;
+      case "assertLocation":
+        assertLocation(param1, param2);
+        return this;
+      case "waitForElementPresent":
+        waitForElementPresent(param1, param2);
+        return this;
+      case "waitForVisible":
+        waitForVisible(param1, param2);
+        return this;
+      case "assertValue":
+      case "waitForValue":
+      case "verifyValue":
+        assertInputValue(param1, param2);
+        return this;
+      case "assertConfirmation":
+        confirm(param1, param2);
+        return this;
+      case "setTimeout":
+        // Ignore
+        return this;
+    }
+
+    throw new IllegalArgumentException("Unsupported action: " + action);
+  }
+
+  private void goTo(String url) {
+    requireNonNull(url, "The url cannot be null");
+
+    URI uri = URI.create(url.replace(" ", "%20"));
+    if (!uri.isAbsolute()) {
+      url = baseUrl + url;
+    }
+
+    System.out.println("goTo " + url);
+    driver.get(url);
+    System.out.println(" - current url " + driver.getCurrentUrl());
+  }
+
+  private void open(String url, String ignored) {
+    if (url.startsWith("/sonar/")) {
+      goTo(url.substring(6));
+    } else {
+      goTo(url);
+    }
+  }
+
+  private LazyDomElement find(String selector) {
+    selector = replacePlaceholders(selector);
+
+    if (selector.startsWith("link=")) {
+      return find("a").withText(selector.substring(5));
+    }
+
+    By by;
+    if (selector.startsWith("//")) {
+      by = new By.ByXPath(selector);
+    } else if (selector.startsWith("xpath=")) {
+      by = new By.ByXPath(selector.substring(6));
+    } else if (selector.startsWith("id=")) {
+      by = new By.ById(selector.substring(3));
+    } else {
+      by = new ByCssSelectorOrByNameOrById(cleanUp(selector));
+    }
+
+    return new LazyDomElement(driver, by);
+  }
+
+  private void click(String selector, String ignored) {
+    find(selector).click();
+  }
+
+  private void check(String selector, String ignored) {
+    find(selector).check();
+  }
+
+  private void selectFrame(final String id, String ignored) {
+    if ("relative=parent".equals(id)) {
+      //driver().switchTo().parentFrame();
+    } else {
+      System.out.println(" - selectFrame(" + id + ")");
+
+      Retry._5_SECONDS.execute(new Runnable() {
+        @Override
+        public void run() {
+          driver.switchTo().frame(id);
+        }
+      });
+    }
+  }
+
+  private String cleanUp(String selector) {
+    if (selector.startsWith("name=")) {
+      return selector.substring(5);
+    }
+    if (selector.startsWith("css=")) {
+      return selector.substring(4);
+    }
+    if (selector.startsWith("id=")) {
+      return "#" + selector.substring(3);
+    }
+    if (selector.startsWith("class=")) {
+      return "." + selector.substring(6);
+    }
+    return selector;
+  }
+
+  private void type(String selector, String text) {
+    find(selector).fill(replacePlaceholders(text));
+  }
+
+  private void select(String selector, String text) {
+    if (text.startsWith("label=")) {
+      find(selector).select(text.substring(6));
+    } else {
+      find(selector).select(text);
+    }
+  }
+
+  private void assertElementPresent(String selector, String ignored) {
+    find(selector).should().beDisplayed();
+  }
+
+  private void assertElementNotPresent(String selector, String ignored) {
+    find(selector).should().not().beDisplayed();
+  }
+
+  private void storeText(String selector, String name) {
+    find(selector).execute(new ExtractVariable(name));
+  }
+
+  private void storeEval(String expression, String name) {
+    String value = driver.executeScript("return " + expression).toString();
+    variables.put(name, value);
+  }
+
+  private class ExtractVariable implements Consumer<WebElement> {
+    private final String name;
+
+    ExtractVariable(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public void accept(WebElement webElement) {
+      variables.put(name, webElement.getText());
+    }
+
+    public String toString() {
+      return "read value into " + name;
+    }
+  }
+
+  private void assertText(String selector, String pattern) {
+    pattern = replacePlaceholders(pattern);
+
+    if (pattern.startsWith("exact:")) {
+      String expectedText = pattern.substring(6);
+      find(selector).withText(expectedText).should().exist();
+      return;
+    }
+
+    if (pattern.startsWith("regexp:")) {
+      find(selector).should().match(Pattern.compile(pattern.substring(7)));
+      return;
+    }
+
+    find(selector).should().match(glob(pattern));
+  }
+
+  private void assertNotText(String selector, String pattern) {
+    pattern = replacePlaceholders(pattern);
+
+    if (pattern.startsWith("exact:")) {
+      String expectedText = pattern.substring(6);
+      find(selector).withText(expectedText).should().not().exist();
+      return;
+    }
+
+    if (pattern.startsWith("regexp:")) {
+      find(selector).should().not().match(Pattern.compile(pattern.substring(7)));
+      return;
+    }
+
+    find(selector).should().not().match(glob(pattern));
+  }
+
+  private Pattern glob(String pattern) {
+    String expectedGlob = pattern.replaceFirst("glob:", "");
+    expectedGlob = expectedGlob.replaceAll("([\\]\\[\\\\{\\}$\\(\\)\\|\\^\\+.])", "\\\\$1");
+    expectedGlob = expectedGlob.replaceAll("\\*", ".*");
+    expectedGlob = expectedGlob.replaceAll("\\?", ".");
+    return Pattern.compile(expectedGlob, Pattern.DOTALL);
+  }
+
+  private void assertTextPresent(String text, String ignored) {
+    find("html").should().contain(text);
+  }
+
+  private void assertTextNotPresent(String text, String ignored) {
+    find("html").should().not().contain(text);
+  }
+
+  private void waitForElementPresent(String selector, String ignored) {
+    find(selector).should().exist();
+  }
+
+  private void waitForVisible(String selector, String ignored) {
+    find(selector).should().beDisplayed();
+  }
+
+  private void assertInputValue(String selector, String text) {
+    find(selector).should().contain(text);
+  }
+
+  private void confirm(final String message, String ignored) {
+    System.out.println(" - confirm(" + message + ")");
+
+    Retry._5_SECONDS.execute(new Runnable() {
+      @Override
+      public void run() {
+        Alert alert = driver.switchTo().alert();
+        if (alert.getText().contains(message)) {
+          alert.accept();
+        }
+      }
+    });
+  }
+
+  private void assertLocation(String urlPattern, String ignored) {
+    assertThat(driver.getCurrentUrl()).matches(glob(urlPattern));
+  }
+
+  private String replacePlaceholders(String text) {
+    for (Map.Entry<String, String> entry : variables.entrySet()) {
+      text = text.replace("${" + entry.getKey() + "}", entry.getValue());
+    }
+    return text;
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/SeleniumDriver.java b/it/it-tests/src/test/java/selenium/SeleniumDriver.java
new file mode 100644 (file)
index 0000000..cd21566
--- /dev/null
@@ -0,0 +1,4 @@
+package selenium;
+
+public interface SeleniumDriver extends org.openqa.selenium.WebDriver, org.openqa.selenium.JavascriptExecutor, org.openqa.selenium.internal.FindsById, org.openqa.selenium.internal.FindsByClassName, org.openqa.selenium.internal.FindsByLinkText, org.openqa.selenium.internal.FindsByName, org.openqa.selenium.internal.FindsByCssSelector, org.openqa.selenium.internal.FindsByTagName, org.openqa.selenium.internal.FindsByXPath, org.openqa.selenium.interactions.HasInputDevices, org.openqa.selenium.HasCapabilities, org.openqa.selenium.TakesScreenshot {
+}
diff --git a/it/it-tests/src/test/java/selenium/Text.java b/it/it-tests/src/test/java/selenium/Text.java
new file mode 100644 (file)
index 0000000..76dfd43
--- /dev/null
@@ -0,0 +1,39 @@
+package selenium;
+
+import com.google.common.base.Joiner;
+import org.openqa.selenium.By;
+
+public abstract class Text {
+  private Text() {
+    // Static utility class
+  }
+
+  public static String doesOrNot(boolean not, String verb) {
+    if (!verb.contains(" ")) {
+      if (not) {
+        return "doesn't " + verb;
+      } else if (verb.endsWith("h")) {
+        return verb + "es";
+      } else {
+        return verb + "s";
+      }
+    }
+
+    String[] verbs = verb.split(" ");
+    verbs[0] = doesOrNot(not, verbs[0]);
+
+    return Joiner.on(" ").join(verbs);
+  }
+
+  public static String isOrNot(boolean not, String state) {
+    return (not ? "is not " : "is ") + state;
+  }
+
+  public static String plural(int n, String word) {
+    return (n + " " + word) + (n <= 1 ? "" : "s");
+  }
+
+  public static String toString(By selector) {
+    return selector.toString().replace("By.selector: ", "").replace("By.cssSelector: ", "");
+  }
+}
diff --git a/it/it-tests/src/test/java/selenium/ThreadSafeDriver.java b/it/it-tests/src/test/java/selenium/ThreadSafeDriver.java
new file mode 100644 (file)
index 0000000..96c16e2
--- /dev/null
@@ -0,0 +1,62 @@
+package selenium;
+
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.remote.UnreachableBrowserException;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+class ThreadSafeDriver {
+  private ThreadSafeDriver() {
+    // Static class
+  }
+
+  static SeleniumDriver makeThreadSafe(final RemoteWebDriver driver) {
+    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          driver.quit();
+        } catch (UnreachableBrowserException e) {
+          // Ignore. The browser was killed properly
+        }
+      }
+    }));
+
+    return (SeleniumDriver) Proxy.newProxyInstance(
+      Thread.currentThread().getContextClassLoader(),
+      findInterfaces(driver),
+      new InvocationHandler() {
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+          if (method.getName().equals("quit")) {
+            return null; // We don't want anybody to quit() our (per thread) driver
+          }
+
+          try {
+            return method.invoke(driver, args);
+          } catch (InvocationTargetException e) {
+            throw e.getCause();
+          }
+        }
+      });
+  }
+
+  private static Class[] findInterfaces(Object driver) {
+    Set<Class<?>> interfaces = new LinkedHashSet<>();
+
+    interfaces.add(SeleniumDriver.class);
+
+    for (Class<?> parent = driver.getClass(); parent != null; ) {
+      Collections.addAll(interfaces, parent.getInterfaces());
+      parent = parent.getSuperclass();
+    }
+
+    return interfaces.toArray(new Class[interfaces.size()]);
+  }
+}
\ No newline at end of file
diff --git a/it/it-tests/src/test/java/selenium/WebElementHelper.java b/it/it-tests/src/test/java/selenium/WebElementHelper.java
new file mode 100644 (file)
index 0000000..82935d7
--- /dev/null
@@ -0,0 +1,22 @@
+package selenium;
+
+import org.openqa.selenium.WebElement;
+
+class WebElementHelper {
+  WebElementHelper() {
+    // Static class
+  }
+
+  public static String text(WebElement element) {
+    String text = element.getText();
+    if (!"".equals(text)) {
+      return nullToEmpty(text);
+    }
+
+    return nullToEmpty(element.getAttribute("value"));
+  }
+
+  private static String nullToEmpty(String text) {
+    return (text == null) ? "" : text;
+  }
+}
index acaffab22eea700d09b0b2f7b086a5eadb250822..23ac3c27c34ca38874258427a5290eb7689ae5c6 100644 (file)
@@ -19,6 +19,7 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.wsclient.services.Server;
 import org.sonar.wsclient.services.ServerQuery;
+import selenium.SeleneseTest;
 import util.ItUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -70,7 +71,7 @@ public class ServerTest {
       // SONAR-3127 - hide passwords
       "/server/ServerTest/settings/hide-passwords.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   @Test
@@ -92,7 +93,7 @@ public class ServerTest {
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("property_relocation",
       "/server/ServerTest/settings/property_relocation.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   /**
index a0c042c9f1846adcdfd66604a6d879cef7e00c9d..617899891d09558e313efc9c8ad5131b8072f6e4 100644 (file)
@@ -22,6 +22,7 @@ import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.wsclient.services.Server;
 import org.sonar.wsclient.services.ServerQuery;
+import selenium.SeleneseTest;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
@@ -51,7 +52,7 @@ public class ServerAdministrationTest {
       // SONAR-4102
       "/server/ServerAdministrationTest/server_id/organisation_must_not_accept_special_chars.html",
       "/server/ServerAdministrationTest/server_id/valid_id.html").build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   @Test
@@ -59,7 +60,7 @@ public class ServerAdministrationTest {
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("server-administration",
       "/server/ServerAdministrationTest/server-administration/system_info.html"
       ).build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   /**
@@ -91,7 +92,7 @@ public class ServerAdministrationTest {
    * SONAR-5197
    */
   @Test
-  public void api_ws_shortcut() throws IOException {
+  public void api_ws_shortcut() throws Exception {
     HttpClient httpclient = new DefaultHttpClient();
     try {
       HttpGet get = new HttpGet(orchestrator.getServer().getUrl() + "/api");
index 725dbf7dddbe25f944ca3b132be811c472e2d2e0..57ab6c3b33f14664c7c97026bcf962a44048b1d4 100644 (file)
@@ -23,6 +23,7 @@ import org.junit.rules.ExpectedException;
 import org.sonar.wsclient.base.HttpException;
 import org.sonar.wsclient.services.PropertyDeleteQuery;
 import org.sonar.wsclient.services.PropertyUpdateQuery;
+import selenium.SeleneseTest;
 import util.ItUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -48,7 +49,7 @@ public class ServerTest {
     if (orchestrator.getConfiguration().getString("sonar.jdbc.dialect").equals("h2")) {
       Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("derby-warnings",
         "/server/ServerTest/derby-warning.html").build();
-      orchestrator.executeSelenese(selenese);
+      new SeleneseTest(selenese).runOn(orchestrator);
     }
   }
 
@@ -59,7 +60,7 @@ public class ServerTest {
   public void hide_jdbc_settings_to_non_admin() {
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("jdbc-settings",
       "/server/ServerTest/hide-jdbc-settings.html").build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   /**
@@ -125,7 +126,7 @@ public class ServerTest {
     // Access dashboard
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("url_ending_by_jsp",
       "/server/ServerTest/url_ending_by_jsp.html").build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   // SONAR-4404
@@ -133,7 +134,7 @@ public class ServerTest {
   public void should_get_settings_default_value() {
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("settings-default-value",
       "/server/ServerTest/settings-default-value.html").build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
 }
index 373f337b2ecf3344dbc63ed5af4833b372728c93..d42907bfb8535c36968b38076f7ab70abb4e1065 100644 (file)
@@ -13,6 +13,7 @@ import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.wsclient.services.Plugin;
 import org.sonar.wsclient.services.UpdateCenterQuery;
+import selenium.SeleneseTest;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static util.ItUtils.pluginArtifact;
@@ -42,7 +43,7 @@ public class UpdateCenterTest {
     Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("server-update-center",
       "/updatecenter/installed-plugins.html")
       .build();
-    orchestrator.executeSelenese(selenese);
+    new SeleneseTest(selenese).runOn(orchestrator);
   }
 
   private Plugin findPlugin(List<Plugin> plugins, String pluginKey) {
index 2c1a2f0ed60a7fc72e5eefe99be99c24bab487fd..58b94913711813cf8d4a8ef226484da264325a04 100644 (file)
@@ -23,7 +23,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head profile="http://selenium-ide.openqa.org/profiles/test-case">
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <title>project-modify-versions</title>
+  <title>multimodule-project-delete-version</title>
 </head>
 <body>
 <table cellpadding="1" cellspacing="1" border="1">
index aa3fb40c3c811d25cfc5a1d3660fc2a22d99ab5d..ef798b5cf36134fa89e0c11467de1e2c67c4382f 100644 (file)
@@ -23,7 +23,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head profile="http://selenium-ide.openqa.org/profiles/test-case">
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <title>project-modify-versions</title>
+  <title>multimodule-project-modify-version</title>
 </head>
 <body>
 <table cellpadding="1" cellspacing="1" border="1">
     <td>version_name_1</td>
     <td>RELEASE</td>
   </tr>
-  <tr>
-    <td>keyUp</td>
-    <td>version_name_1</td>
-    <td>a</td>
-  </tr>
   <tr>
     <td>clickAndWait</td>
     <td>save_version_1</td>
index 3d32118ce9384d9fa53ec7113581c09e9e690d4e..67b10d78c2fcf2e9dd2a5b9114be96f9c3e2212e 100644 (file)
@@ -39,7 +39,7 @@
         <td></td>
     </tr>
     <tr>
-        <td>verifyValue</td>
+        <td>assertValue</td>
         <td>smtp_host</td>
         <td>localhost</td>
     </tr>