diff --git a/hamster-selenium-component-mat/pom.xml b/hamster-selenium-component-mat/pom.xml
index 9ab17423..54451476 100644
--- a/hamster-selenium-component-mat/pom.xml
+++ b/hamster-selenium-component-mat/pom.xml
@@ -1,4 +1,28 @@
+
+
* div (current) -< span -< span -< li (target) @@ -71,7 +71,7 @@ public SourceBuilder anywhereRelative() { } /** - * Adds the prefix ".//tag" to search any depth child. For example: + * Adds the xpath ".//tag" to search any depth child. For example: * *
* div (current) -< span -< span -< li (target) @@ -85,7 +85,7 @@ public SourceBuilder anywhereRelative(@Nullable String tag) { } /** - * Adds the prefix "//*" to search any depth element absolutely. + * Adds the xpath "//*" to search any depth element absolutely. * *
* div (current)
div (another) -< span -< span -< li (target)
@@ -98,7 +98,7 @@ public SourceBuilder anywhere() {
}
/**
- * Adds the prefix "//tag" to search any depth element absolutely.
+ * Adds the xpath "//tag" to search any depth element absolutely.
*
*
* div (current)
div (another) -< span -< span -< li (target)
@@ -112,7 +112,7 @@ public SourceBuilder anywhere(String tag) {
}
/**
- * Adds the prefix "./*" to to search relative elements
+ * Adds the xpath "./*" to to search relative elements
*
*
* div (current) -< li (target) @@ -125,7 +125,7 @@ public SourceBuilder relative() { } /** - * Adds the prefix "./tag" to to search relative elements + * Adds the xpath "./tag" to to search relative elements * *
* div (current) -< li (target)
@@ -139,7 +139,7 @@ public SourceBuilder relative(String tag) {
}
/**
- * Builds with tag as * and empty prefix.
+ * Builds with tag as * and empty xpath.
*
* @return created next builder {@link SourceBuilder} instance
*/
@@ -148,7 +148,7 @@ public SourceBuilder empty() {
}
/**
- * Builds with tag and empty prefix.
+ * Builds with tag and empty xpath.
*
* @param tag the target tag name to search, defaulted to * if blank
* @return created next builder {@link SourceBuilder} instance
@@ -165,49 +165,68 @@ public SourceBuilder empty(String tag) {
* @since 1.5
*/
public static class SourceBuilder implements By2Builder {
- private final String prefix;
+ private final String xpath;
+ @Nullable
+ private final OperatorType operator;
/**
- * Constructs an instance with non-null prefix.
+ * Constructs an instance with non-null xpath.
*
- * @param prefix the prefix generated by previous {@link PrefixBuilder}.
+ * @param xpath the xpath generated by previous {@link PrefixBuilder}.
*/
- SourceBuilder(String prefix) {
- this.prefix = prefix;
+ SourceBuilder(String xpath) {
+ this(xpath, null);
+ }
+
+
+ /**
+ * Constructs an instance with non-null xpath.
+ *
+ * @param xpath the xpath generated by previous {@link PrefixBuilder}.
+ * @param operator the and / or operator, could be null
+ */
+ SourceBuilder(String xpath, @Nullable OperatorType operator) {
+ this.xpath = xpath;
+ this.operator = operator;
}
/**
* Builds the source as {@code text()}
*
- * @return created next build {@link MethodBuilder} instance
+ * @return created next build {@link WithNotMethodBuilder} instance
*/
- public MethodBuilder text() {
- return new MethodBuilder(prefix, "text()");
+ public WithNotMethodBuilder text() {
+ return new WithNotMethodBuilder(xpath, "text()", operator);
}
/**
* Builds the source as {@code name()}
*
- * @return created next build {@link MethodBuilder} instance
+ * @return created next build {@link WithNotMethodBuilder} instance
*/
- public MethodBuilder name() {
- return new MethodBuilder(prefix, "name()");
+ public WithNotMethodBuilder name() {
+ return new WithNotMethodBuilder(xpath, "name()", operator);
}
/**
* Builds the source as attribute
*
* @param attr the attribute to match
- * @return created next build {@link MethodBuilder} instance
+ * @return created next build {@link WithNotMethodBuilder} instance
*/
- public MethodBuilder attr(String attr) {
+ public WithNotMethodBuilder attr(String attr) {
String source = attr.startsWith("@") ? attr : "@" + attr;
- return new MethodBuilder(prefix, source);
+ return new WithNotMethodBuilder(xpath, source, operator);
}
@Override
public By build() {
- return By.xpath(prefix);
+ return By.xpath(xpath);
+ }
+
+ @Override
+ public String xpathString() {
+ return xpath;
}
/**
@@ -216,32 +235,56 @@ public By build() {
* @return the next created {@link AxesBuilder} builder instance
*/
public AxesBuilder axes() {
- return new AxesBuilder(prefix);
+ return new AxesBuilder(xpath);
}
}
/**
- * Builds the method as the last step. Note this is not a {@link By2Builder}.
+ * Method Builder with not() function.
*
* @author Jack Yin
- * @since 1.5
+ * @since 1.11
*/
- public static class MethodBuilder {
+ public static class WithNotMethodBuilder extends MethodBuilder {
- private final String prefix;
- private final String source;
+ WithNotMethodBuilder(String xpath, String source, OperatorType operator) {
+ super(xpath, source, operator);
+ }
/**
- * Builds the source as the last step.
+ * Wraps the next method with not()
*
- * @author Jack Yin
- * @since 1.5
+ * @return the created next build {@link MethodBuilder} instance
*/
- MethodBuilder(String prefix, String source) {
- requireNonNull(prefix);
+ public MethodBuilder not() {
+ return new MethodBuilder(xpath, source, true, operator);
+ }
+ }
+
+ /**
+ * Builds the method as the last step. Note this is not a {@link By2Builder}.
+ *
+ * @author Jack Yin
+ * @since 1.5
+ */
+ public static class MethodBuilder {
+
+ protected final String xpath;
+ protected final String source;
+ protected final boolean notOperator;
+ protected final OperatorType operator;
+
+ MethodBuilder(String xpath, String source, @Nullable OperatorType operator) {
+ this(xpath, source, false, operator);
+ }
+
+ MethodBuilder(String xpath, String source, boolean notOperator, @Nullable OperatorType operator) {
+ requireNonNull(xpath);
requireNonNull(source);
- this.prefix = prefix;
+ this.xpath = xpath;
this.source = source;
+ this.notOperator = notOperator;
+ this.operator = operator;
}
/**
@@ -251,7 +294,7 @@ public static class MethodBuilder {
* @return the generated by xpath
*/
public AxesBuilder exact(String term) {
- return new AxesBuilder(prefix + String.format("[%s=%s]", source, enrichQuote(term)));
+ return new AxesBuilder(buildXpath("%s=%s", term));
}
/**
@@ -261,7 +304,7 @@ public AxesBuilder exact(String term) {
* @return the generated by xpath
*/
public AxesBuilder contains(String term) {
- return new AxesBuilder(prefix + String.format("[contains(%s,%s)]", source, enrichQuote(term)));
+ return new AxesBuilder(buildXpath("contains(%s,%s)", term));
}
/**
@@ -271,7 +314,7 @@ public AxesBuilder contains(String term) {
* @return the generated by xpath
*/
public AxesBuilder startsWith(String term) {
- return new AxesBuilder(prefix + String.format("[starts-with(%s,%s)]", source, enrichQuote(term)));
+ return new AxesBuilder(buildXpath("starts-with(%s,%s)", term));
}
/**
@@ -281,22 +324,36 @@ public AxesBuilder startsWith(String term) {
* @return the generated by xpath
*/
public AxesBuilder matches(String patternString) {
- return new AxesBuilder(prefix + String.format("[matches(%s,%s)]", source, enrichQuote(patternString)));
+ return new AxesBuilder(buildXpath("matches(%s,%s)", patternString));
+ }
+
+ private String buildXpath(String template, String value) {
+ if (notOperator) {
+ template = "not(" + template + ")";
+ }
+
+ String formatted = String.format(template, source, enrichQuote(value));
+
+ if (operator != null) {
+ return xpath.substring(0, xpath.length() - 1) + operator.getValue() + formatted + "]";
+ } else {
+ return xpath + "[" + formatted + "]";
+ }
}
}
public static class AxesBuilder implements By2Builder {
- private final String prefix;
+ private final String xpath;
/**
* Constructs an instance with previous built xpath.
*
- * @param prefix the current xpath built in previous step.
+ * @param xpath the current xpath built in previous step.
*/
- public AxesBuilder(String prefix) {
- requireNonNull(prefix);
- this.prefix = prefix;
+ public AxesBuilder(String xpath) {
+ requireNonNull(xpath);
+ this.xpath = xpath;
}
/**
@@ -315,7 +372,7 @@ public SourceBuilder child() {
* @return the next created {@link SourceBuilder} builder instance.
*/
public SourceBuilder child(String tag) {
- return new SourceBuilder(prefix + "/child::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/child::" + defaultIfBlank(tag, ANY_TAG));
}
/**
@@ -336,7 +393,7 @@ public SourceBuilder descendant() {
* @return the next created {@link SourceBuilder} builder instance.
*/
public SourceBuilder descendant(String tag) {
- return new SourceBuilder(prefix + "/descendant::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/descendant::" + defaultIfBlank(tag, ANY_TAG));
}
/**
@@ -356,7 +413,7 @@ public SourceBuilder parent() {
* @return the next created {@link SourceBuilder} builder instance.
*/
public SourceBuilder parent(String tag) {
- return new SourceBuilder(prefix + "/parent::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/parent::" + defaultIfBlank(tag, ANY_TAG));
}
/**
@@ -379,7 +436,7 @@ public SourceBuilder ancestor() {
* @return the next created {@link SourceBuilder} instance.
*/
public SourceBuilder ancestor(String tag) {
- return new SourceBuilder(prefix + "/ancestor::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/ancestor::" + defaultIfBlank(tag, ANY_TAG));
}
/**
@@ -400,7 +457,7 @@ public SourceBuilder precedingSibling() {
* @return the next created {@link SourceBuilder} instance.
*/
public SourceBuilder precedingSibling(String tag) {
- return new SourceBuilder(prefix + "/preceding-sibling::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/preceding-sibling::" + defaultIfBlank(tag, ANY_TAG));
}
/**
@@ -421,7 +478,7 @@ public SourceBuilder followingSibling() {
* @return the next created {@link SourceBuilder} instance.
*/
public SourceBuilder followingSibling(String tag) {
- return new SourceBuilder(prefix + "/following-sibling::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/following-sibling::" + defaultIfBlank(tag, ANY_TAG));
}
/**
@@ -442,7 +499,7 @@ public SourceBuilder following() {
* @return the next created {@link SourceBuilder} instance.
*/
public SourceBuilder following(String tag) {
- return new SourceBuilder(prefix + "/following::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/following::" + defaultIfBlank(tag, ANY_TAG));
}
/**
@@ -463,12 +520,37 @@ public SourceBuilder preceding() {
* @return the next created {@link SourceBuilder} instance.
*/
public SourceBuilder preceding(String tag) {
- return new SourceBuilder(prefix + "/preceding::" + defaultIfBlank(tag, ANY_TAG));
+ return new SourceBuilder(xpath + "/preceding::" + defaultIfBlank(tag, ANY_TAG));
+ }
+
+ /**
+ * Insert next expression with and logical operator. [@value="aa" and contains(text(), "bb")]
+ *
+ * @return the next created {@link SourceBuilder} instance with {@link OperatorType#AND} operator.
+ */
+ public SourceBuilder and() {
+ return new SourceBuilder(xpath, OperatorType.AND);
+ }
+
+ /**
+ * Insert next expression with and logical operator. [@value="aa" or contains(text(), "bb")]
+ *
+ * @return the next created {@link SourceBuilder} instance with {@link OperatorType#OR} operator.
+ */
+ public SourceBuilder or() {
+ return new SourceBuilder(xpath, OperatorType.OR);
}
@Override
public By build() {
- return By.xpath(prefix);
+ return By.xpath(xpath);
}
+
+ @Override
+ public String xpathString() {
+ return xpath;
+ }
+
+
}
}
diff --git a/hamster-selenium-core/src/test/java/com/github/grossopa/selenium/core/locator/SimpleXpathBuilderTest.java b/hamster-selenium-core/src/test/java/com/github/grossopa/selenium/core/locator/SimpleXpathBuilderTest.java
index 2a0ffa8b..426b5aa6 100644
--- a/hamster-selenium-core/src/test/java/com/github/grossopa/selenium/core/locator/SimpleXpathBuilderTest.java
+++ b/hamster-selenium-core/src/test/java/com/github/grossopa/selenium/core/locator/SimpleXpathBuilderTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2021 the original author or authors.
+ * Copyright © 2023 the original author or authors.
*
* Licensed under the The MIT License (MIT) (the "License");
* You may obtain a copy of the License at
@@ -253,8 +253,42 @@ void testBuilder32() {
@Test
void testBuilder33() {
- assertEquals(By2.xpath("./div"),
- builder.relative("div").build());
+ assertEquals(By2.xpath("./div"), builder.relative("div").build());
+ }
+
+ @Test
+ void testBuilder34() {
+ assertEquals(By2.xpath("div[not(starts-with(@some-attr,'start\"char'))]"
+ + "/preceding::span/following-sibling::tag[not(matches(@data-value,\"some-pattern\"))]"),
+ builder.empty("div").attr("some-attr").not().startsWith("start\"char").preceding("span").axes()
+ .followingSibling("tag").attr("data-value").not().matches("some-pattern").build());
+ }
+
+ @Test
+ void testBuilder35() {
+ assertEquals(By2.xpath(
+ "div[not(starts-with(@some-attr,'start\"char')) and text()=\"some char\" or starts-with(name(),\"abc\")]"
+ + "/preceding::span/following-sibling::tag[not(matches(@data-value,\"some-pattern\"))]"),
+ builder.empty("div").attr("some-attr").not().startsWith("start\"char").and().text().exact("some char")
+ .or().name().startsWith("abc").preceding("span").axes().followingSibling("tag")
+ .attr("data-value").not().matches("some-pattern").build());
+ }
+
+ @Test
+ void testBuilder36() {
+ assertEquals(builder.anywhereRelative("div").attr("abc").not().contains("123").build(),
+ builder.anywhereRelative("div").attr("@abc").not().contains("123").build());
+ }
+
+ @Test
+ void testXpathString1() {
+ assertEquals("//*[contains(text(),\"abc\")]", builder.anywhere().text().contains("abc").xpathString());
+ }
+
+ @Test
+ void testXpathString2() {
+ assertEquals("//*[contains(text(),\"abc\")]/ancestor::*",
+ builder.anywhere().text().contains("abc").ancestor().xpathString());
}
}
diff --git a/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/StartDriverServiceWinEdge.java b/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/StartDriverServiceWinEdge.java
index 8fe43cc6..1ac68c3b 100644
--- a/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/StartDriverServiceWinEdge.java
+++ b/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/StartDriverServiceWinEdge.java
@@ -39,7 +39,7 @@
*/
public class StartDriverServiceWinEdge {
- public static final String EXECUTABLE_PATH = "C:\\work\\software\\drivers\\msedgedriver_107.0.1418.35.exe";
+ public static final String EXECUTABLE_PATH = "C:\\work\\software\\drivers\\msedgedriver_111.0.1661.51.exe";
public static final int PORT = 38383;
@@ -47,7 +47,7 @@ public class StartDriverServiceWinEdge {
public static void main(String[] args) throws IOException {
DriverConfig config = new DriverConfig();
config.setDriverExecutablePath(EXECUTABLE_PATH);
- config.setDriverVersion("107.0.1418.35");
+ config.setDriverVersion("111.0.1661.51");
config.setType(WebDriverType.EDGE);
config.setPort(PORT);
diff --git a/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/html/HtmlShowCase.java b/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/html/HtmlShowCase.java
index ab055478..a1a719ad 100644
--- a/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/html/HtmlShowCase.java
+++ b/hamster-selenium-examples/src/test/java/com/github/grossopa/selenium/examples/html/HtmlShowCase.java
@@ -89,6 +89,25 @@ public void testTableNoHeader() {
assertEquals("700", table.getBodyRow(2).getCells().get(0).getText());
assertEquals("800", table.getBodyRow(2).getCells().get(1).getText());
assertEquals("900", table.getBodyRow(2).getCells().get(2).getText());
+
+ List