(Arrays.asList(element));
- return new SeleniumQueryObject(driver, elements, NO_PREVIOUS);
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/SeleniumQuery.java b/src/main/java/io/github/seleniumquery/SeleniumQuery.java
index b05c4090..de8146b8 100644
--- a/src/main/java/io/github/seleniumquery/SeleniumQuery.java
+++ b/src/main/java/io/github/seleniumquery/SeleniumQuery.java
@@ -1,229 +1,230 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.github.seleniumquery;
-import org.openqa.selenium.WebDriver;
+import io.github.seleniumquery.browser.BrowserFunctionsWithDeprecatedFunctions;
import org.openqa.selenium.WebElement;
-import org.openqa.selenium.internal.WrapsDriver;
+
+import java.util.List;
/**
- * The seleniumQuery factory for objects.
+ * The seleniumQuery objects factory.
*
- * Recommended way of use is to import statically the function:
- * import static io.github.seleniumquery.SeleniumQuery.$;
- *
- *
- * And use it like:
- * $("selector").function()
- *
- *
- * Other uses (aliases) include jQuery() and sQ():
- * jQuery("selector").function()
- * sQ("selector").function()
- *
+ * Recommended way of use is to import statically the function:
+ *
+ * import static io.github.seleniumquery.SeleniumQuery.$;
+ *
+ * And use it like:
+ *
+ * $.url("http://example.com");
+ * $("selector").function();
+ *
+ * The default browser/driver is employed when a seleniumQuery object is built using $(".selector");.
+ * A different browser can be used by using the {@link io.github.seleniumquery.SeleniumQueryBrowser} class:
+ *
+ * SeleniumQueryBrowser chrome = new SeleniumQueryBrowser();
+ * chrome .driver().useChrome();
+ * chrome .$(".selector").val("123");
+ *
+ * Other uses (aliases) include jQuery() and sQ():
+ *
+ * jQuery("selector").function();
+ * sQ("selector").function();
+ *
*
* @author acdcjunior
- * @since 0.2.0
+ * @author ricardo-sc
+ * @since 0.9.0
*/
public class SeleniumQuery {
/**
- * The seleniumQuery global object. Works as $ (actually, the dollar sign is an alias to this).
- * Use sQ("selector").function() or sQ.property.function()
- *
- * Example:
- * sQ("div").text();
- * sQ.location.href("http://www.google.com");
- *
- * @since 0.2.0
- */
- public static final SeleniumQueryStatic sQ = new SeleniumQueryStatic();
-
- /**
- * The seleniumQuery global object.
+ * The seleniumQuery global browser functions object.
*
- * Use $("selector").function() or $.property.function()
- *
- * Example:
- * $("div").text();
- * $.location.href("http://www.google.com");
- *
- * @since 0.2.0
- */
- public static final SeleniumQueryStatic $ = sQ;
-
- /**
- * The seleniumQuery global object.
This works as an alias to $.
- *
- * @since 0.3.0
- */
- public static final SeleniumQueryStatic jQuery = sQ;
-
- /**
- * The seleniumQuery constructor. Works as $ (actually, the dollar sign is an alias to this).
- * Use sQ("selector").function() or sQ.property.function()
- *
- * Example:
- * sQ("div").text();
- * sQ.location.href("http://www.google.com");
- *
- * @since 0.2.0
- */
- public static SeleniumQueryObject sQ(String selector) {
- return new SeleniumQueryObject(sQ.browser.getDefaultDriver(), selector);
- }
-
- /**
- * The seleniumQuery constructor. Works as $ (actually, the dollar sign is an alias to this).
- * Use sQ("selector").function() or sQ.property.function()
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
*
- * Example:
- * sQ("div").text();
- * sQ.location.href("http://www.google.com");
- *
- * @since 0.2.0
- */
- public static SeleniumQueryObject sQ(WebDriver driver, String selector) {
- return new SeleniumQueryObject(driver, selector);
- }
-
- /**
- * The seleniumQuery constructor. Works as $ (actually, the dollar sign is an alias to this).
- * Use sQ("selector").function() or sQ.property.function()
- *
- * Example:
- * sQ("div").text();
- * sQ.location.href("http://www.google.com");
- *
- * @since 0.3.0
- */
- public static SeleniumQueryObject sQ(WebElement element) {
- return sQ(((WrapsDriver) element).getWrappedDriver(), element);
- }
-
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @since 0.9.0
+ */
+ public static final BrowserFunctionsWithDeprecatedFunctions $ = new BrowserFunctionsWithDeprecatedFunctions();
+
/**
- * The seleniumQuery constructor. Works as $ (actually, the dollar sign is an alias to this).
- * Use sQ("selector").function() or sQ.property.function()
- *
- * Example:
- * sQ("div").text();
- * sQ.location.href("http://www.google.com");
- *
- * @since 0.3.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
*/
- public static SeleniumQueryObject sQ(WebDriver driver, WebElement element) {
- return SQLocalFactory.getInstance().createWithInvalidSelectorAndNoPrevious(driver, element);
- }
+ public static final BrowserFunctionsWithDeprecatedFunctions sQ = $;
/**
- * The seleniumQuery constructor. Works as $ (actually, the dollar sign is an alias to this).
- * Use sQ("selector").function() or sQ.property.function()
- *
- * Example:
- * sQ("div").text();
- * sQ.location.href("http://www.google.com");
- *
- * @since 0.3.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
*/
- public static SeleniumQueryObject sQ(SeleniumQueryObject seleniumQueryObject) {
- return seleniumQueryObject;
- }
-
+ public static final BrowserFunctionsWithDeprecatedFunctions jQuery = $;
+
/**
- * The seleniumQuery constructor.
- * Use $("selector").function() or $.property.function()
+ * The seleniumQuery global browser functions object.
+ *
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
*
- * Example:
- * $("div").text();
- * $.location.href("http://www.google.com");
- *
- * @since 0.2.0
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @param selector A selector. Can be a CSS3 selector, a jQuery/Sizzle/seleniumQuery extended selector or an
+ * XPath expression - if the argument starts with (, / or an XPath axis,
+ * such as descendant-or-self::.
+ * @return A {@link SeleniumQueryObject} containing all elements in matched by the selector.
+ *
+ * @since 0.9.0
*/
public static SeleniumQueryObject $(String selector) {
- return sQ(selector);
+ return ObjectLocalFactory.createWithValidSelectorAndNoPrevious($.driver().get(), selector);
}
-
+
/**
- * The seleniumQuery constructor.
- * Use $("selector").function() or $.property.function()
+ * The seleniumQuery global browser functions object.
+ *
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
*
- * Example:
- * $("div").text();
- * $.location.href("http://www.google.com");
- *
- * @since 0.2.0
- */
- public static SeleniumQueryObject $(WebDriver driver, String selector) {
- return sQ(driver, selector);
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @param elements One or more {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing the given element.
+ *
+ * @since 0.9.0
+ */
+ public static SeleniumQueryObject $(WebElement... elements) {
+ return ObjectLocalFactory.createWithInvalidSelectorAndNoPrevious($.driver().get(), elements);
}
-
+
/**
- * The seleniumQuery object constructor.
- * Use $("selector").function() or $.property.function()
+ * The seleniumQuery global browser functions object.
+ *
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
*
- * Example:
- * $("div").text();
- * $.location.href("http://www.google.com");
- *
- * @since 0.3.0
- */
- public static SeleniumQueryObject $(WebElement element) {
- return sQ(element);
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @param elements A list of {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing all given elements.
+ *
+ * @since 0.9.0
+ */
+ public static SeleniumQueryObject $(List elements) {
+ return ObjectLocalFactory.createWithInvalidSelectorAndNoPrevious($.driver().get(), elements);
}
-
+
/**
- * @since 0.5.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
+ *
+ * @param selector A selector. Can be a CSS3 selector, a jQuery/Sizzle/seleniumQuery extended selector or an
+ * XPath expression - if the argument starts with (, / or an XPath axis,
+ * such as descendant-or-self::.
+ * @return A {@link SeleniumQueryObject} containing all elements in matched by the selector.
+ *
+ * @since 0.9.0
*/
- public static SeleniumQueryObject $(WebDriver driver, WebElement element) {
- return sQ(driver, element);
+ public static SeleniumQueryObject sQ(String selector) {
+ return $(selector);
}
-
+
/**
- * The seleniumQuery object constructor.
- * Use $("selector").function() or $.property.function()
- *
- * Example:
- * $("div").text();
- * $.location.href("http://www.google.com");
- *
- * @since 0.3.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
+ *
+ * @param elements One or more {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing the given element.
+ *
+ * @since 0.9.0
*/
- public static SeleniumQueryObject $(SeleniumQueryObject seleniumQueryObject) {
- return sQ(seleniumQueryObject);
+ public static SeleniumQueryObject sQ(WebElement... elements) {
+ return $(elements);
}
-
+
/**
- * The seleniumQuery constructor.
This function is an alias to $().
- *
- * @since 0.3.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
+ *
+ * @param elements A list of {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing all given elements.
+ *
+ * @since 0.9.0
*/
- public static SeleniumQueryObject jQuery(String selector) {
- return sQ(selector);
+ public static SeleniumQueryObject sQ(List elements) {
+ return $(elements);
}
-
+
/**
- * The seleniumQuery constructor.
This function is an alias to $().
- *
- * @since 0.3.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
+ *
+ * @param selector A selector. Can be a CSS3 selector, a jQuery/Sizzle/seleniumQuery extended selector or an
+ * XPath expression - if the argument starts with (, / or an XPath axis,
+ * such as descendant-or-self::.
+ * @return A {@link SeleniumQueryObject} containing all elements in matched by the selector.
+ *
+ * @since 0.9.0
*/
- public static SeleniumQueryObject jQuery(WebDriver driver, String selector) {
- return sQ(driver, selector);
+ public static SeleniumQueryObject jQuery(String selector) {
+ return $(selector);
}
-
+
/**
- * The seleniumQuery constructor.
This function is an alias to $().
- *
- * @since 0.3.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
+ *
+ * @param elements One or more {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing the given element.
+ *
+ * @since 0.9.0
*/
- public static SeleniumQueryObject jQuery(WebElement element) {
- return sQ(element);
+ public static SeleniumQueryObject jQuery(WebElement... elements) {
+ return $(elements);
}
-
+
/**
- * The seleniumQuery constructor.
This function is an alias to $().
- *
- * @since 0.3.0
+ * The seleniumQuery global browser functions object.
This works as an alias to $.
+ *
+ * @param elements A list of {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing all given elements.
+ *
+ * @since 0.9.0
*/
- public static SeleniumQueryObject jQuery(SeleniumQueryObject seleniumQueryObject) {
- return sQ(seleniumQueryObject);
+ public static SeleniumQueryObject jQuery(List elements) {
+ return $(elements);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/io/github/seleniumquery/SeleniumQueryBrowser.java b/src/main/java/io/github/seleniumquery/SeleniumQueryBrowser.java
new file mode 100644
index 00000000..4b23aba4
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/SeleniumQueryBrowser.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery;
+
+import io.github.seleniumquery.browser.BrowserFunctions;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * THe seleniumQuery Browser, consisting of seleniumQuery decoration-ish over a specific instance of WebDriver.
+ *
+ * @author acdcjunior
+ * @author ricardo-sc
+ * @since 0.9.0
+ */
+public class SeleniumQueryBrowser {
+
+ /**
+ * The seleniumQuery browser functions object.
+ *
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
+ *
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @since 0.9.0
+ */
+ public final BrowserFunctions $ = new BrowserFunctions();
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ */
+ public final BrowserFunctions sQ = $;
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ */
+ public final BrowserFunctions jQuery = $;
+
+ /**
+ * The seleniumQuery browser functions object.
+ *
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
+ *
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @param selector A selector. Can be a CSS3 selector, a jQuery/Sizzle/seleniumQuery extended selector or an
+ * XPath expression - if the argument starts with (, / or an XPath axis,
+ * such as descendant-or-self::.
+ * @return A {@link SeleniumQueryObject} containing all elements in matched by the selector.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject $(String selector) {
+ return ObjectLocalFactory.createWithValidSelectorAndNoPrevious(this.$.driver().get(), selector);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
+ *
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
+ *
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @param elements One or more {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing the given element.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject $(WebElement... elements) {
+ return ObjectLocalFactory.createWithInvalidSelectorAndNoPrevious(this.$.driver().get(), elements);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
+ *
+ * Use $.function() for browser-scoped actions and
+ * $("selector").function() for tasks that should operate on groups of elements.
+ *
+ *
+ * Examples:
+ *
+ * $.driver().useFirefox();
+ * $.url("http://www.google.com");
+ * $("div").text();
+ *
+ *
+ * @param elements A list of {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing all given elements.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject $(List elements) {
+ return ObjectLocalFactory.createWithInvalidSelectorAndNoPrevious(this.$.driver().get(), elements);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ *
+ * @param selector A selector. Can be a CSS3 selector, a jQuery/Sizzle/seleniumQuery extended selector or an
+ * XPath expression - if the argument starts with (, / or an XPath axis,
+ * such as descendant-or-self::.
+ * @return A {@link SeleniumQueryObject} containing all elements in matched by the selector.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject sQ(String selector) {
+ return $(selector);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ *
+ * @param elements One or more {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing the given element.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject sQ(WebElement... elements) {
+ return $(elements);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ *
+ * @param elements A list of {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing all given elements.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject sQ(List elements) {
+ return $(elements);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ *
+ * @param selector A selector. Can be a CSS3 selector, a jQuery/Sizzle/seleniumQuery extended selector or an
+ * XPath expression - if the argument starts with (, / or an XPath axis,
+ * such as descendant-or-self::.
+ * @return A {@link SeleniumQueryObject} containing all elements in matched by the selector.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject jQuery(String selector) {
+ return $(selector);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ *
+ * @param elements One or more {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing the given element.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject jQuery(WebElement... elements) {
+ return $(elements);
+ }
+
+ /**
+ * The seleniumQuery browser functions object.
This works as an alias to $.
+ *
+ * @param elements A list of {@link WebElement}s to initialize a {@link SeleniumQueryObject} with.
+ * @return A {@link SeleniumQueryObject} containing all given elements.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject jQuery(List elements) {
+ return $(elements);
+ }
+
+}
diff --git a/src/main/java/io/github/seleniumquery/SeleniumQueryConfig.java b/src/main/java/io/github/seleniumquery/SeleniumQueryConfig.java
index 61f798a7..638ad112 100644
--- a/src/main/java/io/github/seleniumquery/SeleniumQueryConfig.java
+++ b/src/main/java/io/github/seleniumquery/SeleniumQueryConfig.java
@@ -1,62 +1,102 @@
-package io.github.seleniumquery;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-public class SeleniumQueryConfig {
-
- static {
- loadProperties();
- }
-
- private static void loadProperties() {
- try {
- SeleniumQueryConfig.properties = new Properties();
- InputStream in = SeleniumQueryConfig.class.getClassLoader().getResourceAsStream("seleniumquery.properties");
- SeleniumQueryConfig.properties.load(in);
- in.close();
- {
- String gtim = SeleniumQueryConfig.properties.getProperty("GLOBAL_TIMEOUT_IN_MILLISSECONDS");
- if (gtim != null && !gtim.isEmpty()) {
- GLOBAL_TIMEOUT_IN_MILLISSECONDS = Long.valueOf(gtim);
- }
- }
- {
- String tis = SeleniumQueryConfig.properties.getProperty("TIMEOUT_IN_SECONDS");
- if (tis != null && !tis.isEmpty()) {
- TIMEOUT_IN_SECONDS = Long.valueOf(tis);
- }
- }
- {
- String tim = SeleniumQueryConfig.properties.getProperty("POLLING_IN_MILLISSECONDS");
- if (tim != null && !tim.isEmpty()) {
- POLLING_IN_MILLISSECONDS = Long.valueOf(tim);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private static Properties properties;
-
- public static String get(String property) {
- return properties.getProperty(property);
- }
-
- private static long GLOBAL_TIMEOUT_IN_MILLISSECONDS = 1500;
- private static long TIMEOUT_IN_SECONDS = 10;
- private static long POLLING_IN_MILLISSECONDS = 900;
-
- public static long getGlobalTimeoutInMillisseconds() {
- return GLOBAL_TIMEOUT_IN_MILLISSECONDS;
- }
- public static long getWaitTimeoutInSeconds() {
- return TIMEOUT_IN_SECONDS;
- }
- public static long getWaitPollingInMillisseconds() {
- return POLLING_IN_MILLISSECONDS;
- }
-
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * THe seleniumQuery config util class.
+ *
+ * @author acdcjunior
+ * @author ricardo-sc
+ * @since 0.9.0
+ */
+public class SeleniumQueryConfig {
+
+ private static final Log LOGGER = LogFactory.getLog(SeleniumQueryConfig.class);
+
+ private static final String PROP_GLOBAL_TIMEOUT = "GLOBAL_TIMEOUT";
+ private static final String PROP_WAITUNTIL_TIMEOUT = "WAITUNTIL_TIMEOUT";
+ private static final String PROP_WAITUNTIL_POLLING_INTERVAL = "WAITUNTIL_POLLING_INTERVAL";
+
+ /**
+ * All times are in milliseconds.
+ */
+ private static long globalTimeout = 1501;
+ private static long waitUntilTimeout = 10001;
+ private static long waitUntilPollingInterval = 901;
+
+ static {
+ loadPropertiesFiles();
+ }
+
+ private static void loadPropertiesFiles() {
+ try {
+ loadPropertiesFileFromClasspath();
+
+ globalTimeout = getLongProperty(PROP_GLOBAL_TIMEOUT, globalTimeout);
+ waitUntilTimeout = getLongProperty(PROP_WAITUNTIL_TIMEOUT, waitUntilTimeout);
+ waitUntilPollingInterval = getLongProperty(PROP_WAITUNTIL_POLLING_INTERVAL, waitUntilPollingInterval);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void loadPropertiesFileFromClasspath() throws IOException {
+ properties = new Properties();
+ LOGGER.debug("Attempting to load properties from seleniumQuery.properties in classpath.");
+ InputStream inputStream = SeleniumQueryConfig.class.getClassLoader().getResourceAsStream("seleniumQuery.properties");
+ if (inputStream == null) {
+ LOGGER.info("No seleniumQuery.properties found in classpath, falling back to defaults.");
+ }
+ properties.load(inputStream);
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+
+ private static long getLongProperty(String propertyName, long defaultValue) {
+ String propertyAsString = SeleniumQueryConfig.properties.getProperty(propertyName);
+ if (propertyAsString == null || propertyAsString.trim().isEmpty()) {
+ return defaultValue;
+ }
+ return Long.valueOf(propertyAsString);
+ }
+
+ private static Properties properties;
+
+ public static String get(String property) {
+ return properties.getProperty(property);
+ }
+
+ public static long getGlobalTimeout() {
+ return globalTimeout;
+ }
+ public static long getWaitUntilTimeout() {
+ return waitUntilTimeout;
+ }
+ public static long getWaitUntilPollingInterval() {
+ return waitUntilPollingInterval;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/SeleniumQueryException.java b/src/main/java/io/github/seleniumquery/SeleniumQueryException.java
new file mode 100644
index 00000000..5ac6dacc
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/SeleniumQueryException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery;
+
+public class SeleniumQueryException extends RuntimeException {
+
+ public SeleniumQueryException(String message) {
+ super(message);
+ }
+
+ public SeleniumQueryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/SeleniumQueryObject.java b/src/main/java/io/github/seleniumquery/SeleniumQueryObject.java
index 5abd4380..16f5458e 100644
--- a/src/main/java/io/github/seleniumquery/SeleniumQueryObject.java
+++ b/src/main/java/io/github/seleniumquery/SeleniumQueryObject.java
@@ -1,33 +1,51 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package io.github.seleniumquery;
import io.github.seleniumquery.by.SeleniumQueryBy;
-import io.github.seleniumquery.functions.AttrFunction;
-import io.github.seleniumquery.functions.ClickFunction;
-import io.github.seleniumquery.functions.ClosestFunction;
-import io.github.seleniumquery.functions.EqFunction;
-import io.github.seleniumquery.functions.FindFunction;
-import io.github.seleniumquery.functions.FocusFunction;
-import io.github.seleniumquery.functions.GetFunction;
-import io.github.seleniumquery.functions.HasClassFunction;
-import io.github.seleniumquery.functions.HtmlFunction;
-import io.github.seleniumquery.functions.IsFunction;
-import io.github.seleniumquery.functions.NotFunction;
-import io.github.seleniumquery.functions.PropFunction;
-import io.github.seleniumquery.functions.RemoveAttrFunction;
-import io.github.seleniumquery.functions.TextFunction;
-import io.github.seleniumquery.functions.ToArrayFunction;
-import io.github.seleniumquery.functions.ValFunction;
-import io.github.seleniumquery.wait.SeleniumQueryQueryUntil;
+import io.github.seleniumquery.functions.as.SeleniumQueryPlugin;
+import io.github.seleniumquery.functions.as.StandardPlugins;
+import io.github.seleniumquery.functions.jquery.attributes.AttrFunction;
+import io.github.seleniumquery.functions.jquery.attributes.HasClassFunction;
+import io.github.seleniumquery.functions.jquery.attributes.PropFunction;
+import io.github.seleniumquery.functions.jquery.attributes.RemoveAttrFunction;
+import io.github.seleniumquery.functions.jquery.events.ClickFunction;
+import io.github.seleniumquery.functions.jquery.forms.FocusFunction;
+import io.github.seleniumquery.functions.jquery.forms.SubmitFunction;
+import io.github.seleniumquery.functions.jquery.forms.ValFunction;
+import io.github.seleniumquery.functions.jquery.manipulation.HtmlFunction;
+import io.github.seleniumquery.functions.jquery.manipulation.TextFunction;
+import io.github.seleniumquery.functions.jquery.miscellaneous.GetFunction;
+import io.github.seleniumquery.functions.jquery.miscellaneous.ToArrayFunction;
+import io.github.seleniumquery.functions.jquery.traversing.filtering.*;
+import io.github.seleniumquery.functions.jquery.traversing.treetraversal.ChildrenFunction;
+import io.github.seleniumquery.functions.jquery.traversing.treetraversal.ClosestFunction;
+import io.github.seleniumquery.functions.jquery.traversing.treetraversal.FindFunction;
+import io.github.seleniumquery.functions.jquery.traversing.treetraversal.ParentFunction;
import io.github.seleniumquery.wait.SeleniumQueryWaitUntil;
-
-import java.util.Iterator;
-import java.util.List;
-
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
+import java.util.*;
+
/**
- * Represents the SeleniumQuery Object: a list of WebElements with special methods.
+ * Represents the SeleniumQuery Object : a list of {@link WebElement}s with special methods.
*
* A seleniumQuery object (similar to the regular JavaScript jQuery object) contains a collection of
* Selenium {@link WebElement}s (JavaScript jQuery is a collection of DOM elements) that have been selected
@@ -35,19 +53,21 @@
* the set of elements in a seleniumQuery object is often called a set of "matched elements" or "selected elements" .
*
*
- * The seleniumQuery object itself behaves much like a {@link List}; it has a {@link SeleniumQueryObject#size()} method and the
- * elements in the list can be accessed by their numeric indices through {@link SeleniumQueryObject#get()}.
+ * The seleniumQuery object itself behaves much like a {@link List}; it has a {@link SeleniumQueryObject#size()} method, the
+ * elements in the list can be accessed by their numeric indices through {@link SeleniumQueryObject#get()} and can also
+ * be iterated through.
*
*
* Just like jQuery, many seleniumQuery methods return the seleniumQuery object itself, so that method calls can be chained.
*
*
* In API calls that return a seleniumQuery object, the value returned will be the original seleniumQuery object unless
- * otherwise documented by that API. API methods such as {@link SeleniumQueryObject#first()} or {@link SeleniumQueryObject#not()}
+ * otherwise documented by that API. API methods such as {@link SeleniumQueryObject#first()} or {@link SeleniumQueryObject#not(String)}
* modify their incoming set and thus return a new seleniumQuery object.
*
+ *
* Whenever you use a "destructive" seleniumQuery method that potentially changes the set of elements in the seleniumQuery object,
- * such as {@link SeleniumQueryObject#first()} or {@link SeleniumQueryObject#not()}, that method actually returns a new seleniumQuery
+ * such as {@link SeleniumQueryObject#first()} or {@link SeleniumQueryObject#not(String)}, that method actually returns a new seleniumQuery
* object with the resulting elements. To return to the previous seleniumQuery object, you use the {@link SeleniumQueryObject#end()} method.
*
*
@@ -56,84 +76,138 @@
*
*
* @author acdcjunior
- * @since 0.2.0
+ * @author ricardo-sc
+ * @since 0.9.0
*/
public class SeleniumQueryObject implements Iterable {
+ private static final Log LOGGER = LogFactory.getLog(SeleniumQueryObject.class);
+
private SeleniumQueryBy by;
private WebDriver driver;
- public List elements;
+ private List elements;
/**
- * The previous (or "father") element, meaning this SeleniumQueryObject was created as result
- * of calling a "destructive" function (such as .not()) on that element.
- * This property is retrieved by a call to .end().
+ * The previous (or "parent") element, meaning this SeleniumQueryObject was created as result
+ * of calling a "destructive" function (such as {@link #not(String)}) on that element.
+ * This property is retrieved by a call to {@link #end()}.
*
- * @since 0.3.0
+ * @since 0.9.0
*/
private SeleniumQueryObject previous;
+ /**************************************************************************************************************************************
+ * SeleniumQueryObject constrcutors
+ **************************************************************************************************************************************/
+
+ protected SeleniumQueryObject(WebDriver driver, String selector) {
+ this.driver = driver;
+ this.by = SeleniumQueryBy.byEnhancedSelector(selector);
+ this.elements = toImmutableRandomAccessList(driver.findElements(this.by));
+ this.previous = null;
+ }
+
+ protected SeleniumQueryObject(WebDriver driver, String selector, List webElements, SeleniumQueryObject previous) {
+ this(driver, SeleniumQueryBy.byEnhancedSelector(selector), webElements, previous);
+ }
+
+ protected SeleniumQueryObject(WebDriver driver, List webElements, SeleniumQueryObject previous) {
+ this(driver, SeleniumQueryBy.NO_SELECTOR_INVALID_BY, webElements, previous);
+ }
+
+ private SeleniumQueryObject(WebDriver driver, SeleniumQueryBy seleniumQueryBy, List webElements, SeleniumQueryObject previous) {
+ this.driver = driver;
+ this.by = seleniumQueryBy;
+ this.elements = toImmutableRandomAccessList(webElements);
+ this.previous = previous;
+ }
+
+ private static List toImmutableRandomAccessList(List els) {
+ return Collections.unmodifiableList(new ArrayList(els));
+ }
+
+ /**************************************************************************************************************************************
+ * Java SeleniumQueryObject waitUntil() and as() functions
+ **************************************************************************************************************************************/
+
/**
- * List of functions that will halt the execution until the specified condition is met.
- *
- * @deprecated @see {@link SeleniumQueryObject#waitUntil()}
+ * List of functions that will halt the execution and requery the selector until the specified condition is met, returning
+ * a new seleniumQuery object at the end.
+ *
+ * This method will wait as specified in the properties file. By default, it waits up to 10 seconds, polling (requerying) every 900 ms.
+ *
+ *
+ * If a different amout of time is needed, either create and/or modify the seleniumQuery.properties file or use this method's
+ * version with a time parameter.
+ *
+ * @return a {@link SeleniumQueryWaitUntil} object for specifying the wait conditions.
+ *
+ * @since 0.9.0
*/
- @Deprecated
- public final SeleniumQueryWaitUntil testUntil() {
+ public final SeleniumQueryWaitUntil waitUntil() {
return new SeleniumQueryWaitUntil(this);
}
-
+
/**
* List of functions that will halt the execution and requery the selector until the specified condition is met, returning
* a new seleniumQuery object at the end.
- *
- * @author acdcjunior
- * @since 0.4.0
+ *
+ * @param waitUntilTimeout Time in milliseconds to wait before a timeout is thrown.
+ * @return a {@link SeleniumQueryWaitUntil} object for specifying the wait conditions.
+ *
+ * @since 0.9.0
*/
- public final SeleniumQueryQueryUntil waitUntil() {
- return new SeleniumQueryQueryUntil(this);
+ public final SeleniumQueryWaitUntil waitUntil(long waitUntilTimeout) {
+ return new SeleniumQueryWaitUntil(this, waitUntilTimeout);
}
-
+
/**
- * The queryUntil() function is deprecated. Use .waitUntil() instead.
- *
- * @deprecated @see {@link SeleniumQueryObject#waitUntil()}
+ * List of functions that will halt the execution and requery the selector until the specified condition is met, returning
+ * a new seleniumQuery object at the end.
+ *
+ * @param waitUntilTimeout Time in milliseconds to wait for the condition.
+ * @param waitUntilPollingInterval Interval in milliseconds between every requery/check.
+ * @return a {@link SeleniumQueryWaitUntil} object for specifying the wait conditions.
+ *
+ * @since 0.9.0
*/
- @Deprecated
- public final SeleniumQueryQueryUntil queryUntil() {
- return waitUntil();
+ public final SeleniumQueryWaitUntil waitUntil(long waitUntilTimeout, long waitUntilPollingInterval) {
+ return new SeleniumQueryWaitUntil(this, waitUntilTimeout, waitUntilPollingInterval);
}
-
-
- /**************************************************************************************************************************************
- * Java SeleniumQueryObject constrcutors
- **************************************************************************************************************************************/
-
- SeleniumQueryObject(WebDriver driver, String selector) {
- this.driver = driver;
- this.by = SeleniumQueryBy.byEnhancedSelector(selector);
- this.elements = driver.findElements(this.by);
- this.previous = null;
- }
-
- SeleniumQueryObject(WebDriver driver, String selector, List webElements, SeleniumQueryObject previous) {
- this.driver = driver;
- this.by = SeleniumQueryBy.byEnhancedSelector(selector);
- this.elements = webElements;
- this.previous = previous;
+
+ /**
+ * Enables several functions (plugins) that allow executing specific tasks, from specific points of view, on
+ * the elements of this {@link io.github.seleniumquery.SeleniumQueryObject}.
+ *
+ * @return A list of functions showing the available tasks that can be performed.
+ *
+ * @since 0.9.0
+ */
+ public StandardPlugins as() {
+ return new StandardPlugins(this);
}
-
- SeleniumQueryObject(WebDriver driver, List webElements, SeleniumQueryObject previous) {
- this.driver = driver;
- this.by = SeleniumQueryBy.NO_SELECTOR_INVALID_BY;
- this.elements = webElements;
- this.previous = previous;
+
+ /**
+ * Enables the execution of functions defined by the plugin sent as argument.
+ *
+ * @param pluginFunction The plugin instance this object will be handed to.
+ * @return An object provided by the plugin.
+ *
+ * @since 0.9.0
+ */
+ public PLUGIN as(SeleniumQueryPlugin pluginFunction) {
+ return pluginFunction.as(this);
}
-
+
/**************************************************************************************************************************************
* Java SeleniumQueryObject specific functions
**************************************************************************************************************************************/
-
+
+ /**
+ * Returns an iterator over the matched elements of this seleniumQuery object.
+ *
+ * @return an iterator over the matched elements of this seleniumQuery object.
+ */
@Override
public Iterator iterator() {
return this.elements.iterator();
@@ -146,7 +220,7 @@ public Iterator iterator() {
public WebDriver getWebDriver() {
return this.driver;
}
-
+
/**
* Returns the By used to search the matched elements of this seleniumQuery object.
* @return the By used to search the matched elements of this seleniumQuery object.
@@ -155,82 +229,86 @@ public SeleniumQueryBy getBy() {
return this.by;
}
-
-
/**************************************************************************************************************************************
* jQuery-emulating functions
**************************************************************************************************************************************/
/**
* Returns the number of elements in the seleniumQuery object.
+ *
* @return the number of elements in the seleniumQuery object.
- *
- * @author acdcjunior
- * @since 0.2.0
+ *
+ * @since 0.9.0
*/
public int size() {
return this.elements.size();
}
-
+
/**
- * Remove elements from the set of matched elements.
- *
+ * Removes elements from the set of matched elements.
+ *
* @param selector A string containing a selector expression to match elements against.
- * @since 1.0.0
+ * @return A seleniumQuery object containing all elements not removed.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject not(String selector) {
return NotFunction.not(this, this.elements, selector);
}
/**
- * Reduce the set of matched elements to the first in the set.
- *
- * Given a seleniumQuery object that represents a set of DOM elements, the .last() method constructs
- * a new seleniumQuery object from the first element in that set.
- *
- * @since 1.0.0
+ * Reduces the set of matched elements to the first in the set.
+ *
+ * Given a seleniumQuery object that represents a set of DOM elements, the .first()
+ * method constructs a new seleniumQuery object from the first element in that set.
+ *
+ * @return A seleniumQuery object containing a single matched element.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject first() {
- return EqFunction.eq(this, this.elements, 0);
+ return FirstFunction.first(this, this.elements);
}
-
+
/**
- * Reduce the set of matched elements to the final one in the set.
- *
- * Given a seleniumQuery object that represents a set of DOM elements, the .last() method constructs
- * a new seleniumQuery object from the last element in that set.
- *
- * @since 1.0.0
+ * Reduces the set of matched elements to the final one in the set.
+ *
+ * Given a seleniumQuery object that represents a set of DOM elements, the .last()
+ * method constructs a new seleniumQuery object from the last element in that set.
+ *
+ * @return A seleniumQuery object containing a single matched element.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject last() {
- return EqFunction.eq(this, this.elements, -1);
+ return LastFunction.last(this, this.elements);
}
-
+
/**
* Reduce the set of matched elements to the one at the specified index.
- *
- * @param index If positive: An integer indicating the 0-based position of the element.
- * If negative: An integer indicating the position of the element, counting backwards
+ *
+ * @param index If positive : An integer indicating the 0-based position of the element.
+ * If negative : An integer indicating the position of the element, counting backwards
* from the last element in the set.
- *
- * @since 1.0.0
+ * @return A seleniumQuery object containing a single matched element.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject eq(int index) {
return EqFunction.eq(this, this.elements, index);
}
/**
- *
- * Get the combined text contents of each element in the set of matched elements, including their descendants.
- *
- *
- *
- * Note: This functions uses Selenium's {@link WebElement#getText()}, and, as jQuery, "Due
- * to variations in the HTML parsers in different browsers, the text returned may vary in
- * newlines and other white space."
- *
- *
- * @since 0.2.0
+ * Gets the combined text contents of each element in the set of matched elements, including
+ * their descendants.
+ *
+ * Note: This functions uses Selenium's {@link WebElement#getText()}, and, as
+ * jQuery, "due to variations in the HTML parsers in different browsers, the text returned may vary
+ * in newlines and other white space."
+ *
+ * @return the text from all elements in the matched set.
+ *
+ * @since 0.9.0
*/
public String text() {
// Warning!
@@ -239,195 +317,330 @@ public String text() {
// More in WebDriver FAQs: https://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_Why_is_it_not_possible_to_interact_with_hidden_elements?
return TextFunction.text(this.elements);
}
-
+
/**
- * @since 0.2.0
+ * Clicks all elements in the set of matched elements, in the
+ * order they were matched.
+ *
+ * @return The same seleniumQuery object.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject click() {
+ LOGGER.debug("Clicking "+this+".");
return ClickFunction.click(this, this.elements);
}
-
+
/**
- * @since 0.2.0
+ * Sets the value of all elements in the set of matched elements.
+ *
+ * @param value The (string) value to be set.
+ * @return The same seleniumQuery object.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject val(String value) {
+ LOGGER.debug("Setting value of "+this+" to: \""+value+"\".");
return ValFunction.val(this, this.elements, value);
}
-
+
/**
- * @since 0.2.0
+ * Sets the value of all elements in the set of matched elements.
+ *
+ * @param value The (number) value to be set.
+ * @return The same seleniumQuery object.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject val(Number value) {
+ LOGGER.debug("Setting value of "+this+" to: "+value+".");
return ValFunction.val(this, this.elements, value);
}
-
+
/**
- * Get the current value of the first element in the set of matched elements.
- *
- * @since 0.2.0
+ * Gets the current value of the first element in the set of matched elements.
+ *
+ * @return The value of the first element.
+ *
+ * @since 0.9.0
*/
public String val() {
return ValFunction.val(this.elements);
}
-
+
/**
- * End the most recent filtering operation in the current chain and return the set of matched elements to its previous state.
+ * Ends the most recent filtering operation in the current chain and returns the set of matched
+ * elements to its previous state.
+ *
* @return the seleniumQuery object that originated the current instance.
- *
- * @author acdcjunior
- * @since 0.3.0
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject end() {
return this.previous;
}
-
+
/**
- * Get the descendants of each element in the current set of matched elements, filtered by a selector.
- *
- * @author acdcjunior
- * @since 0.4.0
+ * Gets the descendants of each element in the current set of matched elements, filtered by a selector.
+ *
+ * @param selector Selector the descendants must match.
+ * @return a {@link SeleniumQueryObject} containing the (filtered) descendants.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject find(String selector) {
return FindFunction.find(this, elements, selector);
}
-
+
/**
- * Get the value of an attribute for the first element in the set of matched elements.
- *
- * @author acdcjunior
- * @since 0.4.0
+ * Gets the value of an attribute for the first element in the set of matched elements.
+ *
+ * @param attributeName The name of the attribute to retrieve the value of.
+ * @return The value of the attribute.
+ *
+ * @since 0.9.0
*/
public String attr(String attributeName) {
return AttrFunction.attr(this, elements, attributeName);
}
-
+
/**
- * Set one or more attributes for every matched element.
- *
- * @author acdcjunior
- * @since 0.4.0
+ * Sets one or more attributes for every matched element.
+ *
+ * Note: If interacting with an <input> element while in a UI test,
+ * it is preferable to use {@link #click()} instead of setting the attributes
+ * through this function, as selenium tests should verify the pages from the user point of
+ * view.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject attr(String attributeName, Object value) {
return AttrFunction.attr(this, elements, attributeName, value);
}
-
+
/**
- * Get the value of a property for the first element in the set of matched elements.
- *
- * @author acdcjunior
- * @since 0.4.0
+ * Gets the value of a property for the first element in the set of matched elements.
+ *
+ * @since 0.9.0
*/
public T prop(String propertyName) {
- return PropFunction.prop(this, elements, propertyName);
+ return PropFunction.prop(this, elements, propertyName);
}
-
+
/**
- * Set one or more properties for every matched element
- *
- * @author acdcjunior
- * @since 0.4.0
+ * Set one or more properties for every matched element
+ *
+ * Note: If interacting with an <input> element while in a UI test,
+ * it is preferable to use {@link #click()} instead of setting the attributes
+ * through this function, as selenium tests should verify the pages from the user point of
+ * view.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject prop(String propertyName, Object value) {
return PropFunction.prop(this, elements, propertyName, value);
}
-
+
/**
- * Retrieve one of the {@link WebElement} matched by the seleniumQuery object.
- *
+ * Retrieves one of the {@link WebElement} matched by the seleniumQuery object.
+ *
* @param index A zero-based integer indicating which element to retrieve.
- * @return the element at the specified index.
- *
- * @author acdcjunior
- * @since 0.4.0
+ * @return The element at the specified index.
+ *
+ * @since 0.9.0
*/
public WebElement get(int index) {
- return GetFunction.get(elements, index);
+ return GetFunction.get(this, index);
}
-
+
/**
- * Retrieve the {@link WebElement}s matched by the seleniumQuery object.
- *
- * @param index A zero-based integer indicating which element to retrieve.
- * @return the element at the specified index.
- *
- * @author acdcjunior
- * @since 0.4.0
+ * Retrieves the {@link WebElement}s matched by the seleniumQuery object.
+ *
+ * @return The (immutable) list of matched elements.
+ *
+ * @since 0.9.0
*/
public List get() {
- return GetFunction.get(elements);
+ return this.elements;
}
-
+
/**
- * Remove an attribute from each element in the set of matched elements.
+ * Removes an attribute from each element in the set of matched elements.
* It can be a space-separated list of attributes.
- *
- * @author acdcjunior
- * @since 0.4.0
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject removeAttr(String attributeNames) {
return RemoveAttrFunction.removeAttr(this, elements, attributeNames);
}
-
+
/**
- * Get the HTML contents of the first element in the set of matched elements.
- *
- * @author acdcjunior
- * @since 0.4.0
+ * Gets the HTML contents of the first element in the set of matched elements.
+ *
+ * @return The HTML of the first element in the set of matched elements.
+ *
+ * @since 0.9.0
*/
public String html() {
- return HtmlFunction.html(this, elements);
+ return HtmlFunction.html(this);
}
-
+
/**
- * Check the current matched set of elements against a selector and return true if
- * at least one of these elements matches the given arguments.
- *
+ * Checks the current matched set of elements against a selector and returns true
+ * if at least one of these elements matches the given arguments.
+ *
* @param selector A string containing a selector expression to match elements against.
- * @since 0.5.0
+ * @since 0.9.0
*/
public boolean is(String selector) {
return IsFunction.is(this, elements, selector);
}
-
+
/**
- * Determine whether any of the matched elements are assigned the given class.
- *
- * @since 1.0.0
+ * Determines whether any of the matched elements are assigned the given class.
+ *
+ * @since 0.9.0
*/
public boolean hasClass(String className) {
return HasClassFunction.hasClass(this, elements, className);
}
/**
- * Retrieve all the elements contained in the seleniumQuery set, as an array.
- *
- * @since 1.0.0
+ * Retrieves all the elements contained in the seleniumQuery set, as an array.
+ *
+ * @return The set of matched elements as an array.
+ *
+ * @since 0.9.0
*/
public WebElement[] toArray() {
return ToArrayFunction.toArray(this, elements);
}
/**
- * For each element in the set, get the first element that matches the selector by
+ * For each element in the set, gets the first element that matches the selector by
* testing the element itself and traversing up through its ancestors in the DOM tree.
- *
- * @since 1.0.0
+ *
+ * @param selector A selector expression to match elements against.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject closest(String selector) {
return ClosestFunction.closest(this, elements, selector);
}
-
+
/**
- * Trigger the focus event on every element of the matched set.
- *
- * Note: The order of the triggering is the order of the elements in the
+ *
Triggers the focus event on every element of the matched set.
+ *
+ * Note: The order of the triggering is the order of the elements in the
* matched list. The last one will end up with the focus, though all of them
* will have it at some point.
- *
- * @since 1.0.0
+ *
+ * @since 0.9.0
*/
public SeleniumQueryObject focus() {
return FocusFunction.focus(this, elements);
}
-
-}
\ No newline at end of file
+
+ /**
+ * Gets the children of each element in the set of matched elements.
+ *
+ * @return A new SeleniumQueryObject, containing the children of each element in the set of matched elements.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject children() {
+ return ChildrenFunction.children(this, elements);
+ }
+
+ /**
+ * Gets the children of each element in the set of matched elements, filtered by a selector.
+ *
+ * @param selector Selector to filter the children.
+ * @return A new SeleniumQueryObject, containing the children of each element in the set of matched elements.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject children(String selector) {
+ return ChildrenFunction.children(this, elements, selector);
+ }
+
+ /**
+ * Get the parent of each element in the current set of matched elements, optionally filtered by a selector.
+ *
+ * Note that $("html").parent() returns an empty matched set, as <html> has no parent elements.
+ *
+ * @return A new SeleniumQueryObject, containing the parent of each element in the set of matched elements.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject parent() {
+ return ParentFunction.parent(this);
+ }
+
+ /**
+ * Get the parent of each element in the current set of matched elements, filtered by a selector.
+ *
+ *
+ * Note that $("html").parent("selector-Matching-HTML-Element") returns an empty matched set,
+ * as <html> has no parent elements.
+ *
+ * @param selector Selector to filter the parents.
+ * @return A new SeleniumQueryObject, containing the parent of each element in the set of matched elements.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject parent(String selector) {
+ return ParentFunction.parent(this, selector);
+ }
+
+ /**
+ * Attempts to submits, in the current order, every element in the matched set.
+ * If a matched element is a form, or an element within a form, then it will be submitted.
+ * If submitting an element causes the current page to change, then this method will block until
+ * the new page is loaded; as consequence, not all elements may get to be submitted - and will be ignored.
+ *
+ * @throws org.openqa.selenium.NoSuchElementException If a submitted element is not a, or within a, form.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public SeleniumQueryObject submit() {
+ SubmitFunction.submit(this);
+ return this;
+ }
+
+ /**
+ * This method will be removed in {@code 0.10.0}.
+ * @deprecated Use: $("selector").as().select().selectByVisibleText(text);
+ * @param text The visible text to match against.
+ * @return A self reference.
+ * @since 0.9.0
+ */
+ @SuppressWarnings("unused")
+ public SeleniumQueryObject selectOptionByVisibleText(String text) {
+ return as().select().selectByVisibleText(text);
+ }
+
+ /**
+ * This method will be removed in {@code 0.10.0}.
+ * @deprecated Use: $("selector").as().select().selectByValue(value);
+ * @param value The value to match against.
+ * @return A self reference.
+ * @since 0.9.0
+ */
+ @SuppressWarnings("unused")
+ public SeleniumQueryObject selectOptionByValue(String value) {
+ return as().select().selectByValue(value);
+ }
+
+ @Override
+ public String toString() {
+ return this.by.toString();
+ }
+
+}
diff --git a/src/main/java/io/github/seleniumquery/SeleniumQueryStatic.java b/src/main/java/io/github/seleniumquery/SeleniumQueryStatic.java
deleted file mode 100644
index 942a1723..00000000
--- a/src/main/java/io/github/seleniumquery/SeleniumQueryStatic.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package io.github.seleniumquery;
-
-import io.github.seleniumquery.globalfunctions.SeleniumQueryDefaultBrowser;
-
-/**
- * Represents the seleniumQuery global object.
- *
- * @author acdcjunior
- * @since 0.2.0
- */
-public class SeleniumQueryStatic {
-
- /**
- * Series of functions that set the default browser behavior.
- *
- * The default browser is emplyed when a seleniumQuery object is build using $(".selector");.
- * A different browser can be used by using $(anotherDriver, ".selector");
- *
- * @author acdcjunior
- * @since 0.2.0
- */
- public final SeleniumQueryDefaultBrowser browser;
-
- public SeleniumQueryStatic() {
- this.browser = new SeleniumQueryDefaultBrowser();
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/BrowserFunctions.java b/src/main/java/io/github/seleniumquery/browser/BrowserFunctions.java
new file mode 100644
index 00000000..ca9d1a43
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/BrowserFunctions.java
@@ -0,0 +1,140 @@
+/*
+Copyright (c) 2014 seleniumQuery authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package io.github.seleniumquery.browser;
+
+import io.github.seleniumquery.browser.driver.SeleniumQueryDriver;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+
+import static java.lang.String.format;
+
+/**
+ * Set of functionality used both by user-managed browsers and global (static) browser.
+ *
+ * @author acdcjunior
+ * @author ricardo-sc
+ *
+ * @since 0.9.0
+ */
+public class BrowserFunctions {
+
+ private static final Log LOGGER = LogFactory.getLog(BrowserFunctions.class);
+
+ private SeleniumQueryDriver globalDriver = new SeleniumQueryDriver();
+
+ /**
+ * Obtains the seleniumQuery's driver tool instance. Through it you can:
+ *
+ * .get() the current WebDriver instance;
+ * call .use*() methods to change the WebDriver currently used
+ *
+ *
+ * @return The seleniumQuery's driver tool instance.
+ */
+ public SeleniumQueryDriver driver() {
+ return globalDriver;
+ }
+
+ /**
+ * Returns the current URL in the browser.
+ *
+ * @return The currently loaded URL.
+ *
+ * @since 0.9.0
+ */
+ public String url() {
+ return driver().get().getCurrentUrl();
+ }
+
+ /**
+ * Opens the given URL in the default browser.
+ *
+ * @param urlToOpen The URL to be opened. Example: "http://seleniumquery.github.io"
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public BrowserFunctions url(String urlToOpen) {
+ LOGGER.debug(format("Opening URL: %s", urlToOpen));
+ driver().get().get(urlToOpen);
+ return this;
+ }
+
+ /**
+ * Opens the given file as a URL in the browser.
+ *
+ * @param fileToOpenAsURL The file to be opened as URL.
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public BrowserFunctions url(File fileToOpenAsURL) {
+ return url(fileToOpenAsURL.toURI().toString());
+ }
+
+ /**
+ * Performs a pause, instructing the the browser (thread) to wait (sleep) for the time
+ * in milliseconds given.
+ *
+ * $.pause(200); // pauses for 200 milliseconds
+ * $.pause(10 * 1000); // pauses for 10 seconds
+ *
+ *
+ * IMPORTANT: 'Pause' is considered to be a bad design practice. It is better to write code
+ * based on what the user will expect, for that consider exploring the {@code .waitUntil()} functions, such as
+ * in $("#someDivThatShouldComeOut").waitUntil().is(":visible");.
+ *
+ * @param timeToPauseInMillis Pause duration, in milliseconds.
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ @SuppressWarnings("deprecation")
+ public BrowserFunctions pause(long timeToPauseInMillis) {
+ LOGGER.debug(format("Pausing for %d milliseconds.", timeToPauseInMillis));
+ new org.openqa.selenium.interactions.PauseAction(timeToPauseInMillis).perform();
+ return this;
+ }
+
+ /**
+ * Attempts to maximize the window of the current browser/driver.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public BrowserFunctions maximizeWindow() {
+ LOGGER.debug("Maximizing window.");
+ driver().get().manage().window().maximize();
+ return this;
+ }
+
+ /**
+ * Quits the WebDriver in use by this seleniumQuery browser.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public BrowserFunctions quit() {
+ driver().quit();
+ return this;
+ }
+
+}
diff --git a/src/main/java/io/github/seleniumquery/browser/BrowserFunctionsWithDeprecatedFunctions.java b/src/main/java/io/github/seleniumquery/browser/BrowserFunctionsWithDeprecatedFunctions.java
new file mode 100644
index 00000000..71374b9c
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/BrowserFunctionsWithDeprecatedFunctions.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser;
+
+import io.github.seleniumquery.browser.driver.SeleniumQueryDriver;
+import org.openqa.selenium.WebDriver;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * Represents the seleniumQuery global browser instance.
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+@SuppressWarnings({"deprecation", "unused"})
+public class BrowserFunctionsWithDeprecatedFunctions extends BrowserFunctions {
+
+ /**
+ *
+ * Series of functions that set the default browser behavior.
+ *
+ *
+ *
+ * This field has been deprecated.
+ *
+ *
+ *
+ * The new place for the functions that were in this object is either {@code $.} or {@code $.driver()}.
+ * In other words, the function {@code $.browser.function();} will either be {@code $.function();} or
+ * {@code $.driver().function();}.
+ * The name may also have changed. To be sure, check the old function's docs for the new name.
+ *
+ *
+ * @since 0.9.0
+ */
+ @Deprecated public final OldBrowserFunctions browser = new OldBrowserFunctions(this);
+
+ /**
+ * The seleniumQuery browser. Adds several utility functions to the WebDriver class.
+ *
+ * @since 0.9.0
+ *
+ * @deprecated This class refers to a deprecated way of accessing some functions and will be removed by the next release.
+ * You'll find the functions for this object in {@code $.function();} or {@code $.driver().function();} .
+ */
+ public class OldBrowserFunctions {
+
+ private BrowserFunctionsWithDeprecatedFunctions browser;
+ public OldBrowserFunctions(BrowserFunctionsWithDeprecatedFunctions browser) { this.browser = browser; }
+
+ /** @deprecated Use: $.driver();
+ * @return A self reference. */
+ public SeleniumQueryDriver globalDriver() { return browser.driver(); }
+
+ /** @deprecated Use: $.driver().use(webDriverInstance);
+ * @param defaultDriver The new global WebDriver instance.
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriver(WebDriver defaultDriver) { browser.driver().use(defaultDriver); return this; }
+
+ /** @deprecated Use: $.driver().useHtmlUnit();
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsHtmlUnit() { browser.driver().useHtmlUnit(); return this; }
+
+ /** @deprecated Use: $.driver().useFirefox();
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsFirefox() { browser.driver().useFirefox(); return this; }
+
+ /** @deprecated Use: $.driver().useChrome();
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsChrome() { browser.driver().useChrome(); return this; }
+
+ /** @deprecated Use: $.driver().useChrome().withPathToChromeDriver(path);
+ * @param path path to ChromeDriver executable (chromedriver.exe/chromedriver).
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsChrome(String path) { browser.driver().useChrome().withPathToChromeDriver(path); return this; }
+
+ /** @deprecated Use: $.driver().useInternetExplorer();
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsIE() { browser.driver().useInternetExplorer(); return this; }
+
+ /** @deprecated Use: $.driver().useInternetExplorer().withPathToIEDriverServerExe(path);
+ * @param path path to IEDriverServer.exe.
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsIE(String path) { browser.driver().useInternetExplorer().withPathToIEDriverServerExe(path); return this; }
+
+ /** @deprecated Use: $.driver().usePhantomJS();
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsPhantomJS() { browser.driver().usePhantomJS(); return this; }
+
+ /** @deprecated Use: $.driver().usePhantomJS().withPathToPhantomJS(path);
+ * @param path path to PhantomJS executable (phantomjs.exe/phantomjs.exe).
+ * @return A self reference. */
+ public OldBrowserFunctions setDefaultDriverAsPhantomJS(String path) { browser.driver().usePhantomJS().withPathToPhantomJS(path); return this; }
+
+ /** @deprecated Use: $.driver().get();
+ * @return the currently set {@link WebDriver}. */
+ public WebDriver getDefaultDriver() { return browser.driver().get(); }
+
+ /** @deprecated Use: $.driver().quit(); */
+ public void quitDefaultDriver() { browser.driver().quit(); }
+
+ /** @deprecated Use: $.driver().quit(); */
+ public void quitDefaultBrowser() { browser.driver().quit(); }
+
+ /** @deprecated Use: $.driver().quit(); */
+ public void quit() { browser.driver().quit(); }
+
+ /** @deprecated Use: $.url(url);
+ * @param url the URL to be opened. */
+ public void openUrl(String url) { browser.url(url); }
+
+ /** @deprecated Use: $.url(file);
+ * @param file the file to be opened as URL. */
+ public void openUrl(File file) { browser.url(file); }
+
+ /** @deprecated Use: $.url(url);
+ * @param url the URL to be opened. */
+ public void open(String url) { browser.url(url); }
+
+ /** @deprecated Use: $.url(file);
+ * @param file the file to be opened as URL. */
+ public void open(File file) { browser.url(file); }
+
+ /** @deprecated Use: $.url(url);
+ * @param url the URL to be opened. */
+ public void url(String url) { browser.url(url); }
+
+ /** @deprecated Use: $.url(file);
+ * @param file the file to be opened as URL. */
+ public void url(File file) { browser.url(file); }
+
+ /** @deprecated Use: $.url();
+ * @return the currently loaded URL. */
+ public String url() { return browser.url(); }
+
+ /** @deprecated Use: $.url();
+ * @return the currently loaded URL. */
+ public String getCurrentUrl() { return browser.url(); }
+
+ /** @deprecated Use: $.pause(timeToPauseInMillis);
+ * @param timeToWait time to wait
+ * @param timeUnit a given unit of time granularity */
+ public void sleep(int timeToWait, TimeUnit timeUnit) { browser.pause(MILLISECONDS.convert(timeToWait, timeUnit)); }
+
+ /** @deprecated Use: $.pause(timeToPauseInMillis);
+ * @param millis pause duration, in milliseconds. */
+ public void sleep(int millis) { browser.pause(millis); }
+
+ /** @deprecated Use: $.maximizeWindow(); */
+ public void maximizeWindow() {
+ browser.maximizeWindow();
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/DriverBuilder.java b/src/main/java/io/github/seleniumquery/browser/driver/DriverBuilder.java
new file mode 100644
index 00000000..5c35fe0e
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/DriverBuilder.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+/**
+ * Builds {@link WebDriver} instances for SeleniumQueryDriver.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public abstract class DriverBuilder> {
+
+ protected DesiredCapabilities desiredCapabilities;
+
+ @SuppressWarnings("unchecked")
+ public T withCapabilities(DesiredCapabilities desiredCapabilities) {
+ this.desiredCapabilities = desiredCapabilities;
+ return (T) this;
+ }
+
+ protected DesiredCapabilities capabilities(DesiredCapabilities defaultDesiredCapabilities) {
+ if (this.desiredCapabilities == null) {
+ return defaultDesiredCapabilities;
+ }
+ return this.desiredCapabilities;
+ }
+
+ protected void overwriteCapabilityIfValueNotNull(DesiredCapabilities capabilities, String capabilityName, Object value) {
+ if (value != null) {
+ capabilities.setCapability(capabilityName, value);
+ }
+ }
+
+ /**
+ * Builds a WebDriver instance based on the default configurations plus the settings
+ * changed through other method calls.
+ *
+ * IMPORTANT: This method should not be public, as the end-user shouldn't be able
+ * to call "build()" - that'd be confusing.
+ *
+ * @return A WebDriver instance based on the previous configurations.
+ */
+ protected abstract WebDriver build();
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/SeleniumQueryDriver.java b/src/main/java/io/github/seleniumquery/browser/driver/SeleniumQueryDriver.java
new file mode 100644
index 00000000..4e4412bf
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/SeleniumQueryDriver.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver;
+
+import io.github.seleniumquery.SeleniumQueryConfig;
+import io.github.seleniumquery.browser.driver.builders.*;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.WebDriver;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents and manages the {@link WebDriver} instance used a specific
+ * {@link io.github.seleniumquery.SeleniumQueryBrowser}.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class SeleniumQueryDriver {
+
+ private static final Log LOGGER = LogFactory.getLog(SeleniumQueryDriver.class);
+
+ private static final DriverBuilder DEFAULT_DRIVER_BUILDER = new HtmlUnitDriverBuilder();
+
+ private DriverBuilder driverBuilder = DEFAULT_DRIVER_BUILDER;
+
+ private WebDriver webDriver;
+
+ /**
+ *
+ * Sets the argument as the current WebDriver instance, quitting the current one, if exists.
+ *
+ *
+ * @param driver The WebDriver instante to be set as current.
+ */
+ public void use(WebDriver driver) {
+ quitAndClearCurrentWebDriver();
+ this.webDriver = driver;
+ }
+
+ /**
+ * Returns the {@link WebDriver} currently set.
+ *
+ *
+ * If no driver has been set, it builds an instance based on previously set options
+ * through the .use[SomeDriver]() methods.
+ *
+ * If no option has been set before (no .use[SomeDriver]() method was called),
+ * it builds and assigns a HtmlUnitDriver instance as driver and returns it.
+ *
+ *
+ *
+ *
+ * @return the currently set {@link WebDriver}.
+ *
+ * @since 0.9.0
+ */
+ public WebDriver get() {
+ if (webDriver == null) {
+ webDriver = this.driverBuilder.build();
+ this.setDriverTimeout(); // TODO unit test
+ }
+ return webDriver;
+ }
+
+ private void setDriverTimeout() { // TODO unit test
+ this.webDriver.manage().timeouts().implicitlyWait(SeleniumQueryConfig.getGlobalTimeout(), TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Quits, if exists, the WebDriver in use by this seleniumQuery browser.
+ *
+ * @since 0.9.0
+ *
+ * @return A self reference.
+ */
+ public SeleniumQueryDriver quit() {
+ if (webDriver == null) { // TODO unit test
+ LOGGER.warn("Called .quit() before initializing WebDriver, nothing was done.");
+ } else {
+ webDriver.quit();
+ webDriver = null;
+ }
+ return this;
+ }
+
+ /**
+ * Sets {@link org.openqa.selenium.htmlunit.HtmlUnitDriver} as the {@link WebDriver} for this seleniumQuery browser instance.
+ *
+ * @return A {@link HtmlUnitDriverBuilder}, allowing further configuration of the driver.
+ *
+ * @since 0.9.0
+ */
+ public HtmlUnitDriverBuilder useHtmlUnit() {
+ return clearCurrentDriverAndAssignNewBuilder(new HtmlUnitDriverBuilder());
+ }
+
+ private T clearCurrentDriverAndAssignNewBuilder(T htmlUnitDriverBuilder) {
+ quitAndClearCurrentWebDriver();
+ this.driverBuilder = htmlUnitDriverBuilder;
+ return htmlUnitDriverBuilder;
+ }
+
+ private void quitAndClearCurrentWebDriver() {
+ if (this.webDriver != null) {
+ this.webDriver.quit();
+ this.webDriver = null;
+ }
+ }
+
+ /**
+ * Sets {@link org.openqa.selenium.firefox.FirefoxDriver} as the {@link WebDriver} for this seleniumQuery browser instance.
+ *
+ * This method looks for the Firefox binary at the system's PATH variable.
+ *
+ * @return A {@link FirefoxDriverBuilder}, allowing further configuration of the driver.
+ *
+ * @since 0.9.0
+ */
+ public FirefoxDriverBuilder useFirefox() {
+ return clearCurrentDriverAndAssignNewBuilder(new FirefoxDriverBuilder());
+ }
+
+ /**
+ * Sets {@link org.openqa.selenium.chrome.ChromeDriver} as the {@link WebDriver} for this seleniumQuery browser instance.
+ *
+ * Note that the Chrome needs a ChromeDriver Server executable to bridge Selenium to the browser and as such
+ * Selenium must know the path to it. It is a file usually named chromedriver.exe (windows) or chromedriver (linux)
+ * and its latest version can be downloaded from
+ * ChromeDriver's download page . You can also check
+ * seleniumQuery and Chrome Driver wiki page
+ * for the other info.
+ *
+ *
+ * This method looks for the ChromeDriver executable (chromedriver.exe/chromedriver) at the CLASSPATH
+ * (tipically at a {@code resources/} folder of a maven project), at the "webdriver.chrome.driver" system property or at the system's PATH variable.
+ * If you wish to directly specify a path, use:
+ *
+ * $.driver().useChrome().withPathToChromeDriver("other/path/to/chromedriver.exe"); // windows
+ * $.driver().useChrome().withPathToChromeDriver("other/path/to/chromedriver"); // linux
+ *
+ *
+ * @return A {@link ChromeDriverBuilder}, allowing further configuration of the driver.
+ *
+ * @since 0.9.0
+ */
+ public ChromeDriverBuilder useChrome() {
+ return clearCurrentDriverAndAssignNewBuilder(new ChromeDriverBuilder());
+ }
+
+ /**
+ * Sets {@link org.openqa.selenium.ie.InternetExplorerDriver} as the {@link WebDriver} for this seleniumQuery browser instance.
+ *
+ * Note that the {@link org.openqa.selenium.ie.InternetExplorerDriver} needs a server executable to bridge selenium to the browser and,
+ * as such, Selenium must know the path to it. It is a file usually named IEDriverServer.exe and its latest
+ * version can be downloaded from
+ * IEDriverServer's download page -- or check
+ * seleniumQuery and IE Driver wiki page
+ * for the latest info.
+ *
+ *
+ * This method looks for the IEDriverServer.exe at the CLASSPATH (tipically at a {@code resources/} folder of a
+ * maven project), at the "webdriver.ie.driver" system property or at the system's PATH variable.
+ * If you wish to directly specify a path, use:
+ *
+ * $.driver().useChrome().withPathToIEDriverServerExe("other/path/to/IEDriverServer.exe");
+ *
+ *
+ * @return A {@link InternetExplorerDriverBuilder}, allowing further configuration of the driver.
+ *
+ * @since 0.9.0
+ */
+ public InternetExplorerDriverBuilder useInternetExplorer() {
+ return clearCurrentDriverAndAssignNewBuilder(new InternetExplorerDriverBuilder());
+ }
+
+ /**
+ * Sets {@link org.openqa.selenium.phantomjs.PhantomJSDriver} as the {@link WebDriver} for this seleniumQuery browser instance.
+ *
+ * Note that the PhantomJS Driver needs a PhantomJS executable to act as browser and as such
+ * Selenium must know the path to it. It is a file usually named phantomjs.exe (windows) or phantomjs (linux)
+ * and its latest version can be downloaded from
+ * PhantomJS download page .
+ *
+ *
+ * This method looks for the PhantomJS executable (phantomjs.exe/phantomjs) at the CLASSPATH
+ * (tipically at a {@code resources/} folder of a maven project), at the "phantomjs.binary.path" system property or at the system's PATH variable.
+ * If you wish to directly specify a path, use:
+ *
+ * $.driver().usePhantomJS().withPathToPhantomJS("other/path/to/phantomjs.exe"); // windows
+ * $.driver().usePhantomJS().withPathToPhantomJS("other/path/to/phantomjs"); // linux
+ *
+ *
+ * @return A {@link PhantomJSDriverBuilder}, allowing further configuration of the driver.
+ *
+ * @since 0.9.0
+ */
+ public PhantomJSDriverBuilder usePhantomJS() {
+ return clearCurrentDriverAndAssignNewBuilder(new PhantomJSDriverBuilder());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/builders/ChromeDriverBuilder.java b/src/main/java/io/github/seleniumquery/browser/driver/builders/ChromeDriverBuilder.java
new file mode 100644
index 00000000..904d56d7
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/builders/ChromeDriverBuilder.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver.builders;
+
+import io.github.seleniumquery.SeleniumQueryException;
+import io.github.seleniumquery.browser.driver.DriverBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import static io.github.seleniumquery.browser.driver.builders.DriverInstantiationUtils.*;
+import static java.lang.String.format;
+
+/**
+ * Builds {@link ChromeDriver} instances for SeleniumQueryDriver.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class ChromeDriverBuilder extends DriverBuilder {
+
+ private static final Log LOGGER = LogFactory.getLog(ChromeDriverBuilder.class);
+
+ private static final String CHROME_DRIVER_EXECUTABLE_SYSTEM_PROPERTY = "webdriver.chrome.driver";
+
+ private static final String EXCEPTION_MESSAGE = " \nDownload the latest release at http://chromedriver.storage.googleapis.com/index.html and place it: \n" +
+ "(1) on the classpath of this project; or\n" +
+ "(2) on the path specified by the \"" + CHROME_DRIVER_EXECUTABLE_SYSTEM_PROPERTY + "\" system property; or\n" +
+ "(3) on a folder in the system's PATH variable; or\n" +
+ "(4) wherever and set the path via $.driver().useChrome().withPathToChromeDriver(\"other/path/to/chromedriver<.exe>\").\n" +
+ "For more information, see https://github.com/seleniumQuery/seleniumQuery/wiki/seleniumQuery-and-Chrome-Driver";
+
+ private static final String BAD_PATH_PROVIDED_EXCEPTION_MESSAGE = "The ChromeDriver Server executable file was not found (or is a directory) at \"%s\"." + EXCEPTION_MESSAGE;
+
+ // not final so they can be changed during test
+ public static String CHROMEDRIVER_EXECUTABLE_WINDOWS = "chromedriver.exe";
+ public static String CHROMEDRIVER_EXECUTABLE_LINUX = "chromedriver";
+
+
+ private String customPathToChromeDriver;
+ private ChromeOptions chromeOptions;
+
+ /**
+ * Sets specific {@link ChromeOptions} options to be used in the {@link ChromeDriver}.
+ *
+ * @param chromeOptions Options to be used.
+ * @return A self reference, allowing further configuration.
+ *
+ * @since 0.9.0
+ */
+ public ChromeDriverBuilder withOptions(ChromeOptions chromeOptions) {
+ this.chromeOptions = chromeOptions;
+ return this;
+ }
+
+ /**
+ * Configures the builder to look for the ChromeDriver executable (chromedriver.exe/chromedriver) at
+ * the specified path.
+ *
+ * @param pathToChromeDriver The path to the executable server file. Examples:
+ * "C:\myFiles\chromedriver.exe"; can be relative, as in "..\stuff\chromedriver";
+ * does not matter if the executable was renamed, such as "wherever/myself/drivers/chromedriver_v12345.exe".
+ *
+ * @return A self reference, allowing further configuration.
+ *
+ * @since 0.9.0
+ */
+ public ChromeDriverBuilder withPathToChromeDriver(String pathToChromeDriver) {
+ this.customPathToChromeDriver = pathToChromeDriver;
+ return this;
+ }
+
+ @Override
+ protected WebDriver build() {
+ DesiredCapabilities capabilities = capabilities(DesiredCapabilities.chrome());
+ overwriteCapabilityIfValueNotNull(capabilities, ChromeOptions.CAPABILITY, this.chromeOptions);
+
+ if (customPathWasProvidedAndExecutableExistsThere(this.customPathToChromeDriver, BAD_PATH_PROVIDED_EXCEPTION_MESSAGE)) {
+ setExecutableSystemProperty(getFullPath(this.customPathToChromeDriver));
+ } else if (executableExistsInClasspath(CHROMEDRIVER_EXECUTABLE_WINDOWS)) {
+ setExecutableSystemProperty(getFullPathForFileInClasspath(CHROMEDRIVER_EXECUTABLE_WINDOWS));
+ } else if (executableExistsInClasspath(CHROMEDRIVER_EXECUTABLE_LINUX)) {
+ setExecutableSystemProperty(getFullPathForFileInClasspath(CHROMEDRIVER_EXECUTABLE_LINUX));
+ }
+ try {
+ return new ChromeDriver(capabilities);
+ } catch (IllegalStateException e) {
+ throwCustomExceptionIfExecutableWasNotFound(e);
+ throw e;
+ }
+ }
+
+ private void setExecutableSystemProperty(String executableFullPath) {
+ LOGGER.debug("Loading ChromeDriver executable from "+executableFullPath);
+ System.setProperty(CHROME_DRIVER_EXECUTABLE_SYSTEM_PROPERTY, executableFullPath);
+ }
+
+ private void throwCustomExceptionIfExecutableWasNotFound(IllegalStateException e) {
+ if (e.getMessage().contains("path to the driver executable must be set")) {
+ throw new SeleniumQueryException(
+ format(
+ "The ChromeDriver server executable (%s/%s) was not found in the classpath, in the \"%s\" system property or in the system's PATH variable. %s",
+ CHROMEDRIVER_EXECUTABLE_WINDOWS, CHROMEDRIVER_EXECUTABLE_LINUX, CHROME_DRIVER_EXECUTABLE_SYSTEM_PROPERTY, EXCEPTION_MESSAGE
+ ), e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/builders/DriverInstantiationUtils.java b/src/main/java/io/github/seleniumquery/browser/driver/builders/DriverInstantiationUtils.java
new file mode 100644
index 00000000..2a1c91f7
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/builders/DriverInstantiationUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver.builders;
+
+import io.github.seleniumquery.SeleniumQueryException;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import static java.lang.String.format;
+
+/**
+ * Utilities for instantiating drivers.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class DriverInstantiationUtils {
+
+ public static String getFullPathForFileInClasspath(String executableFileName) {
+ String slashExecutableFileName = "/" + executableFileName;
+ URL executableFileInTheClassPathUrl = DriverInstantiationUtils.class.getResource(slashExecutableFileName);
+ if (executableFileInTheClassPathUrl == null) {
+ return DriverInstantiationUtils.class.getResource("/").getPath() + slashExecutableFileName;
+ }
+ return executableFileInTheClassPathUrl.getPath();
+ }
+
+ static String getFullPath(String file) {
+ try {
+ File driverServerExecutableFile = new File(file);
+ return driverServerExecutableFile.getCanonicalPath();
+ } catch (IOException e) {
+ throw new SeleniumQueryException("Unable to get canonical path for "+file, e);
+ }
+ }
+
+ static boolean isValidFile(File driverServerExecutableFile) {
+ return driverServerExecutableFile.exists() && !driverServerExecutableFile.isDirectory();
+ }
+
+ static boolean executableExistsInClasspath(String file) {
+ String strPath = getFullPathForFileInClasspath(file);
+ File driverServerExecutableFile = new File(strPath);
+ return isValidFile(driverServerExecutableFile);
+ }
+
+ static boolean customPathWasProvidedAndExecutableExistsThere(String pathToExecutable, String exceptionMessage) {
+ boolean customPathWasProvided = pathToExecutable != null;
+ if (!customPathWasProvided) {
+ return false;
+ }
+ File driverServerExecutableFile = new File(pathToExecutable);
+ if (!isValidFile(driverServerExecutableFile)) {
+ throw new SeleniumQueryException(format(exceptionMessage, getFullPath(pathToExecutable)));
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/builders/FirefoxDriverBuilder.java b/src/main/java/io/github/seleniumquery/browser/driver/builders/FirefoxDriverBuilder.java
new file mode 100644
index 00000000..45b7ebdf
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/builders/FirefoxDriverBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver.builders;
+
+import io.github.seleniumquery.browser.driver.DriverBuilder;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.firefox.FirefoxProfile;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+/**
+ * Builds {@link FirefoxDriver} instances for SeleniumQueryDriver.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class FirefoxDriverBuilder extends DriverBuilder {
+
+ private Boolean enableJavaScript;
+ private FirefoxProfile firefoxProfile;
+
+ /**
+ * Configures Firefox to have JavaScript disabled.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public FirefoxDriverBuilder withoutJavaScript() {
+ this.enableJavaScript = false;
+ return this;
+ }
+
+ /**
+ * Sets specific {@link FirefoxProfile} to be used in the {@link FirefoxDriver}.
+ *
+ * @param firefoxProfile Profile to be used.
+ * @return A self reference, allowing further configuration.
+ *
+ * @since 0.9.0
+ */
+ public FirefoxDriverBuilder withProfile(FirefoxProfile firefoxProfile) {
+ this.firefoxProfile = firefoxProfile;
+ return this;
+ }
+
+ @Override
+ protected WebDriver build() {
+ DesiredCapabilities capabilities = capabilities(DesiredCapabilities.firefox());
+
+ FirefoxProfile profile = this.firefoxProfile != null ? this.firefoxProfile : new FirefoxProfile();
+ if (enableJavaScript != null) {
+ profile.setPreference("javascript.enabled", this.enableJavaScript);
+ }
+ capabilities.setCapability(FirefoxDriver.PROFILE, profile);
+
+ return new FirefoxDriver(capabilities);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/builders/HtmlUnitDriverBuilder.java b/src/main/java/io/github/seleniumquery/browser/driver/builders/HtmlUnitDriverBuilder.java
new file mode 100644
index 00000000..f3e49948
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/builders/HtmlUnitDriverBuilder.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver.builders;
+
+import io.github.seleniumquery.browser.driver.DriverBuilder;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.htmlunit.HtmlUnitDriver;
+import org.openqa.selenium.remote.BrowserType;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import static org.openqa.selenium.remote.CapabilityType.*;
+
+/**
+ * Builds {@link HtmlUnitDriver} instances for SeleniumQueryDriver.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class HtmlUnitDriverBuilder extends DriverBuilder {
+
+ private static final String DEFAULT_EMULATED_BROWSER_NAME = BrowserType.CHROME;
+ private static final String DEFAULT_EMULATED_BROWSER_VERSION = null; // HtmlUnit does not use version when browser is chrome
+
+ private String emulatedBrowserName = DEFAULT_EMULATED_BROWSER_NAME;
+ private String emulatedBrowserVersion = DEFAULT_EMULATED_BROWSER_VERSION;
+
+ private Boolean javaScriptEnabled;
+
+ /**
+ * Configures HtmlUnit to have JavaScript disabled.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public HtmlUnitDriverBuilder withoutJavaScript() {
+ this.javaScriptEnabled = false;
+ return this;
+ }
+
+ /**
+ * Configures HtmlUnit to have JavaScript enabled.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public HtmlUnitDriverBuilder withJavaScript() {
+ this.javaScriptEnabled = true;
+ return this;
+ }
+
+ /**
+ * Configures HtmlUnit to emulate latest available Firefox version.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public HtmlUnitDriverBuilder emulatingFirefox() {
+ this.emulatedBrowserName = BrowserType.FIREFOX;
+ return this;
+ }
+
+ /**
+ * Configures HtmlUnit to emulate Chrome.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public HtmlUnitDriverBuilder emulatingChrome() {
+ this.emulatedBrowserName = BrowserType.CHROME;
+ return this;
+ }
+
+ /**
+ * Configures HtmlUnit to emulate latest available Internet Explorer version.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public HtmlUnitDriverBuilder emulatingInternetExplorer() {
+ return emulatingInternetExplorer11();
+ }
+
+ /**
+ * Configures HtmlUnit to emulate Internet Explorer 11.
+ *
+ * @return A self reference.
+ *
+ * @since 0.9.0
+ */
+ public HtmlUnitDriverBuilder emulatingInternetExplorer11() {
+ this.emulatedBrowserName = BrowserType.IE;
+ this.emulatedBrowserVersion = "11";
+ return this;
+ }
+
+ /**
+ * Configures HtmlUnit to emulate Internet Explorer 9.
+ *
+ * @return A self reference.
+ * @deprecated as of HtmlUnit 2.14
+ *
+ * @since 0.9.0
+ */
+ @SuppressWarnings("deprecation")
+ public HtmlUnitDriverBuilder emulatingInternetExplorer9() {
+ this.emulatedBrowserName = BrowserType.IE;
+ this.emulatedBrowserVersion = "9";
+ return this;
+ }
+
+ /**
+ * Configures HtmlUnit to emulate Internet Explorer 8.
+ *
+ * @return A self reference.
+ * @deprecated as of HtmlUnit 2.14
+ *
+ * @since 0.9.0
+ */
+ @SuppressWarnings("deprecation")
+ public HtmlUnitDriverBuilder emulatingInternetExplorer8() {
+ this.emulatedBrowserName = BrowserType.IE;
+ this.emulatedBrowserVersion = "8";
+ return this;
+ }
+
+ @Override
+ protected WebDriver build() {
+ DesiredCapabilities capabilities = capabilities(DesiredCapabilities.htmlUnitWithJs());
+ overwriteCapabilityIfValueNotNull(capabilities, BROWSER_NAME, this.emulatedBrowserName);
+ overwriteCapabilityIfValueNotNull(capabilities, VERSION, this.emulatedBrowserVersion);
+ overwriteCapabilityIfValueNotNull(capabilities, SUPPORTS_JAVASCRIPT, this.javaScriptEnabled);
+ return new HtmlUnitDriver(capabilities);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/builders/InternetExplorerDriverBuilder.java b/src/main/java/io/github/seleniumquery/browser/driver/builders/InternetExplorerDriverBuilder.java
new file mode 100644
index 00000000..7c7b3f23
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/builders/InternetExplorerDriverBuilder.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver.builders;
+
+import io.github.seleniumquery.SeleniumQueryException;
+import io.github.seleniumquery.browser.driver.DriverBuilder;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.ie.InternetExplorerDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.SessionNotFoundException;
+
+import static io.github.seleniumquery.browser.driver.builders.DriverInstantiationUtils.*;
+import static java.lang.String.format;
+
+/**
+ * Builds {@link InternetExplorerDriver} instances for SeleniumQueryDriver.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class InternetExplorerDriverBuilder extends DriverBuilder {
+
+ private static final String IE_DRIVER_EXECUTABLE_SYSTEM_PROPERTY = "webdriver.ie.driver";
+
+ private static final String EXCEPTION_MESSAGE = " \nDownload the latest release at http://selenium-release.storage.googleapis.com/index.html and place it: \n" +
+ "(1) on the classpath of this project; or\n" +
+ "(2) on the path specified by the \"" + IE_DRIVER_EXECUTABLE_SYSTEM_PROPERTY + "\" system property; or\n" +
+ "(3) on a folder in the system's PATH variable; or\n" +
+ "(4) wherever and set the path via $.driver().useInternetExplorer().withPathToIEDriverServerExe(\"other/path/to/IEDriverServer.exe\").\n" +
+ "For more information, see https://github.com/seleniumQuery/seleniumQuery/wiki/seleniumQuery-and-IE-Driver";
+
+ private static final String BAD_PATH_PROVIDED_EXCEPTION_MESSAGE = "The IEDriverServer executable file was not found (or is a directory) at \"%s\"." + EXCEPTION_MESSAGE;
+
+ public static String IEDRIVERSERVER_EXE = "IEDriverServer.exe"; // not final so it can be changed during test
+
+ private String customPathToIEDriverServerExe;
+
+ /**
+ * Configures the builder to look for the IEDriverServer.exe at the path specified by the argument.
+ *
+ * @param pathToIEDriverServerExe The path to the executable server file. Examples:
+ * "C:\\myFiles\\IEDriverServer.exe"; can be relative, as in "..\\stuff\\IEDriverServer.exe";
+ * does not matter if the executable was renamed, such as "drivers\\ie\\iedriverserver_v12345.exe".
+ *
+ * @return A self reference, allowing further configuration.
+ *
+ * @since 0.9.0
+ */
+ public InternetExplorerDriverBuilder withPathToIEDriverServerExe(String pathToIEDriverServerExe) {
+ this.customPathToIEDriverServerExe = pathToIEDriverServerExe;
+ return this;
+ }
+
+ @Override
+ protected WebDriver build() {
+ DesiredCapabilities capabilities = capabilities(DesiredCapabilities.chrome());
+
+ if (customPathWasProvidedAndExecutableExistsThere(this.customPathToIEDriverServerExe , BAD_PATH_PROVIDED_EXCEPTION_MESSAGE)) {
+ System.setProperty(IE_DRIVER_EXECUTABLE_SYSTEM_PROPERTY, getFullPath(this.customPathToIEDriverServerExe));
+ } else if (DriverInstantiationUtils.executableExistsInClasspath(IEDRIVERSERVER_EXE)) {
+ System.setProperty(IE_DRIVER_EXECUTABLE_SYSTEM_PROPERTY, getFullPathForFileInClasspath(IEDRIVERSERVER_EXE));
+ }
+ try {
+ return new InternetExplorerDriver(capabilities);
+ } catch (IllegalStateException e) {
+ throwCustomExceptionIfExecutableNotFound(e);
+ throw e;
+ } catch (SessionNotFoundException e) {
+ throwCustomExceptionIfProtectedModeMustBeTheSame(e);
+ throw e;
+ }
+ }
+
+ private void throwCustomExceptionIfExecutableNotFound(IllegalStateException e) {
+ if (e.getMessage().contains("path to the driver executable must be set")) {
+ throw new SeleniumQueryException(
+ format(
+ "The IEDriverServer executable (%s) was not found in the classpath, in the \"%s\" system property or in the system's PATH variable.%s",
+ IEDRIVERSERVER_EXE, IE_DRIVER_EXECUTABLE_SYSTEM_PROPERTY, EXCEPTION_MESSAGE
+ ), e);
+ }
+ }
+
+ private void throwCustomExceptionIfProtectedModeMustBeTheSame(SessionNotFoundException e) {
+ String message = e.getLocalizedMessage();
+ if (message != null && message.contains("Protected Mode")) {
+ throw new SeleniumQueryException("IE Driver requires Protected Mode settings to be the same for all zones. Go to\n\t\t" +
+ "'Tools' -> 'Internet Options' -> 'Security Tab', and set all zones to the same protected mode," +
+ " be it enabled or disabled, does not matter.\n\t\t" +
+ "If this does not solve the problem, or for more info, check our IE Driver wiki page at: " +
+ "https://github.com/seleniumQuery/seleniumQuery/wiki/seleniumQuery-and-IE-Driver", e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/browser/driver/builders/PhantomJSDriverBuilder.java b/src/main/java/io/github/seleniumquery/browser/driver/builders/PhantomJSDriverBuilder.java
new file mode 100644
index 00000000..e3d90c86
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/browser/driver/builders/PhantomJSDriverBuilder.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.browser.driver.builders;
+
+import io.github.seleniumquery.SeleniumQueryException;
+import io.github.seleniumquery.browser.driver.DriverBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.phantomjs.PhantomJSDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import static io.github.seleniumquery.browser.driver.builders.DriverInstantiationUtils.*;
+import static java.lang.String.format;
+
+/**
+ * Builds {@link PhantomJSDriver} instances for SeleniumQueryDriver.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class PhantomJSDriverBuilder extends DriverBuilder {
+
+ private static final Log LOGGER = LogFactory.getLog(PhantomJSDriverBuilder.class);
+
+ private static final String PHANTOMJS_EXECUTABLE_SYSTEM_PROPERTY = "phantomjs.binary.path";
+
+ private static final String EXCEPTION_MESSAGE = " \nDownload the latest release at http://phantomjs.org/download.html and place it: \n" +
+ "(1) on the classpath of this project; or\n" +
+ "(2) on the path specified by the \"" + PHANTOMJS_EXECUTABLE_SYSTEM_PROPERTY + "\" system property; or\n" +
+ "(3) on a folder in the system's PATH variable; or\n" +
+ "(4) on the path set in the \""+PHANTOMJS_EXECUTABLE_SYSTEM_PROPERTY+"\" capability; or\n" +
+ "(5) wherever and set the path via $.driver.usePhantomJS().withPathToPhantomJS(\"other/path/to/phantomjs<.exe>\").\n" +
+ "For more information, see https://github.com/seleniumQuery/seleniumQuery/wiki/seleniumQuery-and-PhantomJS-Driver";
+
+ private static final String BAD_PATH_PROVIDED_EXCEPTION_MESSAGE = "The PhantomJS executable file was not found (or is a directory) at \"%s\"." + EXCEPTION_MESSAGE;
+
+ // not final so they can be changed during test
+ public static String PHANTOMJS_EXECUTABLE_WINDOWS = "phantomjs.exe";
+ public static String PHANTOMJS_EXECUTABLE_LINUX = "phantomjs";
+
+ private String customPathToPhantomJs;
+
+ /**
+ * Sets the path used by the PhantomJSDriver to find the PhantomJS executable.
+ * @param pathToPhantomJs Path to PhantomJS executable (phantomjs.exe/phantomjs.exe).
+ * @return A self reference.
+ */
+ public PhantomJSDriverBuilder withPathToPhantomJS(String pathToPhantomJs) {
+ this.customPathToPhantomJs = pathToPhantomJs;
+ return this;
+ }
+
+ @Override
+ protected WebDriver build() {
+ DesiredCapabilities capabilities = capabilities(new DesiredCapabilities());
+
+ if (customPathWasProvidedAndExecutableExistsThere(this.customPathToPhantomJs, BAD_PATH_PROVIDED_EXCEPTION_MESSAGE)) {
+ setExecutableSystemProperty(getFullPath(this.customPathToPhantomJs));
+ } else if (executableExistsInClasspath(PHANTOMJS_EXECUTABLE_WINDOWS)) {
+ setExecutableSystemProperty(getFullPathForFileInClasspath(PHANTOMJS_EXECUTABLE_WINDOWS));
+ } else if (executableExistsInClasspath(PHANTOMJS_EXECUTABLE_LINUX)) {
+ setExecutableSystemProperty(getFullPathForFileInClasspath(PHANTOMJS_EXECUTABLE_LINUX));
+ }
+ try {
+ return new PhantomJSDriver(capabilities);
+ } catch (IllegalStateException e) {
+ throwCustomExceptionIfExecutableWasNotFound(e);
+ throw e;
+ }
+ }
+
+ private void setExecutableSystemProperty(String executableFullPath) {
+ LOGGER.debug("Loading PhantomJS executable from "+executableFullPath);
+ System.setProperty(PHANTOMJS_EXECUTABLE_SYSTEM_PROPERTY, executableFullPath);
+ }
+
+ private void throwCustomExceptionIfExecutableWasNotFound(IllegalStateException e) {
+ if (e.getMessage().contains("path to the driver executable must be set")) {
+ throw new SeleniumQueryException(
+ format(
+ "The PhantomJS executable (%s/%s) was not found in the classpath, in the \"%s\" system property or in the system's PATH variable. %s",
+ PHANTOMJS_EXECUTABLE_WINDOWS, PHANTOMJS_EXECUTABLE_LINUX, PHANTOMJS_EXECUTABLE_SYSTEM_PROPERTY, EXCEPTION_MESSAGE
+ ), e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/DriverVersionUtils.java b/src/main/java/io/github/seleniumquery/by/DriverVersionUtils.java
new file mode 100644
index 00000000..9397370a
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/DriverVersionUtils.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.By;
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.htmlunit.HtmlUnitDriver;
+import org.openqa.selenium.phantomjs.PhantomJSDriver;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Contains some utility functions for dealing with WebDrivers, such as inspecting their version.
+ *
+ * @author acdcjunior
+ * @author ricardo-sc
+ *
+ * @since 0.9.0
+ */
+public class DriverVersionUtils {
+
+ private static DriverVersionUtils instance = new DriverVersionUtils();
+
+ public static DriverVersionUtils getInstance() {
+ return instance;
+ }
+
+ // for test purposes
+ public static void setInstance(DriverVersionUtils instance) {
+ DriverVersionUtils.instance = instance;
+ }
+
+ private static final Log LOGGER = LogFactory.getLog(DriverVersionUtils.class);
+
+ private Map> driverSupportedPseudos = new HashMap>();
+
+ public boolean hasNativeSupportForPseudo(WebDriver driver, String pseudoClass) {
+ Map driverMap = getDriverMap(driver);
+ Boolean supports = driverMap.get(pseudoClass);
+ if (supports == null) {
+ boolean supported = testPseudoClassNativeSupport(pseudoClass, driver);
+ driverMap.put(pseudoClass, supported);
+ return supported;
+ }
+ return supports;
+ }
+
+ private boolean testPseudoClassNativeSupport(String pseudo, SearchContext context) {
+ try {
+ By.cssSelector("#AAA_SomeIdThatShouldNotExist"+pseudo).findElements(context);
+ return true;
+ } catch (Exception ignored) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the Map for the given driver, initializing it if necessary.
+ */
+ private Map getDriverMap(WebDriver driver) {
+ Map driverMap = driverSupportedPseudos.get(driver);
+ if (driverMap == null) {
+ driverMap = new HashMap();
+ driverSupportedPseudos.put(driver, driverMap);
+ }
+ return driverMap;
+ }
+
+ public static boolean isHtmlUnitDriverEmulatingIE(WebDriver driver) {
+ return DriverVersionUtils.getInstance().isHtmlUnitDriver(driver) && getEmulatedBrowser((HtmlUnitDriver) driver).startsWith("IE");
+ }
+
+ public static boolean isHtmlUnitDriverEmulatingIEBelow11(WebDriver driver) {
+ if (!DriverVersionUtils.getInstance().isHtmlUnitDriver(driver)) {
+ return false;
+ }
+ String emulatedBrowser = getEmulatedBrowser((HtmlUnitDriver) driver);
+ try {
+ int ieVersion = Integer.parseInt(emulatedBrowser.substring(2));
+ return ieVersion < 11;
+ } catch (NumberFormatException e) {
+ LOGGER.debug("Error while inspecting HtmlUnitDriver IE version.", e);
+ return false;
+ }
+ }
+
+ private static String getEmulatedBrowser(HtmlUnitDriver htmlUnitDriver) {
+ try {
+ // #HtmlUnit #reflection #hack
+ Method getWebClientMethod = HtmlUnitDriver.class.getDeclaredMethod("getWebClient");
+ boolean wasAccessibleBefore = getWebClientMethod.isAccessible();
+ getWebClientMethod.setAccessible(true);
+ WebClient webClient = (WebClient) getWebClientMethod.invoke(htmlUnitDriver);
+ getWebClientMethod.setAccessible(wasAccessibleBefore);
+ return webClient.getBrowserVersion().toString();
+ } catch (Exception e) {
+ LOGGER.debug("Error while inspecting HtmlUnitDriver version.", e);
+ return "";
+ }
+ }
+
+ public boolean isPhantomJSDriver(WebDriver driver) {
+ return driver instanceof PhantomJSDriver;
+ }
+
+ public boolean isHtmlUnitDriver(WebDriver driver) {
+ return driver instanceof HtmlUnitDriver;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selector/SelectorUtils.java b/src/main/java/io/github/seleniumquery/by/SelectorUtils.java
similarity index 61%
rename from src/main/java/io/github/seleniumquery/selector/SelectorUtils.java
rename to src/main/java/io/github/seleniumquery/by/SelectorUtils.java
index 10d3fe45..88f2af88 100644
--- a/src/main/java/io/github/seleniumquery/selector/SelectorUtils.java
+++ b/src/main/java/io/github/seleniumquery/by/SelectorUtils.java
@@ -1,163 +1,231 @@
-package io.github.seleniumquery.by.evaluator;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.commons.lang3.StringEscapeUtils;
-import org.openqa.selenium.By;
-import org.openqa.selenium.SearchContext;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.internal.WrapsDriver;
-
-public class SelectorUtils {
-
- /**
- * Sequence to be used in regexes to prevent matching escaped symbols.
- *
- *
- * For instance:
- * String myRegex = ESCAPED_SLASHES + ":pseudo"
- *
- *
- * Will NOT match the string "\:pseudo", as it will consider the ":"
- * to be escaped and thus the "pseudo" will not really be a :pseudo but
- * a string ":pseudo".
- *
- *
- * On the other hande, it WILL match the string "\\:pseudo", because it considers
- * the char before a "\" by itself and thus not a escaping chat to the ":".
- *
- */
- public static final String ESCAPED_SLASHES = "(? to be displayed. Firefox and Chrome don't consider it visible, so we don't.
- return element.isDisplayed() && !element.getTagName().equals("title");
- }
-
- public static List itselfWithSiblings(WebElement element) {
- WebElement parent = SelectorUtils.parent(element);
- // if parent is null, then element is , thus have no siblings
- if (parent == null) {
- return new ArrayList(Arrays.asList(element));
- }
- return parent.findElements(By.xpath("./*"));
- }
-
- public static List getPreviousSiblings(WebElement elementItSelf) {
- List itselfWithSiblings = SelectorUtils.itselfWithSiblings(elementItSelf);
-
- List previousSiblings = new ArrayList();
- for (WebElement siblingOrItSelf : itselfWithSiblings) {
- if (elementItSelf.equals(siblingOrItSelf)) {
- break;
- }
- previousSiblings.add(siblingOrItSelf);
- }
- return previousSiblings;
- }
-
- public static WebElement getPreviousSibling(WebElement element) {
- List previousSiblings = getPreviousSiblings(element);
- int siblingCount = previousSiblings.size();
- // no previous siblings
- if (siblingCount == 0) {
- return null;
- }
- return previousSiblings.get(siblingCount-1);
- }
-
- /**
- * Escapes a CSS identifier.
- *
- * Future references for CSS escaping:
- * http://mathiasbynens.be/notes/css-escapes
- * http://mothereff.in/css-escapes
- * https://github.com/mathiasbynens/mothereff.in/blob/master/css-escapes/eff.js
- */
- public static String escapeSelector(String unescapedSelector) {
- String escapedSelector = unescapedSelector;
- if (Character.isDigit(unescapedSelector.charAt(0))) {
- escapedSelector = "\\\\3"+unescapedSelector.charAt(0)+" "+escapedSelector.substring(1);
- }
- escapedSelector = escapedSelector.replace(":", "\\:");
- if (escapedSelector.charAt(0) == '-') {
- if (escapedSelector.length() == 1
- ||
- (escapedSelector.length() > 1 && (Character.isDigit(escapedSelector.charAt(1)) || escapedSelector.charAt(1) == '-'))
- ) {
- escapedSelector = "\\"+escapedSelector;
- }
- }
- return escapedSelector;
- }
-
- /**
- * Escapes the attributes values into a valid CSS string.
- * Deals with the way the CSS parser gives the attributes' values to us.
- */
- public static String escapeAttributeValue(String attributeValue) {
- // " comes escaped already, so we unescape them (otherwise the next step will escape its \)
- attributeValue = attributeValue.replace("\\\"", "\"");
- // now we escape all \
- attributeValue = attributeValue.replace("\\", "\\\\");
- // and escape "
- attributeValue = attributeValue.replace("\"", "\\\"");
- // finally, surround with "s
- return '"'+attributeValue+'"';
- }
-
- public static WebDriver getWebDriver(SearchContext context) {
- if (context instanceof WebDriver) {
- return (WebDriver) context;
- }
- if (context instanceof WrapsDriver) {
- return ((WrapsDriver) context).getWrappedDriver();
- }
- throw new RuntimeException("We don't know how to extract the WebDriver from this context: "
- +context.getClass()+" -> "+context);
- }
-
- /**
- * Escapes the attributes values into a valid CSS string.
- * Deals with the way the CSS parser gives the attributes' values to us.
- */
- public static String unescapeString(String stringValue) {
- String escapedString = stringValue;
- char firstChar = stringValue.charAt(0);
- if (firstChar == '\'' || firstChar == '"') {
- escapedString = StringEscapeUtils.unescapeEcmaScript(stringValue);
- escapedString = escapedString.substring(1, escapedString.length()-1);
- }
- return escapedString;
- }
-
+package io.github.seleniumquery.by;
+
+import com.gargoylesoftware.htmlunit.html.HtmlElement;
+import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.By;
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.htmlunit.HtmlUnitWebElement;
+import org.openqa.selenium.internal.WrapsDriver;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class SelectorUtils {
+
+ private static final Log LOGGER = LogFactory.getLog(SelectorUtils.class);
+
+ /**
+ * Sequence to be used in regexes to prevent matching escaped symbols.
+ *
+ *
+ * For instance:
+ * String myRegex = ESCAPED_SLASHES + ":pseudo"
+ *
+ *
+ * Will NOT match the string "\:pseudo", as it will consider the ":"
+ * to be escaped and thus the "pseudo" will not really be a :pseudo but
+ * a string ":pseudo".
+ *
+ *
+ * On the other hande, it WILL match the string "\\:pseudo", because it considers
+ * the char before a "\" by itself and thus not a escaping chat to the ":".
+ *
+ */
+ public static final String ESCAPED_SLASHES = "(?." +
+ " Still, it could be something else, thus this logging.", e);
+ return null;
+ }
+ }
+
+ public static List parents(WebElement element) {
+ return element.findElements(By.xpath("ancestor::node()[count(ancestor-or-self::html) > 0]"));
+ }
+
+ public static String lang(WebElement element) {
+ WebElement currElement = element;
+ while (currElement != null) {
+ String lang = currElement.getAttribute("lang");
+ // #Cross-Driver
+ // Absent lang attribute returns:
+ // - null in HtmlUnitDriver
+ // - "" in FirefoxDriver
+ if (lang != null && !lang.isEmpty()) {
+ return lang;
+ }
+ currElement = SelectorUtils.parent(currElement);
+ }
+ return null;
+ }
+
+ public static boolean hasAttribute(WebElement element, String localName) {
+ return element.getAttribute(localName) != null;
+ }
+
+ public static boolean isVisible(WebElement element) {
+ boolean elementIsNotVisible = !element.isDisplayed();
+ if (elementIsNotVisible) {
+ return false;
+ }
+ // at this point, it is visible!
+
+ // And either we are not in HtmlUnitDriver
+ if (!(element instanceof HtmlUnitWebElement)) {
+ return true; // in that case, if the driver said it was visible, then it is
+ }
+ // ...or we are in HtmlUnitDriver.
+
+ // #Cross-Driver
+ // HtmlUnitDriver, says some elements are visible when they arent. So we must do some additional
+ // checking to correct its result.
+
+ // CHECKING #1: if JS is OFF, HtmlUnitDriver says everyone is visible, when that's not the case!
+ // so we do some force-checking. If it finds the element is not visible, we return as so.
+ if (!isHtmlUnitWebElementReallyDisplayed(element)) {
+ return false;
+ }
+
+ // CHECKING #2: If all verification above passed, then we still think it is visible.
+ // The last checking is seeing if the given element is under :
+ // Firefox and Chrome don't consider elements directly under (such as, commonly, , , etc.,
+ // EXCEPT for ) to be visible, so we filter them, because HtmlUnitDriver thinks they ARE visible.
+ boolean isBodyOrChildOfBody = !element.findElements(By.xpath("ancestor-or-self::body")).isEmpty();
+ // in other words, it is visible only if it is or if it has as its parent
+ return isBodyOrChildOfBody;
+ }
+
+ private static boolean isHtmlUnitWebElementReallyDisplayed(WebElement webElement) {
+ try {
+ // #Cross-Driver #HtmlUnit #reflection #hack
+ HtmlUnitWebElement htmlUnitWebElement = (HtmlUnitWebElement) webElement;
+ Method getElementMethod = HtmlUnitWebElement.class.getDeclaredMethod("getElement");
+ getElementMethod.setAccessible(true);
+ HtmlElement htmlElement = (HtmlElement) getElementMethod.invoke(htmlUnitWebElement);
+ return !(htmlElement instanceof HtmlHiddenInput) && htmlElement.isDisplayed();
+ } catch (Exception e) {
+ LOGGER.debug("Unable to retrieve real HtmlUnitWebElement#isDisplayed().", e);
+ return true;
+ }
+ }
+
+ public static List itselfWithSiblings(WebElement element) {
+ WebElement parent = SelectorUtils.parent(element);
+ // if parent is null, then element is , thus have no siblings
+ if (parent == null) {
+ return new ArrayList(Arrays.asList(element));
+ }
+ return getDirectChildren(parent);
+ }
+
+ public static List getDirectChildren(WebElement parent) {
+ return parent.findElements(By.xpath("./*"));
+ }
+
+ public static List getPreviousSiblings(WebElement elementItSelf) {
+ List itselfWithSiblings = SelectorUtils.itselfWithSiblings(elementItSelf);
+
+ List previousSiblings = new ArrayList();
+ for (WebElement siblingOrItSelf : itselfWithSiblings) {
+ if (elementItSelf.equals(siblingOrItSelf)) {
+ break;
+ }
+ previousSiblings.add(siblingOrItSelf);
+ }
+ return previousSiblings;
+ }
+
+ public static WebElement getPreviousSibling(WebElement element) {
+ List previousSiblings = getPreviousSiblings(element);
+ int siblingCount = previousSiblings.size();
+ // no previous siblings
+ if (siblingCount == 0) {
+ return null;
+ }
+ return previousSiblings.get(siblingCount-1);
+ }
+
+ /**
+ * Escapes a CSS identifier.
+ *
+ * Future references for CSS escaping:
+ * http://mathiasbynens.be/notes/css-escapes
+ * http://mothereff.in/css-escapes
+ * https://github.com/mathiasbynens/mothereff.in/blob/master/css-escapes/eff.js
+ */
+ public static String escapeSelector(String unescapedSelector) {
+ String escapedSelector = unescapedSelector;
+ if (Character.isDigit(unescapedSelector.charAt(0))) {
+ escapedSelector = "\\\\3"+unescapedSelector.charAt(0)+" "+escapedSelector.substring(1);
+ }
+ escapedSelector = escapedSelector.replace(":", "\\:");
+ if (escapedSelector.charAt(0) == '-') {
+ if (escapedSelector.length() == 1
+ ||
+ (escapedSelector.length() > 1 && (Character.isDigit(escapedSelector.charAt(1)) || escapedSelector.charAt(1) == '-'))
+ ) {
+ escapedSelector = "\\"+escapedSelector;
+ }
+ }
+ return escapedSelector;
+ }
+
+ /**
+ * Escapes the attributes values into a valid CSS string.
+ * Deals with the way the CSS parser gives the attributes' values to us.
+ */
+ public static String escapeAttributeValue(String attributeValue) {
+ // " comes escaped already, so we unescape them (otherwise the next step will escape its \)
+ attributeValue = attributeValue.replace("\\\"", "\"");
+ // now we escape all \
+ attributeValue = attributeValue.replace("\\", "\\\\");
+ // and escape "
+ attributeValue = attributeValue.replace("\"", "\\\"");
+ // finally, surround with "s
+ return '"'+attributeValue+'"';
+ }
+
+ public static WebDriver getWebDriver(SearchContext context) {
+ if (context instanceof WebDriver) {
+ return (WebDriver) context;
+ }
+ if (context instanceof WrapsDriver) {
+ return ((WrapsDriver) context).getWrappedDriver();
+ }
+ throw new RuntimeException("We don't know how to extract the WebDriver from this context: "
+ +context.getClass()+" -> "+context);
+ }
+
+ /**
+ * Escapes the attributes values into a valid CSS string.
+ * Deals with the way the CSS parser gives the attributes' values to us.
+ */
+ public static String unescapeString(String stringValue) {
+ String escapedString = stringValue;
+ char firstChar = stringValue.charAt(0);
+ if (firstChar == '\'' || firstChar == '"') {
+ escapedString = StringEscapeUtils.unescapeEcmaScript(stringValue);
+ escapedString = escapedString.substring(1, escapedString.length()-1);
+ }
+ return escapedString;
+ }
+
+ public static String intoEscapedXPathString(String value) {
+ if (value.indexOf('\'') == -1) {
+ return "'" + value + "'";
+ }
+ return "concat('" + value.replace("'", "', \"'\", '") + "')";
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/SeleniumQueryBy.java b/src/main/java/io/github/seleniumquery/by/SeleniumQueryBy.java
index 0d074115..83c9229f 100644
--- a/src/main/java/io/github/seleniumquery/by/SeleniumQueryBy.java
+++ b/src/main/java/io/github/seleniumquery/by/SeleniumQueryBy.java
@@ -1,145 +1,148 @@
-package io.github.seleniumquery.by;
-
-import io.github.seleniumquery.by.evaluator.SelectorUtils;
-import io.github.seleniumquery.by.selector.CompiledSelectorList;
-import io.github.seleniumquery.by.selector.SeleniumQueryCssCompiler;
-
-import java.util.List;
-
-import org.openqa.selenium.By;
-import org.openqa.selenium.SearchContext;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-
-/**
- * This By is a combination of the By.xpath and By.css, where the css is also enhanced with some of
- * sizzle's selectors.
- *
- *
- * @author acdcjunior
- * @since 0.2.0
- */
-public class SeleniumQueryBy extends By {
-
- /**
- * A By to be used in an element created with no By. Attempting to filter elements through this By
- * will throw a RuntimeException.
- *
- * @author acdcjunior
- * @since 0.3.0
- */
- public static final SeleniumQueryBy NO_SELECTOR_INVALID_BY = new SeleniumQueryBy(null) {
- @Override public List findElements(SearchContext context) {
- throw new RuntimeException("This object was instantiated without a selector, you cannot search " +
- "elements based on it as the string used to match it is unavailable.\n" +
- "Try not using more than one .queryUntil() in a single line.");
- }
- };
-
- /**
- * Enhanced selector is not just the CSS selector, it also supports XPath expressions and some
- * Sizzle enhancements.
- *
- * @author acdcjunior
- * @since 0.2.0
- */
- public static SeleniumQueryBy byEnhancedSelector(final String selector) {
- if (selector == null) {
- throw new IllegalArgumentException("Cannot find elements when the selector is null");
- }
- return new SeleniumQueryBy(selector);
- }
-
- private final String selector;
- private boolean selectorIsXPathExpression;
-
-
- /**
- * Constructs a SeleniumQueryBy for the given selector.
- * @param selector the selector you need the elements to match.
- *
- * @author acdcjunior
- * @since 0.2.0
- */
- private SeleniumQueryBy(String selector) {
- this.selector = selector;
- this.selectorIsXPathExpression = isXPathExpression(selector);
- }
-
-
- /**
- * Returns the list of elements that match this By in the given context.
- * @return the list of elements that match this By in the given context.
- *
- * @author acdcjunior
- * @since 0.2.0
- */
- @Override
- public List findElements(SearchContext context) {
- if (this.selectorIsXPathExpression) {
- return new By.ByXPath(this.selector).findElements(context);
- }
- return this.enhancedCssFindElements(context);
- }
-
- /**
- * Returns the string representation of this By.
- * @return the string representation of this By.
- *
- * @author acdcjunior
- * @since 0.2.0
- */
- @Override
- public String toString() {
- return "$(\"" + selector + "\")";
- }
-
- /**
- * If it begins with "/" or "(/" or "(((((/", we assume the selector given is a XPath expression.
- */
- private boolean isXPathExpression(String selector) {
- return selector != null && selector.matches("(\\s+\\(\\s+)*/.*");
- }
-
- /**
- * Zero-based.
- *
- * @param position
- * @return the selector string for the element at position.
- *
- * @author acdcjunior
- * @since 0.2.0
- */
- public String getSelectorForElementAtPosition(int position) {
- if (this.selectorIsXPathExpression) {
- // notice that, in XPath, the position is one-based.
- return "("+this.selector+")["+ (position+1) +"]";
- }
- return this.selector+":eq("+position+")";
- }
-
- /**
- * Compiles the selector for the given context (the context
- * will determinate what selectors are natively supported and what selectors should
- * be handled by SeleniumQuery) and matches elements based on it.
- *
- * @since 0.6.2
- */
- private List enhancedCssFindElements(SearchContext context) {
- WebDriver driver = SelectorUtils.getWebDriver(context);
- CompiledSelectorList compileSelectorList = SeleniumQueryCssCompiler.compileSelectorList(driver, this.selector);
- return compileSelectorList.execute(context);
- }
-
- /**
- * Returns the selector used in the creation of this By.
- * @return the selector used in the creation of this By.
- *
- * @author acdcjunior
- * @since 0.3.0
- */
- public String getSelector() {
- return this.selector;
- }
-
-}
\ No newline at end of file
+/*
+Copyright (c) 2014 seleniumQuery authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package io.github.seleniumquery.by;
+
+import io.github.seleniumquery.SeleniumQueryException;
+import io.github.seleniumquery.by.xpath.TagComponentList;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import org.openqa.selenium.By;
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * This By is a combination of the By.xpath and By.css, where the CSS3, XPath, jQuery/Sizzle and others
+ * selectors are supported.
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class SeleniumQueryBy extends By {
+
+ /**
+ * A {@link By} to be used in an element created with no By. Attempting to filter elements through this
+ * will throw an exception.
+ * @since 0.9.0
+ */
+ public static final SeleniumQueryBy NO_SELECTOR_INVALID_BY = new SeleniumQueryBy(null) {
+ @Override public List findElements(SearchContext context) {
+ throw new SeleniumQueryException("This object was instantiated without a selector, you cannot search " +
+ "elements based on it as the string used to match it is unavailable.\n" +
+ "Try not using more than one .waitUntil() in a single line.");
+ }
+ };
+
+ private static final String STARTING_BRACES = "(\\s*\\(\\s*)*";
+ private static final String XPATH_AXES = "ancestor|ancestor-or-self|attribute|child|descendant|descendant-or-self|following|following-sibling|parent|preceding|preceding-sibling|self";
+
+ private static final Pattern XPATH_EXPRESSION_PATTERN = Pattern.compile(STARTING_BRACES + "(/|(" + XPATH_AXES + ")::).*");
+
+ /**
+ * Enhanced selector is not just the CSS selector, it also supports XPath expressions and some
+ * Sizzle enhancements.
+ *
+ * @since 0.9.0
+ */
+ public static SeleniumQueryBy byEnhancedSelector(final String selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("Cannot find elements when the selector is null");
+ }
+ return new SeleniumQueryBy(selector);
+ }
+
+ private final String selector;
+ private boolean selectorIsXPathExpression;
+
+
+ /**
+ * Constructs a SeleniumQueryBy for the given selector.
+ * @param selector the selector you need the elements to match.
+ */
+ private SeleniumQueryBy(String selector) {
+ this.selector = selector;
+ this.selectorIsXPathExpression = isXPathExpression(selector);
+ }
+
+
+ /**
+ * Returns the list of elements that match this By in the given context.
+ * @return the list of elements that match this By in the given context.
+ * @since 0.9.0
+ */
+ @Override
+ public List findElements(SearchContext context) {
+ if (this.selectorIsXPathExpression) {
+ return new By.ByXPath(this.selector).findElements(context);
+ }
+ return this.enhancedCssFindElements(context);
+ }
+
+ /**
+ * Returns the string representation of this By.
+ * @return the string representation of this By.
+ * @since 0.9.0
+ */
+ @Override
+ public String toString() {
+ return "$(\"" + selector + "\")";
+ }
+
+ /**
+ * If it begins with "/" or "(/" or "(((((/" or an XPath Axis, we assume the selector given is a XPath expression.
+ */
+ static boolean isXPathExpression(String selector) {
+ return selector != null && XPATH_EXPRESSION_PATTERN.matcher(selector).matches();
+ }
+
+ /**
+ * Zero-based.
+ *
+ * @param position Zero-based index of the wanted element.
+ * @return the selector string for the element at position.
+ * @since 0.9.0
+ */
+ public String getSelectorForElementAtPosition(int position) {
+ if (this.selectorIsXPathExpression) {
+ // notice that, in XPath, the position is one-based.
+ return "("+this.selector+")["+ (position+1) +"]";
+ }
+ return this.selector+":eq("+position+")";
+ }
+
+ /**
+ * Compiles the selector for the given context (the context
+ * will determinate what selectors are natively supported and what selectors should
+ * be handled by SeleniumQuery) and matches elements based on it.
+ */
+ private List enhancedCssFindElements(SearchContext context) {
+ TagComponentList xPathLocator = XPathComponentCompilerService.compileSelectorList(this.selector);
+ return xPathLocator.findWebElements(context);
+ }
+
+ /**
+ * Returns the selector used in the creation of this By.
+ * @return the selector used in the creation of this By.
+ * @since 0.9.0
+ */
+ public String getSelector() {
+ return this.selector;
+ }
+
+}
diff --git a/src/main/java/io/github/seleniumquery/by/WebElementUtils.java b/src/main/java/io/github/seleniumquery/by/WebElementUtils.java
new file mode 100644
index 00000000..722cfbe0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/WebElementUtils.java
@@ -0,0 +1,80 @@
+package io.github.seleniumquery.by;
+
+import org.openqa.selenium.WebElement;
+
+/**
+ * Several functions for dealing with {@link WebElement}s.
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class WebElementUtils {
+
+ public static boolean elementHasAttribute(WebElement element, String attributeName, String attributeValue) {
+ return attributeValue.equalsIgnoreCase(element.getAttribute(attributeName));
+ }
+
+ public static boolean isTextareaTag(WebElement element) {
+ return "textarea".equals(element.getTagName());
+ }
+
+ public static boolean isSelectTag(WebElement element) {
+ return "select".equals(element.getTagName());
+ }
+
+ public static boolean isOptionTag(WebElement element) {
+ return "option".equals(element.getTagName());
+ }
+
+ public static boolean isInputTag(WebElement element) {
+ return "input".equals(element.getTagName());
+ }
+
+ public static boolean isInputFileTag(WebElement element) {
+ return isInputTagWithType(element, "file");
+ }
+
+ private static boolean isInputTagWithType(WebElement element, String file) {
+ return isInputTag(element) && elementHasAttribute(element, "type", file);
+ }
+
+ public static boolean isInputRadioTag(WebElement element) {
+ return isInputTagWithType(element, "radio");
+ }
+
+ public static boolean isInputCheckboxTag(WebElement element) {
+ return isInputTagWithType(element, "checkbox");
+ }
+
+ public static boolean isInputHiddenTag(WebElement element) {
+ return isInputTagWithType(element, "hidden");
+ }
+
+ public static boolean isInputButtonTag(WebElement element) {
+ return isInputTagWithType(element, "button");
+ }
+
+ public static boolean isInputSubmitTag(WebElement element) {
+ return isInputTagWithType(element, "submit");
+ }
+
+ public static boolean isContentEditable(WebElement element) {
+ if (element == null) {
+ return false;
+ }
+ String contenteditable = element.getAttribute("contenteditable");
+ if ("false".equalsIgnoreCase(contenteditable)) {
+ return false;
+ }
+ if ("".equals(contenteditable) || "true".equalsIgnoreCase(contenteditable)) {
+ return true;
+ }
+ // no contenteditable; or
+ // concontenteditable == "inherit"; or
+ // concontenteditable == "anything";
+ // then we consider as "inherit"
+ return isContentEditable(SelectorUtils.parent(element));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selector/CssConditionalSelector.java b/src/main/java/io/github/seleniumquery/by/css/CssConditionalSelector.java
similarity index 50%
rename from src/main/java/io/github/seleniumquery/selector/CssConditionalSelector.java
rename to src/main/java/io/github/seleniumquery/by/css/CssConditionalSelector.java
index ef663e0b..5345579a 100644
--- a/src/main/java/io/github/seleniumquery/selector/CssConditionalSelector.java
+++ b/src/main/java/io/github/seleniumquery/by/css/CssConditionalSelector.java
@@ -1,18 +1,17 @@
-package io.github.seleniumquery.by.evaluator;
-
-import java.util.Map;
-
-import io.github.seleniumquery.by.selector.CompiledSelector;
+package io.github.seleniumquery.by.css;
+import io.github.seleniumquery.by.xpath.component.ConditionComponent;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.w3c.css.sac.Condition;
import org.w3c.css.sac.Selector;
-public interface CSSCondition {
+import java.util.Map;
+
+public interface CssConditionalSelector {
boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selector, T condtition);
- CompiledSelector compileCondition(WebDriver driver, Map stringMap, Selector selector, T condition);
+ C conditionToXPath(Map stringMap, Selector simpleSelector, T condition);
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selector/CssSelector.java b/src/main/java/io/github/seleniumquery/by/css/CssSelector.java
similarity index 50%
rename from src/main/java/io/github/seleniumquery/selector/CssSelector.java
rename to src/main/java/io/github/seleniumquery/by/css/CssSelector.java
index a9576a2b..ccd5e618 100644
--- a/src/main/java/io/github/seleniumquery/selector/CssSelector.java
+++ b/src/main/java/io/github/seleniumquery/by/css/CssSelector.java
@@ -1,18 +1,25 @@
-package io.github.seleniumquery.by.evaluator;
-
-
-import java.util.Map;
-
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-
-public interface CSSSelector extends SqCSSCompiler {
-
- /**
- * Tests if the given element, under the given driver, matches the selector.
- * @param stringMap TODO
- * @param stringMap map of strings that were extracted from the selector
- */
- boolean is(WebDriver driver, WebElement element, Map stringMap, T selector);
-
+package io.github.seleniumquery.by.css;
+
+import io.github.seleniumquery.by.xpath.component.XPathComponent;
+
+import java.util.Map;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * A selector, capable of being transformed into {@link XPathComponent},
+ * or testing if a given {@link WebElement} matched it.
+ */
+public interface CssSelector {
+
+ /**
+ * Tests if the given element, under the given driver, matches the selector.
+ *
+ * @param stringMap map of strings that were extracted from the selector
+ */
+ boolean is(WebDriver driver, WebElement element, Map stringMap, T selector);
+
+ C toXPath(Map stringMap, T selector);
+
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/CssSelectorFactory.java b/src/main/java/io/github/seleniumquery/by/css/CssSelectorFactory.java
new file mode 100644
index 00000000..abc578ae
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/CssSelectorFactory.java
@@ -0,0 +1,64 @@
+package io.github.seleniumquery.by.css;
+
+import io.github.seleniumquery.by.css.combinators.DescendantCssSelector;
+import io.github.seleniumquery.by.css.combinators.DirectAdjacentCssSelector;
+import io.github.seleniumquery.by.css.combinators.DirectDescendantCssSelector;
+import io.github.seleniumquery.by.css.combinators.GeneralAdjacentCssSelector;
+import io.github.seleniumquery.by.css.conditionals.ConditionalCssSelector;
+import io.github.seleniumquery.by.css.tagname.TagNameSelector;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import io.github.seleniumquery.by.xpath.component.XPathComponent;
+import org.w3c.css.sac.Selector;
+
+/**
+ * Picks a high level CssSelector based on the Selector type.
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class CssSelectorFactory {
+
+ private static final ConditionalCssSelector conditionalCssSelector = new ConditionalCssSelector();
+ private static final TagNameSelector tagNameSelector = new TagNameSelector();
+ private static final DescendantCssSelector descendantCssSelector = new DescendantCssSelector();
+ private static final DirectDescendantCssSelector directDescendantCssSelector = new DirectDescendantCssSelector();
+ private static final DirectAdjacentCssSelector directAdjacentCssSelector = new DirectAdjacentCssSelector();
+ private static final GeneralAdjacentCssSelector generalAdjacentCssSelector = new GeneralAdjacentCssSelector();
+
+ @SuppressWarnings("unchecked")
+ public static CssSelector parsedSelectorToCssSelector(Selector parsedSimpleSelector) {
+ return (CssSelector) CssSelectorFactory.getSelector(parsedSimpleSelector);
+ }
+
+ private static CssSelector extends Selector, ? extends XPathComponent> getSelector(Selector selector) {
+ switch (selector.getSelectorType()) {
+ case Selector.SAC_CONDITIONAL_SELECTOR:
+ return conditionalCssSelector;
+
+ case Selector.SAC_ELEMENT_NODE_SELECTOR:
+ return tagNameSelector;
+
+ // COMBINATORS
+ case Selector.SAC_DESCENDANT_SELECTOR:
+ return descendantCssSelector;
+ case Selector.SAC_CHILD_SELECTOR:
+ return directDescendantCssSelector;
+ case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
+ return directAdjacentCssSelector;
+ // the parser returns this code for the "E ~ F" selector. Go figure...
+ case Selector.SAC_ANY_NODE_SELECTOR:
+ return generalAdjacentCssSelector;
+
+ case Selector.SAC_ROOT_NODE_SELECTOR:
+ case Selector.SAC_NEGATIVE_SELECTOR:
+ case Selector.SAC_TEXT_NODE_SELECTOR:
+ case Selector.SAC_CDATA_SECTION_NODE_SELECTOR:
+ case Selector.SAC_PROCESSING_INSTRUCTION_NODE_SELECTOR:
+ case Selector.SAC_COMMENT_NODE_SELECTOR:
+ case Selector.SAC_PSEUDO_ELEMENT_SELECTOR:
+ default:
+ return new UnknownCssSelector(selector.getSelectorType());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/CssSelectorMatcherService.java b/src/main/java/io/github/seleniumquery/by/css/CssSelectorMatcherService.java
new file mode 100644
index 00000000..5f12beec
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/CssSelectorMatcherService.java
@@ -0,0 +1,32 @@
+package io.github.seleniumquery.by.css;
+
+import io.github.seleniumquery.by.preparser.CSSParsedSelectorList;
+import io.github.seleniumquery.by.preparser.CSSSelectorParser;
+
+import java.util.Map;
+
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SelectorList;
+
+public class CssSelectorMatcherService {
+
+ public static boolean elementMatchesStringSelector(WebDriver driver, WebElement element, String selector) {
+ CSSParsedSelectorList CSSParsedSelectorList = CSSSelectorParser.parseSelector(selector);
+ SelectorList selectorList = CSSParsedSelectorList.getSelectorList();
+ for (int i = 0; i < selectorList.getLength(); i++) {
+ if (CssSelectorMatcherService.elementMatchesSelector(driver, element, CSSParsedSelectorList.getStringMap(), selectorList.item(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean elementMatchesSelector(WebDriver driver, WebElement element, Map stringMap, Selector selector) {
+ CssSelector cssSelector = CssSelectorFactory.parsedSelectorToCssSelector(selector);
+ return cssSelector.is(driver, element, stringMap, selector);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/UnknownCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/UnknownCssSelector.java
new file mode 100644
index 00000000..3bb41653
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/UnknownCssSelector.java
@@ -0,0 +1,36 @@
+package io.github.seleniumquery.by.css;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.Map;
+
+/**
+ * Represents an unknown CSS selector type.
+ */
+public class UnknownCssSelector implements CssSelector {
+
+ private static final Log LOGGER = LogFactory.getLog(UnknownCssSelector.class);
+
+ private short type;
+
+ public UnknownCssSelector(short type) {
+ this.type = type;
+ }
+
+ @Override
+ public boolean is(WebDriver driver, WebElement element, Map stringMap, T selector) {
+ throw new RuntimeException("CSS "+selector.getClass().getSimpleName()+" of type "+type+" is invalid or not supported!");
+ }
+
+ @Override
+ public ConditionSimpleComponent toXPath(Map stringMap, T selector) {
+ // if it is unknown, we can't convert it, so we simply ignore it
+ LOGGER.warn("CSS Selector '"+selector+"' is unknown. Ignoring it.");
+ return new ConditionSimpleComponent();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/attributes/AttributeEvaluatorUtils.java b/src/main/java/io/github/seleniumquery/by/css/attributes/AttributeEvaluatorUtils.java
new file mode 100644
index 00000000..2933bebb
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/AttributeEvaluatorUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import org.w3c.css.sac.AttributeCondition;
+
+/**
+ * Utility methods for XPath attributes.
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class AttributeEvaluatorUtils {
+
+ /**
+ * Returns a XPath expression that finds the value of the {@code type} attribute and return
+ * its lowercased value.
+ */
+ public static final String TYPE_ATTR_LC_VAL = toLowerCase(toXPathAttribute("type"));
+
+ public static String getXPathAttribute(AttributeCondition attributeCondition) {
+ String attributeName = attributeCondition.getLocalName();
+ return toXPathAttribute(attributeName);
+ }
+
+ public static String toXPathAttribute(String attributeName) {
+ String escapedAttributeName = SelectorUtils.intoEscapedXPathString(attributeName).toLowerCase();
+ return "@*[" + toLowerCase("name()") + " = " + escapedAttributeName + "]";
+ }
+
+ public static String toLowerCase(String s) {
+ return "translate(" + s + ", 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/attributes/ClassAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/ClassAttributeCssSelector.java
new file mode 100644
index 00000000..1ab5ac9c
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/ClassAttributeCssSelector.java
@@ -0,0 +1,49 @@
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * .class
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class ClassAttributeCssSelector implements CssConditionalSelector {
+
+ private static final String CLASS_ATTRIBUTE = "class";
+
+ /**
+ * see {@link org.w3c.css.sac.Condition#SAC_CLASS_CONDITION}
+ *
+ * This condition checks for a specified class. Example: .example
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ if (!SelectorUtils.hasAttribute(element, CLASS_ATTRIBUTE)) {
+ return false;
+ }
+ String wantedClassName = attributeCondition.getValue();
+ String classAttributeValue = element.getAttribute(CLASS_ATTRIBUTE);
+ return Arrays.asList(classAttributeValue.split("\\s+")).contains(wantedClassName);
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String wantedClassName = attributeCondition.getValue();
+ String unescapedClassName = StringEscapeUtils.unescapeJava(wantedClassName);
+ // nothing to do, everyone supports filtering by class
+ return new ConditionSimpleComponent("[contains(concat(' ', normalize-space(@class), ' '), ' " + unescapedClassName + " ')]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsPrefixAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsPrefixAttributeCssSelector.java
new file mode 100644
index 00000000..820d54e8
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsPrefixAttributeCssSelector.java
@@ -0,0 +1,48 @@
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
+import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
+
+/**
+ * [languages|="fr"]
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class ContainsPrefixAttributeCssSelector implements CssConditionalSelector {
+
+ /**
+ * see {@link org.w3c.css.sac.Condition#SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION}
+ *
+ * This condition checks if the value is in a hypen-separated list of values in a specified attribute. example:
+ * [languages|="fr"]
+ *
+ * Case INsensitve
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ String wantedValue = attributeCondition.getValue();
+ String actualValue = element.getAttribute(attributeCondition.getLocalName());
+ return equalsIgnoreCase(actualValue, wantedValue) || startsWithIgnoreCase(actualValue, wantedValue+'-');
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String attributeName = AttributeEvaluatorUtils.getXPathAttribute(attributeCondition);
+ String wantedValue = SelectorUtils.intoEscapedXPathString(attributeCondition.getValue());
+ String wantedValueWithSuffix = SelectorUtils.intoEscapedXPathString(attributeCondition.getValue() + "-");
+ return new ConditionSimpleComponent("[(" + attributeName + " = " + wantedValue + " or starts-with(" + attributeName + ", " + wantedValueWithSuffix + "))]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsSubstringAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsSubstringAttributeCssSelector.java
new file mode 100644
index 00000000..52169882
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsSubstringAttributeCssSelector.java
@@ -0,0 +1,50 @@
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
+
+/**
+ * [attribute*=stringToContain]
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class ContainsSubstringAttributeCssSelector implements CssConditionalSelector {
+
+ /**
+ * Currently it is (mistakenly?) mapped to the type {@link org.w3c.css.sac.Condition#SAC_ATTRIBUTE_CONDITION}.
+ * The factory then inspects the actual type and redirects here.
+ *
+ * This selector is:
+ * [attribute*=stringToContain]
+ *
+ * CASE INsensitive!
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ String attributeName = attributeCondition.getLocalName();
+ if (!SelectorUtils.hasAttribute(element, attributeName)) {
+ return false;
+ }
+ String wantedValue = attributeCondition.getValue();
+ String attributeValue = element.getAttribute(attributeName);
+ return containsIgnoreCase(attributeValue, wantedValue);
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String attributeName = AttributeEvaluatorUtils.getXPathAttribute(attributeCondition);
+ String wantedValue = SelectorUtils.intoEscapedXPathString(attributeCondition.getValue());
+ return new ConditionSimpleComponent("[contains(" + attributeName + ", " + wantedValue + ")]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/attributes/ContainsWordAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsWordAttributeCssSelector.java
similarity index 51%
rename from src/main/java/io/github/seleniumquery/selectors/attributes/ContainsWordAttributeCssSelector.java
rename to src/main/java/io/github/seleniumquery/by/css/attributes/ContainsWordAttributeCssSelector.java
index 87acbfb6..a566b3cc 100644
--- a/src/main/java/io/github/seleniumquery/selectors/attributes/ContainsWordAttributeCssSelector.java
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/ContainsWordAttributeCssSelector.java
@@ -1,59 +1,59 @@
-package io.github.seleniumquery.by.evaluator.conditionals.attributes;
-
-import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
-
-import java.util.Map;
-
-import io.github.seleniumquery.by.evaluator.CSSCondition;
-import io.github.seleniumquery.by.evaluator.SelectorUtils;
-import io.github.seleniumquery.by.selector.CompiledSelector;
-
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-import org.w3c.css.sac.AttributeCondition;
-import org.w3c.css.sac.Selector;
-
-public class ContainsWordAttributeEvaluator implements CSSCondition {
-
- private static final ContainsWordAttributeEvaluator instance = new ContainsWordAttributeEvaluator();
- public static ContainsWordAttributeEvaluator getInstance() {
- return instance;
- }
- private ContainsWordAttributeEvaluator() { }
-
-
- public static final String CONTAINS_WORD_ATTRIBUTE_SELECTOR_SYMBOL = "~=";
-
- /**
- * @see {@link org.w3c.css.sac.Condition#SAC_ONE_OF_ATTRIBUTE_CONDITION}
- *
- * This condition checks for a value in a space-separated values in a specified attribute example:
- * [values~="10"]
- *
- * Case INsensitive!
- */
- @Override
- public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
- String attributeName = attributeCondition.getLocalName();
- if (!SelectorUtils.hasAttribute(element, attributeName)) {
- return false;
- }
- String wantedValue = attributeCondition.getValue();
- String attributeValue = element.getAttribute(attributeName);
- String[] values = attributeValue.split(" ");
- for (String value : values) {
- if (equalsIgnoreCase(value, wantedValue)){
- return true;
- }
- }
- return false;
- }
-
- @Override
- public CompiledSelector compileCondition(WebDriver driver, Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
- // nothing to do, everyone supports this selector
- return AttributeEvaluatorUtils.createAttributeNoFilterCompiledSelector(attributeCondition,
- CONTAINS_WORD_ATTRIBUTE_SELECTOR_SYMBOL);
- }
-
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
+
+/**
+ * [values~="10"]
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class ContainsWordAttributeCssSelector implements CssConditionalSelector {
+
+ public static final String CONTAINS_WORD_ATTRIBUTE_SELECTOR_SYMBOL = "~=";
+
+ /**
+ * see {@link org.w3c.css.sac.Condition#SAC_ONE_OF_ATTRIBUTE_CONDITION}
+ *
+ * This condition checks for a value in a space-separated values in a specified attribute example:
+ * [values~="10"]
+ *
+ * Case INsensitive!
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ String attributeName = attributeCondition.getLocalName();
+ if (!SelectorUtils.hasAttribute(element, attributeName)) {
+ return false;
+ }
+ String wantedValue = attributeCondition.getValue();
+ String attributeValue = element.getAttribute(attributeName);
+ String[] values = attributeValue.split(" ");
+ for (String value : values) {
+ if (equalsIgnoreCase(value, wantedValue)){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String attributeName = AttributeEvaluatorUtils.getXPathAttribute(attributeCondition);
+ String wantedValueSurroundedBySpaces = SelectorUtils.intoEscapedXPathString(" " + attributeCondition.getValue() + " ");
+
+ return new ConditionSimpleComponent("[contains(concat(' ', normalize-space(" + attributeName + "), ' '), " + wantedValueSurroundedBySpaces + ")]");
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/attributes/EndsWithAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/EndsWithAttributeCssSelector.java
similarity index 51%
rename from src/main/java/io/github/seleniumquery/selectors/attributes/EndsWithAttributeCssSelector.java
rename to src/main/java/io/github/seleniumquery/by/css/attributes/EndsWithAttributeCssSelector.java
index acba5a2d..dd3eefe5 100644
--- a/src/main/java/io/github/seleniumquery/selectors/attributes/EndsWithAttributeCssSelector.java
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/EndsWithAttributeCssSelector.java
@@ -1,25 +1,25 @@
-package io.github.seleniumquery.by.evaluator.conditionals.attributes;
-
-import static org.apache.commons.lang3.StringUtils.endsWith;
-
-import java.util.Map;
-
-import io.github.seleniumquery.by.evaluator.CSSCondition;
-import io.github.seleniumquery.by.selector.CompiledSelector;
+package io.github.seleniumquery.by.css.attributes;
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.w3c.css.sac.AttributeCondition;
import org.w3c.css.sac.Selector;
-public class EndsWithAttributeEvaluator implements CSSCondition {
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.endsWith;
+
+/**
+ * [attribute$=stringToEnd]
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class EndsWithAttributeCssSelector implements CssConditionalSelector {
- private static final EndsWithAttributeEvaluator instance = new EndsWithAttributeEvaluator();
- public static EndsWithAttributeEvaluator getInstance() {
- return instance;
- }
- private EndsWithAttributeEvaluator() { }
-
public static final String ENDS_WITH_ATTRIBUTE_SELECTOR_SYMBOL = "$=";
/**
@@ -39,9 +39,11 @@ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
- // nothing to do, everyone supports this selector
- return AttributeEvaluatorUtils.createAttributeNoFilterCompiledSelector(attributeCondition, ENDS_WITH_ATTRIBUTE_SELECTOR_SYMBOL);
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String attributeName = AttributeEvaluatorUtils.getXPathAttribute(attributeCondition);
+ String attrValue = attributeCondition.getValue();
+ String wantedValue = SelectorUtils.intoEscapedXPathString(attrValue);
+ return new ConditionSimpleComponent("[substring(" + attributeName + ", string-length(" + attributeName + ")-" + (attrValue.length() - 1) + ") = " + wantedValue + "]");
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/attributes/EqualsOrHasAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/EqualsOrHasAttributeCssSelector.java
new file mode 100644
index 00000000..0fcc792c
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/EqualsOrHasAttributeCssSelector.java
@@ -0,0 +1,66 @@
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
+
+/**
+ * [simple]
+ * [restart="never"]
+ *
+ * #Cross-Driver
+ * Who knows why, HtmlUnitDriver, while emulating IE, bugs on the selector: [title="a\\tc"]
+ * So we should never allow HtmlUnitDriver+Emulating IE to handle attribute selectors natively...
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class EqualsOrHasAttributeCssSelector implements CssConditionalSelector {
+
+ public static final String EQUALS_ATTRIBUTE_SELECTOR_SYMBOL = "=";
+
+ /**
+ * see {@link org.w3c.css.sac.Condition#SAC_ATTRIBUTE_CONDITION}
+ *
+ * This condition checks an attribute. example:
+ *
+ * [simple]
+ * [restart="never"]
+ *
+ * Case INsensitive!
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ String attributeName = attributeCondition.getLocalName();
+ // [attribute=wantedValue]
+ if (attributeCondition.getSpecified()) {
+ String wantedValue = attributeCondition.getValue();
+ String actualValue = element.getAttribute(attributeName);
+ return equalsIgnoreCase(actualValue, wantedValue);
+ }
+ // [attribute]
+ return SelectorUtils.hasAttribute(element, attributeName);
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String attributeName = AttributeEvaluatorUtils.getXPathAttribute(attributeCondition);
+ // [attribute=wantedValue]
+ if (attributeCondition.getSpecified()) {
+ String wantedValue = SelectorUtils.intoEscapedXPathString(attributeCondition.getValue());
+ return new ConditionSimpleComponent("[" + attributeName + " = " + wantedValue + "]");
+ }
+ // [attribute]
+ return new ConditionSimpleComponent("[" + attributeName + "]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/attributes/IdAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/IdAttributeCssSelector.java
new file mode 100644
index 00000000..736b82b3
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/IdAttributeCssSelector.java
@@ -0,0 +1,50 @@
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import io.github.seleniumquery.by.xpath.component.special.IdConditionComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+/**
+ * #id
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class IdAttributeCssSelector implements CssConditionalSelector {
+
+ private static final String ID_ATTRIBUTE = "id";
+
+ /**
+ * see {@link org.w3c.css.sac.Condition#SAC_ID_CONDITION}
+ *
+ * This condition checks an id attribute. Example:
+ *
+ * #myId
+ *
+ * CASE SENSITIVE!
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ if (!SelectorUtils.hasAttribute(element, ID_ATTRIBUTE)) {
+ return false;
+ }
+ String wantedId = attributeCondition.getValue();
+ String actualId = element.getAttribute(ID_ATTRIBUTE);
+ return actualId.equals(wantedId);
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String wantedId = attributeCondition.getValue();
+ return new IdConditionComponent(wantedId);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/attributes/StartsWithAttributeCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/attributes/StartsWithAttributeCssSelector.java
similarity index 52%
rename from src/main/java/io/github/seleniumquery/selectors/attributes/StartsWithAttributeCssSelector.java
rename to src/main/java/io/github/seleniumquery/by/css/attributes/StartsWithAttributeCssSelector.java
index af9d360b..136ffe9c 100644
--- a/src/main/java/io/github/seleniumquery/selectors/attributes/StartsWithAttributeCssSelector.java
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/StartsWithAttributeCssSelector.java
@@ -1,48 +1,48 @@
-package io.github.seleniumquery.by.evaluator.conditionals.attributes;
-
-import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
-
-import java.util.Map;
-
-import io.github.seleniumquery.by.evaluator.CSSCondition;
-import io.github.seleniumquery.by.selector.CompiledSelector;
-
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-import org.w3c.css.sac.AttributeCondition;
-import org.w3c.css.sac.Selector;
-
-public class StartsWithAttributeEvaluator implements CSSCondition {
-
- private static final StartsWithAttributeEvaluator instance = new StartsWithAttributeEvaluator();
- public static StartsWithAttributeEvaluator getInstance() {
- return instance;
- }
- private StartsWithAttributeEvaluator() { }
-
-
- public static final String STARTS_WITH_ATTRIBUTE_SELECTOR_SYMBOL = "^=";
-
- /**
- * Currently it is (mistakenly?) mapped to the type {@link org.w3c.css.sac.Condition#SAC_ATTRIBUTE_CONDITION}.
- * The factory then inspects the actual type and redirects here.
- *
- * This selector is:
- * [attribute^=stringToStart]
- *
- * CASE INsensitive!
- */
- @Override
- public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
- String wantedValue = attributeCondition.getValue();
- String actualValue = element.getAttribute(attributeCondition.getLocalName());
- return startsWithIgnoreCase(actualValue, wantedValue);
- }
-
- @Override
- public CompiledSelector compileCondition(WebDriver driver, Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
- // nothing to do, everyone supports this selector
- return AttributeEvaluatorUtils.createAttributeNoFilterCompiledSelector(attributeCondition, STARTS_WITH_ATTRIBUTE_SELECTOR_SYMBOL);
- }
-
+package io.github.seleniumquery.by.css.attributes;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
+
+/**
+ * [attribute^=stringToStart]
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class StartsWithAttributeCssSelector implements CssConditionalSelector {
+
+ public static final String STARTS_WITH_ATTRIBUTE_SELECTOR_SYMBOL = "^=";
+
+ /**
+ * Currently it is (mistakenly?) mapped to the type {@link org.w3c.css.sac.Condition#SAC_ATTRIBUTE_CONDITION}.
+ * The factory then inspects the actual type and redirects here.
+ *
+ * This selector is:
+ * [attribute^=stringToStart]
+ *
+ * CASE INsensitive!
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ String wantedValue = attributeCondition.getValue();
+ String actualValue = element.getAttribute(attributeCondition.getLocalName());
+ return startsWithIgnoreCase(actualValue, wantedValue);
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, AttributeCondition attributeCondition) {
+ String attributeName = AttributeEvaluatorUtils.getXPathAttribute(attributeCondition);
+ String wantedValue = SelectorUtils.intoEscapedXPathString(attributeCondition.getValue());
+ return new ConditionSimpleComponent("[starts-with(" + attributeName + ", " + wantedValue + ")]");
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/attributes/package-info.java b/src/main/java/io/github/seleniumquery/by/css/attributes/package-info.java
similarity index 66%
rename from src/main/java/io/github/seleniumquery/selectors/attributes/package-info.java
rename to src/main/java/io/github/seleniumquery/by/css/attributes/package-info.java
index a06fbd85..fbf87156 100644
--- a/src/main/java/io/github/seleniumquery/selectors/attributes/package-info.java
+++ b/src/main/java/io/github/seleniumquery/by/css/attributes/package-info.java
@@ -3,4 +3,4 @@
*
* http://www.w3.org/TR/css3-selectors/#attribute-selectors
*/
-package io.github.seleniumquery.by.evaluator.conditionals.attributes;
\ No newline at end of file
+package io.github.seleniumquery.by.css.attributes;
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/combinators/DescendantCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/combinators/DescendantCssSelector.java
new file mode 100644
index 00000000..179e2b89
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/combinators/DescendantCssSelector.java
@@ -0,0 +1,52 @@
+package io.github.seleniumquery.by.css.combinators;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssSelector;
+import io.github.seleniumquery.by.css.CssSelectorMatcherService;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.DescendantGeneralComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SimpleSelector;
+
+import java.util.Map;
+
+/**
+ * E F
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class DescendantCssSelector implements CssSelector {
+
+ @Override
+ public boolean is(WebDriver driver, WebElement element, Map stringMap, DescendantSelector descendantSelector) {
+ if (CssSelectorMatcherService.elementMatchesSelector(driver, element, stringMap, descendantSelector.getSimpleSelector())) {
+
+ WebElement ancestor = SelectorUtils.parent(element);
+
+ while (ancestor != null) {
+ if (CssSelectorMatcherService.elementMatchesSelector(driver, ancestor, stringMap, descendantSelector.getAncestorSelector())) {
+ return true;
+ }
+ ancestor = SelectorUtils.parent(ancestor);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public TagComponent toXPath(Map stringMap, DescendantSelector descendantSelector) {
+ Selector ancestorCSSSelector = descendantSelector.getAncestorSelector();
+ TagComponent ancestorCompiled = XPathComponentCompilerService.compileSelector(stringMap, ancestorCSSSelector);
+
+ SimpleSelector descendantCSSSelector = descendantSelector.getSimpleSelector();
+ TagComponent childrenCompiled = XPathComponentCompilerService.compileSelector(stringMap, descendantCSSSelector);
+
+ return DescendantGeneralComponent.combine(ancestorCompiled, childrenCompiled);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/combinators/DirectAdjacentCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/combinators/DirectAdjacentCssSelector.java
new file mode 100644
index 00000000..1a69e0c9
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/combinators/DirectAdjacentCssSelector.java
@@ -0,0 +1,40 @@
+package io.github.seleniumquery.by.css.combinators;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssSelector;
+import io.github.seleniumquery.by.css.CssSelectorMatcherService;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.*;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.SiblingSelector;
+
+import java.util.Map;
+
+/**
+ * E + F
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class DirectAdjacentCssSelector implements CssSelector {
+
+ @Override
+ public boolean is(WebDriver driver, WebElement element, Map stringMap, SiblingSelector siblingSelector) {
+ WebElement previousElement = SelectorUtils.getPreviousSibling(element);
+ return CssSelectorMatcherService.elementMatchesSelector(driver, previousElement, stringMap, siblingSelector.getSelector())
+ && CssSelectorMatcherService.elementMatchesSelector(driver, element, stringMap, siblingSelector.getSiblingSelector());
+ }
+
+ @Override
+ public TagComponent toXPath(Map stringMap, SiblingSelector siblingSelector) {
+ TagComponent previousCompiledExpression = XPathComponentCompilerService.compileSelector(stringMap, siblingSelector.getSelector());
+ TagComponent siblingSelectorCompiledAdjacentExpression = XPathComponentCompilerService.compileSelector(stringMap, siblingSelector.getSiblingSelector());
+
+ ConditionSimpleComponent positionOne = new ConditionSimpleComponent("[position() = 1]");
+ TagComponent siblingAtPositionOne = siblingSelectorCompiledAdjacentExpression.cloneAndCombineTo(positionOne);
+
+ return AdjacentComponent.combine(previousCompiledExpression, siblingAtPositionOne);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/combinators/DirectDescendantCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/combinators/DirectDescendantCssSelector.java
new file mode 100644
index 00000000..b92b16cf
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/combinators/DirectDescendantCssSelector.java
@@ -0,0 +1,42 @@
+package io.github.seleniumquery.by.css.combinators;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssSelector;
+import io.github.seleniumquery.by.css.CssSelectorMatcherService;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.DescendantDirectComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.DescendantSelector;
+
+import java.util.Map;
+
+/**
+ * PARENT > ELEMENT
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class DirectDescendantCssSelector implements CssSelector {
+
+ @Override
+ public boolean is(WebDriver driver, WebElement element, Map stringMap, DescendantSelector descendantSelector) {
+ WebElement parent = SelectorUtils.parent(element);
+ //noinspection SimplifiableIfStatement
+ if (parent.getTagName().equals("html")) {
+ return false;
+ }
+ return CssSelectorMatcherService.elementMatchesSelector(driver, element, stringMap, descendantSelector.getSimpleSelector())
+ && CssSelectorMatcherService.elementMatchesSelector(driver, parent, stringMap, descendantSelector.getAncestorSelector());
+ }
+
+ @Override
+ public TagComponent toXPath(Map stringMap, DescendantSelector descendantSelector) {
+ TagComponent parentComponent = XPathComponentCompilerService.compileSelector(stringMap, descendantSelector.getAncestorSelector());
+ TagComponent childComponent = XPathComponentCompilerService.compileSelector(stringMap, descendantSelector.getSimpleSelector());
+
+ return DescendantDirectComponent.combine(parentComponent, childComponent);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/combinators/GeneralAdjacentCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/combinators/GeneralAdjacentCssSelector.java
new file mode 100644
index 00000000..c2fee0dd
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/combinators/GeneralAdjacentCssSelector.java
@@ -0,0 +1,62 @@
+package io.github.seleniumquery.by.css.combinators;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssSelector;
+import io.github.seleniumquery.by.css.CssSelectorMatcherService;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.AdjacentComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.SiblingSelector;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * E ~ PRE
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class GeneralAdjacentCssSelector implements CssSelector {
+
+ /**
+ * http://www.w3.org/TR/css3-selectors/#general-sibling-combinators
+ *
+ *
Example:
+ *
+ *
h1 ~ pre
+ *
+ *
represents a pre element following an h1. It is a correct and valid, but partial, description of:
+ *
<h1>Definition of the function a</h1>
+ * <p>Function a(x) has to be applied to all figures in the table.</p>
+ * <pre>function a(x) = 12x/13.5</pre>
+ *
+ */
+ @Override
+ public boolean is(WebDriver driver, WebElement element, Map stringMap, SiblingSelector siblingSelector) {
+ boolean elementMatchesSelectorSecondPart = CssSelectorMatcherService.elementMatchesSelector(driver, element, stringMap, siblingSelector.getSiblingSelector());
+ if (!elementMatchesSelectorSecondPart) {
+ return false;
+ }
+
+ List previousSiblings = SelectorUtils.getPreviousSiblings(element);
+ for (WebElement previousSibling : previousSiblings) {
+ boolean previousSiblingMatchesSelectorFirstPart = CssSelectorMatcherService.elementMatchesSelector(driver, previousSibling, stringMap, siblingSelector.getSelector());
+ if (previousSiblingMatchesSelectorFirstPart) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public TagComponent toXPath(Map stringMap, SiblingSelector siblingSelector) {
+ TagComponent previousElementCompiled = XPathComponentCompilerService.compileSelector(stringMap, siblingSelector.getSelector());
+ TagComponent siblingElementCompiled = XPathComponentCompilerService.compileSelector(stringMap, siblingSelector.getSiblingSelector());
+
+ return AdjacentComponent.combine(previousElementCompiled, siblingElementCompiled);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/combinators/package-info.java b/src/main/java/io/github/seleniumquery/by/css/combinators/package-info.java
similarity index 51%
rename from src/main/java/io/github/seleniumquery/selectors/combinators/package-info.java
rename to src/main/java/io/github/seleniumquery/by/css/combinators/package-info.java
index 51cd53bb..2ca8f8df 100644
--- a/src/main/java/io/github/seleniumquery/selectors/combinators/package-info.java
+++ b/src/main/java/io/github/seleniumquery/by/css/combinators/package-info.java
@@ -1,4 +1,4 @@
/**
* http://www.w3.org/TR/css3-selectors/#combinators
*/
-package io.github.seleniumquery.by.evaluator.combinators;
\ No newline at end of file
+package io.github.seleniumquery.by.css.combinators;
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/conditionals/AndConditionalCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/conditionals/AndConditionalCssSelector.java
new file mode 100644
index 00000000..99b4e811
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/conditionals/AndConditionalCssSelector.java
@@ -0,0 +1,53 @@
+package io.github.seleniumquery.by.css.conditionals;
+
+import com.steadystate.css.parser.selectors.ConditionalSelectorImpl;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SimpleSelector;
+
+import java.util.Map;
+
+/**
+ * E.firstCondition.secondCondition
+ *
+ * see {@link Condition#SAC_AND_CONDITION}
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class AndConditionalCssSelector implements CssConditionalSelector {
+
+ private ConditionalCssSelector conditionalEvaluator;
+
+ public AndConditionalCssSelector(ConditionalCssSelector conditionalEvaluator) {
+ this.conditionalEvaluator = conditionalEvaluator;
+ }
+
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, CombinatorCondition combinatorCondition) {
+ ConditionalSelectorImpl selectorUpToThisPointPlusFirstCondition = new ConditionalSelectorImpl(
+ (SimpleSelector) selectorUpToThisPoint,
+ combinatorCondition.getFirstCondition());
+
+ return conditionalEvaluator.isCondition(driver, element, stringMap, selectorUpToThisPoint, combinatorCondition.getFirstCondition())
+ && conditionalEvaluator.isCondition(driver, element, stringMap, selectorUpToThisPointPlusFirstCondition, combinatorCondition.getSecondCondition());
+ }
+
+ @Override
+ public ConditionComponent conditionToXPath(Map stringMap, Selector selectorUpToThisPoint, CombinatorCondition combinatorCondition) {
+ ConditionalSelectorImpl selectorUpToThisPointPlusFirstCondition = new ConditionalSelectorImpl(
+ (SimpleSelector) selectorUpToThisPoint,
+ combinatorCondition.getFirstCondition());
+
+ ConditionComponent firstCondition = conditionalEvaluator.conditionToXPath(stringMap, selectorUpToThisPoint, combinatorCondition.getFirstCondition());
+ ConditionComponent secondCondition = conditionalEvaluator.conditionToXPath(stringMap, selectorUpToThisPointPlusFirstCondition, combinatorCondition.getSecondCondition());
+ return firstCondition.cloneAndCombineTo(secondCondition);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/conditionals/ConditionalCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/conditionals/ConditionalCssSelector.java
new file mode 100644
index 00000000..635c198d
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/conditionals/ConditionalCssSelector.java
@@ -0,0 +1,55 @@
+package io.github.seleniumquery.by.css.conditionals;
+
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.css.CssSelector;
+import io.github.seleniumquery.by.css.CssSelectorMatcherService;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SimpleSelector;
+
+import java.util.Map;
+
+
+public class ConditionalCssSelector implements CssSelector {
+
+ private final ConditionalCssSelectorFactory conditionalCssSelectorFactory = new ConditionalCssSelectorFactory(this);
+
+ @Override
+ public boolean is(WebDriver driver, WebElement element, Map stringMap, ConditionalSelector conditionalSelector) {
+ Condition condition = conditionalSelector.getCondition();
+ SimpleSelector simpleSelector = conditionalSelector.getSimpleSelector();
+ return CssSelectorMatcherService.elementMatchesSelector(driver, element, stringMap, simpleSelector)
+ && isCondition(driver, element, stringMap, simpleSelector, condition);
+ }
+
+ @Override
+ public TagComponent toXPath(Map stringMap, ConditionalSelector conditionalSelector) {
+ Condition condition = conditionalSelector.getCondition();
+ SimpleSelector simpleSelector = conditionalSelector.getSimpleSelector();
+ TagComponent tagComponent = XPathComponentCompilerService.compileSelector(stringMap, simpleSelector);
+ ConditionComponent compiledCondition = conditionToXPath(stringMap, simpleSelector, condition);
+ return tagComponent.cloneAndCombineTo(compiledCondition);
+ }
+
+ /**
+ * Gets the given condition's CssSelector and tests if the element matches it.
+ */
+ boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector simpleSelector, Condition condition) {
+ @SuppressWarnings("unchecked")
+ CssConditionalSelector evaluator = (CssConditionalSelector) conditionalCssSelectorFactory.getSelector(condition);
+ return evaluator.isCondition(driver, element, stringMap, simpleSelector, condition);
+ }
+
+ ConditionComponent conditionToXPath(Map stringMap, Selector simpleSelector, Condition condition) {
+ @SuppressWarnings("unchecked")
+ CssConditionalSelector evaluator = (CssConditionalSelector) conditionalCssSelectorFactory.getSelector(condition);
+ return evaluator.conditionToXPath(stringMap, simpleSelector, condition);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/conditionals/ConditionalCssSelectorFactory.java b/src/main/java/io/github/seleniumquery/by/css/conditionals/ConditionalCssSelectorFactory.java
new file mode 100644
index 00000000..5c131919
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/conditionals/ConditionalCssSelectorFactory.java
@@ -0,0 +1,82 @@
+package io.github.seleniumquery.by.css.conditionals;
+
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.css.attributes.ClassAttributeCssSelector;
+import io.github.seleniumquery.by.css.attributes.ContainsPrefixAttributeCssSelector;
+import io.github.seleniumquery.by.css.attributes.ContainsSubstringAttributeCssSelector;
+import io.github.seleniumquery.by.css.attributes.ContainsWordAttributeCssSelector;
+import io.github.seleniumquery.by.css.attributes.EndsWithAttributeCssSelector;
+import io.github.seleniumquery.by.css.attributes.EqualsOrHasAttributeCssSelector;
+import io.github.seleniumquery.by.css.attributes.IdAttributeCssSelector;
+import io.github.seleniumquery.by.css.attributes.StartsWithAttributeCssSelector;
+import io.github.seleniumquery.by.css.pseudoclasses.LangPseudoClassEvaluator;
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassCssSelector;
+
+import io.github.seleniumquery.by.xpath.component.ConditionComponent;
+import org.w3c.css.sac.Condition;
+
+/**
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class ConditionalCssSelectorFactory {
+
+ private final AndConditionalCssSelector andConditionalCssSelector;
+ private final StartsWithAttributeCssSelector startsWithAttributeCssSelector = new StartsWithAttributeCssSelector();
+ private final EndsWithAttributeCssSelector endsWithAttributeCssSelector = new EndsWithAttributeCssSelector();
+ private final ContainsSubstringAttributeCssSelector containsSubstringAttributeCssSelector = new ContainsSubstringAttributeCssSelector();
+ private final EqualsOrHasAttributeCssSelector equalsOrHasAttributeCssSelector = new EqualsOrHasAttributeCssSelector();
+ private final IdAttributeCssSelector idAttributeCssSelector = new IdAttributeCssSelector();
+ private final ContainsWordAttributeCssSelector containsWordAttributeCssSelector = new ContainsWordAttributeCssSelector();
+ private final ContainsPrefixAttributeCssSelector containsPrefixAttributeCssSelector = new ContainsPrefixAttributeCssSelector();
+ private final ClassAttributeCssSelector classAttributeCssSelector = new ClassAttributeCssSelector();
+ private final PseudoClassCssSelector pseudoClassCssSelector = new PseudoClassCssSelector();
+ private final LangPseudoClassEvaluator langPseudoClassEvaluator = new LangPseudoClassEvaluator();
+
+ public ConditionalCssSelectorFactory(ConditionalCssSelector conditionalCssSelector) {
+ this.andConditionalCssSelector = new AndConditionalCssSelector(conditionalCssSelector);
+ }
+
+ public CssConditionalSelector extends Condition, ? extends ConditionComponent> getSelector(Condition condition) {
+ switch (condition.getConditionType()) {
+ case Condition.SAC_AND_CONDITION:
+ return andConditionalCssSelector;
+ case Condition.SAC_OR_CONDITION:
+ // if the exception below gets thrown, this means the CSS Parser has changed and
+ // we must update our code as well.
+ throw new RuntimeException("The Condition.SAC_OR_CONDITION is not used by the CSS Parser. " +
+ "This version of seleniumQuery is not compatible with the CSS Parser present in the" +
+ "classpath.");
+
+ case Condition.SAC_ATTRIBUTE_CONDITION:
+ if (condition instanceof com.steadystate.css.parser.selectors.PrefixAttributeConditionImpl) {
+ return startsWithAttributeCssSelector;
+ }
+ if (condition instanceof com.steadystate.css.parser.selectors.SuffixAttributeConditionImpl) {
+ return endsWithAttributeCssSelector;
+ }
+ if (condition instanceof com.steadystate.css.parser.selectors.SubstringAttributeConditionImpl) {
+ return containsSubstringAttributeCssSelector;
+ }
+ // else: condition is most probably a instance of com.steadystate.css.parser.selectors.AttributeConditionImpl
+ return equalsOrHasAttributeCssSelector;
+ case Condition.SAC_ID_CONDITION:
+ return idAttributeCssSelector;
+ case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
+ return containsWordAttributeCssSelector;
+ case Condition.SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION:
+ return containsPrefixAttributeCssSelector;
+ case Condition.SAC_CLASS_CONDITION:
+ return classAttributeCssSelector;
+
+ case Condition.SAC_PSEUDO_CLASS_CONDITION:
+ return pseudoClassCssSelector;
+ case Condition.SAC_LANG_CONDITION:
+ return langPseudoClassEvaluator;
+
+ default:
+ return new UnknownConditionalCssSelector(condition.getConditionType());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/conditionals/UnknownConditionalCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/conditionals/UnknownConditionalCssSelector.java
new file mode 100644
index 00000000..d59d0c9d
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/conditionals/UnknownConditionalCssSelector.java
@@ -0,0 +1,40 @@
+package io.github.seleniumquery.by.css.conditionals;
+
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+/**
+ * Represents an unknown condition type.
+ */
+public class UnknownConditionalCssSelector implements CssConditionalSelector {
+
+ private static final Log LOGGER = LogFactory.getLog(UnknownConditionalCssSelector.class);
+
+ private short type;
+
+ public UnknownConditionalCssSelector(short type) {
+ this.type = type;
+ }
+
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, T condition) {
+ throw new RuntimeException("CSS condition "+condition.getClass().getSimpleName()+" of type "+type+" is invalid or not supported!");
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, T condition) {
+ // if it is unknown, we can't convert it, so we simply ignore it
+ LOGGER.warn("CSS Selector Condition '"+condition+"' is unknown. Ignoring it.");
+ return new ConditionSimpleComponent(ElementFilter.FILTER_NOTHING);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ButtonPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ButtonPseudoClass.java
new file mode 100644
index 00000000..13a319af
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ButtonPseudoClass.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * http://api.jquery.com/button-selector/
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class ButtonPseudoClass implements PseudoClass {
+
+ private static final String INPUT = "input";
+ private static final String BUTTON = "button";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return BUTTON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return (INPUT.equals(element.getTagName()) && BUTTON.equalsIgnoreCase(element.getAttribute("type")))
+ ||
+ BUTTON.equals(element.getTagName());
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[(" +
+ "(self::input and translate(@type,'BUTTON','button') = 'button') " +
+ "or " +
+ "self::button" +
+ ")]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/CheckboxPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/CheckboxPseudoClass.java
new file mode 100644
index 00000000..91ca8d00
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/CheckboxPseudoClass.java
@@ -0,0 +1,15 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+/**
+ * http://api.jquery.com/checkbox-selector/
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class CheckboxPseudoClass extends InputTypeAttributePseudoClass {
+
+ public CheckboxPseudoClass() {
+ super("checkbox");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/CheckedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/CheckedPseudoClass.java
new file mode 100644
index 00000000..3c9fc4de
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/CheckedPseudoClass.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import static io.github.seleniumquery.by.WebElementUtils.*;
+import static io.github.seleniumquery.by.css.pseudoclasses.SelectedPseudoClass.SELECTED_PSEUDO_CONDITION;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:checked
+ *
+ * #Cross-Driver
+ * In HtmlUnitDriver, document.querySelectorAll(":checked") is not consistent, so we should consider it as
+ * not supported;
+ * In PhantomJSDriver, document.querySelectorAll(":checked") does not work for tags, so we should
+ * consider it as not supported as well!
+ *
+ * @author acdcjunior
+ * @author ricardo-sc
+ *
+ * @since 0.9.0
+ */
+public class CheckedPseudoClass implements PseudoClass {
+
+ private static final String CHECKED_PSEUDO_CLASS_NO_COLON = "checked";
+
+ public static final ElementFilter CHECKED_FILTER = new PseudoClassFilter(new CheckedPseudoClass());
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return CHECKED_PSEUDO_CLASS_NO_COLON.equalsIgnoreCase(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return isChecked(element);
+ }
+
+ public boolean isChecked(WebElement element) {
+ // #Cross-Driver
+ // PhantomJS: When we call element.isSelected() on an element that is not selectable,
+ // PhantomJS throws an exception, so we must check the element type before calling isSelected().
+ return isCheckableTag(element) && element.isSelected();
+ }
+
+ private boolean isCheckableTag(WebElement element) {
+ return isOptionTag(element) || isInputRadioTag(element) || isInputCheckboxTag(element);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ // NOTE: This XPath does not work. Sometimes an element is checked WITHOUT having a checked attribute
+ return new ConditionSimpleComponent("[" +
+ "(" +
+ "(self::input and (@type = 'radio' or @type = 'checkbox') and @checked) " +
+ "or " +
+ "(" + SELECTED_PSEUDO_CONDITION + ")" +
+ ")" +
+ "]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ContainsPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ContainsPseudoClass.java
new file mode 100644
index 00000000..c635f3cb
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ContainsPseudoClass.java
@@ -0,0 +1,36 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :contains()
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class ContainsPseudoClass implements PseudoClass {
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.matches("contains\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String textToContain = pseudoClassSelector.getPseudoClassContent();
+ textToContain = SelectorUtils.unescapeString(textToContain);
+ return element.getText().contains(textToContain);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String textToContain = pseudoClassSelector.getPseudoClassContent();
+ textToContain = SelectorUtils.unescapeString(textToContain);
+ String wantedTextToContain = SelectorUtils.intoEscapedXPathString(textToContain);
+ return new ConditionSimpleComponent("[contains(string(.), " + wantedTextToContain + ")]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/DisabledPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/DisabledPseudoClass.java
new file mode 100644
index 00000000..2d67c54d
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/DisabledPseudoClass.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.DriverVersionUtils;
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.commons.lang3.StringUtils.join;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:disabled
+ *
+ * #Cross-Driver
+ * HtmlUnitDriver has problems with :disabled, so we consider it can never be handler by the browser
+ * by "problems" we mean it is inconsistent, changing depending on what browser it is attempting to emulate
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class DisabledPseudoClass implements PseudoClass {
+
+ private static final String DISABLED_PSEUDO_CLASS_NO_COLON = "disabled";
+
+ private static final String INPUT = "input";
+ private static final String BUTTON = "button";
+ private static final String OPTION = "option";
+ private static final String OPTGROUP = "optgroup";
+ private static final String SELECT = "select";
+ private static final String TEXTAREA = "textarea";
+
+ public static final List DISABLEABLE_TAGS = Arrays.asList(INPUT, BUTTON, OPTGROUP, OPTION, SELECT, TEXTAREA);
+
+ public static final String DISABLEABLE_TAGS_XPATH = "(self::" + join(DISABLEABLE_TAGS, " or self::") + ")";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return DISABLED_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ // #Cross-Driver
+ // When there is a not disabled under a disabled , HtmlUnitDriver considers
+ // the to be enabled, when it is not
+ if (DriverVersionUtils.getInstance().isHtmlUnitDriver(driver) && OPTION.equals(element.getTagName())) {
+ WebElement optionParent = SelectorUtils.parent(element);
+ if (OPTGROUP.equals(optionParent.getTagName()) && !optionParent.isEnabled()) {
+ return true;
+ }
+ }
+ return !element.isEnabled() && DISABLEABLE_TAGS.contains(element.getTagName());
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[(@disabled and " + DISABLEABLE_TAGS_XPATH + ")]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EmptyPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EmptyPseudoClass.java
new file mode 100644
index 00000000..d07fcfb0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EmptyPseudoClass.java
@@ -0,0 +1,49 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import static io.github.seleniumquery.by.DriverVersionUtils.isHtmlUnitDriverEmulatingIEBelow11;
+
+/**
+ * :empty
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class EmptyPseudoClass implements PseudoClass {
+
+ private static final Log LOGGER = LogFactory.getLog(EmptyPseudoClass.class);
+ private static final String EMPTY_PSEUDO_CLASS_NO_COLON = "empty";
+
+ private final ParentPseudoClass parentPseudoClass = new ParentPseudoClass();
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return EMPTY_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ boolean isEmpty = !parentPseudoClass.isParent(element);
+ // #Cross-Driver
+ if (isEmpty && isHtmlUnitDriverEmulatingIEBelow11(driver)) {
+ LOGGER.warn("The outcome of the selector with the pseudo-class \":empty\" could be affected:" +
+ " HtmlUnidDriver emulating IE below 11 considers elements" +
+ " with space-only content (e.g. \"
\") to be empty, while for other browsers" +
+ " they are not! There is no workaround for this, as HtmlUnitDriver ignored the spaces during" +
+ " the DOM parsing phase, and we have no means to know now if the elements had spaces (that" +
+ " were ignored) or if they were just empty.");
+ }
+ return isEmpty;
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[count(.//*) = 0]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EnabledPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EnabledPseudoClass.java
new file mode 100644
index 00000000..7e92f68b
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EnabledPseudoClass.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.DriverVersionUtils;
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:enabled
+ *
+ * #Cross-Driver
+ * HtmlUnitDriver has problems with :enabled, so we consider it can never be handler by the browser
+ * by "problems" we mean it is inconsistent, changing depending on what browser it is attempting to emulate.
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class EnabledPseudoClass implements PseudoClass {
+
+ private static final String ENABLED_PSEUDO_CLASS_NO_COLON = "enabled";
+
+ private static final String OPTGROUP_TAG = "optgroup";
+ private static final String OPTION_TAG = "option";
+ public static final List ENABLEABLE_TAGS = DisabledPseudoClass.DISABLEABLE_TAGS;
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return ENABLED_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ // #Cross-Driver
+ // When there is a not disabled under a disabled , HtmlUnitDriver considers
+ // the to be enabled, when it is not
+ if (DriverVersionUtils.getInstance().isHtmlUnitDriver(driver) && OPTION_TAG.equals(element.getTagName())) {
+ WebElement optionParent = SelectorUtils.parent(element);
+ if (OPTGROUP_TAG.equals(optionParent.getTagName()) && !optionParent.isEnabled()) {
+ return false;
+ }
+ }
+ return element.isEnabled() && ENABLEABLE_TAGS.contains(element.getTagName());
+ }
+
+ public static final String ENABLED_XPATH = "(not(@disabled) and " + DisabledPseudoClass.DISABLEABLE_TAGS_XPATH + ")";
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[" + ENABLED_XPATH + "]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EqPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EqPseudoClass.java
new file mode 100644
index 00000000..ed25c417
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EqPseudoClass.java
@@ -0,0 +1,65 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionToAllComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * :eq()
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class EqPseudoClass implements PseudoClass {
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.matches("eq\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String eqIndex = pseudoClassSelector.getPseudoClassContent();
+ if (!eqIndex.matches("[+-]?\\d+")) {
+ throw new RuntimeException("The :eq() pseudo-class requires an integer but got: " + eqIndex);
+ }
+ if (eqIndex.charAt(0) == '+') {
+ eqIndex = eqIndex.substring(1);
+ }
+ int index = Integer.valueOf(eqIndex);
+
+ return EqPseudoClass.isEq(driver, element, pseudoClassSelector, index);
+ }
+
+ static boolean isEq(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector, int index) {
+ TagComponent compiledSelector = XPathComponentCompilerService.compileSelector(pseudoClassSelector.getStringMap(), pseudoClassSelector.getSelector());
+ List elements = compiledSelector.findWebElements(driver);
+ if (index < 0) {
+ return elements.size() >= -index && elements.get(elements.size() + index).equals(element);
+ }
+ return elements.size() > index && elements.get(index).equals(element);
+ }
+
+ @Override
+ public ConditionToAllComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String eqIndex = pseudoClassSelector.getPseudoClassContent();
+ if (!eqIndex.matches("[+-]?\\d+")) {
+ throw new IllegalArgumentException("The :eq() pseudo-class requires an integer but got: " + eqIndex);
+ }
+ if (eqIndex.charAt(0) == '+') {
+ eqIndex = eqIndex.substring(1);
+ }
+ int index = Integer.valueOf(eqIndex);
+
+ if (index >= 0) {
+ return new ConditionToAllComponent("[position() = " + (index + 1) + "]");
+ }
+ String xPathExpression = "[position() = (last()-" + (-index - 1) + ")]";
+ return new ConditionToAllComponent(xPathExpression);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EvenPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EvenPseudoClass.java
new file mode 100644
index 00000000..a4b897d0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/EvenPseudoClass.java
@@ -0,0 +1,39 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionToAllComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * :even
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class EvenPseudoClass implements PseudoClass {
+
+ private static final String EVEN_PSEUDO_CLASS_NO_COLON = "even";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return EVEN_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ TagComponent compiledSelector = XPathComponentCompilerService.compileSelector(pseudoClassSelector.getStringMap(), pseudoClassSelector.getSelector());
+ List elements = compiledSelector.findWebElements(driver);
+ return elements.indexOf(element) % 2 == 0;
+ }
+
+ @Override
+ public ConditionToAllComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ // notice that XPath is 1-based and :even is not.
+ return new ConditionToAllComponent("[(position() mod 2) = 1]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FilePseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FilePseudoClass.java
new file mode 100644
index 00000000..4e8f0800
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FilePseudoClass.java
@@ -0,0 +1,16 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+/**
+ * http://api.jquery.com/file-selector/
+ *
+ * @since 0.9.0
+ *
+ * @author acdcjunior
+ */
+public class FilePseudoClass extends InputTypeAttributePseudoClass {
+
+ public FilePseudoClass() {
+ super("file");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FirstChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FirstChildPseudoClass.java
new file mode 100644
index 00000000..54e75f45
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FirstChildPseudoClass.java
@@ -0,0 +1,39 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class FirstChildPseudoClass implements PseudoClass {
+
+ private static final String FIRST_CHILD_PSEUDO_CLASS_NO_COLON = "first-child";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return FIRST_CHILD_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ WebElement parent = SelectorUtils.parent(element);
+ /* parent is null when element is */
+ //noinspection SimplifiableIfStatement
+ if (parent == null) {
+ return false;
+ }
+ return SelectorUtils.itselfWithSiblings(element).get(0).equals(element);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[position() = 1]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FirstPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FirstPseudoClass.java
new file mode 100644
index 00000000..9312c0fa
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FirstPseudoClass.java
@@ -0,0 +1,32 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionToAllComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :first
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class FirstPseudoClass implements PseudoClass {
+
+ private static final String FIRST_PSEUDO_CLASS_NO_COLON = "first";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return FIRST_PSEUDO_CLASS_NO_COLON.equalsIgnoreCase(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return EqPseudoClass.isEq(driver, element, pseudoClassSelector, 0);
+ }
+
+ @Override
+ public ConditionToAllComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionToAllComponent("[position() = 1]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FocusPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FocusPseudoClass.java
new file mode 100644
index 00000000..34862dba
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FocusPseudoClass.java
@@ -0,0 +1,39 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:focus
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class FocusPseudoClass implements PseudoClass {
+
+ private static final String FOCUS_PSEUDO_CLASS_NO_COLON = "focus";
+
+ public static final ElementFilter FOCUS_FILTER = new PseudoClassFilter(new FocusPseudoClass());
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return FOCUS_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ WebElement currentlyActiveElement = driver.switchTo().activeElement();
+ return element.equals(currentlyActiveElement);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ UnsupportedXPathPseudoClassException.xPathFiltersAreNotImplementedYed(":focus");
+
+ // #no-xpath
+ return new ConditionSimpleComponent(FOCUS_FILTER);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FocusablePseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FocusablePseudoClass.java
new file mode 100644
index 00000000..192f48cd
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/FocusablePseudoClass.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import static io.github.seleniumquery.by.SelectorUtils.isVisible;
+
+/**
+ * see -> http://api.jqueryui.com/focusable-selector/
+ * No browser supports :focusable natively.
+ *
+ * Some elements are natively focusable, while others require explicitly setting a tab index. In all cases, the element must be visible in order to be focusable.
+ *
+ * Elements of the following type are focusable:
+ * - input, if not disabled;
+ * - select, if not disabled;
+ * - textarea, if not disabled;
+ * - button if not disabled;
+ * - anchors, if they have an href or tabindex attribute.
+ * - area elements are focusable if they are inside a named map, have an href attribute, and there is a visible image using the map.
+ * - ALL OTHER elements are focusable based solely on their tabindex attribute and visibility.
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class FocusablePseudoClass implements PseudoClass {
+
+ private static final String FOCUSABLE_PSEUDO_CLASS_NO_COLON = "focusable";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return FOCUSABLE_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ if (!isVisible(element)) {
+ return false;
+ }
+ if (DisabledPseudoClass.DISABLEABLE_TAGS.contains(element.getTagName())) {
+ return element.isEnabled();
+ }
+ if (element.getTagName().equals("a") && element.getAttribute("href") != null) {
+ return true;
+ }
+ //noinspection SimplifiableIfStatement
+ if (element.getTagName().equals("area") && element.getAttribute("href") != null /* && inside a named map */ /* && there is a visible image using the map */) {
+ return true;
+ }
+ return element.getAttribute("tabindex") != null;
+ }
+
+ private final ElementFilter focusablePseudoClassFilter = new PseudoClassFilter(this);
+
+ // //button[.='OK' and not(ancestor::div[contains(@style,'display:none')]) and ]
+
+
+ // upon changing the expression below, check also the one at :tabbable
+ public static final String FOCUSABLE_XPATH =
+ // is visible and...
+ " ("
+ + " ("
+ + " (local-name() = 'input' or local-name() = 'button' or local-name() = 'optgroup' or local-name() = 'option' or local-name() = 'select' or local-name() = 'textarea')"
+ + " and "
+ + EnabledPseudoClass.ENABLED_XPATH
+ + " ) "
+ + " or "
+ + " (local-name() = 'a' and @href) "
+ + " or "
+ + " (local-name() = 'area' and @href)"
+ + " or "
+ + " @tabindex"
+ + ")";
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ UnsupportedXPathPseudoClassException.xPathFiltersAreNotImplementedYed(":focusable");
+
+ // #no-xpath
+ // after filtering with XPath, we still must check visibility
+ return new ConditionSimpleComponent("[" + FOCUSABLE_XPATH + "]", focusablePseudoClassFilter);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/GtPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/GtPseudoClass.java
new file mode 100644
index 00000000..e2bc2030
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/GtPseudoClass.java
@@ -0,0 +1,78 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionToAllComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * :gt(#)
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class GtPseudoClass implements PseudoClass {
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.matches("gt\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String gtIndex = pseudoClassSelector.getPseudoClassContent();
+ if (!gtIndex.matches("[+-]?\\d+")) {
+ throw new RuntimeException("The :gt() pseudo-class requires an integer but got: " + gtIndex);
+ }
+ if (gtIndex.charAt(0) == '+') {
+ gtIndex = gtIndex.substring(1);
+ }
+ int index = Integer.valueOf(gtIndex);
+
+ return GtPseudoClass.isGt(driver, element, pseudoClassSelector, index);
+ }
+
+ private static boolean isGt(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector, int wantedIndex) {
+ TagComponent compiledSelector = XPathComponentCompilerService.compileSelector(pseudoClassSelector.getStringMap(), pseudoClassSelector.getSelector());
+ List elements = compiledSelector.findWebElements(driver);
+ if (elements.isEmpty()) {
+ return false;
+ }
+ int actuallyWantedIndex = wantedIndex;
+ if (wantedIndex < 0) {
+ actuallyWantedIndex = elements.size() + wantedIndex;
+ }
+
+ if (elements.size() <= actuallyWantedIndex) {
+ return false;
+ }
+ int indexFound = elements.indexOf(element);
+ //noinspection SimplifiableIfStatement
+ if (indexFound == -1) {
+ return false;
+ }
+ return indexFound > actuallyWantedIndex;
+ }
+
+ @Override
+ public ConditionToAllComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String eqIndex = pseudoClassSelector.getPseudoClassContent();
+ if (!eqIndex.matches("[+-]?\\d+")) {
+ throw new RuntimeException("The :gt() pseudo-class requires an integer but got: " + eqIndex);
+ }
+ if (eqIndex.charAt(0) == '+') {
+ eqIndex = eqIndex.substring(1);
+ }
+ int index = Integer.valueOf(eqIndex);
+
+ if (index >= 0) {
+ return new ConditionToAllComponent("[position() > " + (index + 1) + "]");
+ }
+ String xPathExpression = "[position() > (last()-" + (-index - 1) + ")]";
+ return new ConditionToAllComponent(xPathExpression);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HasPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HasPseudoClass.java
new file mode 100644
index 00000000..f70bf87f
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HasPseudoClass.java
@@ -0,0 +1,42 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.TagComponentList;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * :has(selector)
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class HasPseudoClass implements PseudoClass {
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.matches("has\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String hasSelector = pseudoClassSelector.getPseudoClassContent();
+
+ TagComponentList compiledSelector = XPathComponentCompilerService.compileSelectorList(hasSelector);
+ List elements = compiledSelector.findWebElements(driver);
+
+ return !elements.isEmpty();
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String notSelector = pseudoClassSelector.getPseudoClassContent();
+ String insideHasXPath = XPathComponentCompilerService.compileSelectorList(notSelector).toXPath();
+ insideHasXPath = insideHasXPath.substring(1, insideHasXPath.length()-1);
+ return new ConditionSimpleComponent("[" + insideHasXPath + "]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HeaderPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HeaderPseudoClass.java
new file mode 100644
index 00000000..7a71f679
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HeaderPseudoClass.java
@@ -0,0 +1,48 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * http://api.jquery.com/header-selector/
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class HeaderPseudoClass implements PseudoClass {
+
+ private static final String HX_XPATH = "[(local-name() = 'h0' or "
+ + "local-name() = 'h1' or "
+ + "local-name() = 'h2' or "
+ + "local-name() = 'h3' or "
+ + "local-name() = 'h4' or "
+ + "local-name() = 'h5' or "
+ + "local-name() = 'h6' or "
+ + "local-name() = 'h7' or "
+ + "local-name() = 'h8' or "
+ + "local-name() = 'h9')]";
+
+ private static final List HEADER_TAGS = Arrays.asList("h0", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "h9");
+
+ private static final String HEADER = "header";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return HEADER.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return HEADER_TAGS.contains(element.getTagName());
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent(HX_XPATH);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HiddenPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HiddenPseudoClass.java
new file mode 100644
index 00000000..0fec127b
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/HiddenPseudoClass.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :hidden
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class HiddenPseudoClass implements PseudoClass {
+
+ private static final String HIDDEN_PSEUDO_CLASS_NO_COLON = "hidden";
+
+ public static final ElementFilter HIDDEN_FILTER = new PseudoClassFilter(new HiddenPseudoClass());
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return HIDDEN_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return !SelectorUtils.isVisible(element);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ UnsupportedXPathPseudoClassException.xPathFiltersAreNotImplementedYed(":hidden");
+
+ // we can't use XPath because it can't see the styles affecting the element's classes, which can pretty much
+ // turn any element, including itself or , visible.
+ return new ConditionSimpleComponent(HIDDEN_FILTER);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ImagePseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ImagePseudoClass.java
new file mode 100644
index 00000000..63031943
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ImagePseudoClass.java
@@ -0,0 +1,15 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+/**
+ * http://api.jquery.com/image-selector/
+ *
+ * @since 0.9.0
+ * @author acdcjunior
+ */
+public class ImagePseudoClass extends InputTypeAttributePseudoClass {
+
+ public ImagePseudoClass() {
+ super("image");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/InputPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/InputPseudoClass.java
new file mode 100644
index 00000000..c9be8aa3
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/InputPseudoClass.java
@@ -0,0 +1,37 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * http://api.jquery.com/input-selector/
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class InputPseudoClass implements PseudoClass {
+
+ public static final List FORM_ELEMENT_TAGS = Arrays.asList("input", "button", "select", "textarea");
+
+ private static final String INPUT = "input";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return INPUT.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return FORM_ELEMENT_TAGS.contains(element.getTagName());
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[(local-name() = 'input' or local-name() = 'button' or local-name() = 'select' or local-name() = 'textarea')]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/InputTypeAttributePseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/InputTypeAttributePseudoClass.java
new file mode 100644
index 00000000..55c4eb5d
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/InputTypeAttributePseudoClass.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * This represents the pseudoclasses that check for the type attribute, such as
+ * :password, that is equivalent to [type="password"].
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+abstract class InputTypeAttributePseudoClass implements PseudoClass {
+
+ private String typeAttributeValue;
+
+ protected InputTypeAttributePseudoClass(String typeAttributeValue) {
+ this.typeAttributeValue = typeAttributeValue;
+ }
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return typeAttributeValue.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return "input".equals(element.getTagName()) && typeAttributeValue.equalsIgnoreCase(element.getAttribute("type"));
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[(self::input and @type = '" + typeAttributeValue + "')]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LangPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LangPseudoClass.java
new file mode 100644
index 00000000..016352d2
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LangPseudoClass.java
@@ -0,0 +1,33 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:lang
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class LangPseudoClass implements PseudoClass {
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.matches("lang-sq\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String wantedLang = pseudoClassSelector.getPseudoClassContent();
+ return wantedLang.equals(SelectorUtils.lang(element));
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String wantedLang = pseudoClassSelector.getPseudoClassContent();
+ return new ConditionSimpleComponent("[ancestor-or-self::*[@lang][1]/@lang = '" + wantedLang + "']");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LangPseudoClassEvaluator.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LangPseudoClassEvaluator.java
new file mode 100644
index 00000000..ef78fb3f
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LangPseudoClassEvaluator.java
@@ -0,0 +1,40 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.LangCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Map;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:lang
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class LangPseudoClassEvaluator implements CssConditionalSelector {
+
+ /*
+ * see also: {@link org.w3c.css.sac.Condition#SAC_LANG_CONDITION}
+ *
+ * This condition checks the language of the node. Example: :lang(fr)
+ */
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, LangCondition langCondition) {
+ String wantedLangIndex = langCondition.getLang();
+ String wantedLang = stringMap.get(wantedLangIndex);
+ return wantedLang.equals(SelectorUtils.lang(element));
+ }
+
+ @Override
+ public ConditionSimpleComponent conditionToXPath(Map stringMap, Selector simpleSelector, LangCondition langCondition) {
+ String wantedLangIndex = langCondition.getLang();
+ String wantedLang = stringMap.get(wantedLangIndex);
+ return new ConditionSimpleComponent("[ancestor-or-self::*[@lang][1]/@lang = '" + wantedLang + "']");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LastPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LastPseudoClass.java
new file mode 100644
index 00000000..67ea0f88
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LastPseudoClass.java
@@ -0,0 +1,32 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionToAllComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :last
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class LastPseudoClass implements PseudoClass {
+
+ private static final String LAST_PSEUDO_CLASS_NO_COLON = "last";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return LAST_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return EqPseudoClass.isEq(driver, element, pseudoClassSelector, -1);
+ }
+
+ @Override
+ public ConditionToAllComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionToAllComponent("[position() = last()]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/LtPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LtPseudoClass.java
similarity index 50%
rename from src/main/java/io/github/seleniumquery/selectors/pseudoclasses/LtPseudoClass.java
rename to src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LtPseudoClass.java
index fa7b867d..0129ca83 100644
--- a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/LtPseudoClass.java
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/LtPseudoClass.java
@@ -1,70 +1,81 @@
-package io.github.seleniumquery.by.evaluator.conditionals.pseudoclasses;
-
-import io.github.seleniumquery.by.selector.CompiledSelector;
-import io.github.seleniumquery.by.selector.SeleniumQueryCssCompiler;
-import io.github.seleniumquery.by.selector.SqCSSFilter;
-
-import java.util.List;
-
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-
-public class LtPseudoClass implements PseudoClass {
-
- private static final LtPseudoClass instance = new LtPseudoClass();
- public static LtPseudoClass getInstance() {
- return instance;
- }
- private LtPseudoClass() { }
-
- @Override
- public boolean isApplicable(String pseudoClassValue) {
- return pseudoClassValue.matches("lt\\(.*\\)");
- }
-
- @Override
- public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
- String ltIndex = pseudoClassSelector.getPseudoClassContent();
- if (!ltIndex.matches("[+-]?\\d+")) {
- throw new RuntimeException("The :lt() pseudo-class requires an integer but got: " + ltIndex);
- }
- if (ltIndex.charAt(0) == '+') {
- ltIndex = ltIndex.substring(1);
- }
- int index = Integer.valueOf(ltIndex);
-
- return LtPseudoClass.isLt(driver, element, pseudoClassSelector, index);
- }
-
- private static boolean isLt(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector, int wantedIndex) {
- if (wantedIndex == 0) {
- return false;
- }
- CompiledSelector compileSelector = SeleniumQueryCssCompiler.compileSelector(driver, pseudoClassSelector.getStringMap(), pseudoClassSelector.getSelector());
- List elements = compileSelector.execute(driver);
- if (elements.isEmpty()) {
- return false;
- }
- int actuallyWantedIndex = wantedIndex;
- if (wantedIndex < 0) {
- actuallyWantedIndex = elements.size() + wantedIndex;
- }
-
- if (elements.size() <= actuallyWantedIndex) {
- return true;
- }
- int indexFound = elements.indexOf(element);
- if (indexFound == -1) {
- return false;
- }
- return indexFound < actuallyWantedIndex;
- }
-
- @Override
- public CompiledSelector compilePseudoClass(WebDriver driver, PseudoClassSelector pseudoClassSelector) {
- // :lt() is an extension selector, no browser implements it natively
- SqCSSFilter ltPseudoClassFilter = new PseudoClassFilter(getInstance(), pseudoClassSelector);
- return CompiledSelector.createFilterOnlySelector(ltPseudoClassFilter);
- }
-
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionToAllComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * :lt(#)
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class LtPseudoClass implements PseudoClass {
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.matches("lt\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String ltIndex = pseudoClassSelector.getPseudoClassContent();
+ if (!ltIndex.matches("[+-]?\\d+")) {
+ throw new RuntimeException("The :lt() pseudo-class requires an integer but got: " + ltIndex);
+ }
+ if (ltIndex.charAt(0) == '+') {
+ ltIndex = ltIndex.substring(1);
+ }
+ int index = Integer.valueOf(ltIndex);
+
+ return LtPseudoClass.isLt(driver, element, pseudoClassSelector, index);
+ }
+
+ private static boolean isLt(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector, int wantedIndex) {
+ if (wantedIndex == 0) {
+ return false;
+ }
+ TagComponent compiledSelector = XPathComponentCompilerService.compileSelector(pseudoClassSelector.getStringMap(), pseudoClassSelector.getSelector());
+ List elements = compiledSelector.findWebElements(driver);
+ if (elements.isEmpty()) {
+ return false;
+ }
+ int actuallyWantedIndex = wantedIndex;
+ if (wantedIndex < 0) {
+ actuallyWantedIndex = elements.size() + wantedIndex;
+ }
+
+ if (elements.size() <= actuallyWantedIndex) {
+ return true;
+ }
+ int indexFound = elements.indexOf(element);
+ //noinspection SimplifiableIfStatement
+ if (indexFound == -1) {
+ return false;
+ }
+ return indexFound < actuallyWantedIndex;
+ }
+
+ @Override
+ public ConditionToAllComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String eqIndex = pseudoClassSelector.getPseudoClassContent();
+ if (!eqIndex.matches("[+-]?\\d+")) {
+ throw new RuntimeException("The :lt() pseudo-class requires an integer but got: " + eqIndex);
+ }
+ if (eqIndex.charAt(0) == '+') {
+ eqIndex = eqIndex.substring(1);
+ }
+ int index = Integer.valueOf(eqIndex);
+
+ if (index >= 0) {
+ return new ConditionToAllComponent("[position() < " + (index + 1) + "]");
+ }
+ String xPathExpression = "[position() < (last()-" + (-index - 1) + ")]";
+ return new ConditionToAllComponent(xPathExpression);
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/NotPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/NotPseudoClass.java
new file mode 100644
index 00000000..4864aa2d
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/NotPseudoClass.java
@@ -0,0 +1,35 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.css.CssSelectorMatcherService;
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:not
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class NotPseudoClass implements PseudoClass {
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.matches("not-sq\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String notSelector = pseudoClassSelector.getPseudoClassContent();
+ return !CssSelectorMatcherService.elementMatchesStringSelector(driver, element, notSelector);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String notSelector = pseudoClassSelector.getPseudoClassContent();
+ String insideNotXPath = XPathComponentCompilerService.compileSelectorList(notSelector).toXPathCondition();
+ return new ConditionSimpleComponent("[not(" + insideNotXPath + ")]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/NthChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/NthChildPseudoClass.java
new file mode 100644
index 00000000..39d559c0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/NthChildPseudoClass.java
@@ -0,0 +1,85 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import io.github.seleniumquery.by.xpath.component.XPathComponent;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.Integer.parseInt;
+
+/**
+ * :nth-child()
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class NthChildPseudoClass implements PseudoClass {
+
+ private static final Pattern NTH_CHILD_REGEX = Pattern.compile("([+-]?\\d*)n(?:\\s*([+-]\\s*\\d+))?");
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return pseudoClassValue.toLowerCase().matches("nth-child\\(.*\\)");
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ XPathComponent xPathComponent = pseudoClassToXPath(pseudoClassSelector);
+ String nthChildExpression = xPathComponent.toSingleXPathExpression();
+ String nthChildExpressionRelativeToParent = "../*"+nthChildExpression;
+ List elements = element.findElements(By.xpath(nthChildExpressionRelativeToParent));
+ return elements.contains(element);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ String nthChildContent = pseudoClassSelector.getPseudoClassContent().trim();
+ // odd --> 2n+1
+ if ("odd".equals(nthChildContent)) {
+ return nthChild(2, 1);
+ // even --> 2n
+ } else if ("even".equals(nthChildContent)) {
+ return nthChild(2, 0);
+ // b --> 0n+b
+ } else if (nthChildContent.matches("[+-]?\\d+")) {
+ return nthChild(0, parseInt(nthChildContent));
+ // n --> 1n --> everyone
+ } else if (nthChildContent.matches("n")) {
+ return new ConditionSimpleComponent("[true()]");
+ // 0n --> nobody
+ } else if (nthChildContent.matches("[+-]?[0]?n")) {
+ return new ConditionSimpleComponent("[false()]");
+ }
+ // an+b --> general case
+ Matcher m = NTH_CHILD_REGEX.matcher(nthChildContent);
+ if (m.find()) {
+ String aString = m.group(1);
+ String bString = m.group(2);
+ int a = aString.matches("[+-]") ? parseInt(aString + "1") : parseInt(aString);
+ int b = (null == bString) ? 0 : parseInt(bString.replaceAll("\\s*", "").replaceAll("^\\+", ""));
+ return nthChild(a, b);
+ }
+ throw new IllegalArgumentException("The :nth-child() pseudo-class must have a content like :nth-child(odd), " +
+ ":nth-child(even), :nth-child(an+b), :nth-child(an) or :nth-child(b), where a and b are integers.");
+ }
+
+ public static ConditionSimpleComponent nthChild(int a, int b) {
+ // a == 0: 0n+b 0n-b
+ if (a == 0) {
+ return new ConditionSimpleComponent("[position() = " + b + "]");
+ }
+ // a < 0: -an+b -an-b
+ if (a < 0) {
+ return new ConditionSimpleComponent("[(position() - " + b + ") mod " + a + " = 0 and position() <= " + b + "]");
+ }
+ // a > 0: an+b an-b
+ return new ConditionSimpleComponent("[(position() - " + b + ") mod " + a + " = 0 and position() >= " + b + "]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OddPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OddPseudoClass.java
new file mode 100644
index 00000000..ce6f0fdd
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OddPseudoClass.java
@@ -0,0 +1,39 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.XPathComponentCompilerService;
+import io.github.seleniumquery.by.xpath.component.ConditionToAllComponent;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.List;
+
+/**
+ * http://api.jquery.com/odd-selector/
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class OddPseudoClass implements PseudoClass {
+
+ private static final String ODD_PSEUDO_CLASS_NO_COLON = "odd";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return ODD_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ TagComponent compiledSelector = XPathComponentCompilerService.compileSelector(pseudoClassSelector.getStringMap(), pseudoClassSelector.getSelector());
+ List elements = compiledSelector.findWebElements(driver);
+ return elements.indexOf(element) % 2 == 1;
+ }
+
+ @Override
+ public ConditionToAllComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ // notice that XPath is 1-based and :odd is not.
+ return new ConditionToAllComponent("[(position() mod 2) = 0]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OnlyChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OnlyChildPseudoClass.java
new file mode 100644
index 00000000..651b3522
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OnlyChildPseudoClass.java
@@ -0,0 +1,44 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class OnlyChildPseudoClass implements PseudoClass {
+
+ private static final String ONLY_CHILD_PSEUDO_CLASS_NO_COLON = "only-child";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return ONLY_CHILD_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ WebElement parent = SelectorUtils.parent(element);
+ //noinspection SimplifiableIfStatement
+ if (parent == null // parent is null when element is
+ || parent.getTagName().equals("html")
+ || parent.getTagName().equals("body")
+ || parent.getTagName().equals("head")) {
+ // I have tested and :only-child never worked direct children of those
+ return false;
+ }
+ return SelectorUtils.itselfWithSiblings(element).size() == 1;
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ // [last() = 1] will not suffice because it may be composed into an expression like //a[last() = 1] which will yield wrong results
+ // So you have to go up and then down again: //a[../*[last() = 1]]
+ return new ConditionSimpleComponent("[../*[last() = 1]]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OnlyOfTypePseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OnlyOfTypePseudoClass.java
new file mode 100644
index 00000000..a94ba4a9
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/OnlyOfTypePseudoClass.java
@@ -0,0 +1,40 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:only-of-type
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class OnlyOfTypePseudoClass implements PseudoClass {
+
+ private static final String ONLY_OF_TYPE_PSEUDO_CLASS_NO_COLON = "only-of-type";
+
+ private final ElementFilter onlyOfTypePseudoClassFilter = new PseudoClassFilter(this);
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return ONLY_OF_TYPE_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ String tagName = element.getTagName();
+ return driver.findElements(By.tagName(tagName)).size() == 1;
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ UnsupportedXPathPseudoClassException.xPathFiltersAreNotImplementedYed(":only-of-type");
+
+ // #no-xpath
+ return new ConditionSimpleComponent(onlyOfTypePseudoClassFilter);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ParentPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ParentPseudoClass.java
new file mode 100644
index 00000000..6014ddc4
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ParentPseudoClass.java
@@ -0,0 +1,57 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.DriverVersionUtils;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :parent
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class ParentPseudoClass implements PseudoClass {
+
+ private static final Log LOGGER = LogFactory.getLog(ParentPseudoClass.class);
+
+ private static final String PARENT_PSEUDO_CLASS_NO_COLON = "parent";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return PARENT_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ boolean isParent = isParent(element);
+ // #Cross-Driver
+ if (!isParent && DriverVersionUtils.isHtmlUnitDriverEmulatingIEBelow11(driver)) {
+ LOGGER.warn("The outcome of the selector with the pseudo-class \":parent\" could be affected:" +
+ " HtmlUnidDriver emulating IE below 11 considers elements " +
+ " with space-only content (e.g. \"
\") to be empty, while for other browsers" +
+ " they are not! There is no workaround for this, as HtmlUnitDriver ignored the spaces during" +
+ " the DOM parsing phase, and we have no means to know now if the elements had spaces (that" +
+ " were ignored) or if they were just empty.");
+ }
+ return isParent;
+ }
+
+ /**
+ * Tests if the element has any children.
+ * @param element The element to be checked if it is a parent.
+ * @return true if the element is a parent (has children), false otherwise.
+ */
+ boolean isParent(WebElement element) {
+ return !element.findElements(By.xpath("self::node()[count(node()) > 0]")).isEmpty();
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[count(node()) > 0]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PasswordPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PasswordPseudoClass.java
new file mode 100644
index 00000000..e36677b8
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PasswordPseudoClass.java
@@ -0,0 +1,15 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+/**
+ * http://api.jquery.com/password-selector/
+ *
+ * @since 0.9.0
+ * @author acdcjunior
+ */
+public class PasswordPseudoClass extends InputTypeAttributePseudoClass {
+
+ public PasswordPseudoClass() {
+ super("password");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PresentPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PresentPseudoClass.java
new file mode 100644
index 00000000..5fd63ea2
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PresentPseudoClass.java
@@ -0,0 +1,45 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.StaleElementReferenceException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :present
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class PresentPseudoClass implements PseudoClass {
+
+ private static final String PRESENT_PSEUDO_CLASS_NO_COLON = "present";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return PRESENT_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return isPresent(element);
+ }
+
+ public boolean isPresent(WebElement webElement) {
+ try {
+ // calling ANY method forces a staleness check
+ webElement.isEnabled();
+ // passed staleness check, thus present
+ return true;
+ } catch (StaleElementReferenceException expected) {
+ // failed staleness check, so not present
+ return false;
+ }
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClass.java
new file mode 100644
index 00000000..af5283fa
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClass.java
@@ -0,0 +1,15 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+public interface PseudoClass {
+
+ boolean isApplicable(String pseudoClassValue);
+
+ boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector);
+
+ C pseudoClassToXPath(PseudoClassSelector pseudoClassSelector);
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassCssSelector.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassCssSelector.java
new file mode 100644
index 00000000..46e33624
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassCssSelector.java
@@ -0,0 +1,64 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.css.CssConditionalSelector;
+import io.github.seleniumquery.by.xpath.component.ConditionComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.Selector;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * :pseudo-classes
+ * :pseudo-classes([args])
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class PseudoClassCssSelector implements CssConditionalSelector {
+
+ private final List pseudoClasses = Arrays.asList(new CheckedPseudoClass(),
+ new SelectedPseudoClass(), new EqPseudoClass(), new OnlyChildPseudoClass(),
+ new ContainsPseudoClass(), new FirstChildPseudoClass(), new NotPseudoClass(),
+ new OnlyOfTypePseudoClass(), new RootPseudoClass(), new PresentPseudoClass(),
+ new EnabledPseudoClass(), new DisabledPseudoClass(), new VisiblePseudoClass(),
+ new HiddenPseudoClass(), new FirstPseudoClass(), new LastPseudoClass(),
+ new CheckboxPseudoClass(), new RadioPseudoClass(), new ImagePseudoClass(),
+ new PasswordPseudoClass(), new FilePseudoClass(), new SubmitPseudoClass(),
+ new ResetPseudoClass(), new ButtonPseudoClass(), new InputPseudoClass(),
+ new HeaderPseudoClass(), new LtPseudoClass(), new GtPseudoClass(),
+ new FocusPseudoClass(), new FocusablePseudoClass(), new TabbablePseudoClass(),
+ new HasPseudoClass(), new LangPseudoClass(), new ParentPseudoClass(),
+ new EmptyPseudoClass(), new TextPseudoClass(), new EvenPseudoClass(),
+ new OddPseudoClass(), new NthChildPseudoClass());
+
+ @Override
+ public boolean isCondition(WebDriver driver, WebElement element, Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ String pseudoClassValue = attributeCondition.getValue();
+ for (PseudoClass pseudoClass : pseudoClasses) {
+ if (pseudoClass.isApplicable(pseudoClassValue)) {
+ return pseudoClass.isPseudoClass(driver, element, new PseudoClassSelector(stringMap, selectorUpToThisPoint, pseudoClassValue));
+ }
+ }
+ System.err.println("Warning: Unsupported pseudo-class: " + pseudoClassValue);
+ return false;
+ }
+
+ @Override
+ public ConditionComponent conditionToXPath(Map stringMap, Selector selectorUpToThisPoint, AttributeCondition attributeCondition) {
+ String pseudoClassValue = attributeCondition.getValue();
+ for (PseudoClass pseudoClass : pseudoClasses) {
+ if (pseudoClass.isApplicable(pseudoClassValue)) {
+ return pseudoClass.pseudoClassToXPath(new PseudoClassSelector(stringMap, selectorUpToThisPoint, pseudoClassValue));
+ }
+ }
+ PseudoClassSelector pseudoClassSelector = new PseudoClassSelector(stringMap, selectorUpToThisPoint, pseudoClassValue);
+ // right now we'll just exit, hoping to cause less problems
+ throw new UnsupportedPseudoClassException(pseudoClassSelector.getOriginalPseudoClassSelector());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/PseudoClassFilter.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassFilter.java
similarity index 79%
rename from src/main/java/io/github/seleniumquery/selectors/pseudoclasses/PseudoClassFilter.java
rename to src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassFilter.java
index ef547051..05400a15 100644
--- a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/PseudoClassFilter.java
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassFilter.java
@@ -1,6 +1,6 @@
-package io.github.seleniumquery.by.evaluator.conditionals.pseudoclasses;
+package io.github.seleniumquery.by.css.pseudoclasses;
-import io.github.seleniumquery.by.selector.SqCSSFilter;
+import io.github.seleniumquery.by.filter.ElementFilter;
import java.util.Iterator;
import java.util.List;
@@ -10,7 +10,7 @@
import org.openqa.selenium.WebElement;
import org.w3c.css.sac.Selector;
-class PseudoClassFilter implements SqCSSFilter {
+class PseudoClassFilter implements ElementFilter {
public static final Map STRING_MAP_NOT_USED = null;
public static final Selector SELECTOR_NOT_USED = null;
@@ -30,7 +30,7 @@ public PseudoClassFilter(PseudoClass pseudoClass, PseudoClassSelector pseudoClas
}
@Override
- public List filter(WebDriver driver, List elements) {
+ public List filterElements(WebDriver driver, List elements) {
for (Iterator iterator = elements.iterator(); iterator.hasNext();) {
WebElement webElement = iterator.next();
if (!pseudoClass.isPseudoClass(driver, webElement, this.pseudoClassSelector)) {
diff --git a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/PseudoClassSelector.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassSelector.java
similarity index 95%
rename from src/main/java/io/github/seleniumquery/selectors/pseudoclasses/PseudoClassSelector.java
rename to src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassSelector.java
index d656a7aa..20ab86a8 100644
--- a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/PseudoClassSelector.java
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/PseudoClassSelector.java
@@ -1,4 +1,4 @@
-package io.github.seleniumquery.by.evaluator.conditionals.pseudoclasses;
+package io.github.seleniumquery.by.css.pseudoclasses;
import java.util.Map;
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/RadioPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/RadioPseudoClass.java
new file mode 100644
index 00000000..6ad42caf
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/RadioPseudoClass.java
@@ -0,0 +1,15 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+/**
+ * http://api.jquery.com/radio-selector/
+ *
+ * @since 0.9.0
+ * @author acdcjunior
+ */
+public class RadioPseudoClass extends InputTypeAttributePseudoClass {
+
+ public RadioPseudoClass() {
+ super("radio");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ResetPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ResetPseudoClass.java
new file mode 100644
index 00000000..112926fe
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/ResetPseudoClass.java
@@ -0,0 +1,33 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * http://api.jquery.com/reset-selector/
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class ResetPseudoClass implements PseudoClass {
+
+ private static final String RESET = "reset";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return RESET.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return ("input".equals(element.getTagName()) || "button".equals(element.getTagName()))
+ && RESET.equalsIgnoreCase(element.getAttribute("type"));
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[(local-name() = 'input' or local-name() = 'button') and @type = 'reset']");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/RootPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/RootPseudoClass.java
new file mode 100644
index 00000000..c83316ad
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/RootPseudoClass.java
@@ -0,0 +1,32 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:root
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class RootPseudoClass implements PseudoClass {
+
+ private static final String ROOT_PSEUDO_CLASS_NO_COLON = "root";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return ROOT_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return element.getTagName().equals("html");
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[local-name() = 'html']");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/SelectedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/SelectedPseudoClass.java
new file mode 100644
index 00000000..5e406b43
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/SelectedPseudoClass.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import static io.github.seleniumquery.by.WebElementUtils.isOptionTag;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:selected
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class SelectedPseudoClass implements PseudoClass {
+
+ public static final ElementFilter SELECTED_FILTER = new PseudoClassFilter(new SelectedPseudoClass());
+
+ // NOTE: This XPath does not work. Sometimes an element is selected WITHOUT having a selected attribute
+ public static final String SELECTED_PSEUDO_CONDITION = "self::option and " +
+ "(" +
+ "@selected or (ancestor::select[not(@multiple) and not(option[@selected])] and position() = 1)" +
+ ")";
+ private static final String SELECTED_PSEUDO_CONDITIONAL_EXPRESSION = "[" + SELECTED_PSEUDO_CONDITION + "]";
+
+ private static final String SELECTED_PSEUDO_CLASS_NO_COLON = "selected";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return SELECTED_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return isSelected(element);
+ }
+
+ public boolean isSelected(WebElement element) {
+ return isOptionTag(element) && element.isSelected();
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent(SELECTED_PSEUDO_CONDITIONAL_EXPRESSION);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/SubmitPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/SubmitPseudoClass.java
new file mode 100644
index 00000000..7a556d17
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/SubmitPseudoClass.java
@@ -0,0 +1,101 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import com.gargoylesoftware.htmlunit.html.DomAttr;
+import com.gargoylesoftware.htmlunit.html.DomElement;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.htmlunit.HtmlUnitWebElement;
+
+import java.lang.reflect.Method;
+
+import static io.github.seleniumquery.by.DriverVersionUtils.isHtmlUnitDriverEmulatingIEBelow11;
+
+/**
+ *
+ * http://api.jquery.com/submit-selector/
+ *
+ *
+ * Notice that :submit is not consistent accross browsers when the type attribute is not defined.
+ * IE, HtmlUnit acting as IE and even FirefoxDriver (not Firefox itself) "implicitly create" a type attribute
+ * when it doesn't exist! (The jQuery docs also talks about this issue!)
+ *
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+public class SubmitPseudoClass implements PseudoClass {
+
+ private static final Log LOGGER = LogFactory.getLog(SubmitPseudoClass.class);
+
+ private static final String SUBMIT = "submit";
+ private static final String INPUT = "input";
+ private static final String BUTTON = "button";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return SUBMIT.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return inputWithTypeSubmit(element) || buttonWithTypeSubmitOrWithoutType(driver, element);
+ }
+
+ private boolean inputWithTypeSubmit(WebElement element) {
+ return INPUT.equals(element.getTagName()) && SUBMIT.equalsIgnoreCase(element.getAttribute("type"));
+ }
+
+ private boolean buttonWithTypeSubmitOrWithoutType(WebDriver driver, WebElement element) {
+ boolean isButtonTag = BUTTON.equals(element.getTagName());
+ if (!isButtonTag) {
+ return false;
+ }
+ boolean isTypeSubmit = SUBMIT.equalsIgnoreCase(element.getAttribute("type"));
+ if (isTypeSubmit) {
+ return true;
+ }
+ if (isHtmlUnitDriverEmulatingIEBelow11(driver)) {
+ return getDeclaredTypeAttributeFromHtmlUnitButton(element) == null;
+ } else {
+ return element.getAttribute("type") == null;
+ }
+ }
+
+ /**
+ * Latest HtmlUnit returns @type=button when type is not set and browser is <=IE9. We don't want that,
+ * we want null if it is not set, so we can decide if it is submit or not. Because if it is null,
+ * then it is :submit. If type is "button", though, it is not.
+ *
+ * #Cross-Driver
+ * #HtmlUnit #reflection #hack
+ */
+ private String getDeclaredTypeAttributeFromHtmlUnitButton(WebElement element) {
+ try {
+ if (element instanceof HtmlUnitWebElement) {
+ Method getElementMethod = HtmlUnitWebElement.class.getDeclaredMethod("getElement");
+ getElementMethod.setAccessible(true);
+ Object htmlUnitElement = getElementMethod.invoke(element);
+ if (htmlUnitElement instanceof DomElement) {
+ DomAttr domAttr = ((DomElement) htmlUnitElement).getAttributesMap().get("type");
+ return domAttr == null ? null : domAttr.getNodeValue();
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.debug("Unable to retrieve declared \"type\" attribute from HtmlUnitWebElement "+element+".", e);
+ }
+ return element.getAttribute("type");
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[("
+ + "( local-name() = 'input' and @type = 'submit' ) or "
+ + "( local-name() = 'button' and (@type = 'submit' or not(@type)) )"
+ + ")]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/TabbablePseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/TabbablePseudoClass.java
similarity index 50%
rename from src/main/java/io/github/seleniumquery/selectors/pseudoclasses/TabbablePseudoClass.java
rename to src/main/java/io/github/seleniumquery/by/css/pseudoclasses/TabbablePseudoClass.java
index c260eaed..a2cfbec2 100644
--- a/src/main/java/io/github/seleniumquery/selectors/pseudoclasses/TabbablePseudoClass.java
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/TabbablePseudoClass.java
@@ -1,8 +1,7 @@
-package io.github.seleniumquery.by.evaluator.conditionals.pseudoclasses;
-
-import io.github.seleniumquery.by.selector.CompiledSelector;
-import io.github.seleniumquery.by.selector.SqCSSFilter;
+package io.github.seleniumquery.by.css.pseudoclasses;
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
@@ -12,24 +11,20 @@
* From http://api.jqueryui.com/tabbable-selector/: "Elements with a negative tab index are :focusable, but not :tabbable."
*
* @author acdcjunior
- * @since 1.0.0
+ * @since 0.9.0
*/
-public class TabbablePseudoClass implements PseudoClass {
-
- private static final TabbablePseudoClass instance = new TabbablePseudoClass();
- public static TabbablePseudoClass getInstance() {
- return instance;
- }
- private TabbablePseudoClass() { }
+public class TabbablePseudoClass implements PseudoClass {
private static final String TABBABLE_PSEUDO_CLASS_NO_COLON = "tabbable";
- private static final FocusablePseudoClass focusable = FocusablePseudoClass.getInstance();
-
+
+ private final FocusablePseudoClass focusable = new FocusablePseudoClass();
+ private final ElementFilter tabbablePseudoClassFilter = new PseudoClassFilter(this);
+
@Override
public boolean isApplicable(String pseudoClassValue) {
return TABBABLE_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
}
-
+
@Override
public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
boolean isFocusable = focusable.isPseudoClass(driver, element, pseudoClassSelector);
@@ -45,12 +40,15 @@ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSe
boolean tabindexIsNegativeInteger = tabindex.matches("\\s*-\\d+\\s*");
return !tabindexIsNegativeInteger;
}
-
- private static final SqCSSFilter tabbablePseudoClassFilter = new PseudoClassFilter(getInstance());
+
+ // see :focusable. change there before here, this selector is highly dependable on :focusable as it is just a small change to it
@Override
- public CompiledSelector compilePseudoClass(WebDriver driver, PseudoClassSelector pseudoClassSelector) {
- // no browser supports :tabbable natively
- return CompiledSelector.createFilterOnlySelector(tabbablePseudoClassFilter);
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ UnsupportedXPathPseudoClassException.xPathFiltersAreNotImplementedYed(":tabbable");
+
+ // #no-xpath
+ System.err.println(":tabbable is not fully XPath supported (if the 'display:none' is in a CSS class, it won't know)!!!");
+ return new ConditionSimpleComponent("[(" + FocusablePseudoClass.FOCUSABLE_XPATH + " and (not(@tabindex) or @tabindex > -1))]", tabbablePseudoClassFilter);
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/TextPseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/TextPseudoClass.java
new file mode 100644
index 00000000..912b7dec
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/TextPseudoClass.java
@@ -0,0 +1,35 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :text
+ * http://api.jquery.com/text-selector/
+ *
+ * @author acdcjunior
+ *
+ * @since 0.9.0
+ */
+class TextPseudoClass implements PseudoClass {
+
+ private static final String TEXT_PSEUDO_CLASS_NO_COLON = "text";
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return TEXT_PSEUDO_CLASS_NO_COLON.equalsIgnoreCase(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return "input".equals(element.getTagName()) &&
+ (element.getAttribute("type") == null || "text".equalsIgnoreCase(element.getAttribute("type")));
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ return new ConditionSimpleComponent("[self::input and (translate(@type,'TEXT','text') = 'text' or not(@type))]");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/UnsupportedPseudoClassException.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/UnsupportedPseudoClassException.java
new file mode 100644
index 00000000..3e17f800
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/UnsupportedPseudoClassException.java
@@ -0,0 +1,21 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import org.w3c.css.sac.SimpleSelector;
+
+import java.util.Map;
+
+public class UnsupportedPseudoClassException extends RuntimeException {
+
+ public UnsupportedPseudoClassException(String pseudoClass) {
+ this(pseudoClass, "");
+ }
+
+ public UnsupportedPseudoClassException(String pseudoClass, String reason) {
+ super("The pseudo-class \""+pseudoClass+"\" is not supported" + (reason.isEmpty() ? "." : ": "+reason));
+ }
+
+ public UnsupportedPseudoClassException(Map stringMap, SimpleSelector selectorUpToThisPoint, String pseudoClassValue) {
+ this(new PseudoClassSelector(stringMap, selectorUpToThisPoint, pseudoClassValue).getOriginalPseudoClassSelector());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/UnsupportedXPathPseudoClassException.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/UnsupportedXPathPseudoClassException.java
new file mode 100644
index 00000000..74da5467
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/UnsupportedXPathPseudoClassException.java
@@ -0,0 +1,15 @@
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+public class UnsupportedXPathPseudoClassException extends UnsupportedPseudoClassException {
+
+ private static final long serialVersionUID = 1L;
+
+ public static boolean xPathFiltersAreNotImplementedYed(String pseudoClass) {
+ throw new UnsupportedXPathPseudoClassException(pseudoClass);
+ }
+
+ public UnsupportedXPathPseudoClassException(String pseudoClass) {
+ super(pseudoClass);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/VisiblePseudoClass.java b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/VisiblePseudoClass.java
new file mode 100644
index 00000000..57753ed9
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/pseudoclasses/VisiblePseudoClass.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.css.pseudoclasses;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.filter.ElementFilter;
+import io.github.seleniumquery.by.xpath.component.ConditionSimpleComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ * :visible
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class VisiblePseudoClass implements PseudoClass {
+
+ public static final String VISIBLE_PSEUDO_CLASS_NO_COLON = "visible";
+
+ public static final ElementFilter VISIBLE_FILTER = new PseudoClassFilter(new VisiblePseudoClass());
+
+ @Override
+ public boolean isApplicable(String pseudoClassValue) {
+ return VISIBLE_PSEUDO_CLASS_NO_COLON.equals(pseudoClassValue);
+ }
+
+ @Override
+ public boolean isPseudoClass(WebDriver driver, WebElement element, PseudoClassSelector pseudoClassSelector) {
+ return SelectorUtils.isVisible(element);
+ }
+
+ @Override
+ public ConditionSimpleComponent pseudoClassToXPath(PseudoClassSelector pseudoClassSelector) {
+ UnsupportedXPathPseudoClassException.xPathFiltersAreNotImplementedYed(":visible");
+
+ // #no-xpath
+ // we can't use XPath because it can't see the styles affecting the element's classes, which can pretty much
+ // turn any element, including itself or , visible.
+ return new ConditionSimpleComponent(VISIBLE_FILTER);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/css/tagname/TagNameSelector.java b/src/main/java/io/github/seleniumquery/by/css/tagname/TagNameSelector.java
new file mode 100644
index 00000000..f4f98e4e
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/css/tagname/TagNameSelector.java
@@ -0,0 +1,31 @@
+package io.github.seleniumquery.by.css.tagname;
+
+import io.github.seleniumquery.by.css.CssSelector;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.w3c.css.sac.ElementSelector;
+
+import java.util.Map;
+
+/**
+ * $("tagname")
+ *
+ * @author acdcjunior
+ * @since 0.9.0
+ */
+public class TagNameSelector implements CssSelector {
+
+ @Override
+ public boolean is(WebDriver driver, WebElement element, Map stringMap, ElementSelector elementSelector) {
+ String name = elementSelector.getLocalName();
+ return name == null || name.equalsIgnoreCase(element.getTagName());
+ }
+
+ @Override
+ public TagComponent toXPath(Map stringMap, ElementSelector selector) {
+ String tagName = selector.toString();
+ return new TagComponent(tagName);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/SQCssSelectorList.java b/src/main/java/io/github/seleniumquery/by/csstree/SQCssSelectorList.java
new file mode 100644
index 00000000..a01a2204
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/SQCssSelectorList.java
@@ -0,0 +1,31 @@
+package io.github.seleniumquery.by.csstree;
+
+import io.github.seleniumquery.by.csstree.selector.SQCssSelector;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class SQCssSelectorList implements Iterable {
+
+ private List sqCssSelectors;
+
+ public SQCssSelectorList(List sqCssSelectors) {
+ this.sqCssSelectors = Collections.unmodifiableList(new ArrayList(sqCssSelectors));
+ }
+
+ public SQCssSelector selector(int i) {
+ return sqCssSelectors.get(i);
+ }
+
+ public SQCssSelector firstSelector() {
+ return selector(0);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return sqCssSelectors.iterator();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssAndCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssAndCondition.java
new file mode 100644
index 00000000..7cd6be16
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssAndCondition.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition;
+
+/**
+ * Chains conditions.
+ * Example:
+ * - tag.A.B
+ * will be parsed/translated into
+ * - tag[AndCondition(ClassCondition("A"), ClassCondition("B"))]
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssAndCondition implements SQCssCondition, SQCssConditionImplementedNotYet {
+
+ private SQCssCondition firstCondition;
+ private SQCssCondition secondCondition;
+
+ public SQCssAndCondition(SQCssCondition firstCondition, SQCssCondition secondCondition) {
+ this.firstCondition = firstCondition;
+ this.secondCondition = secondCondition;
+ }
+
+ public SQCssCondition getFirstCondition() {
+ return firstCondition;
+ }
+
+ public SQCssCondition getSecondCondition() {
+ return secondCondition;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssCondition.java
new file mode 100644
index 00000000..2a41a707
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssCondition.java
@@ -0,0 +1,4 @@
+package io.github.seleniumquery.by.csstree.condition;
+
+public interface SQCssCondition {
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssConditionImplementedLocators.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssConditionImplementedLocators.java
new file mode 100644
index 00000000..01e97eed
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssConditionImplementedLocators.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition;
+
+import io.github.seleniumquery.by.locator.SQLocator;
+
+/**
+ * This is temporary.
+ *
+ * While I don't implement all SQCssConditions, I go applying this interface to those
+ * that I implement the toSQLocator() method.
+ *
+ * After all of them are complete, I move this method to SQCssCondition and remove this interface.
+ */
+public interface SQCssConditionImplementedLocators {
+ SQLocator toSQLocator(SQLocator leftLocator);
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssConditionImplementedNotYet.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssConditionImplementedNotYet.java
new file mode 100644
index 00000000..cf248014
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssConditionImplementedNotYet.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition;
+
+/**
+ * This also very is temporary.
+ * Just used to mark not implemented yet pseudos, so my work to find them is easier.
+ */
+@Deprecated
+public interface SQCssConditionImplementedNotYet { }
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssUnknownConditionException.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssUnknownConditionException.java
new file mode 100644
index 00000000..4533789b
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/SQCssUnknownConditionException.java
@@ -0,0 +1,11 @@
+package io.github.seleniumquery.by.csstree.condition;
+
+import org.w3c.css.sac.Condition;
+
+public class SQCssUnknownConditionException extends RuntimeException {
+
+ public SQCssUnknownConditionException(Condition condition) {
+ super("CSS condition " + condition.getClass().getSimpleName() + " (type=" + condition.getConditionType() + ") is invalid or not supported!");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssAttributeCondition.java
new file mode 100644
index 00000000..169c3cc1
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssAttributeCondition.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssCondition;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.SQLocator;
+import io.github.seleniumquery.by.locator.SQLocatorUtils;
+
+/**
+ * A class that holds an attribute name and a wanted value.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public abstract class SQCssAttributeCondition implements SQCssCondition, SQCssConditionImplementedLocators {
+
+ protected String attributeName;
+ protected String wantedValue;
+
+ protected SQCssAttributeCondition(String attributeName, String wantedValue) {
+ this.attributeName = attributeName;
+ this.wantedValue = wantedValue;
+ }
+
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ public String getWantedValue() {
+ return wantedValue;
+ }
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ CSSLocator newCssSelector = leftLocator.getCSSLocator().merge(toCSS());
+ String newXPathExpression = SQLocatorUtils.conditionalSimpleXPathMerge(leftLocator.getXPathExpression(), toXPath());
+ return new SQLocator(newCssSelector, newXPathExpression, leftLocator);
+ }
+
+ protected abstract CSSLocator toCSS();
+
+ protected abstract String toXPath();
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssClassAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssClassAttributeCondition.java
new file mode 100644
index 00000000..80338277
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssClassAttributeCondition.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssCondition;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.SQLocator;
+import io.github.seleniumquery.by.locator.SQLocatorUtils;
+
+/**
+ * .class
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssClassAttributeCondition implements SQCssCondition, SQCssConditionImplementedLocators {
+
+ private String unescapedClassName;
+
+ public SQCssClassAttributeCondition(String unescapedClassName) {
+ this.unescapedClassName = unescapedClassName;
+ }
+
+ public String getClassName() {
+ return unescapedClassName;
+ }
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ CSSLocator newCssSelector = leftLocator.getCSSLocator().merge(toCSS());
+ String newXPathExpression = SQLocatorUtils.conditionalSimpleXPathMerge(leftLocator.getXPathExpression(), toXPath());
+ return new SQLocator(newCssSelector, newXPathExpression, leftLocator);
+ }
+
+ private CSSLocator toCSS() {
+ return new CSSLocator("." + this.unescapedClassName);
+ }
+
+ private String toXPath() {
+ return "contains(concat(' ', normalize-space(@class), ' '), ' " + unescapedClassName + " ')";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsPrefixAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsPrefixAttributeCondition.java
new file mode 100644
index 00000000..5d6c2bf0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsPrefixAttributeCondition.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils;
+import io.github.seleniumquery.by.locator.CSSLocator;
+
+/**
+ * [hreflang|="en"]
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssContainsPrefixAttributeCondition extends SQCssAttributeCondition {
+
+ public SQCssContainsPrefixAttributeCondition(String attributeName, String wantedValue) {
+ super(attributeName, wantedValue);
+ }
+
+ protected CSSLocator toCSS() {
+ return new CSSLocator("[" + this.attributeName + "|='" + this.wantedValue + "']");
+ }
+
+ protected String toXPath() {
+ String attrName = AttributeEvaluatorUtils.toXPathAttribute(this.attributeName);
+ String attrValue = SelectorUtils.intoEscapedXPathString(this.wantedValue);
+ String attrValueWithSuffix = SelectorUtils.intoEscapedXPathString(this.wantedValue + "-");
+ return "("+attrName+" = "+attrValue+" or starts-with("+ attrName+", "+attrValueWithSuffix+"))";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsSubstringAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsSubstringAttributeCondition.java
new file mode 100644
index 00000000..bdbceae8
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsSubstringAttributeCondition.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils;
+import io.github.seleniumquery.by.locator.CSSLocator;
+
+/**
+ * [attribute*=stringToContain]
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssContainsSubstringAttributeCondition extends SQCssAttributeCondition {
+
+ public SQCssContainsSubstringAttributeCondition(String attributeName, String wantedValue) {
+ super(attributeName, wantedValue);
+ }
+
+ protected CSSLocator toCSS() {
+ return new CSSLocator("[" + this.attributeName + "*='" + this.wantedValue + "']");
+ }
+
+ protected String toXPath() {
+ String escapedAttributeName = AttributeEvaluatorUtils.toXPathAttribute(this.attributeName);
+ String escapedWantedValue = SelectorUtils.intoEscapedXPathString(this.wantedValue);
+ return "contains(" + escapedAttributeName + ", " + escapedWantedValue + ")";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsWordAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsWordAttributeCondition.java
new file mode 100644
index 00000000..3d511f69
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssContainsWordAttributeCondition.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils;
+import io.github.seleniumquery.by.locator.CSSLocator;
+
+/**
+ * [values~="10"]
+ *
+ * Case INsensitive!
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssContainsWordAttributeCondition extends SQCssAttributeCondition {
+
+ public SQCssContainsWordAttributeCondition(String attributeName, String wantedValue) {
+ super(attributeName, wantedValue);
+ }
+
+ protected CSSLocator toCSS() {
+ return new CSSLocator("[" + this.attributeName + "~='" + this.wantedValue + "']");
+ }
+
+ protected String toXPath() {
+ String escapedAttributeName = AttributeEvaluatorUtils.toXPathAttribute(this.attributeName);
+ String escapedWantedValueSurroundedBySpaces = SelectorUtils.intoEscapedXPathString(" " + this.wantedValue + " ");
+ return "contains(concat(' ', normalize-space(" + escapedAttributeName + "), ' '), " + escapedWantedValueSurroundedBySpaces + ")";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssEndsWithAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssEndsWithAttributeCondition.java
new file mode 100644
index 00000000..4b5c8c47
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssEndsWithAttributeCondition.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils;
+import io.github.seleniumquery.by.locator.CSSLocator;
+
+/**
+ * [attribute$=stringToEnd]
+ *
+ * CASE SENSITIVE! http://api.jquery.com/attribute-ends-with-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssEndsWithAttributeCondition extends SQCssAttributeCondition {
+
+ public SQCssEndsWithAttributeCondition(String attributeName, String wantedValue) {
+ super(attributeName, wantedValue);
+ }
+
+ protected CSSLocator toCSS() {
+ return new CSSLocator("[" + this.attributeName + "$='" + this.wantedValue + "']");
+ }
+
+ protected String toXPath() {
+ String escapedAttributeName = AttributeEvaluatorUtils.toXPathAttribute(this.attributeName);
+ String attrValue = this.wantedValue;
+ String escapedWantedValue = SelectorUtils.intoEscapedXPathString(this.wantedValue);
+ return "substring("+escapedAttributeName+", string-length(" + escapedAttributeName + ")-" +
+ (attrValue.length() - 1) + ") = " + escapedWantedValue;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssEqualsOrHasAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssEqualsOrHasAttributeCondition.java
new file mode 100644
index 00000000..ec97c5c3
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssEqualsOrHasAttributeCondition.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils;
+import io.github.seleniumquery.by.locator.CSSLocator;
+
+/**
+ * [simple]
+ * [restart="never"]
+ *
+ * Case INsensitive!
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssEqualsOrHasAttributeCondition extends SQCssAttributeCondition {
+
+ /**
+ * [simple]
+ * Attribute value is null in this case.
+ */
+ public SQCssEqualsOrHasAttributeCondition(String attributeName) {
+ super(attributeName, null);
+ }
+
+ /**
+ * [restart="never"]
+ */
+ public SQCssEqualsOrHasAttributeCondition(String attributeName, String wantedValue) {
+ super(attributeName, wantedValue);
+ }
+
+ protected CSSLocator toCSS() {
+ if (this.wantedValue != null) {
+ return new CSSLocator("[" + this.attributeName + "=" + this.wantedValue + "]");
+ }
+ return new CSSLocator("[" + this.attributeName + "]");
+ }
+
+ protected String toXPath() {
+ if (this.wantedValue != null) {
+ String escapedWantedValue = SelectorUtils.intoEscapedXPathString(this.wantedValue);
+ return AttributeEvaluatorUtils.toXPathAttribute(this.attributeName) + "=" + escapedWantedValue;
+ }
+ return AttributeEvaluatorUtils.toXPathAttribute(this.attributeName);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssIdAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssIdAttributeCondition.java
new file mode 100644
index 00000000..e0cce6ae
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssIdAttributeCondition.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssCondition;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.SQLocator;
+import io.github.seleniumquery.by.locator.SQLocatorUtils;
+
+/**
+ * #id
+ *
+ * CASE SENSITIVE!
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssIdAttributeCondition implements SQCssCondition, SQCssConditionImplementedLocators {
+
+ private String id;
+
+ public SQCssIdAttributeCondition(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ CSSLocator newCssSelector = leftLocator.getCSSLocator().merge(toCSS());
+ String newXPathExpression = SQLocatorUtils.conditionalSimpleXPathMerge(leftLocator.getXPathExpression(), toXPath());
+ return new SQLocator(newCssSelector, newXPathExpression, leftLocator);
+ }
+
+ private CSSLocator toCSS() {
+ return new CSSLocator("#" + this.id);
+ }
+
+ private String toXPath() {
+ return "@id = '" + id + "'";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssStartsWithAttributeCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssStartsWithAttributeCondition.java
new file mode 100644
index 00000000..37ad9fc2
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/attribute/SQCssStartsWithAttributeCondition.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.attribute;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils;
+import io.github.seleniumquery.by.locator.CSSLocator;
+
+/**
+ * [attribute^=stringToStart]
+ *
+ * CASE INsensitive!
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssStartsWithAttributeCondition extends SQCssAttributeCondition {
+
+ public SQCssStartsWithAttributeCondition(String attributeName, String wantedValue) {
+ super(attributeName, wantedValue);
+ }
+
+ protected CSSLocator toCSS() {
+ return new CSSLocator("[" + this.attributeName + "^='" + this.wantedValue + "']");
+ }
+
+ protected String toXPath() {
+ String escapedAttributeName = AttributeEvaluatorUtils.toXPathAttribute(this.attributeName);
+ String escapedWantedValue = SelectorUtils.intoEscapedXPathString(this.wantedValue);
+ return "starts-with(" + escapedAttributeName + ", " + escapedWantedValue + ")";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssFunctionalIndexArgumentPseudoClassCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssFunctionalIndexArgumentPseudoClassCondition.java
new file mode 100644
index 00000000..569e9c89
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssFunctionalIndexArgumentPseudoClassCondition.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import org.openqa.selenium.InvalidSelectorException;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Superclass for all functional pseudo-classes that have an integer index as argument.
+ * E.g. {@code *:my-pseudo(123),*:my-pseudo(+77),*:my-pseudo(-35)}
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public abstract class SQCssFunctionalIndexArgumentPseudoClassCondition extends SQCssFunctionalPseudoClassCondition {
+
+ private static final Pattern INDEX_REGEX = Pattern.compile("^\\s*([+-]?\\d+)\\s*$");
+
+ public SQCssFunctionalIndexArgumentPseudoClassCondition(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ protected int getArgumentAsIndex() {
+ String eqPseudoClassArgument = getArgument();
+ Matcher m = INDEX_REGEX.matcher(eqPseudoClassArgument);
+ boolean isArgumentAnInteger = m.find();
+ if (!isArgumentAnInteger) {
+ String reason = String.format("The :%s() pseudo-class requires an integer as argument but got: \"%s\".",
+ getPseudoClassName(), eqPseudoClassArgument);
+ throw new InvalidSelectorException(reason);
+ }
+ String integerIndex = m.group(1);
+ if (integerIndex.startsWith("+")) {
+ integerIndex = integerIndex.substring(1);
+ }
+ return Integer.valueOf(integerIndex);
+ }
+
+ protected abstract String getPseudoClassName();
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssFunctionalPseudoClassCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssFunctionalPseudoClassCondition.java
new file mode 100644
index 00000000..2c6f3388
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssFunctionalPseudoClassCondition.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+
+public class SQCssFunctionalPseudoClassCondition extends SQCssPseudoClassCondition {
+
+ protected PseudoClassSelector pseudoClassSelector;
+ protected String argument;
+
+ public SQCssFunctionalPseudoClassCondition(PseudoClassSelector pseudoClassSelector) {
+ this.pseudoClassSelector = pseudoClassSelector;
+ this.argument = pseudoClassSelector.getPseudoClassContent();
+ }
+
+ public String getArgument() {
+ return argument;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssPseudoClassCondition.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssPseudoClassCondition.java
new file mode 100644
index 00000000..27d0e869
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/SQCssPseudoClassCondition.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssCondition;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.locator.SQLocator;
+
+public abstract class SQCssPseudoClassCondition implements SQCssCondition, SQCssConditionImplementedLocators {
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ return getSQCssLocatorGenerationStrategy().toSQLocator(leftLocator);
+ }
+
+ public SQCssConditionImplementedLocators getSQCssLocatorGenerationStrategy() {
+ throw new RuntimeException("This method will be abstract. It is not yet because I want the project " +
+ "to compile while I'm implementing everyone.");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssAnimatedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssAnimatedPseudoClass.java
new file mode 100644
index 00000000..f5a4d7bd
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssAnimatedPseudoClass.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.UnsupportedPseudoClassException;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.locator.SQLocator;
+
+/**
+ * To implement this selector, we would need to use JavaScript and access jQuery's internals.
+ * Considering the utility of this selector (from the user's point of view) is minimal, we aren't including it.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssAnimatedPseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedLocators {
+
+ public static final String PSEUDO = "animated";
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ throw new UnsupportedPseudoClassException(":animated", "This selector uses internals of jQuery that nor seleniumQuery, " +
+ "neither the user should access.");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssEqPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssEqPseudoClass.java
new file mode 100644
index 00000000..c5b2e007
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssEqPseudoClass.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalIndexArgumentPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.XPathMergeStrategy;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :eq(index)
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssEqPseudoClass extends SQCssFunctionalIndexArgumentPseudoClassCondition {
+
+ public static final String PSEUDO = "eq";
+
+ public NeverNativelySupportedPseudoClass eqPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ int index = getArgumentAsIndex();
+ if (index >= 0) {
+ return XPathLocator.pureXPath("position() = " + (index + 1));
+ }
+ int positionFromLast = -index - 1;
+ if (positionFromLast == 0) {
+ return XPathLocator.pureXPath("position() = last()");
+ }
+ return XPathLocator.pureXPath("position() = (last()-" + positionFromLast + ")");
+ }
+ @Override
+ public XPathMergeStrategy xPathMergeStrategy() {
+ return XPathMergeStrategy.CONDITIONAL_TO_ALL_XPATH_MERGE;
+ }
+ };
+
+ public SQCssEqPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return eqPseudoClassLocatorGenerationStrategy;
+ }
+
+ @Override
+ protected String getPseudoClassName() {
+ return PSEUDO;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssEvenPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssEvenPseudoClass.java
new file mode 100644
index 00000000..3750eb83
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssEvenPseudoClass.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :even
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssEvenPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "even";
+
+ public NeverNativelySupportedPseudoClass evenPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("(position() mod 2) = 1");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return evenPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssFirstPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssFirstPseudoClass.java
new file mode 100644
index 00000000..b935bb2d
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssFirstPseudoClass.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+
+/**
+ * :first
+ * https://api.jquery.com/first-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssFirstPseudoClass extends SQCssEqPseudoClass {
+
+ public static final String PSEUDO = "first";
+
+ public SQCssFirstPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public String getArgument() {
+ return "0";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssGtPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssGtPseudoClass.java
new file mode 100644
index 00000000..0196b679
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssGtPseudoClass.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalIndexArgumentPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.XPathMergeStrategy;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :gt(index)
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssGtPseudoClass extends SQCssFunctionalIndexArgumentPseudoClassCondition {
+
+ public static final String PSEUDO = "gt";
+
+ public NeverNativelySupportedPseudoClass gtPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ int index = getArgumentAsIndex();
+ if (index >= 0) {
+ return XPathLocator.pureXPath("position() > " + (index + 1));
+ }
+ return XPathLocator.pureXPath("position() > (last()-" + (-index - 1) + ")");
+ }
+ @Override
+ public XPathMergeStrategy xPathMergeStrategy() {
+ return XPathMergeStrategy.CONDITIONAL_TO_ALL_XPATH_MERGE;
+ }
+ };
+
+ public SQCssGtPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return gtPseudoClassLocatorGenerationStrategy;
+ }
+
+ @Override
+ protected String getPseudoClassName() {
+ return PSEUDO;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssHeaderPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssHeaderPseudoClass.java
new file mode 100644
index 00000000..09b3ba44
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssHeaderPseudoClass.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :header
+ * https://api.jquery.com/header-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssHeaderPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "header";
+
+ public static final String HEADER_XPATH_EXPRESSION = "(" +
+ "self::h0 | self::h1 | self::h2 | self::h3 | self::h4 | " +
+ "self::h5 | self::h6 | self::h7 | self::h8 | self::h9" +
+ ")";
+
+ public NeverNativelySupportedPseudoClass headerPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath(HEADER_XPATH_EXPRESSION);
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return headerPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLangPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLangPseudoClass.java
new file mode 100644
index 00000000..28fd4286
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLangPseudoClass.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+
+/**
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:lang
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssLangPseudoClass extends SQCssFunctionalPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ // :lang(), similar to :not(), gets translated into :lang-sq() by the pre-parser
+ public static final String PSEUDO = "lang-sq";
+
+ /* when used without args, such as "div:lang", the pre-parser does not translate it. it is invalid,
+ but we still match it, so we can return a proper error message */
+ public static final String PSEUDO_PURE_LANG = "lang";
+
+ public SQCssLangPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLastPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLastPseudoClass.java
new file mode 100644
index 00000000..9ff5b49d
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLastPseudoClass.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+
+/**
+ * :last
+ * https://api.jquery.com/last-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssLastPseudoClass extends SQCssEqPseudoClass {
+
+ public static final String PSEUDO = "last";
+
+ public SQCssLastPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public String getArgument() {
+ return "-1";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLtPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLtPseudoClass.java
new file mode 100644
index 00000000..6a6d1a92
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssLtPseudoClass.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalIndexArgumentPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.XPathMergeStrategy;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :lt(index)
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssLtPseudoClass extends SQCssFunctionalIndexArgumentPseudoClassCondition {
+
+ public static final String PSEUDO = "lt";
+
+ public NeverNativelySupportedPseudoClass gtPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ int index = getArgumentAsIndex();
+ if (index >= 0) {
+ return XPathLocator.pureXPath("position() < " + (index + 1));
+ }
+ return XPathLocator.pureXPath("position() < (last()-" + (-index - 1) + ")");
+ }
+ @Override
+ public XPathMergeStrategy xPathMergeStrategy() {
+ return XPathMergeStrategy.CONDITIONAL_TO_ALL_XPATH_MERGE;
+ }
+ };
+
+ public SQCssLtPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return gtPseudoClassLocatorGenerationStrategy;
+ }
+
+ @Override
+ protected String getPseudoClassName() {
+ return PSEUDO;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssNotPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssNotPseudoClass.java
new file mode 100644
index 00000000..b3beb159
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssNotPseudoClass.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+
+public class SQCssNotPseudoClass extends SQCssFunctionalPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ // :not() are translated into :not-sq() by the pre-parser
+ public static final String PSEUDO = "not-sq";
+
+ /* when used without args, such as "div:not", the pre-parser does not translate it. it is invalid,
+ but we still match it, so we can return a proper error message */
+ public static final String PSEUDO_PURE_NOT = "not";
+
+ public SQCssNotPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssNthPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssNthPseudoClass.java
new file mode 100644
index 00000000..48f2ec91
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssNthPseudoClass.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+
+/**
+ * :nth() is an alias to :eq().
+ * https://github.com/seleniumQuery/seleniumQuery/issues/27
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssNthPseudoClass extends SQCssEqPseudoClass {
+
+ public static final String PSEUDO = "nth";
+
+ public SQCssNthPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ protected String getPseudoClassName() {
+ return PSEUDO;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssOddPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssOddPseudoClass.java
new file mode 100644
index 00000000..c2733023
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssOddPseudoClass.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :odd
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssOddPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "odd";
+
+ public NeverNativelySupportedPseudoClass oddPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("(position() mod 2) = 0");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return oddPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssRootPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssRootPseudoClass.java
new file mode 100644
index 00000000..ff349b07
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssRootPseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssRootPseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "root";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssTargetPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssTargetPseudoClass.java
new file mode 100644
index 00000000..a3a854b4
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/basicfilter/SQCssTargetPseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.basicfilter;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssTargetPseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "target";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/NthArgument.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/NthArgument.java
new file mode 100644
index 00000000..491bf8db
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/NthArgument.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import org.openqa.selenium.InvalidSelectorException;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class represents arguments in the "nth" format, such as {@code even}, {@code odd} or {@code an+b}.
+ *
+ * NOTE: The current implementation was built to work in a generic form and does the job perfectly. Still,
+ * it is a generic "raw" implementation, meaning it does no "optimizations", such as converting 2n+2 into 2n,
+ * or absurd arguments (e.g. -2n-2) into XPath's {@code false()}. We did this this time now because we don't see
+ * the need for those optimizations right now. If that changes in the future, they should be easy to add.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+class NthArgument {
+
+ private static final Pattern B_REGEX = Pattern.compile("[+-]?\\d+");
+ private static final Pattern ANB_REGEX = Pattern.compile("([+-]?\\d*)n(?:\\s*([+-]\\s*\\d+))?");
+
+ private final Integer a;
+ private final Integer b;
+
+ public NthArgument(String argument) {
+ String trimmedArg = argument.trim();
+ if (even(trimmedArg)) {
+ this.a = 2;
+ this.b = null;
+ } else if (odd(trimmedArg)) {
+ this.a = 2;
+ this.b = 1;
+ } else if (bOnly(trimmedArg)) {
+ this.a = null;
+ this.b = parseSupposedInt(trimmedArg);
+ } else if (anb(trimmedArg)) {
+ Matcher m = extractArgumentsFromRegexGroups(trimmedArg);
+ this.a = a(m.group(1));
+ this.b = b(m.group(2));
+ } else {
+ throw createInvalidArgumentException(argument);
+ }
+ }
+
+ /**
+ * Tests if the argument is "even", as in :nth-child(even) or :nth-last-child(even)
+ */
+ private boolean even(String trimmedArg) {
+ return "even".equals(trimmedArg);
+ }
+
+ /**
+ * Tests if the argument is "odd", as in :nth-child(odd) or :nth-last-child(odd)
+ */
+ private boolean odd(String trimmedArg) {
+ return "odd".equals(trimmedArg);
+ }
+
+ /**
+ * Tests if arguments is just b, as in :nth-child(5) or :nth-last-child(6)
+ */
+ private boolean bOnly(String trimmedArg) {
+ return B_REGEX.matcher(trimmedArg).matches();
+ }
+
+ /**
+ * Tests if arguments is an+b, as in :nth-child(3n+8) or :nth-last-child(-2n+3)
+ */
+ private boolean anb(String trimmedArg) {
+ return ANB_REGEX.matcher(trimmedArg).matches();
+ }
+
+ private int a(String aString) {
+ if (aString.isEmpty()) {
+ return 1;
+ }
+ if ("-".equals(aString)) {
+ return -1;
+ }
+ if ("+".equals(aString)) {
+ return 1;
+ }
+ return parseSupposedInt(aString);
+ }
+
+ private Integer b(String bString) {
+ if (bString == null) {
+ return null;
+ }
+ return parseSupposedInt(bString);
+ }
+
+ private int parseSupposedInt(String supposedInteger) {
+ String intWithoutSpaces = supposedInteger.replaceAll("\\s", "");
+ String intWithoutSpacesAndLeadingPlusSign = intWithoutSpaces.replaceAll("^\\+", "");
+ return Integer.parseInt(intWithoutSpacesAndLeadingPlusSign);
+ }
+
+ private Matcher extractArgumentsFromRegexGroups(String trimmedArg) {
+ Matcher m = ANB_REGEX.matcher(trimmedArg);
+ // we know it matches, otherwise this method would not have been called
+ // I will not put an IF here "because another method may call this". If another method
+ // ever calls this, then whoever made it call this place an if here!
+ //noinspection ResultOfMethodCallIgnored
+ m.matches();
+ return m;
+ }
+
+ private InvalidSelectorException createInvalidArgumentException(String argument) {
+ String reason = String.format("The :nth-child() pseudo-class must have an argument like" +
+ " :nth-child(odd), :nth-child(even), :nth-child(an+b), :nth-child(an) or" +
+ " :nth-child(b) - where a and b are positive or negative integers -, but was :nth-child(%s).", argument);
+ return new InvalidSelectorException(reason);
+ }
+
+ public String toCSS() {
+ String sa = a != null ? a+"n" : "";
+ String sb = b != null && b != 0 ? (b > 0 && a != null? "+"+b : ""+b) : "";
+ return sa+sb;
+ }
+
+ public String toXPath(String function) {
+ int realA = a != null ? a : 0;
+ if (realA == 0) {
+ return function + " = "+b;
+ }
+ int realB = b != null ? b : 0;
+ char operator = realA < 0 ? '<' : '>';
+ return "(" + function + " - " + realB + ") mod " + realA + " = 0 and " + function + " "+operator+"= " + realB;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssFirstChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssFirstChildPseudoClass.java
new file mode 100644
index 00000000..4fb871b4
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssFirstChildPseudoClass.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+
+/**
+ * :first-child
+ * https://api.jquery.com/first-child-selector/
+ * https://developer.mozilla.org/pt-BR/docs/Web/CSS/:first-child
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssFirstChildPseudoClass extends SQCssNthChildPseudoClass {
+
+ public static final String PSEUDO = "first-child";
+
+ public SQCssFirstChildPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public String getArgument() {
+ return "1";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssFirstOfTypePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssFirstOfTypePseudoClass.java
new file mode 100644
index 00000000..787b4476
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssFirstOfTypePseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssFirstOfTypePseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "first-of-type";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssLastChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssLastChildPseudoClass.java
new file mode 100644
index 00000000..745f042a
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssLastChildPseudoClass.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+
+/**
+ * :last-child()
+ * https://api.jquery.com/last-child-selector/
+ * https://developer.mozilla.org/pt-BR/docs/Web/CSS/:last-child
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssLastChildPseudoClass extends SQCssNthLastChildPseudoClass {
+
+ public static final String PSEUDO = "last-child";
+
+ public SQCssLastChildPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public String getArgument() {
+ return "1";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssLastOfTypePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssLastOfTypePseudoClass.java
new file mode 100644
index 00000000..9b19ce5e
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssLastOfTypePseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssLastOfTypePseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "last-of-type";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthChildPseudoClass.java
new file mode 100644
index 00000000..184b9fb0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthChildPseudoClass.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.MaybeNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :nth-child()
+ * https://api.jquery.com/nth-child-selector/
+ * https://developer.mozilla.org/pt-BR/docs/Web/CSS/:nth-child
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssNthChildPseudoClass extends SQCssFunctionalPseudoClassCondition {
+
+ public static final String PSEUDO = "nth-child";
+
+ public MaybeNativelySupportedPseudoClass nthChildPseudoClassLocatorGenerationStrategy = new MaybeNativelySupportedPseudoClass() {
+ @Override
+ public String pseudoClassForCSSNativeSupportCheck() {
+ return ":"+PSEUDO+"(1)";
+ }
+
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ NthArgument nthArgument = getNthChildArgument();
+ return new CSSLocator(":"+PSEUDO+"("+nthArgument.toCSS()+")");
+ }
+
+ @Override
+ public XPathLocator toXPath() {
+ NthArgument nthArgument = getNthChildArgument();
+ return XPathLocator.pureXPath(nthArgument.toXPath("position()"));
+ }
+ };
+
+ public SQCssNthChildPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public MaybeNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return nthChildPseudoClassLocatorGenerationStrategy;
+ }
+
+ private NthArgument getNthChildArgument() {
+ return new NthArgument(getArgument());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthLastChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthLastChildPseudoClass.java
new file mode 100644
index 00000000..bd0d35e8
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthLastChildPseudoClass.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.MaybeNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :nth-last-child()
+ * https://api.jquery.com/nth-last-child-selector/
+ * https://developer.mozilla.org/pt-BR/docs/Web/CSS/:nth-last-child
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssNthLastChildPseudoClass extends SQCssFunctionalPseudoClassCondition {
+
+ public static final String PSEUDO = "nth-last-child";
+
+ public MaybeNativelySupportedPseudoClass nthLastChildPseudoClassLocatorGenerationStrategy = new MaybeNativelySupportedPseudoClass() {
+ @Override
+ public String pseudoClassForCSSNativeSupportCheck() {
+ return ":"+PSEUDO+"(1)";
+ }
+
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ NthArgument nthArgument = getNthChildArgument();
+ return new CSSLocator(":"+PSEUDO+"("+nthArgument.toCSS()+")");
+ }
+
+ @Override
+ public XPathLocator toXPath() {
+ NthArgument nthArgument = getNthChildArgument();
+ return XPathLocator.pureXPath(nthArgument.toXPath("(last()+1-position())"));
+ }
+ };
+
+ public SQCssNthLastChildPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ @Override
+ public MaybeNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return nthLastChildPseudoClassLocatorGenerationStrategy;
+ }
+
+ private NthArgument getNthChildArgument() {
+ return new NthArgument(getArgument());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthLastOfTypePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthLastOfTypePseudoClass.java
new file mode 100644
index 00000000..190b48ea
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthLastOfTypePseudoClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+
+public class SQCssNthLastOfTypePseudoClass extends SQCssFunctionalPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "nth-last-of-type";
+
+ public SQCssNthLastOfTypePseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthOfTypePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthOfTypePseudoClass.java
new file mode 100644
index 00000000..56703195
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssNthOfTypePseudoClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+
+public class SQCssNthOfTypePseudoClass extends SQCssFunctionalPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "nth-of-type";
+
+ public SQCssNthOfTypePseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssOnlyChildPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssOnlyChildPseudoClass.java
new file mode 100644
index 00000000..c43bb185
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssOnlyChildPseudoClass.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :only-child
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssOnlyChildPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "only-child";
+
+ public NeverNativelySupportedPseudoClass onlyChildPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("../*[last() = 1]");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return onlyChildPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssOnlyOfTypePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssOnlyOfTypePseudoClass.java
new file mode 100644
index 00000000..18b7dbb0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/childfilter/SQCssOnlyOfTypePseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.childfilter;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssOnlyOfTypePseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "only-of-type";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssContainsPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssContainsPseudoClass.java
new file mode 100644
index 00000000..aed2169b
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssContainsPseudoClass.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.contentfilter;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :contains()
+ * https://api.jquery.com/contains-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssContainsPseudoClass extends SQCssFunctionalPseudoClassCondition {
+
+ public static final String PSEUDO = "contains";
+
+ public SQCssContainsPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+ public NeverNativelySupportedPseudoClass containsPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ String textToContain = getArgument();
+ textToContain = SelectorUtils.unescapeString(textToContain);
+ String wantedTextToContain = SelectorUtils.intoEscapedXPathString(textToContain);
+ return XPathLocator.pureXPath("contains(string(.), " + wantedTextToContain + ")");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return containsPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssEmptyPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssEmptyPseudoClass.java
new file mode 100644
index 00000000..c41f8d86
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssEmptyPseudoClass.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.contentfilter;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :empty
+ * https://api.jquery.com/empty-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssEmptyPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "empty";
+
+ public NeverNativelySupportedPseudoClass emptyPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("count(.//*) = 0");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return emptyPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssHasPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssHasPseudoClass.java
new file mode 100644
index 00000000..1a4034d2
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssHasPseudoClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.contentfilter;
+
+import io.github.seleniumquery.by.css.pseudoclasses.PseudoClassSelector;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssFunctionalPseudoClassCondition;
+
+public class SQCssHasPseudoClass extends SQCssFunctionalPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "has";
+
+ public SQCssHasPseudoClass(PseudoClassSelector pseudoClassSelector) {
+ super(pseudoClassSelector);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssParentPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssParentPseudoClass.java
new file mode 100644
index 00000000..fff672b5
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/contentfilter/SQCssParentPseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.contentfilter;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssParentPseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "parent";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/done-pseudo-classes.txt b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/done-pseudo-classes.txt
new file mode 100644
index 00000000..62e8f9e9
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/done-pseudo-classes.txt
@@ -0,0 +1,58 @@
+basicfilter:
+ - OK :animated (WILL-NOT-ADD: requires access to jQuery's internals)
+ - OK :eq()
+ - OK :even
+ - OK :first
+ - OK :gt()
+ - OK :header
+ - DELAYED :lang()
+ - OK :last
+ - OK :lt()
+ - :not() ------------------------------------------------------ TODO
+ - OK :nth
+ - OK :odd
+ - DELAYED :root
+ - DELAYED :target
+childfilter
+ - OK :first-child
+ - DELAYED :first-of-type
+ - OK :last-child
+ - DELAYED :last-of-type
+ - OK :nth-child()
+ - OK :nth-last-child()
+ - DELAYED :nth-last-of-type()
+ - DELAYED :nth-of-type()
+ - OK :only-child
+ - DELAYED :only-of-type
+contentfilter
+ - OK :contains()
+ - OK :empty
+ - :has() ------------------------------------------------------ TODO
+ - DELAYED :parent
+form
+ - OK :button
+ - OK :checkbox
+ - OK :checked
+ - OK :disabled
+ - OK :enabled
+ - OK :file
+ - OK :focus
+ - OK :image
+ - OK :input
+ - OK :password
+ - OK :radio
+ - OK :reset
+ - OK :selected
+ - OK :submit
+ - OK :text
+jquery-ui
+ - DELAYED :focusable
+ - DELAYED :tabbable
+seleniumquery
+ - DELAYED :blank
+ - DELAYED :filled
+ - OK :present
+ - DELAYED :unchecked
+visibility
+ - OK :hidden
+ - OK :visible
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssButtonPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssButtonPseudoClass.java
new file mode 100644
index 00000000..68b9ad93
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssButtonPseudoClass.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+import static io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils.TYPE_ATTR_LC_VAL;
+
+/**
+ * :button
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssButtonPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "button";
+
+ public NeverNativelySupportedPseudoClass buttonPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("((self::input and " + TYPE_ATTR_LC_VAL + " = 'button') or self::button)");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return buttonPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssCheckboxPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssCheckboxPseudoClass.java
new file mode 100644
index 00000000..6971dfeb
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssCheckboxPseudoClass.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+/**
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssCheckboxPseudoClass extends SQCssInputTypeAttributePseudoClass {
+
+ public static final String PSEUDO = "checkbox";
+
+ public SQCssCheckboxPseudoClass() {
+ super(PSEUDO);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssCheckedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssCheckedPseudoClass.java
new file mode 100644
index 00000000..135ceb5a
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssCheckedPseudoClass.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.DriverVersionUtils;
+import io.github.seleniumquery.by.css.pseudoclasses.CheckedPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.MaybeNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+import org.openqa.selenium.WebDriver;
+
+import static io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils.TYPE_ATTR_LC_VAL;
+
+/**
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:checked
+ *
+ *
+ *
+ *
#Cross-Driver
+ * In PhantomJSDriver and HtmlUnitDriver, document.querySelectorAll(":checked") does not work
+ * for {@code } tags, so we should consider it as not supported!
+ *
+ * Issue in PhantomJS: https://github.com/ariya/phantomjs/issues/11550
+ *
+ * We have a test (integration.crossdriver.driverbugs.PhantomJSAndHtmlUnitCheckedSelectorBugTest) that asserts these
+ * bugs continue to exist.
+ *
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssCheckedPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "checked";
+ public static final String CHECKED_PSEUDO = ":checked";
+
+ public MaybeNativelySupportedPseudoClass checkedPseudoClassLocatorGenerationStrategy = new MaybeNativelySupportedPseudoClass() {
+ @Override
+ public boolean isThisCSSPseudoClassNativelySupportedOn(WebDriver webDriver) {
+ return isDriverWhereCheckedSelectorHasNoBugs(webDriver) && super.isThisCSSPseudoClassNativelySupportedOn(webDriver);
+ }
+
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ return new CSSLocator(CHECKED_PSEUDO);
+ }
+
+ @Override
+ public XPathLocator toXPath() {
+ return new XPathLocator(xPathExpression(), CheckedPseudoClass.CHECKED_FILTER);
+ }
+
+ private String xPathExpression() {
+ return "((self::input and ("+ TYPE_ATTR_LC_VAL +" = 'radio' or "+ TYPE_ATTR_LC_VAL +" = 'checkbox')) or self::option)";
+ }
+ };
+
+ @Override
+ public MaybeNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return checkedPseudoClassLocatorGenerationStrategy;
+ }
+
+ public static boolean isDriverWhereCheckedSelectorHasNoBugs(WebDriver webDriver) {
+ DriverVersionUtils driverVsUtils = DriverVersionUtils.getInstance();
+ return !driverVsUtils.isPhantomJSDriver(webDriver) && !driverVsUtils.isHtmlUnitDriver(webDriver);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssDisabledPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssDisabledPseudoClass.java
new file mode 100644
index 00000000..0a96bd8c
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssDisabledPseudoClass.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.css.pseudoclasses.DisabledPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.MaybeNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :disabled
+ * https://api.jquery.com/disabled-selector/
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:disabled
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssDisabledPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "disabled";
+ public static final String DISABLED_PSEUDO = ":" + PSEUDO;
+
+ public MaybeNativelySupportedPseudoClass disabledPseudoClassLocatorGenerationStrategy = new MaybeNativelySupportedPseudoClass() {
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ return new CSSLocator(DISABLED_PSEUDO);
+ }
+
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("(@disabled and " + DisabledPseudoClass.DISABLEABLE_TAGS_XPATH + ")");
+ }
+ };
+
+ @Override
+ public MaybeNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return disabledPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssEnabledPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssEnabledPseudoClass.java
new file mode 100644
index 00000000..769ab099
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssEnabledPseudoClass.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.css.pseudoclasses.DisabledPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.MaybeNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :enabled
+ * https://api.jquery.com/enabled-selector/
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/:enabled
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssEnabledPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "enabled";
+ public static final String ENABLED_PSEUDO = ":" + PSEUDO;
+
+ public MaybeNativelySupportedPseudoClass enabledPseudoClassLocatorGenerationStrategy = new MaybeNativelySupportedPseudoClass() {
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ return new CSSLocator(ENABLED_PSEUDO);
+ }
+
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("(not(@disabled) and " + DisabledPseudoClass.DISABLEABLE_TAGS_XPATH + ")");
+ }
+ };
+
+ @Override
+ public MaybeNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return enabledPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssFilePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssFilePseudoClass.java
new file mode 100644
index 00000000..1446e490
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssFilePseudoClass.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+/**
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssFilePseudoClass extends SQCssInputTypeAttributePseudoClass {
+
+ public static final String PSEUDO = "file";
+
+ public SQCssFilePseudoClass() {
+ super(PSEUDO);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssFocusPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssFocusPseudoClass.java
new file mode 100644
index 00000000..764b870c
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssFocusPseudoClass.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.css.pseudoclasses.FocusPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * PhantomJS may have problems with this:
+ * https://github.com/ariya/phantomjs/issues/10427
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssFocusPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "focus";
+
+ public NeverNativelySupportedPseudoClass hiddenPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.filterOnly(FocusPseudoClass.FOCUS_FILTER);
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return hiddenPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssImagePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssImagePseudoClass.java
new file mode 100644
index 00000000..b8097b45
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssImagePseudoClass.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+/**
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssImagePseudoClass extends SQCssInputTypeAttributePseudoClass {
+
+ public static final String PSEUDO = "image";
+
+ public SQCssImagePseudoClass() {
+ super(PSEUDO);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssInputPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssInputPseudoClass.java
new file mode 100644
index 00000000..371bd0f2
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssInputPseudoClass.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.css.pseudoclasses.InputPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+import static org.apache.commons.lang3.StringUtils.join;
+
+/**
+ * :input
+ * https://api.jquery.com/input-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssInputPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "input";
+ public static final String INPUT_TAGS_XPATH = "(self::" + join(InputPseudoClass.FORM_ELEMENT_TAGS, " or self::") + ")";
+
+ public NeverNativelySupportedPseudoClass inputPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath(INPUT_TAGS_XPATH);
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return inputPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssInputTypeAttributePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssInputTypeAttributePseudoClass.java
new file mode 100644
index 00000000..def7fca1
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssInputTypeAttributePseudoClass.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.AlwaysNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+import static io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils.TYPE_ATTR_LC_VAL;
+
+/**
+ * This represents the pseudoclasses that check for the type attribute, such as
+ * :password, that is equivalent to [type="password"].
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+abstract class SQCssInputTypeAttributePseudoClass extends SQCssPseudoClassCondition {
+
+ private String typeAttributeValue;
+
+ public AlwaysNativelySupportedPseudoClass inputTypePseudoClassLocatorGenerationStrategy = new AlwaysNativelySupportedPseudoClass() {
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ return new CSSLocator("input", "[type=\"" + typeAttributeValue + "\"]");
+ }
+
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("(self::input and " + TYPE_ATTR_LC_VAL + " = '" + typeAttributeValue + "')");
+ }
+ };
+
+ protected SQCssInputTypeAttributePseudoClass(String typeAttributeValue) {
+ this.typeAttributeValue = typeAttributeValue;
+ }
+
+ @Override
+ public AlwaysNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return inputTypePseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssPasswordPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssPasswordPseudoClass.java
new file mode 100644
index 00000000..2a5fb568
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssPasswordPseudoClass.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+/**
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssPasswordPseudoClass extends SQCssInputTypeAttributePseudoClass {
+
+ public static final String PSEUDO = "password";
+
+ public SQCssPasswordPseudoClass() {
+ super(PSEUDO);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssRadioPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssRadioPseudoClass.java
new file mode 100644
index 00000000..2c615de0
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssRadioPseudoClass.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+/**
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssRadioPseudoClass extends SQCssInputTypeAttributePseudoClass {
+
+ public static final String PSEUDO = "radio";
+
+ public SQCssRadioPseudoClass() {
+ super(PSEUDO);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssResetPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssResetPseudoClass.java
new file mode 100644
index 00000000..1c0f5c40
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssResetPseudoClass.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+import static io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils.TYPE_ATTR_LC_VAL;
+
+/**
+ * :reset selector
+ * http://api.jquery.com/reset-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssResetPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "reset";
+
+ public NeverNativelySupportedPseudoClass inputPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("((self::input or self::button) and " + TYPE_ATTR_LC_VAL + " = 'reset')");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return inputPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssSelectedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssSelectedPseudoClass.java
new file mode 100644
index 00000000..305ebc64
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssSelectedPseudoClass.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.css.pseudoclasses.SelectedPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.MaybeNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * :selected
+ * https://api.jquery.com/selected-selector/
+ *
+ * :selected is a "maybe natively supported" because it can be translated into option:checked (but
+ * only if the browser supports - without bugs - the :checked pseudo).
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssSelectedPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "selected";
+
+ public MaybeNativelySupportedPseudoClass selectedPseudoClassLocatorGenerationStrategy = new MaybeNativelySupportedPseudoClass() {
+ @Override
+ public boolean isThisCSSPseudoClassNativelySupportedOn(WebDriver webDriver) {
+ return SQCssCheckedPseudoClass.isDriverWhereCheckedSelectorHasNoBugs(webDriver)
+ && super.isThisCSSPseudoClassNativelySupportedOn(webDriver);
+ }
+
+ @Override
+ public String pseudoClassForCSSNativeSupportCheck() {
+ return SQCssCheckedPseudoClass.CHECKED_PSEUDO;
+ }
+
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ return new CSSLocator("option", ":checked");
+ }
+
+ @Override
+ public XPathLocator toXPath() {
+ return new XPathLocator("self::option", SelectedPseudoClass.SELECTED_FILTER);
+ }
+ };
+
+ @Override
+ public MaybeNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return selectedPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssSubmitPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssSubmitPseudoClass.java
new file mode 100644
index 00000000..e38d9f31
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssSubmitPseudoClass.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+import static io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils.TYPE_ATTR_LC_VAL;
+
+/**
+ * :submit
+ * https://api.jquery.com/submit-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssSubmitPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "submit";
+ public static final String SUBMIT_XPATH_EXPRESSION = "(" +
+ "(self::input and " + TYPE_ATTR_LC_VAL + " = 'submit')" +
+ " or " +
+ "(self::button and (" + TYPE_ATTR_LC_VAL + " = 'submit' or not(@type)))" +
+ ")";
+
+ public NeverNativelySupportedPseudoClass submitPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath(SUBMIT_XPATH_EXPRESSION);
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return submitPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssTextPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssTextPseudoClass.java
new file mode 100644
index 00000000..50de3ada
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/form/SQCssTextPseudoClass.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.form;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+import static io.github.seleniumquery.by.css.attributes.AttributeEvaluatorUtils.TYPE_ATTR_LC_VAL;
+
+/**
+ * :text
+ * https://api.jquery.com/text-selector/
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssTextPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "text";
+
+ public NeverNativelySupportedPseudoClass textPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("(self::input and (" + TYPE_ATTR_LC_VAL + " = 'text' or not(@type)))");
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return textPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/jqueryui/SQCssFocusablePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/jqueryui/SQCssFocusablePseudoClass.java
new file mode 100644
index 00000000..62fd53a4
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/jqueryui/SQCssFocusablePseudoClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.jqueryui;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+/**
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssFocusablePseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "focusable";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/jqueryui/SQCssTabbablePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/jqueryui/SQCssTabbablePseudoClass.java
new file mode 100644
index 00000000..65af8c21
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/jqueryui/SQCssTabbablePseudoClass.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.jqueryui;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+/**
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssTabbablePseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "tabbable";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/AlwaysNativelySupportedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/AlwaysNativelySupportedPseudoClass.java
new file mode 100644
index 00000000..3947f1b1
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/AlwaysNativelySupportedPseudoClass.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import org.openqa.selenium.WebDriver;
+
+public abstract class AlwaysNativelySupportedPseudoClass extends MaybeNativelySupportedPseudoClass implements SQCssConditionImplementedLocators {
+
+ @Override
+ public boolean isThisCSSPseudoClassNativelySupportedOn(WebDriver webDriver) {
+ return true;
+ }
+
+ @Override
+ public abstract CSSLocator toCssWhenNativelySupported();
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/MaybeNativelySupportedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/MaybeNativelySupportedPseudoClass.java
new file mode 100644
index 00000000..72f58369
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/MaybeNativelySupportedPseudoClass.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy;
+
+import io.github.seleniumquery.by.DriverVersionUtils;
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.SQLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+import org.openqa.selenium.WebDriver;
+
+import static io.github.seleniumquery.by.locator.CSSLocator.CSS_NOT_NATIVELY_SUPPORTED;
+
+/**
+ * Represents a strategy where the selector may or may not be natively supported by the driver.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public abstract class MaybeNativelySupportedPseudoClass implements SQCssConditionImplementedLocators {
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ if (isThisCSSPseudoClassNativelySupportedOn(leftLocator.getWebDriver())) {
+ return new SQLocator(
+ leftLocator.getWebDriver(),
+ leftLocator.getCSSLocator().merge(toCssWhenNativelySupported()),
+ leftLocator.getXPathLocator().merge(toXPath(), xPathMergeStrategy())
+ );
+ } else {
+ return new SQLocator(
+ leftLocator.getWebDriver(),
+ CSS_NOT_NATIVELY_SUPPORTED,
+ leftLocator.getXPathLocator().merge(toXPath(), xPathMergeStrategy())
+ );
+ }
+ }
+
+ public boolean isThisCSSPseudoClassNativelySupportedOn(WebDriver webDriver) {
+ return DriverVersionUtils.getInstance().hasNativeSupportForPseudo(webDriver, pseudoClassForCSSNativeSupportCheck());
+ }
+
+ /**
+ * Returns the pseudo-class selector that will be used to check if the driver supports this pseudo.
+ * Example: {@code ":nth-child(1)"}
+ * This will be appended to an id selector for the checking, e.g., if the value returned for this
+ * method is {@code ":my-crazy-pseudo(1)"}, then we will send to the browser something like
+ * {@code "#randomId:my-crazy-pseudo(1)"} which will be considered supported if no exception is thrown.
+ * @return The css selector to be appended to an id selector for checking if the selector is supported.
+ */
+ public String pseudoClassForCSSNativeSupportCheck() {
+ return toCssWhenNativelySupported().toString();
+ }
+
+ public abstract CSSLocator toCssWhenNativelySupported();
+
+ public XPathMergeStrategy xPathMergeStrategy() {
+ return XPathMergeStrategy.CONDITIONAL_SIMPLE_XPATH_MERGE;
+ }
+
+ public abstract XPathLocator toXPath();
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/NeverNativelySupportedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/NeverNativelySupportedPseudoClass.java
new file mode 100644
index 00000000..763802fa
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/NeverNativelySupportedPseudoClass.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedLocators;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Pseudos extending this class will never ever even check for native support.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public abstract class NeverNativelySupportedPseudoClass extends MaybeNativelySupportedPseudoClass implements SQCssConditionImplementedLocators {
+
+ @Override
+ public boolean isThisCSSPseudoClassNativelySupportedOn(WebDriver webDriver) {
+ return false;
+ }
+
+ /**
+ * Due to the {@link NeverNativelySupportedPseudoClass#isThisCSSPseudoClassNativelySupportedOn(org.openqa.selenium.WebDriver)}
+ * always returning false, this method will actually never be called.
+ * I do know this smells like a violation of LSP, but I, for the love of Yoda, couldn't figure out a better way!
+ */
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ throw new UnsupportedOperationException();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/XPathMergeStrategy.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/XPathMergeStrategy.java
new file mode 100644
index 00000000..aab11ecf
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/locatorgenerationstrategy/XPathMergeStrategy.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy;
+
+import io.github.seleniumquery.by.locator.SQLocatorUtils;
+
+/**
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public abstract class XPathMergeStrategy {
+
+ // TODO these methods do not have unit tests specific to them
+ // I'm leaving them without unit tests right now because I get a feeling they
+ // will be moved or the method they call will be inlined.
+ // Still, do something about it when you move them or inline those.
+ public static final XPathMergeStrategy CONDITIONAL_SIMPLE_XPATH_MERGE = new XPathMergeStrategy() {
+ @Override
+ public String mergeXPath(String leftXPathExpression, String rightXPathExpression) {
+ return SQLocatorUtils.conditionalSimpleXPathMerge(leftXPathExpression, rightXPathExpression);
+ }
+ };
+
+ public static final XPathMergeStrategy CONDITIONAL_TO_ALL_XPATH_MERGE = new XPathMergeStrategy() {
+ @Override
+ public String mergeXPath(String leftXPathExpression, String rightXPathExpression) {
+ return SQLocatorUtils.conditionalToAllXPathMerge(leftXPathExpression, rightXPathExpression);
+ }
+ };
+
+ public abstract String mergeXPath(String leftXPathExpression, String rightXPathExpression);
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssBlankPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssBlankPseudoClass.java
new file mode 100644
index 00000000..c47b4aa2
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssBlankPseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.seleniumquery;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssBlankPseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "blank";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssFilledPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssFilledPseudoClass.java
new file mode 100644
index 00000000..ebd24dfd
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssFilledPseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.seleniumquery;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssFilledPseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "filled";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssPresentPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssPresentPseudoClass.java
new file mode 100644
index 00000000..4e43f8de
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssPresentPseudoClass.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.seleniumquery;
+
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.AlwaysNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.MaybeNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :present
+ * https://github.com/seleniumQuery/seleniumQuery/wiki/seleniumQuery-Selectors#extra---seleniumquery-only-selectors
+ *
+ * Matches all elements that are attached to the DOM. It is the "identity" selector, basically. It
+ * should not affect the other selector it is used with.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssPresentPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "present";
+
+ public MaybeNativelySupportedPseudoClass presentPseudoClassLocatorGenerationStrategy = new AlwaysNativelySupportedPseudoClass() {
+ @Override
+ public CSSLocator toCssWhenNativelySupported() {
+ return CSSLocator.universalSelector();
+ }
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.pureXPath("true()");
+ }
+ };
+
+ @Override
+ public MaybeNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return presentPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssUncheckedPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssUncheckedPseudoClass.java
new file mode 100644
index 00000000..a4f747d5
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/seleniumquery/SQCssUncheckedPseudoClass.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.seleniumquery;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssConditionImplementedNotYet;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+
+public class SQCssUncheckedPseudoClass extends SQCssPseudoClassCondition implements SQCssConditionImplementedNotYet {
+
+ public static final String PSEUDO = "unchecked";
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/visibility/SQCssHiddenPseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/visibility/SQCssHiddenPseudoClass.java
new file mode 100644
index 00000000..207f6de7
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/visibility/SQCssHiddenPseudoClass.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.visibility;
+
+import io.github.seleniumquery.by.css.pseudoclasses.HiddenPseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :hidden
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssHiddenPseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "hidden";
+
+ public NeverNativelySupportedPseudoClass hiddenPseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.filterOnly(HiddenPseudoClass.HIDDEN_FILTER);
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return hiddenPseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/visibility/SQCssVisiblePseudoClass.java b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/visibility/SQCssVisiblePseudoClass.java
new file mode 100644
index 00000000..b05cb16b
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/condition/pseudoclass/visibility/SQCssVisiblePseudoClass.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.condition.pseudoclass.visibility;
+
+import io.github.seleniumquery.by.css.pseudoclasses.VisiblePseudoClass;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.SQCssPseudoClassCondition;
+import io.github.seleniumquery.by.csstree.condition.pseudoclass.locatorgenerationstrategy.NeverNativelySupportedPseudoClass;
+import io.github.seleniumquery.by.locator.XPathLocator;
+
+/**
+ * :visible
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssVisiblePseudoClass extends SQCssPseudoClassCondition {
+
+ public static final String PSEUDO = "visible";
+
+ public NeverNativelySupportedPseudoClass visiblePseudoClassLocatorGenerationStrategy = new NeverNativelySupportedPseudoClass() {
+ @Override
+ public XPathLocator toXPath() {
+ return XPathLocator.filterOnly(VisiblePseudoClass.VISIBLE_FILTER);
+ }
+ };
+
+ @Override
+ public NeverNativelySupportedPseudoClass getSQCssLocatorGenerationStrategy() {
+ return visiblePseudoClassLocatorGenerationStrategy;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/package-info.java b/src/main/java/io/github/seleniumquery/by/csstree/package-info.java
new file mode 100644
index 00000000..0226ff6e
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package contains the classes that form the parse tree of a parsed CSS Selector.
+ */
+package io.github.seleniumquery.by.csstree;
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssConditionalSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssConditionalSelector.java
new file mode 100644
index 00000000..5a86ad69
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssConditionalSelector.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.selector;
+
+import io.github.seleniumquery.by.csstree.condition.SQCssCondition;
+import io.github.seleniumquery.by.csstree.condition.attribute.SQCssClassAttributeCondition;
+import io.github.seleniumquery.by.locator.SQLocator;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Conditional selector, simply an union of a selector and a condition.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssConditionalSelector implements SQCssSelector {
+
+ private final SQCssSelector sqCssSelector;
+ private final SQCssCondition sqCssCondition;
+
+ public SQCssConditionalSelector(SQCssSelector sqCssSelector, SQCssCondition sqCssCondition) {
+ this.sqCssSelector = sqCssSelector;
+ this.sqCssCondition = sqCssCondition;
+ }
+
+ public SQCssSelector getSqCssSelector() {
+ return sqCssSelector;
+ }
+
+ public SQCssCondition getSqCssCondition() {
+ return sqCssCondition;
+ }
+
+ @Override
+ public SQLocator toSQLocator(WebDriver webDriver) {
+ SQLocator locator = sqCssSelector.toSQLocator(webDriver);
+ return ((SQCssClassAttributeCondition) sqCssCondition).toSQLocator(locator);
+ }
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ SQLocator locator = sqCssSelector.toSQLocator(leftLocator);
+ return ((SQCssClassAttributeCondition) sqCssCondition).toSQLocator(locator);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssSelector.java
new file mode 100644
index 00000000..14cb8ecc
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssSelector.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.selector;
+
+import io.github.seleniumquery.by.locator.SQLocator;
+import org.openqa.selenium.WebDriver;
+
+public interface SQCssSelector {
+
+ /**
+ * Converts this selector into a locator.
+ * @param webDriver The driver that will be checked for native CSS support of selectors.
+ * @return The locator, having the best strategy to find elements based on this selector.
+ */
+ SQLocator toSQLocator(WebDriver webDriver);
+
+ /**
+ * Appends this selector's locator into a previously generated locator, which always represent
+ * the leftmost part of the selector/expression, generated up to this point.
+ * The csstree is traversed "in-order".
+ *
+ * @param leftLocator The locator generated by the leftmost part of the tree.
+ * @return The leftLocator with this selector's behavior appended.
+ */
+ SQLocator toSQLocator(SQLocator leftLocator);
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssTagNameSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssTagNameSelector.java
new file mode 100644
index 00000000..0b3d17aa
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssTagNameSelector.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.selector;
+
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.SQLocator;
+import io.github.seleniumquery.by.locator.SQLocatorUtils;
+import org.openqa.selenium.WebDriver;
+
+import static io.github.seleniumquery.by.locator.CSSLocator.fromTag;
+import static io.github.seleniumquery.by.locator.XPathLocator.pureXPath;
+
+/**
+ * Element or tag selector. Example: {@code "div"}.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssTagNameSelector implements SQCssSelector {
+
+ private String tagName;
+
+ public SQCssTagNameSelector(String tagName) {
+ this.tagName = tagName;
+ }
+
+ public String getTagName() {
+ return tagName;
+ }
+
+ @Override
+ public SQLocator toSQLocator(WebDriver webDriver) {
+ return new SQLocator(webDriver, toCSS(), pureXPath(".//*[" + toXPath() + "]"));
+ }
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ CSSLocator newCssSelector = leftLocator.getCSSLocator().merge(toCSS());
+ String newXPathExpression = SQLocatorUtils.conditionalSimpleXPathMerge(leftLocator.getXPathExpression(), toXPath());
+ return new SQLocator(newCssSelector, newXPathExpression, leftLocator);
+ }
+
+ private String toXPath() {
+ return "*".equals(this.tagName) ? "true()" : "self::"+tagName;
+ }
+
+ private CSSLocator toCSS() {
+ return fromTag(this.tagName);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssUnknownSelectorException.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssUnknownSelectorException.java
new file mode 100644
index 00000000..badba60f
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/SQCssUnknownSelectorException.java
@@ -0,0 +1,17 @@
+package io.github.seleniumquery.by.csstree.selector;
+
+import org.w3c.css.sac.Selector;
+
+/**
+ * Informs right away that the given selector is not known.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+public class SQCssUnknownSelectorException extends RuntimeException {
+
+ public SQCssUnknownSelectorException(Selector selector) {
+ super("CSS selector \""+selector.getClass().getSimpleName() + "\" (type="+ selector.getSelectorType() + ") is invalid or not supported!");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssCombinationSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssCombinationSelector.java
new file mode 100644
index 00000000..a6cbf463
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssCombinationSelector.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015 seleniumQuery authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.seleniumquery.by.csstree.selector.combinator;
+
+import io.github.seleniumquery.by.csstree.selector.SQCssSelector;
+import io.github.seleniumquery.by.locator.CSSLocator;
+import io.github.seleniumquery.by.locator.SQLocator;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * An ABSTRACT class that is used as base implementation for all the combination selectors.
+ *
+ * @author acdcjunior
+ * @since 0.10.0
+ */
+abstract class SQCssCombinationSelector implements SQCssSelector {
+
+ private final String cssCombinator;
+ private final String xPathCombinator;
+ protected SQCssSelector leftSideSelector;
+ protected SQCssSelector rightSideSelector;
+
+ public SQCssCombinationSelector(String cssCombinator, String xPathCombinator,
+ SQCssSelector leftSideSelector, SQCssSelector rightSideSelector) {
+ this.cssCombinator = cssCombinator;
+ this.xPathCombinator = xPathCombinator;
+ this.leftSideSelector = leftSideSelector;
+ this.rightSideSelector = rightSideSelector;
+ }
+
+ @Override
+ public SQLocator toSQLocator(WebDriver webDriver) {
+ SQLocator sqLocator = leftSideSelector.toSQLocator(webDriver);
+ CSSLocator combinatorLocator = sqLocator.getCSSLocator().combineAsLeftPart(this.cssCombinator);
+ SQLocator directAdjacentIntermediateLocator = new SQLocator(combinatorLocator,
+ sqLocator.getXPathExpression() + this.xPathCombinator, sqLocator);
+ return rightSideSelector.toSQLocator(directAdjacentIntermediateLocator);
+ }
+
+ @Override
+ public SQLocator toSQLocator(SQLocator leftLocator) {
+ throw new UnsupportedOperationException("Due to the way the CSS Tree is created by the CSS Parser, this " +
+ "order of evaluation should never occurr.");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDescendantSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDescendantSelector.java
new file mode 100644
index 00000000..58b5b346
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDescendantSelector.java
@@ -0,0 +1,19 @@
+package io.github.seleniumquery.by.csstree.selector.combinator;
+
+import io.github.seleniumquery.by.csstree.selector.SQCssSelector;
+
+public class SQCssDescendantSelector extends SQCssCombinationSelector {
+
+ public SQCssDescendantSelector(SQCssSelector leftSideSelector, SQCssSelector rightSideSelector) {
+ super(" ", "//*[true()]", leftSideSelector, rightSideSelector);
+ }
+
+ public SQCssSelector getAncestorSelector() {
+ return leftSideSelector;
+ }
+
+ public SQCssSelector getDescendantSelector() {
+ return rightSideSelector;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDirectAdjacentSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDirectAdjacentSelector.java
new file mode 100644
index 00000000..5a829d5c
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDirectAdjacentSelector.java
@@ -0,0 +1,19 @@
+package io.github.seleniumquery.by.csstree.selector.combinator;
+
+import io.github.seleniumquery.by.csstree.selector.SQCssSelector;
+
+public class SQCssDirectAdjacentSelector extends SQCssCombinationSelector {
+
+ public SQCssDirectAdjacentSelector(SQCssSelector leftSideSelector, SQCssSelector rightSideSelector) {
+ super("+", "/following-sibling::*[position() = 1]", leftSideSelector, rightSideSelector);
+ }
+
+ public SQCssSelector getLeftSideSelector() {
+ return leftSideSelector;
+ }
+
+ public SQCssSelector getRightSideSelector() {
+ return rightSideSelector;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDirectDescendantSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDirectDescendantSelector.java
new file mode 100644
index 00000000..1c8736ba
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssDirectDescendantSelector.java
@@ -0,0 +1,19 @@
+package io.github.seleniumquery.by.csstree.selector.combinator;
+
+import io.github.seleniumquery.by.csstree.selector.SQCssSelector;
+
+public class SQCssDirectDescendantSelector extends SQCssCombinationSelector {
+
+ public SQCssDirectDescendantSelector(SQCssSelector leftSideSelector, SQCssSelector rightSideSelector) {
+ super(">", "/*[true()]", leftSideSelector, rightSideSelector);
+ }
+
+ public SQCssSelector getAncestorSelector() {
+ return leftSideSelector;
+ }
+
+ public SQCssSelector getDescendantSelector() {
+ return rightSideSelector;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssGeneralAdjacentSelector.java b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssGeneralAdjacentSelector.java
new file mode 100644
index 00000000..21792a72
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/csstree/selector/combinator/SQCssGeneralAdjacentSelector.java
@@ -0,0 +1,19 @@
+package io.github.seleniumquery.by.csstree.selector.combinator;
+
+import io.github.seleniumquery.by.csstree.selector.SQCssSelector;
+
+public class SQCssGeneralAdjacentSelector extends SQCssCombinationSelector {
+
+ public SQCssGeneralAdjacentSelector(SQCssSelector leftSideSelector, SQCssSelector rightSideSelector) {
+ super("~", "/following-sibling::*[true()]", leftSideSelector, rightSideSelector);
+ }
+
+ public SQCssSelector getLeftSideSelector() {
+ return leftSideSelector;
+ }
+
+ public SQCssSelector getRightSideSelector() {
+ return rightSideSelector;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/filter/DescendantGeneralCombinatorFilter.java b/src/main/java/io/github/seleniumquery/by/filter/DescendantGeneralCombinatorFilter.java
new file mode 100644
index 00000000..0fb41a68
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/filter/DescendantGeneralCombinatorFilter.java
@@ -0,0 +1,77 @@
+package io.github.seleniumquery.by.filter;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.xpath.component.TagComponent;
+import io.github.seleniumquery.functions.jquery.traversing.treetraversal.ClosestFunction;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * It will need:
+ * - the parent CSS selector, so it matches using closest(): at least one parent must match it
+ * - the CONTEXT (root element) of the search, as it can't go after it (if you don't stop at it, its parent may
+ * match and then be included in the result set when it shouldn't)
+ */
+public class DescendantGeneralCombinatorFilter implements ElementFilter {
+
+ private final ElementFilterList ancestorFilters;
+ private final ElementFilterList childrenFilters;
+
+ private DescendantGeneralCombinatorFilter(WebElement context,
+ String ancestorXPathExpression,
+ ElementFilterList ancestorFilters,
+ String childrenExpression,
+ ElementFilterList childrenFilters) {
+ this.ancestorFilters = ancestorFilters;
+ this.childrenFilters = childrenFilters;
+ }
+
+ /**
+ * Every element MUST abide to the CHILDREN filter.
+ *
+ * Then, every remaining, must have AT LEAST ONE ANCESTOR that matches the ANCESTOR FILTER.
+ */
+ @Override
+ public List filterElements(WebDriver driver, List elements) {
+ List filteredChildren = childrenFilters.filter(driver, elements);
+
+ outerFor:for (Iterator iterator = filteredChildren.iterator(); iterator.hasNext();) {
+ WebElement currentChild = iterator.next();
+
+ // closest() starts in the element, we don't want that because we are testing the parent on the descendant selector
+ WebElement startingElement = SelectorUtils.parent(currentChild);
+
+ String cssSelector = null;// ancestorCompiled.getCssSelector();
+ WebElement matchingAncestor = ClosestFunction.closest(driver, startingElement, cssSelector);
+ while (matchingAncestor != null) {
+
+ List mas = ancestorFilters.filter(driver, new ArrayList(Arrays.asList(matchingAncestor)));
+ boolean theMatchedAncestorMatchesTheFilter = !mas.isEmpty();
+ if (theMatchedAncestorMatchesTheFilter) {
+ continue outerFor; // this element's ancestor is OK, keep it, continue to next element
+ }
+
+ // walks up one step, otherwise closest will match the same element again
+ matchingAncestor = SelectorUtils.parent(matchingAncestor);
+
+ matchingAncestor = ClosestFunction.closest(driver, matchingAncestor, cssSelector);
+ }
+ iterator.remove();
+ }
+ return filteredChildren;
+ }
+
+ public static List parents(WebElement element, TagComponent selector) {
+ return parentz(element, selector.toXPathCondition());
+ }
+
+ private static List parentz(WebElement element, String xPathCondition) {
+ return element.findElements(By.xpath("ancestor::node()[count(ancestor-or-self::html) > 0 and " + xPathCondition.substring(1)));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/filter/ElementFilter.java b/src/main/java/io/github/seleniumquery/by/filter/ElementFilter.java
new file mode 100644
index 00000000..cac712b5
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/filter/ElementFilter.java
@@ -0,0 +1,23 @@
+package io.github.seleniumquery.by.filter;
+
+import java.util.List;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+public interface ElementFilter {
+
+ public static final ElementFilter FILTER_NOTHING = new ElementFilter() {
+ @Override
+ public List filterElements(WebDriver driver, List elements) {
+ return elements;
+ }
+ @Override
+ public String toString() {
+ return "FILTER_NOTHING";
+ }
+ };
+
+ List filterElements(WebDriver driver, List elements);
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/seleniumquery/by/filter/ElementFilterList.java b/src/main/java/io/github/seleniumquery/by/filter/ElementFilterList.java
new file mode 100644
index 00000000..0e1104a5
--- /dev/null
+++ b/src/main/java/io/github/seleniumquery/by/filter/ElementFilterList.java
@@ -0,0 +1,83 @@
+package io.github.seleniumquery.by.filter;
+
+import io.github.seleniumquery.by.SelectorUtils;
+import io.github.seleniumquery.by.css.pseudoclasses.UnsupportedPseudoClassException;
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * This class should be IMMUTABLE.
+ *
+ * @author acdcjunior
+ * @author ricardo-sc
+ *
+ * @since 0.10.0
+ */
+public class ElementFilterList {
+
+ public static ElementFilterList asFilterList(ElementFilter... filters) {
+ return new ElementFilterList(asList(filters));
+ }
+
+ public static final ElementFilterList FILTER_NOTHING_LIST = new ElementFilterList(Collections.emptyList()) {
+ @Override
+ public List filter(WebDriver driver, List elements) {
+ return elements;
+ }
+ @Override
+ public ElementFilterList merge(ElementFilterList elementFilterList) {
+ return elementFilterList;
+ }
+ };
+
+ private List elementFilters;
+
+ public ElementFilterList(List elementFilters) {
+ this.elementFilters = Collections.unmodifiableList(elementFilters);
+ }
+
+ public List filter(SearchContext context, List elements) {
+ WebDriver driver = SelectorUtils.getWebDriver(context);
+ return filter(driver, elements);
+ }
+
+ public List filter(WebDriver driver, List elements) {
+ if (this.elementFilters.size() > 0) {
+ // we are currently disabling the filter support
+ // we will only take it back when the system is stable
+ throw new UnsupportedPseudoClassException("The current selector is not yet supported. Please try a simpler one.");
+ }
+ for (ElementFilter elementFilter : elementFilters) {
+ elements = elementFilter.filterElements(driver, elements);
+ }
+ return elements;
+ }
+
+ public boolean isEmpty() {
+ return elementFilters.isEmpty();
+ }
+
+ public List getElementFilters() {
+ return elementFilters;
+ }
+
+ public ElementFilterList merge(ElementFilterList elementFilterList) {
+ if (elementFilterList == FILTER_NOTHING_LIST) {
+ return this;
+ }
+ List myFilters = this.getElementFilters();
+ List