All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.qmetry.qaf.automation.step.JavaStep Maven / Gradle / Ivy

Go to download

Functional test automation framework for web, mobile-web, mobile native and web-service

There is a newer version: 4.0.0-RC3
Show newest version
/*******************************************************************************
 * QMetry Automation Framework provides a powerful and versatile platform to
 * author
 * Automated Test Cases in Behavior Driven, Keyword Driven or Code Driven
 * approach
 * Copyright 2016 Infostretch Corporation
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE
 * You should have received a copy of the GNU General Public License along with
 * this program in the name of LICENSE.txt in the root folder of the
 * distribution. If not, see https://opensource.org/licenses/gpl-3.0.html
 * See the NOTICE.TXT file in root folder of this source files distribution
 * for additional information regarding copyright ownership and licenses
 * of other open source software / files used by QMetry Automation Framework.
 * For any inquiry or need additional information, please contact
 * [email protected]
 *******************************************************************************/

package com.qmetry.qaf.automation.step;

import static com.qmetry.qaf.automation.core.ConfigurationManager.getBundle;
import static com.qmetry.qaf.automation.util.ClassUtil.getAnnotation;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.configuration.ConfigurationMap;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.lang.text.StrSubstitutor;
import org.json.JSONException;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.qmetry.qaf.automation.core.TestBaseProvider;
import com.qmetry.qaf.automation.data.MetaData;
import com.qmetry.qaf.automation.gson.GsonDeserializerObjectWrapper;
import com.qmetry.qaf.automation.gson.ObjectWrapper;
import com.qmetry.qaf.automation.ui.api.TestPage;
import com.qmetry.qaf.automation.ui.webdriver.QAFWebElement;
import com.qmetry.qaf.automation.util.JSONUtil;

/**
 * com.qmetry.qaf.automation.step.JavaStep.java
 * 
 * @author chirag.jayswal
 */
@XmlRootElement
public class JavaStep extends BaseTestStep {
	/**
	 * For internal use only.
	 */
	public static final String ATTACH_LISTENER = "attach.javastep.listener";
	protected transient Method method;
	private Object stepProvider;
	// package access
	String signature = "";
	private boolean qafStepImpl = true;

	public JavaStep(Method method) {
		this(method, "", "");
	}

	public JavaStep(Method method, String name, String description) {
		this.method = method;
		this.name = name;
		this.description = description;
		init();
	}

	public boolean isQafStepImpl() {
		return qafStepImpl;
	}

	private void init() {
		fileName = method.getDeclaringClass().getName();
		MetaData stepMetaData = getAnnotation(method, MetaData.class);
		MetaData classMetaData = getAnnotation(method.getDeclaringClass(), MetaData.class);
		QAFTestStep step = getAnnotation(method, QAFTestStep.class);

		if (null != classMetaData && isNotBlank(classMetaData.value())) {
			try {
				metaData = JSONUtil.toMap(classMetaData.value());
			} catch (JSONException e) {
				System.err.println(metaData + " is not valid json map for step meta-data");
			}
		}

		setMetaData();
		if (null != stepMetaData && isNotBlank(stepMetaData.value())) {
			try {
				// keep class meta-data which is not in step meta-data, override
				// common
				metaData.putAll(JSONUtil.toMap(stepMetaData.value()));
			} catch (JSONException e) {
				System.err.println(metaData + " is not valid json map for step meta-data");
			}
		}

		if (isBlank(name)) {
			QAFTestStepProvider provider = method.getDeclaringClass().getAnnotation(QAFTestStepProvider.class);

			String prefix = (provider != null) && isNotBlank(provider.prefix()) ? provider.prefix() + "." : "";
			name = prefix + ((step != null) && isNotBlank(step.stepName()) ? step.stepName() : method.getName());

		}
		if (step != null) {
			threshold = step.threshold();
			if (isNotBlank(step.description())) {
				// highest priority to QAFTestStep annotation if multiple step
				// definition way opted
				description = step.description();
				qafStepImpl = true;
			}
		}
		if (isBlank(description)) {
			description = name;
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.qmetry.qaf.automation.step.TestStep#execute(java.lang.Object[])
	 */
	@Override
	protected Object doExecute() {
		try {
			Object stepProvider = getStepProvider();
			// block joint-point listener
			TestBaseProvider.instance().get().getContext().setProperty(ATTACH_LISTENER, false);
			TestBaseProvider.instance().get().getContext().setProperty("current.teststep", this);
			method.setAccessible(true);
			Object[] args = processArgs(method, actualArgs);
			return method.invoke(stepProvider, args);
		} catch (IllegalArgumentException e) {
			throw new StepInvocationException(this, "Unable to invoke JavaStep with given arguments: " + getName()
					+ Arrays.toString(actualArgs) + "\nat " + getSignature(), true);

		} catch (IllegalAccessException e) {
			throw new StepInvocationException(this,
					"Unable to invoke JavaStep: " + getName() + Arrays.toString(actualArgs) + "\nat " + getSignature(),
					true);

		} catch (InvocationTargetException e) {
			if (e.getCause() instanceof Error) {
				throw (Error) e.getCause();
			}
			if (e.getCause() instanceof RuntimeException) {
				throw (RuntimeException) e.getCause();
			}
			throw new StepInvocationException(this, e.getCause());
		} catch (InstantiationException e) {
			throw new StepInvocationException(this,
					"Unable to Instantiate JavaStep: " + getName() + Arrays.toString(actualArgs) + getSignature(),
					true);
		}
	}

	protected Object getStepProvider() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		return stepProvider == null ? getClassInstance(method.getDeclaringClass()) : stepProvider;
	}

	/**
	 * @return the method
	 */
	public Method getMethod() {
		return method;
	}

	@Override
	public String getSignature() {
		return signature;
	}

	protected Object[] processArgs(Method method, Object... objects) {
		int noOfParams = method.getParameterTypes().length;
		if (noOfParams == 0) {
			return null;
		}
		Object[] params = new Object[noOfParams];
		Map context = getStepExecutionTracker().getContext();

		try {
			if ((noOfParams == (objects.length - 1)) && method.getParameterTypes()[noOfParams - 1].isArray()) {
				// case of optional arguments!...
				System.arraycopy(objects, 0, params, 0, objects.length);
				params[noOfParams - 1] = "[]";
			} else {
				System.arraycopy(objects, 0, params, 0, noOfParams);
			}
		} catch (Exception e) {
			throw new RuntimeException("Wrong number of parameters, Expected " + noOfParams
					+ " parameters but Actual is " + (objects == null ? "0" : objects.length));
		}

		Gson gson = new GsonBuilder().setDateFormat("dd-MM-yyyy")
				.registerTypeAdapter(ObjectWrapper.class, new GsonDeserializerObjectWrapper()).create();

		description = StrSubstitutor.replace(description, context);
		description = getBundle().getSubstitutor().replace(description);
		for (int i = 0; i < noOfParams; i++) {
			Class paramType = method.getParameterTypes()[i];

			if ((params[i] instanceof String)) {
				String pstr = (String) params[i];

				if (pstr.startsWith("${") && pstr.endsWith("}")) {
					String pname = pstr.substring(2, pstr.length() - 1);
					params[i] = context.containsKey(pstr) ? context.get(pstr)
							: context.containsKey(pname) ? context.get(pname)
									: getBundle().containsKey(pstr) ? getObject(pstr, paramType) : getPropValue(pname,paramType);
				} else if (pstr.indexOf("$") >= 0) {
					pstr = StrSubstitutor.replace(pstr, context);
					params[i] = getBundle().getSubstitutor().replace(pstr);
				}

			}
			if (String.class.isAssignableFrom(paramType)) {
				continue;
			}
			try {
				String strVal = gson.toJson(params[i]);
				if (params[i] instanceof String) {
					strVal = String.valueOf(params[i]);
				}

				strVal = getBundle().getSubstitutor().replace(strVal);
				strVal = StrSubstitutor.replace(strVal, context);

				try {
					// prevent gson from expressing integers as floats
					ObjectWrapper w = gson.fromJson(strVal, ObjectWrapper.class);
					Object obj = w.getObject();
					try {
						params[i] = paramType.cast(obj);
					} catch (Exception e) {
						JsonElement j = gson.toJsonTree(obj);
						params[i] = gson.fromJson(j, paramType);
					}
				} catch (Exception e) {
					params[i] = gson.fromJson(strVal, paramType);
				}
			} catch (Exception e) {
			}
		}
		return params;
	}

	@Override
	public TestStep clone() {
		JavaStep cloneObj = new JavaStep(method);
		if (null != actualArgs) {
			cloneObj.actualArgs = actualArgs.clone();
		}
		return cloneObj;
	}

	public void getSubSteps() {
		if (method.getReturnType().isInstance(QAFWebElement.class)) {

		}
	}

	private Object getClassInstance(Class cls) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		if (getBundle().getBoolean("step.provider.sharedinstance", false) && isSharableInstance(cls)) {
			// allow class variable sharing among steps
			Object obj = getBundle().getObject(cls.getName());
			if (null == obj) {
				obj = createInstance(cls);
				inject(obj);
				getBundle().setProperty(cls.getName(), obj);
			}
			return obj;
		}
		Object obj = createInstance(cls);
		inject(obj);
		return obj;
	}

	private void inject(Object obj) {
		try {
			//new ElementFactory().initFields(obj);
			Field[] flds = obj.getClass().getDeclaredFields();
			for(Field fld : flds){
				if (fld.isAnnotationPresent(Inject.class)) {
					fld.setAccessible(true);
					Object value = getClassInstance(fld.getType());
					fld.set(obj, value);
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
			
		}
	}

	private Object createInstance(Class cls) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		try {
			return cls.newInstance();
		} catch (Exception e) {
				//only public constructors with or without parameter(s) to be considered!...
				Constructor con = cls.getConstructors()[0];
				con.setAccessible(true);
				ArrayList args = new ArrayList();
				for (Class param : con.getParameterTypes()) {
					args.add(getClassInstance(param));
				}
			return	con.newInstance(args.toArray(new Object[args.size()]));
				
		}
	}

	private boolean isSharableInstance(Class cls) {

		if (TestPage.class.isAssignableFrom(cls) || QAFWebElement.class.isAssignableFrom(cls)) {
			return false;
		}
		return true;
	}

	private void setMetaData() {

		Annotation[] allAnnotations = method.getAnnotations();
		for (Annotation annotation : allAnnotations) {
			if (annotation instanceof MetaData || annotation instanceof QAFTestStep)
				continue;

			Method[] annotationMethods = annotation.annotationType().getDeclaredMethods();
			for (Method annotationMethod : annotationMethods) {
				Object objVal;
				try {
					objVal = annotationMethod.invoke(annotation);
					String key = annotationMethod.getName();
					metaData.put(key, objVal);
					if (key.equalsIgnoreCase("value") && isTestStepAnnotation(annotation)) {
						description = (String) objVal;
						qafStepImpl = false;

					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private boolean isTestStepAnnotation(Annotation annotation) {
		List annotationPkgs = getBundle().getList("step.annotation.pkgs", Arrays.asList("cucumber.api.java"));

		for (String pkg : annotationPkgs) {
			if (annotation.annotationType().getName().indexOf(pkg) >= 0) {
				return true;
			}
		}
		return false;
	}

	private Object getPropValue(String pname,  Class paramType) {
		Object o = getBundle().subset(pname);
		if (o instanceof HierarchicalConfiguration && ((HierarchicalConfiguration) o).getRoot().getValue() == null
				&& ((HierarchicalConfiguration) o).getRoot().getChildrenCount() > 0) {
			return new ConfigurationMap(getBundle().subset(pname));
		}
		return getObject(pname, paramType);
	}
	
	private Object getObject(String key, Class paramType){
		Object o = getBundle().getProperty(key);
		if(o.getClass().isAssignableFrom(paramType)){
			return o;
		}
		if(paramType.isArray()){
			return getBundle().getList(key).toArray();
		}
		if(o instanceof List){
			return ((List)o).get(0);
		}
		return o;
	}
}