Repository: telekom/testerra
Issue: UIElement `hasWords` assertion occures `PatternSyntaxException`

**Describe the bug**
Given is the Element `<label for="inputMillis"> TimeOut in Millis (0-inf) </label>`

**To Reproduce**

````java
element.expect().text().hasWords("TimeOut in Millis").is(true); // --> passed
element.expect().text().hasWords("TimeOut in Millis (0-inf)").is(true); // -->java.util.regex.PatternSyntaxException: Unclosed group near index xx
````

**Expected behavior**
The second assertion should passed, too.

**Actual behavior**
The method `DefaultStringAssertion.hasWords()` cannot handle special characters like `(`, because there is no escaping. The pattern matcher take the `(` for grouping.

**Solution**
Escape the characters like `(`, `)`, `[`, `]`, ...

**Environment(please complete the following information):**
Testerra 2


Changed files:
driver-ui/src/main/java/eu/tsystems/mms/tic/testframework/internal/asserts/DefaultStringAssertion.java
integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementActionTests.java
integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementCommonTests.java

diff --git a/driver-ui/src/main/java/eu/tsystems/mms/tic/testframework/internal/asserts/DefaultStringAssertion.java b/driver-ui/src/main/java/eu/tsystems/mms/tic/testframework/internal/asserts/DefaultStringAssertion.java
index 5b41bef90..3e9eb7cee 100644
--- a/driver-ui/src/main/java/eu/tsystems/mms/tic/testframework/internal/asserts/DefaultStringAssertion.java
+++ b/driver-ui/src/main/java/eu/tsystems/mms/tic/testframework/internal/asserts/DefaultStringAssertion.java
@@ -22,17 +22,19 @@
 package eu.tsystems.mms.tic.testframework.internal.asserts;
 
 import eu.tsystems.mms.tic.testframework.logging.Loggable;
+
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Default implementation of {@link StringAssertion}
+ *
  * @author Mike Reiche
  */
 public class DefaultStringAssertion<T> extends DefaultQuantityAssertion<T> implements StringAssertion<T>, Loggable {
 
-
     public DefaultStringAssertion(AbstractPropertyAssertion parentAssertion, AssertionProvider<T> provider) {
         super(parentAssertion, provider);
     }
@@ -99,8 +101,14 @@ public class DefaultStringAssertion<T> extends DefaultQuantityAssertion<T> imple
 
     @Override
     public BinaryAssertion<Boolean> hasWords(List<String> words) {
-        final String wordsList = String.join("|", words);
-        final Pattern wordsPattern = Pattern.compile("\\\\b(" + wordsList + ")\\\\b", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
+        final Pattern nonWordAtBegin = Pattern.compile("^\\\\W");
+        final Pattern nonWordAtTheEnd = Pattern.compile("\\\\W$");
+
+        final String wordsList = words.stream()
+                .map(word -> (nonWordAtBegin.matcher(word).find() ? "\\\\B" : "\\\\b") + "\\\\Q" + word) // word boundary for begin
+                .map(word -> word + "\\\\E" + (nonWordAtTheEnd.matcher(word).find() ? "\\\\B" : "\\\\b")) // word boundary for end
+                .collect(Collectors.joining("|"));
+        final Pattern wordsPattern = Pattern.compile(wordsList, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
 
         return propertyAssertionFactory.createWithParent(DefaultBinaryAssertion.class, this, new AssertionProvider<Boolean>() {
             @Override
@@ -108,7 +116,7 @@ public class DefaultStringAssertion<T> extends DefaultQuantityAssertion<T> imple
                 int found = 0;
                 Matcher matcher = wordsPattern.matcher(provider.getActual().toString());
                 while (matcher.find()) found++;
-                return found >= words.size();
+                return found == words.size();
             }
 
             @Override
@@ -118,7 +126,6 @@ public class DefaultStringAssertion<T> extends DefaultQuantityAssertion<T> imple
         });
     }
 
-
     @Override
     public QuantityAssertion<Integer> length() {
         return propertyAssertionFactory.createWithParent(DefaultQuantityAssertion.class, this, new AssertionProvider<Integer>() {
diff --git a/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementActionTests.java b/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementActionTests.java
index 9db4e66d4..75172bc18 100644
--- a/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementActionTests.java
+++ b/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementActionTests.java
@@ -24,7 +24,6 @@ import eu.tsystems.mms.tic.testframework.AbstractExclusiveTestSitesTest;
 import eu.tsystems.mms.tic.testframework.common.Testerra;
 import eu.tsystems.mms.tic.testframework.core.pageobjects.testdata.WebTestPage;
 import eu.tsystems.mms.tic.testframework.exceptions.TimeoutException;
-import eu.tsystems.mms.tic.testframework.pageobjects.Attribute;
 import eu.tsystems.mms.tic.testframework.pageobjects.UiElement;
 import eu.tsystems.mms.tic.testframework.testing.AssertProvider;
 import eu.tsystems.mms.tic.testframework.testing.TestController;
diff --git a/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementCommonTests.java b/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementCommonTests.java
index 4bbfe365a..2b3c717f0 100644
--- a/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementCommonTests.java
+++ b/integration-tests/src/test/java/eu/tsystems/mms/tic/testframework/test/guielement/UiElementCommonTests.java
@@ -27,6 +27,7 @@ import eu.tsystems.mms.tic.testframework.exceptions.ElementNotFoundException;
 import eu.tsystems.mms.tic.testframework.exceptions.UiElementAssertionError;
 import eu.tsystems.mms.tic.testframework.internal.asserts.ImageAssertion;
 import eu.tsystems.mms.tic.testframework.pageobjects.UiElement;
+import eu.tsystems.mms.tic.testframework.pageobjects.XPath;
 import eu.tsystems.mms.tic.testframework.testing.AssertProvider;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -105,4 +106,20 @@ public class UiElementCommonTests extends AbstractExclusiveTestSitesTest<WebTest
         Assert.assertEquals(empty.toString(true), "WebTestPage -> EmptyUiElement(SubmitButton)");
     }
 
+    @Test
+    public void testT10_UiElement_text_assert() {
+        WebTestPage page = getPage();
+        UiElement element = page.getFinder().find(XPath.from("label").text().hasWords("TimeOut in Millis (0-inf)"));
+        element.expect().text().hasWords("TimeOut in Millis (0-inf)").is(true);
+        element.expect().text().hasWords("TimeOut in Millis").is(true);
+        element.expect().text().hasWords("(0-inf)").is(true);
+        element.expect().text().hasWords("TimeOut in Millis".split("\\\\s+")).is(true);
+        element.expect().text().hasWords("TimeOut", "in", "Millis").is(true);
+        element.expect().text().hasWords("tIMEoUT", "iN", "mILLIS").is(true);
+        element.expect().text().hasWords("tIMEoUT in mILLIS").is(true);
+
+        element.expect().text().hasWords("Out").is(false);
+        element.expect().text().hasWords("TimeOut", "in", "foo").is(false);
+    }
+
 }