com.mark59.selenium.corejmeterimpl.SeleniumAbstractJavaSamplerClient Maven / Gradle / Ivy
Show all versions of mark59-selenium-implementation Show documentation
/*
* Copyright 2019 Insurance Australia Group Limited
*
* 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.mark59.selenium.corejmeterimpl;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.PageLoadStrategy;
import org.openqa.selenium.WebDriver;
import com.mark59.core.utils.IpUtilities;
import com.mark59.core.utils.Log4jConfigurationHelper;
import com.mark59.core.utils.Mark59Constants;
import com.mark59.core.utils.Mark59Utils;
import com.mark59.selenium.drivers.SeleniumDriverFactory;
import com.mark59.selenium.drivers.SeleniumDriverWrapper;
/**
* Selenium flavored extension of the JMeter Java Sampler AbstractJavaSamplerClient.
* This a core class of the Mark59 Selenium implementation, and should be extended when creating
* a JMeter-ready Selenium script.
*
* Implementation of abstract method {@link #runSeleniumTest(JavaSamplerContext, JmeterFunctionsForSeleniumScripts, WebDriver)}
* should contain the test, with parameterisation handled by {@link #additionalTestParameters()}.
* See the 'DataHunter' samples provided for implementation details.
*
*
Includes a number of standard parameters expected for a Selenium WebDriver.
*
* @see SeleniumDriverFactory#makeDriverWrapper(Map)
* @see SeleniumDriverFactory#getDriverBuilderOfType
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setHeadless(boolean)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setPageLoadStrategy(PageLoadStrategy)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setSize(int width, int height)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setProxy(org.openqa.selenium.Proxy)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setAdditionalOptions(java.util.List)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setWriteBrowserLogfile(boolean)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setAlternateBrowser(java.nio.file.Path)
* @see IpUtilities#localIPisNotOnListOfIPaddresses(String)
* @see JmeterFunctionsForSeleniumScripts
*
* @author Philip Webb
* Written: Australian Winter 2019
*/
public abstract class SeleniumAbstractJavaSamplerClient extends AbstractJavaSamplerClient {
public static Logger LOG = LogManager.getLogger(SeleniumAbstractJavaSamplerClient.class);
protected Arguments jmeterArguments = new Arguments();
protected JmeterFunctionsForSeleniumScripts jm;
protected SeleniumDriverWrapper seleniumDriverWrapper;
protected WebDriver driver;
private KeepBrowserOpen keepBrowserOpen = KeepBrowserOpen.NEVER;
protected String thread = Thread.currentThread().getName();
protected String tgName = null;
protected AbstractThreadGroup tg = null;
protected static final Map defaultArgumentsMap;
static {
Map staticMap = new LinkedHashMap();
staticMap.put("______________________ driver settings: ________________________", "Refer Mark59 User Guide : http://mark59.com");
staticMap.put(SeleniumDriverFactory.DRIVER, Mark59Constants.DEFAULT_DRIVER);
staticMap.put(SeleniumDriverFactory.HEADLESS_MODE, String.valueOf(true));
staticMap.put(SeleniumDriverFactory.PAGE_LOAD_STRATEGY, PageLoadStrategy.NORMAL.toString());
staticMap.put(SeleniumDriverFactory.BROWSER_DIMENSIONS, Mark59Constants.DEFAULT_BROWSER_DIMENSIONS);
staticMap.put(SeleniumDriverFactory.PROXY, "");
staticMap.put(SeleniumDriverFactory.ADDITIONAL_OPTIONS, "");
staticMap.put(SeleniumDriverFactory.WRITE_FFOX_BROWSER_LOGFILE, String.valueOf(false));
staticMap.put("______________________ logging settings: _______________________", "Expected values: 'default', 'buffer', 'write' or 'off' ");
staticMap.put(SeleniumDriverWrapper.LOG_SCREENSHOTS_AT_START_OF_TRANSACTIONS, SeleniumDriverWrapper.DEFAULT);
staticMap.put(SeleniumDriverWrapper.LOG_SCREENSHOTS_AT_END_OF_TRANSACTIONS, SeleniumDriverWrapper.DEFAULT);
staticMap.put(SeleniumDriverWrapper.LOG_PAGE_SOURCE_AT_START_OF_TRANSACTIONS, SeleniumDriverWrapper.DEFAULT);
staticMap.put(SeleniumDriverWrapper.LOG_PAGE_SOURCE_AT_END_OF_TRANSACTIONS, SeleniumDriverWrapper.DEFAULT);
staticMap.put(SeleniumDriverWrapper.LOG_PERF_LOG_AT_END_OF_TRANSACTIONS, SeleniumDriverWrapper.DEFAULT);
staticMap.put("______________________ miscellaneous: __________________________", "");
staticMap.put(IpUtilities.RESTRICT_TO_ONLY_RUN_ON_IPS_LIST, "");
staticMap.put("___________________" , "");
staticMap.put("script build information: ", "using mark59-selenium-implementation version " + Mark59Constants.MARK59_VERSION);
defaultArgumentsMap = Collections.unmodifiableMap(staticMap);
}
/**
* Creates the list of parameters with default values, as they would appear on the JMeter GUI for the JavaSampler being implemented.
* A standard set of parameters are defined (defaultArgumentsMap). Additionally,an implementing class (the script extending this class)
* can add additional parameters (or override the standard defaults) via the additionalTestParameters() method.
*
* @see #additionalTestParameters()
* @see org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient
*/
@Override
public Arguments getDefaultParameters() {
return Mark59Utils.mergeMapWithAnOverrideMap(defaultArgumentsMap, additionalTestParameters());
}
@Override
public void setupTest(JavaSamplerContext context) {
super.setupTest(context);
}
/**
* Used to define required parameters for the test and their default values.
*
Ultimately used to build a Map of key-value pairs that will be available throughout the 'Mark59' framework
* for whatever customization is required for your test or driver implementation.
* Note: Can be used to override predefined parameters (the defaultArgumentsMap in SeleniumAbstractJavaSamplerClient)
*
* @see SeleniumDriverFactory#makeDriverWrapper(Map)
* @see SeleniumDriverFactory#getDriverBuilderOfType
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setHeadless(boolean)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setPageLoadStrategy(PageLoadStrategy)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setSize(int width, int height)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setProxy(org.openqa.selenium.Proxy)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setAdditionalOptions(java.util.List)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setWriteBrowserLogfile(boolean)
* @see com.mark59.selenium.drivers.SeleniumDriverBuilder#setAlternateBrowser(java.nio.file.Path)
* @see IpUtilities#localIPisNotOnListOfIPaddresses(String)
* @see JmeterFunctionsForSeleniumScripts
*
* @return the updated list of JMeter arguments with any required changes
*/
protected abstract Map additionalTestParameters();
/**
* {@inheritDoc}
*
* Note the use of the catch on AssertionError - as this is NOT an Exception but an Error, and therefore need to be explicitly caught.
*/
@Override
public SampleResult runTest(JavaSamplerContext context) {
if (LOG.isDebugEnabled()) LOG.debug(this.getClass().getName() + " : exectuing runTest" );
if ( context.getJMeterContext() != null && context.getJMeterContext().getThreadGroup() != null ) {
tg = context.getJMeterContext().getThreadGroup();
tgName = tg.getName();
}
if (IpUtilities.localIPisNotOnListOfIPaddresses(context.getParameter(IpUtilities.RESTRICT_TO_ONLY_RUN_ON_IPS_LIST))){
LOG.info("Thread Group " + tgName + " is stopping (not on 'Restrict to IP List')" );
if (tg!=null) tg.stop();
return null;
}
Map jmeterRuntimeArgumentsMap = convertJmeterArgumentsToMap(context);
try {
seleniumDriverWrapper = new SeleniumDriverFactory().makeDriverWrapper(jmeterRuntimeArgumentsMap) ;
} catch (Exception e) {
LOG.error("ERROR : " + this.getClass() + ". Fatal error has occured for Thread Group " + tgName
+ " while attempting to initiate the selenium Driver!" );
LOG.error(e.getMessage());
e.printStackTrace();
return null;
}
driver = (WebDriver)seleniumDriverWrapper.getDriverPackage() ;
jm = new JmeterFunctionsForSeleniumScripts(Thread.currentThread().getName(), seleniumDriverWrapper, jmeterRuntimeArgumentsMap);
try {
LOG.debug(">> running test ");
runSeleniumTest(context, jm, driver);
jm.tearDown();
LOG.debug("<< finished test" );
} catch (Exception | AssertionError e) {
scriptExceptionHandling(context, e);
} finally {
if (! keepBrowserOpen.equals(KeepBrowserOpen.ALWAYS ) ) {
seleniumDriverWrapper.driverDispose();
}
}
return jm.getMainResult();
}
/**
* Log and record this script execution as a failure.
*
* @see #userActionsOnScriptFailure(JavaSamplerContext, JmeterFunctionsForSeleniumScripts, WebDriver)
* @param context the current JavaSamplerContext
* @param e can be an exception or Assertion error
*/
protected void scriptExceptionHandling(JavaSamplerContext context, Throwable e) {
System.err.println("ERROR : " + this.getClass() + ". Exception " + e.getClass().getName() + " thrown. See log and screenshot directory for details. Stack trace:");
e.printStackTrace();
LOG.error("ERROR : " + this.getClass() + ". Exception " + e.getClass().getName() + " thrown", e);
try {
seleniumDriverWrapper.documentExceptionState(new Exception(e));
} catch (Exception ex) {
LOG.error("ERROR : " + this.getClass() + ". An exception occured during scriptExceptionHandling (documentExceptionState) " + ex.getClass().getName() + " thrown", e);
ex.printStackTrace();
}
try {
userActionsOnScriptFailure(context, jm, driver);
} catch (Exception errorHandlingException) {
LOG.error("ERROR : " + this.getClass() + ". An exception occured during scriptExceptionHandling (userActionsOnScriptFailure) " + errorHandlingException.getClass().getName() + " thrown", errorHandlingException);
errorHandlingException.printStackTrace();
}
jm.failTest();
jm.tearDown();
if (keepBrowserOpen.equals(KeepBrowserOpen.ONFAILURE)){
// force browser to stay open
keepBrowserOpen = KeepBrowserOpen.ALWAYS;
}
}
/**
* Intended to be an override in scripts where some user interactions is required when a script fails
* (via the browser if still available, or other application interface such as an API call).
* An example of such an interaction may be to force a user-id logout, where re-entry into the user is
* needed, and the user may be otherwise be left in an uncertain state.
*
If an exception occurs during execution the of this method, the exception is logged and the method simply exited
* (the original failure will still be handled by the Mark59 framework).
*
* @param context the current JavaSamplerContext
* @param jm the current JmeterFunctionsForSeleniumScripts
* @param driver the current WebDriver
*/
protected void userActionsOnScriptFailure(JavaSamplerContext context, JmeterFunctionsForSeleniumScripts jm, WebDriver driver) {
};
/**
* Method to be implemented containing the actual test steps.
*
* @param context the current JavaSamplerContext
* @param jm the current JmeterFunctionsForSeleniumScripts
* @param driver the current WebDriver
*/
protected abstract void runSeleniumTest(JavaSamplerContext context, JmeterFunctionsForSeleniumScripts jm, WebDriver driver);
protected Map convertJmeterArgumentsToMap(JavaSamplerContext context) {
Map jmeterArgumentsAsMap = new HashMap<>();
for (Iterator iterator = context.getParameterNamesIterator(); iterator.hasNext();) {
String paramKey = (String) iterator.next();
jmeterArgumentsAsMap.put(paramKey, context.getParameter(paramKey) );
}
if (LOG.isDebugEnabled()) LOG.debug("context parameters at convert... : " + Arrays.toString(jmeterArgumentsAsMap.entrySet().toArray()));
return jmeterArgumentsAsMap;
}
/**
* Convenience method to a single-threaded script execution (rather than needing to use Jmeter). See method runMultiThreadedSeleniumTest to executed a multi-thread test
* It is assumed you are executing for debug purposes, so the default is to keep the browser open at test end the test fails(unless running in headless mode).
*
* @see com.mark59.selenium.corejmeterimpl.KeepBrowserOpen
* @see Log4jConfigurationHelper
* @see #runMultiThreadedSeleniumTest(int, int)
* @see #runMultiThreadedSeleniumTest(int, int, Map)
* @param keepBrowserOpen see KeepBrowserOpen
*/
protected void runSeleniumTest(KeepBrowserOpen keepBrowserOpen ) {
mockJmeterProperties();
JavaSamplerContext context = new JavaSamplerContext( getDefaultParameters() );
this.keepBrowserOpen = keepBrowserOpen;
if (String.valueOf(true).equalsIgnoreCase(context.getParameter(SeleniumDriverFactory.HEADLESS_MODE))) {
this.keepBrowserOpen = KeepBrowserOpen.NEVER;
};
LOG.info("keepBrowserOpen is set to "+ this.keepBrowserOpen);
setupTest(context);
runTest(context);
}
/**
* @see #runSeleniumTest(KeepBrowserOpen)
* @see #runMultiThreadedSeleniumTest(int, int)
* @see #runMultiThreadedSeleniumTest(int, int, Map)
*/
protected void runSeleniumTest() {
runSeleniumTest(KeepBrowserOpen.ONFAILURE);
}
/**
* Convenience method to directly execute multiple script threads (rather than needing to use JMeter).
* For example:
* thisTest.runMultiThreadedSeleniumTest(2, 2000);
*
* @see #runSeleniumTest(KeepBrowserOpen)
* @see #runMultiThreadedSeleniumTest(int, int, Map)
*
* @param numberOfThreads number Of Java Threads
* @param threadStartGapMs time between start of each thread in milliseconds
*/
protected void runMultiThreadedSeleniumTest(int numberOfThreads, int threadStartGapMs) {
runMultiThreadedSeleniumTest(numberOfThreads, threadStartGapMs, new HashMap>());
}
/**
* Convenience method to directly execute multiple script threads (rather than needing to use JMeter). For example,
* if you want to user a user-defined parameter called "USER
", and switch off headless mode for one of four threads running:
*
* Map<String, java.util.List<String>>threadParameters = new java.util.LinkedHashMap<String,java.util.List<String>>();
* threadParameters.put("USER", java.util.Arrays.asList( "USER-MATTHEW", "USER-MARK", "USER-LUKE", "USER-JOHN"));
* threadParameters.put(SeleniumDriverFactory.HEADLESS_MODE, java.util.Arrays.asList( "true" , "false" , "true" , "true"));
* thisTest.runMultiThreadedSeleniumTest(4, 2000, threadParameters);
*
*
* @see #runSeleniumTest(KeepBrowserOpen)
* @see #runMultiThreadedSeleniumTest(int, int)
*
* @param numberOfThreads number Of Java Threads
* @param threadStartGapMs time between start of each thread in milliseconds
* @param threadParameters parameter key and list of values to be passed to each thread (needs to be at least as many entries as number of threads)
*/
protected void runMultiThreadedSeleniumTest(int numberOfThreads, int threadStartGapMs, Map>threadParameters) {
mockJmeterProperties();
Map thisThreadParameters = new LinkedHashMap();
for (int i = 1; i <= numberOfThreads; i++) {
for (Entry> entry : threadParameters.entrySet()) {
if ( entry.getValue().size() >= i) {
thisThreadParameters.put(entry.getKey() , entry.getValue().get(i-1));
}
}
if (!thisThreadParameters.isEmpty()){
LOG.info(" Thread Override Parameters for thread " + String.format("%03d", i) + " : " + Arrays.toString(thisThreadParameters.entrySet().toArray()));
}
new Thread(new SeleniumTestThread(this.getClass(), thisThreadParameters), String.format("%03d", i)).start();
if (i
* If JMeter properties become relevant to a particular script for some reason, it is suggested the required
* 'jmeter.properties' file be included in the root of the project.
*/
protected void mockJmeterProperties() {
File f = new File("./jmeter.properties");
if(f.exists() && !f.isDirectory()) {
LOG.debug("loading supplied jmeter.properties file");
JMeterUtils.loadJMeterProperties("./jmeter.properties");
}
}
/**
* Convenience inner class to enable multi-thread testing outside of JMeter
*/
public class SeleniumTestThread implements Runnable {
private Class extends SeleniumAbstractJavaSamplerClient> testClass;
private Map thisThreadParametersOverride;
public SeleniumTestThread(Class extends SeleniumAbstractJavaSamplerClient> testClass, Map thisThreadParametersOverride) {
this.testClass = testClass;
this.thisThreadParametersOverride = thisThreadParametersOverride;
}
public void run() {
SeleniumAbstractJavaSamplerClient testInstance = null;
try {
testInstance = testClass.getDeclaredConstructor().newInstance();
} catch (Exception e) { e.printStackTrace(); System.out.println(" Error " + e.getMessage() ); }
Arguments thisThreadParameterAuguments = Mark59Utils.mergeMapWithAnOverrideMap(getDefaultParameters().getArgumentsAsMap(), thisThreadParametersOverride);
JavaSamplerContext context = new JavaSamplerContext( thisThreadParameterAuguments );
testInstance.setupTest(context);
testInstance.runTest(context);
}
}
}