package com.gfk.senbot.framework.data; import static org.junit.Assert.fail; import java.beans.PropertyDescriptor; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.openqa.selenium.By; import org.openqa.selenium.By.ById; import org.openqa.selenium.By.ByXPath; import org.openqa.selenium.support.PageFactory; import org.springframework.beans.BeanUtils; import com.gfk.senbot.framework.context.CucumberManager; import com.gfk.senbot.framework.context.SenBotContext; import com.gfk.senbot.framework.context.SpringPropertiesExposer; /** * A class to define global variables and reference them by a logical human readable way. * This will help you instead to write tests like: * <pre> * Given I am looking at page https://localhost:8080/some_path/to/a/user_page.html?name=valie123456 * </pre> * as like this: (which is easier to read) * <pre> * Given I am looking at the "user page" * </pre> * * This is achieved by linking the url to the "user page" name as follows: * <pre> * static{ * referenceService.addPageReference("user page", "https://localhost:8080/some_path/to/a/user_page.html?name=valie123456"); * } * </pre> * * The same reference mechanism is available for element locators to avoid long xpath in the tests and for {@link GenericUser}'s so that you can say things like * <pre> * Given I am loged in as a "admin" user * </pre> * * * @author joostschouten * */ public class SenBotReferenceService { public static final String NAME_SPACE_PREFIX = "NS:"; public static final String SCENARIO_NAME_SPACE_PREFIX = "SNS:"; /** * Map to store generic reference objects by their respective class and name */ private Map<Class, Map<String, Object>> referenceMaps = new HashMap<Class, Map<String, Object>>(); private Map<String, String> pageReferenceToPageUrlMap = new CaseinsensitiveMap<String>(); /** * Allows for referencing a page or view instantatable by the {@link PageFactory} */ private Map<String, Class> pageRepresentationMap = new CaseinsensitiveMap<Class>(); private ThreadLocal<String> nameSpaceThreadLocale = new ThreadLocal<String>() { @Override protected String initialValue() { return "NS" + new Integer(UUID.randomUUID().hashCode()).toString() + "-"; } }; private final CucumberManager cucumberManager; /** * Constructor * * @param populatorClassName * * @throws ClassNotFoundException * @throws SecurityException * @throws NoSuchMethodException * @throws IllegalArgumentException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException * @throws IOException */ public SenBotReferenceService(String populatorClassName, CucumberManager cucumberManager) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException { this.cucumberManager = cucumberManager; if (!StringUtils.isBlank(populatorClassName)) { Constructor<?> constructor = Class.forName(populatorClassName).getConstructor(); ReferenceServicePopulator populator = (ReferenceServicePopulator) constructor.newInstance(); populator.populate(this); } } /** * Map a {@link GenericUser} to a reference name * * @param refName * @param user * @throws InvocationTargetException * @throws IllegalAccessException */ public void addUser(String refName, GenericUser user) { addReference(GenericUser.class, refName, user); } <T> void addConfigurableReferenceObject(String refName, T object) { Map<String, T> objectReferenceMap = getObjectReferenceMap((Class<T>)object.getClass()); } /** * Map a {@link String} url to a reference name * * @param pageReference * @param pageUrl */ public void addPageReference(String pageReference, String pageUrl) { pageReferenceToPageUrlMap.put(pageReference, pageUrl); } /** * Map a {@link By} element locator to a reference name * * @param locatorReference * @param locator * * @deprecated use {@link SenBotReferenceService#addPageRepresentationReference(String, Class)} in stead to define views. Referencing elements at a global level * is a bad idea as it becomes congested within no time and the element context is completely lost */ public void addLocatorReference(String locatorReference, By locator) { Map<String, By> objectReferenceMap = getObjectReferenceMap(By.class); objectReferenceMap.put(locatorReference, locator); } /** * Find a {@link By} locator by its reference name * * @param elementReference * @return {@link By} */ public By getElementLocatorForElementReference(String elementReference) { Map<String, By> objectReferenceMap = getObjectReferenceMap(By.class); By elementLocator = objectReferenceMap.get(elementReference); if (elementLocator == null) { fail("No elementLocator is found for element name: '" + elementReference + "'. Available element references are: " + objectReferenceMap.keySet().toString()); } return elementLocator; } /** * Find a {@link By} locator by its reference name and add something to the xpath before the element gets returned * The drawback of using this method is, that all locators are converted into By.xpath * * @param elementReference The name under which the refference is found * @param apendix The part of the xpath that shall be added * @return {@link By} */ public By getElementLocatorForElementReference(String elementReference, String apendix) { Map<String, By> objectReferenceMap = getObjectReferenceMap(By.class); By elementLocator = objectReferenceMap.get(elementReference); if (elementLocator instanceof ById) { String xpathExpression = elementLocator.toString().replaceAll("By.id: ", "//*[@id='") + "']" + apendix; elementLocator = By.xpath(xpathExpression); } else if (elementLocator instanceof ByXPath) { String xpathExpression = elementLocator.toString().replaceAll("By.xpath: ", "") + apendix; elementLocator = By.xpath(xpathExpression); } else { fail("ElementLocator conversion error"); } if (elementLocator == null) { fail("No elementLocator is found for element name: '" + elementReference + "'. Available element references are: " + objectReferenceMap.keySet().toString()); } return elementLocator; } public void addPageRepresentationReference(String name, Class clazz) { pageRepresentationMap.put(name, clazz); } /** * Find a {@link GenericUser} by its reference name * * @param userType * @return {@link GenericUser} */ public GenericUser getUserForUserReference(String userReference) { GenericUser user = getReference(GenericUser.class, userReference); if (user == null) { fail("No user of type '" + userReference + "' is found. Available user references are: " + getObjectReferenceMap(GenericUser.class).keySet().toString()); } return user; } /** * Find a {@link String} url by its reference name * @param pageReference * @return {@link String} representing the url to the page mapped to the passed in page name */ public String getUrlForPageReference(String pageReference) { String url = pageReferenceToPageUrlMap.get(pageReference); if (url == null) { fail("No url is found for page name: '" + pageReference + "'. Available page references are: " + pageReferenceToPageUrlMap.keySet().toString()); } return url; } public Class getPageRepresentationReference(String pageRepresentationReference) { Class ret = pageRepresentationMap.get(pageRepresentationReference); if (ret == null) { fail("No Class is found for page/view name: '" + pageRepresentationReference + "'. Available page references are: " + pageRepresentationMap.keySet().toString()); } return ret; } public <T> T getPageRepresentationInstance(Class<T> referenceClassType) { return SenBotContext.getSenBotContext().getSeleniumManager().getViewRepresentation(referenceClassType); } /** * Extends the name space prefix with the actual name space * All tests that need to ensure privacy, i.e. no other test shall mess up their data, shall * use name spacing. * Data that can be messed up by other tests and there fore needs to be unique shall contain the name space prefix * that will be replaced at run time. * * @param plainString The string that contains the name spacing string * @return The namespacenized string * @throws RuntimeExpression In case the name spacing string lives at the wrong location */ public String namespaceString(String plainString) throws RuntimeException { if(plainString.startsWith(NAME_SPACE_PREFIX)) { return (plainString.replace(NAME_SPACE_PREFIX, nameSpaceThreadLocale.get())); } if(plainString.startsWith(SCENARIO_NAME_SPACE_PREFIX)) { if(cucumberManager.getCurrentScenarioGlobals() == null) { throw new ScenarioNameSpaceAccessOutsideScenarioScopeException("You cannot fetch a Scneario namespace outside the scope of a scenario"); } return (plainString.replace(SCENARIO_NAME_SPACE_PREFIX, cucumberManager.getCurrentScenarioGlobals().getNameSpace())); } else { return plainString; } } public <T> void addReference(Class<T> referenceClass, String referenceName, T referenceObject) { Map<String, T> refMap = getObjectReferenceMap(referenceClass); PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(referenceObject.getClass()); for(PropertyDescriptor descriptor : propertyDescriptors) { //ignore all property types that are not strings for now if(String.class.equals(descriptor.getPropertyType())) { String findProp = referenceClass.getSimpleName() + "." + referenceName + "." + descriptor.getName(); String foundValue = SpringPropertiesExposer.getProperty(findProp); if(foundValue != null) { try { org.apache.commons.beanutils.BeanUtils.setProperty(referenceObject, descriptor.getName(), foundValue); } catch (Exception e) { throw new RuntimeException("Exception while setting " + descriptor.getName() + " on " + referenceObject, e); } } } } refMap.put(referenceName, referenceObject); } public <T> Map<String, T> getObjectReferenceMap(Class<T> referenceClass) { Map<String, T> refMap = (Map<String, T>) referenceMaps.get(referenceClass); if (refMap == null) { refMap = new CaseinsensitiveMap<T>(); referenceMaps.put(referenceClass, (Map<String, Object>) refMap); } return refMap; } public <T> T getReference(Class<T> referenceClass, String referenceName) { T ret = getObjectReferenceMap(referenceClass).get(referenceName); if(ret instanceof PreAccessExecutable) { ((PreAccessExecutable) ret ).preAccess(); } return ret; } }