Skip to content

Commit

Permalink
Release 1.10-RC1 (#156)
Browse files Browse the repository at this point in the history
Update JustTestLahRunner to match current Cucumber version.
Fix broken initializing of page objects.
Avoid potential NPEs and improve logging.
Some quick fixes in justtestlah-demos.
Avoid using deprecated methods.
  • Loading branch information
martinschneider authored Jun 7, 2021
1 parent f50ff97 commit e946d48
Show file tree
Hide file tree
Showing 19 changed files with 168 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.amazonaws.services.devicefarm.model.ListDevicesResult;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
Expand All @@ -35,9 +36,11 @@ public class DeviceFilterFactoryTest {

@Mock private ListDevicesResult resultBusy;

private AutoCloseable mocks;

@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
mocks = MockitoAnnotations.openMocks(this);
when(awsService.getAws()).thenReturn(awsDeviceFarm);
when(properties.getProperty("platform")).thenReturn("android");
doReturn(resultHighlyAvailable)
Expand All @@ -59,6 +62,11 @@ public void setUp() {
target = new DeviceFilterFactory(properties, awsService);
}

@AfterEach
public void finish() throws Exception {
mocks.close();
}

@Test
public void testGetDeviceFilters() {
when(resultHighlyAvailable.getDevices()).thenReturn(List.of(new Device().withName("test1")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.nio.file.Paths;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.apache.maven.shared.invoker.PrintStreamHandler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
Expand All @@ -25,12 +27,23 @@ public class TestPackagerTest {

@Mock private PropertiesHolder properties;

private AutoCloseable mocks;

@BeforeEach
public void setup() {
mocks = MockitoAnnotations.openMocks(this);
}

@AfterEach
public void finish() throws Exception {
mocks.close();
}

@Test
public void testMavenPackaging()
throws MavenInvocationException, MalformedURLException, IOException,
ReflectiveOperationException {
String currentPath = Paths.get("").toFile().getAbsolutePath();
MockitoAnnotations.initMocks(this);
when(properties.getProperty("aws.demo.path")).thenReturn(currentPath);
when(properties.getProperty("aws.testpackage.name")).thenReturn("justtestlah-awsdevicefarm");
ByteArrayOutputStream logOutput = new ByteArrayOutputStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
Expand All @@ -19,9 +21,20 @@ public class TestSpecFactoryTest {

@Mock private PropertiesHolder properties;

private AutoCloseable mocks;

@BeforeEach
public void setup() {
mocks = MockitoAnnotations.openMocks(this);
}

@AfterEach
public void finish() throws Exception {
mocks.close();
}

@Test
public void testCreateTestSpec() throws IOException, URISyntaxException {
MockitoAnnotations.initMocks(this);
when(properties.getProperties()).thenReturn(new Properties());

List<String> expected =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.time.Clock;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -55,8 +54,13 @@
/**
* Custom JUnit runner to dynamically set Cucumber options. Based on {@link
* io.cucumber.junit.Cucumber}.
*
* <p>There are two reasons for this class: 1. setting Cucumber options at runtime and 2. running
* AWS Devicefarm tests as JUnit tests on the client side. 1. might no longer be necessary with the
* latest Cucumber version. If so, we should likely use a separate runner class for the AWS
* use-case.
*/
public class JustTestLahRunner extends ParentRunner<ParentRunner<?>> {
public final class JustTestLahRunner extends ParentRunner<ParentRunner<?>> {

private static final String CLOUDPROVIDER_AWS = "aws";
private static final String CLOUDPROVIDER_LOCAL = "local";
Expand All @@ -65,18 +69,17 @@ public class JustTestLahRunner extends ParentRunner<ParentRunner<?>> {

private static final Logger LOG = LoggerFactory.getLogger(JustTestLahRunner.class);

private List<ParentRunner<?>> children = new ArrayList<>();
private List<Feature> features = new ArrayList<>();
private Plugins plugins = null;
private EventBus bus = null;
private PropertiesHolder properties = new PropertiesHolder();
private List<ParentRunner<?>> children;
private EventBus bus;
private List<Feature> features;
private Plugins plugins;
private CucumberExecutionContext context;
private boolean multiThreadingAssumed = false;
private CucumberExecutionContext context = null;

private PropertiesHolder properties = new PropertiesHolder();
private static final String CLOUD_PROVIDER = "cloudprovider";
private static final String PLATFORM_KEY = "platform";
private static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";

private Runner awsRunner;

/**
Expand All @@ -96,7 +99,9 @@ public JustTestLahRunner(Class<?> clazz) throws InitializationError, IOException
bridgeLogging();

if (properties.getProperty(CLOUD_PROVIDER, CLOUDPROVIDER_LOCAL).equals(CLOUDPROVIDER_AWS)) {
CucumberOptionsBuilder.setCucumberOptions(properties);
LOG.info("Using qa.justtestlah.awsdevicefarm.AWSTestRunner");
initCucumber(clazz);
awsRunner = getAWSRunner(clazz);
} else {
CucumberOptionsBuilder.setCucumberOptions(properties);
Expand All @@ -107,8 +112,10 @@ public JustTestLahRunner(Class<?> clazz) throws InitializationError, IOException
/**
* This is the code taken from {@link io.cucumber.junit.Cucumber}
*
* @param clazz {@link Class}
* @throws InitializationError {@link InitializationError}
* <p>Constructor called by JUnit.
*
* @param clazz the class with the @RunWith annotation.
* @throws org.junit.runners.model.InitializationError if there is another problem
*/
private void initCucumber(Class<?> clazz) throws InitializationError {
Assertions.assertNoCucumberAnnotatedMethods(clazz);
Expand Down Expand Up @@ -168,7 +175,7 @@ private void initCucumber(Class<?> clazz) throws InitializationError {
this.plugins.addPlugin(exitStatus);

ObjectFactoryServiceLoader objectFactoryServiceLoader =
new ObjectFactoryServiceLoader(runtimeOptions);
new ObjectFactoryServiceLoader(classLoader, runtimeOptions);
ObjectFactorySupplier objectFactorySupplier =
new ThreadLocalObjectFactorySupplier(objectFactoryServiceLoader);
BackendSupplier backendSupplier =
Expand Down Expand Up @@ -197,7 +204,6 @@ private void initCucumber(Class<?> clazz) throws InitializationError {
})
.filter(runner -> !runner.isEmpty())
.collect(toList());

LOG.info(
"Found {} feature(s) in {}: {}",
features.size(),
Expand Down Expand Up @@ -259,6 +265,36 @@ public void evaluate() throws Throwable {
}
}

/** this method uses reflection to avoid a compile-time dependency on justtestlah-awsdevicefarm */
private Runner getAWSRunner(Class<?> clazz) {
try {
return (Runner)
Class.forName("qa.justtestlah.awsdevicefarm.AWSTestRunner")
.getConstructor(Class.class)
.newInstance(clazz);
} catch (InstantiationException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| NoSuchMethodException
| SecurityException
| ClassNotFoundException exception) {
LOG.error(
"Unable to create an instance of qa.justtestlah.awsdevicefarm.AWSTestRunner. Ensure justtestlah-aws is on your classpath (check your Maven pom.xml).",
exception);
}
return null;
}

@Override
public void run(RunNotifier notifier) {
if (properties.getProperty(CLOUD_PROVIDER, CLOUDPROVIDER_LOCAL).equals(CLOUDPROVIDER_AWS)) {
awsRunner.run(notifier);
} else {
super.run(notifier);
}
}

private void bridgeLogging() {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
Expand Down Expand Up @@ -294,34 +330,4 @@ public Description getDescription() {
return super.getDescription();
}
}

/** this method uses reflection to avoid a compile-time dependency on justtestlah-awsdevicefarm */
private Runner getAWSRunner(Class<?> clazz) {
try {
return (Runner)
Class.forName("qa.justtestlah.awsdevicefarm.AWSTestRunner")
.getConstructor(Class.class)
.newInstance(clazz);
} catch (InstantiationException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| NoSuchMethodException
| SecurityException
| ClassNotFoundException exception) {
LOG.error(
"Unable to create an instance of qa.justtestlah.awsdevicefarm.AWSTestRunner. Ensure justtestlah-aws is on your classpath (check your Maven pom.xml).",
exception);
}
return null;
}

@Override
public void run(RunNotifier notifier) {
if (properties.getProperty(CLOUD_PROVIDER, CLOUDPROVIDER_LOCAL).equals(CLOUDPROVIDER_AWS)) {
awsRunner.run(notifier);
} else {
super.run(notifier);
}
}
}
22 changes: 17 additions & 5 deletions justtestlah-core/src/main/java/qa/justtestlah/base/Base.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ public class Base implements ApplicationContextAware, InitializingBean {
public void afterPropertiesSet() {
initPages();
}

/**
* inject the page objects (without using @org.springframework.beans.factory.annotation.Autowired
* annotations)
* Recursively inject the page objects (without
* using @org.springframework.beans.factory.annotation.Autowired annotations)
*/
@SuppressWarnings("squid:S3011")
public void initPages() {
public synchronized void initPages() {
LOG.info("Initializing page objects for class {}", this.getClass());
Class<?> clazz = this.getClass();
while (clazz != Base.class) {
Expand All @@ -37,9 +38,20 @@ public void initPages() {
field.setAccessible(true);
try {
LOG.debug("Loading page object {} of type {}", field.getName(), field.getType());
field.set(this, applicationContext.getBean(field.getType()));
Object bean = applicationContext.getBean(field.getType());
if (bean != null) {
field.set(this, bean);
((Base) bean).initPages();
} else {
LOG.error(
"Couldn't inject non-existing page {} into {}. Skipping!",
field.getType().getSimpleName(),
this.getClass().getSimpleName());
}
} catch (BeansException | IllegalArgumentException | IllegalAccessException exception) {
LOG.error("Error initializing page objects for class {}", this.getClass());
LOG.error(
String.format("Error initializing page objects for class %s", this.getClass()),
exception);
LOG.error("Exception", exception);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.appium.java_client.HasSettings;
import io.appium.java_client.Setting;
import java.io.File;
import java.time.Duration;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
Expand Down Expand Up @@ -217,8 +218,8 @@ public T verify() {
*
* <p>Performs Galen checks, if enabled.
*
* @param timeout the timeout for identifying the first element. Note, that there is no timeout
* for any subsequent checks!
* @param timeout the timeout in milliseconds for identifying the first element. Note, that there
* is no timeout for any subsequent checks!
* @return this page object
*/
@SuppressWarnings("unchecked")
Expand All @@ -241,9 +242,9 @@ public T verify(int timeout) {
try {
// only use the timeout for the first check
if (initialCheck) {
$(identifier).waitUntil(appear, timeout).isDisplayed();
$(identifier).shouldBe(appear, Duration.ofMillis(timeout)).isDisplayed();
} else {
$(identifier).waitUntil(appear, 0).isDisplayed();
$(identifier).shouldBe(appear, Duration.ZERO).isDisplayed();
}
initialCheck = false;
} catch (ElementNotFound exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ public class CucumberOptionsBuilder {

private static final String PLATFORM_KEY = "platform";
private static final String FEATURES_DIRECTORY_KEY = "features.directory";
private static final String CUCUMBER_REPORT_DIRECTORY_KEY = "cucumber.report.directory";
private static final String JUSTTESTLAH_SPRING_CONTEXT_KEY = "justtestlah.use.springcontext";
private static final String DEFAULT_CUCUMBER_REPORT_DIRECTORY = "target/report/cucumber";
private static final String DEFAULT_PLATFORM = "web";
private static final String DELIMITER = ",";
private static final String TAGS_KEY = "tags";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public void receive(TestCaseStarted event) {
"Scenario: {} ({}:{})",
testCase.getName(),
testCase.getUri(),
// TODO: getLine() has been deprecated. Once this gets removed from Cucumber, we
// will
// simply drop it from the log too
testCase.getLine());
}
};
Expand Down Expand Up @@ -66,6 +69,10 @@ public void receive(TestCaseFinished event) {
event.getTestCase().getName(),
result.getDuration().toSeconds());
} else {
String errorMsg = error.getMessage();
if (errorMsg != null) {
errorMsg = errorMsg.replaceAll("[\\t\\n\\r]+", " ");
}
SpringContext.getBean(TestLogWriter.class)
.log(
LogLevel.INFO,
Expand All @@ -74,7 +81,7 @@ public void receive(TestCaseFinished event) {
result.getStatus(),
event.getTestCase().getName(),
result.getDuration().toSeconds(),
error.getMessage().replaceAll("[\\t\\n\\r]+", " "));
errorMsg);
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private void initializeTestDataObjectRegistry() {
LOG.info("Scanning classpath for test data classes");
ClassGraph classGraph = new ClassGraph().enableAnnotationInfo();
if (modelPackage != null && !modelPackage.isEmpty()) {
classGraph = classGraph.whitelistPackages(modelPackage);
classGraph = classGraph.acceptPackages(modelPackage);
}
try (ScanResult scanResult = classGraph.scan()) {
for (ClassInfo routeClassInfo :
Expand All @@ -112,7 +112,12 @@ private void initializeTestDataObjectRegistry() {
}

public <T> T get(Class<T> type, String name) {
return (T) testData.get(type).get(name);
try {
return (T) testData.get(type).get(name);
} catch (NullPointerException e) {
throw new RuntimeException(
String.format("Error fetching test data. Test data map: %s", testData), e);
}
}

public <T> T get(Class<T> type) {
Expand Down
Loading

0 comments on commit e946d48

Please sign in to comment.