/*******************************************************************************
* Copyright 2017 Ivan Shubin http://galenframework.com
*
* 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 com.galenframework.speclang2.pagespec;
import com.galenframework.page.selenium.ScreenElement;
import com.galenframework.page.selenium.SeleniumPage;
import com.galenframework.page.selenium.ViewportElement;
import com.galenframework.parser.*;
import com.galenframework.specs.page.PageSection;
import com.galenframework.javascript.GalenJsExecutor;
import com.galenframework.page.AbsentPageElement;
import com.galenframework.page.Page;
import com.galenframework.page.PageElement;
import com.galenframework.speclang2.specs.SpecReader;
import com.galenframework.specs.page.Locator;
import com.galenframework.specs.page.PageSpec;
import com.galenframework.speclang2.pagespec.rules.Rule;
import com.galenframework.speclang2.pagespec.rules.RuleParser;
import com.galenframework.suite.reader.Context;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import org.mozilla.javascript.*;
public class PageSpecHandler implements VarsParserJsFunctions {
private final PageSpec pageSpec;
private final Page page;
private final String contextPath;
private final SpecReader specReader;
private final GalenJsExecutor jsExecutor;
private final VarsParser varsParser;
private final List<Pair<Rule, PageRule>> pageRules;
private final List<String> processedImports = new LinkedList<>();
private final List<String> processedScripts = new LinkedList<>();
private final Properties properties;
private final Map<String, Object> jsVariables;
private final SectionFilter sectionFilter;
public PageSpecHandler(PageSpec pageSpec, Page page,
SectionFilter sectionFilter,
String contextPath, Properties properties,
Map<String, Object> jsVariables
) {
this.pageSpec = pageSpec;
this.page = page;
this.sectionFilter = sectionFilter;
this.contextPath = contextPath;
this.specReader = new SpecReader();
this.jsExecutor = createGalenJsExecutor(this);
this.pageRules = new LinkedList<>();
this.jsVariables = jsVariables;
if (properties != null) {
this.properties = properties;
} else {
this.properties = new Properties();
}
this.varsParser = new VarsParser(new Context(), this.properties, jsExecutor);
if (jsVariables != null) {
setGlobalVariables(jsVariables);
}
}
public PageSpecHandler(PageSpecHandler copy, String contextPath) {
this.pageSpec = copy.pageSpec;
this.page = copy.page;
this.contextPath = contextPath;
this.specReader = copy.specReader;
this.jsExecutor = copy.jsExecutor;
this.varsParser = copy.varsParser;
this.sectionFilter = copy.sectionFilter;
this.pageRules = copy.pageRules;
this.properties = copy.properties;
this.jsVariables = copy.jsVariables;
}
private static GalenJsExecutor createGalenJsExecutor(final PageSpecHandler pageSpecHandler) {
GalenJsExecutor js = new GalenJsExecutor();
js.putObject("_pageSpecHandler", pageSpecHandler);
js.evalScriptFromLibrary("GalenSpecProcessing.js");
if (pageSpecHandler.page instanceof SeleniumPage) {
SeleniumPage seleniumPage = (SeleniumPage) pageSpecHandler.page;
js.putObject("screen", new JsPageElement("screen", new ScreenElement(seleniumPage.getDriver())));
js.putObject("viewport", new JsPageElement("viewport", new ViewportElement(seleniumPage.getDriver())));
}
js.getScope().defineProperty("isVisible", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (args.length == 0) {
throw new IllegalArgumentException("Should take string argument, got nothing");
}
if (args[0] == null) {
throw new IllegalArgumentException("Object name should be null");
}
return pageSpecHandler.isVisible(args[0].toString());
}
}, ScriptableObject.DONTENUM);
js.getScope().defineProperty("isPresent", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (args.length == 0) {
throw new IllegalArgumentException("Should take string argument, got nothing");
}
if (args[0] == null) {
throw new IllegalArgumentException("Object name should be null");
}
return pageSpecHandler.isPresent(args[0].toString());
}
}, ScriptableObject.DONTENUM);
js.getScope().defineProperty("count", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (args.length == 0) {
throw new IllegalArgumentException("Should take string argument, got nothing");
}
if (args[0] == null) {
throw new IllegalArgumentException("Object name should be null");
}
return pageSpecHandler.count(args[0].toString());
}
}, ScriptableObject.DONTENUM);
js.getScope().defineProperty("find", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return pageSpecHandler.find(getSingleStringArgument(args));
}
}, ScriptableObject.DONTENUM);
js.getScope().defineProperty("findAll", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return pageSpecHandler.findAll(getSingleStringArgument(args));
}
}, ScriptableObject.DONTENUM);
js.getScope().defineProperty("first", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return pageSpecHandler.first(getSingleStringArgument(args));
}
}, ScriptableObject.DONTENUM);
js.getScope().defineProperty("last", new BaseFunction() {
@Override
public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return pageSpecHandler.last(getSingleStringArgument(args));
}
}, ScriptableObject.DONTENUM);
return js;
}
private static String getSingleStringArgument(Object[] args) {
String singleArgument = null;
if (args.length == 0) {
throw new IllegalArgumentException("Should take one string argument, got none");
} else if (args[0] == null) {
throw new IllegalArgumentException("Pattern should not be null");
} else if (args[0] instanceof NativeJavaObject) {
NativeJavaObject njo = (NativeJavaObject) args[0];
singleArgument = njo.unwrap().toString();
} else {
singleArgument = args[0].toString();
}
return singleArgument;
}
public Object isVisible(String objectName) {
for (Map.Entry<String, Locator> object : pageSpec.getObjects().entrySet()) {
if (object.getKey().equals(objectName)) {
PageElement pageElement = page.getObject(object.getKey(), object.getValue());
return pageElement != null && pageElement.isPresent() && pageElement.isVisible();
}
}
return Boolean.FALSE;
}
public Object isPresent(String objectName) {
for (Map.Entry<String, Locator> object : pageSpec.getObjects().entrySet()) {
if (object.getKey().equals(objectName)) {
PageElement pageElement = page.getObject(object.getKey(), object.getValue());
return pageElement != null && pageElement.isPresent();
}
}
return Boolean.FALSE;
}
public PageSpec buildPageSpec() {
PageSpec cleanedSpec = new PageSpec();
cleanedSpec.setObjects(pageSpec.getObjects());
cleanedSpec.setSections(cleanEmptySections(pageSpec.getSections()));
cleanedSpec.setObjectGroups(pageSpec.getObjectGroups());
return cleanedSpec;
}
private List<PageSection> cleanEmptySections(List<PageSection> sections) {
List<PageSection> cleanedSections = new LinkedList<>();
for (PageSection pageSection : sections) {
PageSection cleanedSection = pageSection.cleanSection();
if (!pageSection.isEmpty()) {
cleanedSections.add(cleanedSection);
}
}
return cleanedSections;
}
public void addSection(PageSection section) {
PageSection sameSection = findSection(section.getName());
if (sameSection != null) {
sameSection.mergeSection(section);
} else {
pageSpec.addSection(section);
}
}
private PageSection findSection(String name) {
for (PageSection pageSection : pageSpec.getSections()) {
if (pageSection.getName().equals(name)) {
return pageSection;
}
}
return null;
}
public SpecReader getSpecReader() {
return specReader;
}
public void addObjectToSpec(String objectName, Locator locator) {
pageSpec.addObject(objectName, locator);
}
@Override
public int count(String regex) {
List<String> objectNames = pageSpec.findOnlyExistingMatchingObjectNames(regex);
return objectNames.size();
}
@Override
public JsPageElement find(String name) {
List<String> objectNames = pageSpec.findOnlyExistingMatchingObjectNames(name);
if (!objectNames.isEmpty()) {
String objectName = objectNames.get(0);
Locator locator = pageSpec.getObjects().get(objectName);
if (locator != null && page != null) {
PageElement pageElement = page.getObject(objectName, locator);
if (pageElement != null) {
return new JsPageElement(objectName, pageElement);
}
}
}
return new JsPageElement(name, new AbsentPageElement());
}
@Override
public JsPageElement[] findAll(String objectsStatements) {
List<String> objectNames = pageSpec.findAllObjectsMatchingStrictStatements(objectsStatements);
List<JsPageElement> jsElements = new ArrayList<>(objectNames.size());
for (String objectName : objectNames) {
Locator locator = pageSpec.getObjects().get(objectName);
PageElement pageElement = null;
if (locator != null) {
pageElement = page.getObject(objectName, locator);
}
if (pageElement != null) {
jsElements.add(new JsPageElement(objectName, pageElement));
} else {
jsElements.add(new JsPageElement(objectName, new AbsentPageElement()));
}
}
return jsElements.toArray(new JsPageElement[jsElements.size()]);
}
@Override
public JsPageElement first(String objectsStatements) {
return extractSingleElement(objectsStatements, list -> list.get(0));
}
@Override
public JsPageElement last(String objectsStatements) {
return extractSingleElement(objectsStatements, list -> list.get(list.size() - 1));
}
private JsPageElement extractSingleElement(String objectsStatements, FilterFunction<String> filterFunction) {
List<String> objectNames = pageSpec.findAllObjectsMatchingStrictStatements(objectsStatements);
PageElement pageElement = null;
String objectName = objectsStatements;
if (!objectNames.isEmpty()) {
objectName = filterFunction.filter(objectNames);
Locator locator = pageSpec.getObjects().get(objectName);
if (locator != null) {
pageElement = page.getObject(objectName, locator);
}
}
if (pageElement != null) {
return new JsPageElement(objectName, pageElement);
} else {
return new JsPageElement(objectName, new AbsentPageElement());
}
}
public void setGlobalVariable(String name, Object value, StructNode source) {
if (!isValidVariableName(name)) {
throw new SyntaxException(source, "Invalid name for variable: " + name);
}
if (value != null && value instanceof NativeJavaObject) {
jsExecutor.putObject(name, ((NativeJavaObject) value).unwrap());
} else {
jsExecutor.putObject(name, value);
}
}
private boolean isValidVariableName(String name) {
if (name.isEmpty()) {
return false;
}
for (int i = 0; i < name.length(); i++) {
int symbol = (int)name.charAt(i);
if (!(symbol > 64 && symbol < 91) //checking uppercase letters
&& !(symbol > 96 && symbol < 123) //checking lowercase letters
&& !(symbol > 47 && symbol < 58 && i > 0) //checking numbers and that its not the first letter
&& symbol != 95) /*underscore*/ {
return false;
}
}
return true;
}
public VarsParser getVarsParser() {
return varsParser;
}
public StructNode processExpressionsIn(StructNode originNode) {
String result;
try {
result = getVarsParser().parse(originNode.getName());
} catch (Exception ex) {
throw new SyntaxException(originNode, "JavaScript error inside statement", ex);
}
StructNode processedNode = new StructNode(result);
processedNode.setPlace(originNode.getPlace());
processedNode.setChildNodes(originNode.getChildNodes());
return processedNode;
}
public void setGlobalVariables(Map<String, Object> variables, StructNode originNode) {
for(Map.Entry<String, Object> variable : variables.entrySet()) {
setGlobalVariable(variable.getKey(), variable.getValue(), originNode);
}
}
public void setGlobalVariables(Map<String, Object> variables) {
setGlobalVariables(variables, StructNode.UNKNOWN_SOURCE);
}
public String getContextPath() {
return contextPath;
}
public List<PageSection> getPageSections() {
return pageSpec.getSections();
}
public void runJavaScriptFromFile(String scriptPath) {
jsExecutor.runJavaScriptFromFile(scriptPath);
}
public String getFullPathToResource(String scriptPath) {
if (contextPath != null) {
return contextPath + "/" + scriptPath;
} else {
return scriptPath;
}
}
public void addRule(String ruleText, PageRule pageRule) {
Rule rule = new RuleParser().parse(ruleText);
pageRules.add(new ImmutablePair<>(rule, pageRule));
}
public List<Pair<Rule, PageRule>> getPageRules() {
return pageRules;
}
public void runJavaScript(String completeScript) {
jsExecutor.eval(completeScript);
}
public List<String> getProcessedImports() {
return processedImports;
}
public List<String> getProcessedScripts() {
return processedScripts;
}
public Page getPage() {
return page;
}
public Properties getProperties() {
return properties;
}
public Map<String, Object> getJsVariables() {
return jsVariables;
}
public SectionFilter getSectionFilter() {
return sectionFilter;
}
public void applyGroupsToObject(String objectName, List<String> groups) {
if (!objectName.isEmpty()) {
for (String groupName : groups) {
groupName = groupName.trim();
List<String> groupObjectsList = pageSpec.getObjectGroups().get(groupName);
if (groupObjectsList != null) {
if (!groupObjectsList.contains(objectName)) {
groupObjectsList.add(objectName);
}
} else {
groupObjectsList = new LinkedList<>();
groupObjectsList.add(objectName);
pageSpec.getObjectGroups().put(groupName, groupObjectsList);
}
}
}
}
public List<String> findAllObjectsMatchingStrictStatements(String objectStatements) {
return pageSpec.findAllObjectsMatchingStrictStatements(objectStatements);
}
}