<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>
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
+import selenium.SeleneseTest;
import static util.ItUtils.projectDir;
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);
}
/**
"/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) {
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;
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);
}
// 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);
}
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();
// 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());
"/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");
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);
}
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);
}
// SONAR-3425
"/administration/suite/ProjectAdministrationTest/module-settings/display-module-settings.html"
).build();
- orchestrator.executeSelenese(selenese);
+ new SeleneseTest(selenese).runOn(orchestrator);
}
private void scanSample() {
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;
"/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
@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");
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;
// 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");
}
// 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");
}
"/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);
}
"/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);
}
"/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);
}
// 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 {
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());
"/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);
}
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);
}
"/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);
}
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
+import selenium.SeleneseTest;
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(){
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;
.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");
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+package selenium;
+
+public interface Consumer<T> {
+ void accept(T t);
+}
--- /dev/null
+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));
+ }
+ });
+ }
+}
--- /dev/null
+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()]));
+ }
+}
--- /dev/null
+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
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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 {
+}
--- /dev/null
+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: ", "");
+ }
+}
--- /dev/null
+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
--- /dev/null
+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;
+ }
+}
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;
// SONAR-3127 - hide passwords
"/server/ServerTest/settings/hide-passwords.html"
).build();
- orchestrator.executeSelenese(selenese);
+ new SeleneseTest(selenese).runOn(orchestrator);
}
@Test
Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("property_relocation",
"/server/ServerTest/settings/property_relocation.html"
).build();
- orchestrator.executeSelenese(selenese);
+ new SeleneseTest(selenese).runOn(orchestrator);
}
/**
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;
// 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
Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("server-administration",
"/server/ServerAdministrationTest/server-administration/system_info.html"
).build();
- orchestrator.executeSelenese(selenese);
+ new SeleneseTest(selenese).runOn(orchestrator);
}
/**
* 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");
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;
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);
}
}
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);
}
/**
// 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
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);
}
}
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;
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) {
<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">
<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>
<td></td>
</tr>
<tr>
- <td>verifyValue</td>
+ <td>assertValue</td>
<td>smtp_host</td>
<td>localhost</td>
</tr>