Windows, Gradle and Cucumber combination throws an IOException upon generating reports - java

I'm working on a project which uses a combination of Windows, Java, Groovy, Gradle and Cucumber. This combination gives me some problems on my Windows machine that my *NIX colleagues are not experiencing. Upon running the gradle build, gradle wants to output some reports. The location and filename of these reports is apparently determined by the definition or output of the Cucumber tests. The name used is unfortunately not something that can be used as a filename, so I'm getting an IOException for each test report.
For the Cucumber test, we use the following structure:
Scenario Outline: Receive and parse ReturnItem from Service
Given The message from service return item outlined in <messagePath>
When We process the message
Then XXX posted a message to YYY on topic <topic> with event <eventType>
And payload matches <resultPath>
| messagePath | topic | eventType | resultPath |
| /test/testxml.xml | test_topic | EVENT_TYPE | /result/result.json |
After running this, I receive the following exception:
Caused by: org.gradle.api.UncheckedIOException: Could not write to file 'C:\xxx\project\build\reports\tests\test\packages\| \test\testxml.xml | test_topic | EVENT_TYPE | \result\result.html'.
at org.gradle.internal.IoActions$TextFileWriterIoAction.execute(IoActions.java:151)
at org.gradle.internal.IoActions$TextFileWriterIoAction.execute(IoActions.java:127)
at org.gradle.internal.IoActions.writeTextFile(IoActions.java:45)
at org.gradle.reporting.HtmlReportRenderer$DefaultHtmlReportContext.renderHtmlPage(HtmlReportRenderer.java:118)
at org.gradle.api.internal.tasks.testing.report.DefaultTestReport$HtmlReportFileGenerator.run(DefaultTestReport.java:147)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
at org.gradle.internal.operations.DefaultBuildOperationExecutor.access$900(DefaultBuildOperationExecutor.java:48)
at org.gradle.internal.operations.DefaultBuildOperationExecutor$ParentPreservingQueueWorker.execute(DefaultBuildOperationExecutor.java:342)
at org.gradle.internal.operations.DefaultBuildOperationQueue$WorkerRunnable.runOperation(DefaultBuildOperationQueue.java:230)
at org.gradle.internal.operations.DefaultBuildOperationQueue$WorkerRunnable.access$600(DefaultBuildOperationQueue.java:172)
at org.gradle.internal.operations.DefaultBuildOperationQueue$WorkerRunnable$1.call(DefaultBuildOperationQueue.java:209)
at org.gradle.internal.operations.DefaultBuildOperationQueue$WorkerRunnable$1.call(DefaultBuildOperationQueue.java:203)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:154)
at org.gradle.internal.operations.DefaultBuildOperationQueue$WorkerRunnable.runBatch(DefaultBuildOperationQueue.java:203)
at org.gradle.internal.operations.DefaultBuildOperationQueue$WorkerRunnable.run(DefaultBuildOperationQueue.java:177)
... 3 more
Caused by: java.io.IOException: Unable to create directory 'C:\xxx\project\project-test\build\reports\tests\test\packages\| \test\testxml.xml | test_topic | EVENT_TYPE | \result'
at org.gradle.internal.IoActions$TextFileWriterIoAction.execute(IoActions.java:141)
... 19 more
Does anybody know how to fix this? The only 'solution' I could come up with is disabling the reports, which works, but is more a workaround than a solution. For disabling I used the following configuration in the gradle.build for this:
apply plugin: 'java'
apply plugin: 'groovy'
test {
reports {
junitXml.enabled = false
html.enabled = false
}
}
(Inspired by: How to make Gradle build produce HTML test report instead of XML default?)

I finally found the culprit. Apparently these filenames correspond to the default behaviour of JUnit for report generation of Cucumber tests. On *NIX, this doesn't provide any problem. On Windows however, this will result in an exception due to the pipes in the Examples. The IOException is somewhat special apparently, since the most exceptions that I found on the internet were FileNotFoundExceptions. That explains why it took me so long to find an answer, I focused on the exception.
The solution here is to use the following JUnitOption as an #CucumberOptions annotation when running the Cucumber tests: --filename-compatible-names.
A code example for Java & Spring looks like this:
#RunWith(Cucumber.class)
#CucumberOptions(junit = {"--filename-compatible-names"})
public class CucumberRunner {
}
It would be nice if these kind of non-breaking OS dependent options would be default instead of optional.

Based upon the information provided, it looks like it's trying to create a directory called:
'C:\xxx\project\project-test\build\reports\tests\test\packages\| \test\testxml.xml | test_topic | EVENT_TYPE | \result'
Can you show the code around passing in messagePath? I suspect you are passing in the entire row of data rather than just the messagePath (I'm going to take a wild guess that you are performing a .toString() on an array instead of passing in the first element of the array)

Related

Parallel testing with cucumber and Gradle

I keep finding posts and articles on how to run parallel tests with Cucumber and Maven, but nothing with Gradle.
I have the following scenario:
Scenario Outline: Author from API matches author on book page
Given user goes on google.com
when user search <item>
and click on <locator>
Then something happens
Examples:
| item | locator |
| item1 | locator1|
| item2 | locator2|
| item3 | locator3|
Is it possible, with Gradle, to run this scenario in parallel, each execution using the values in "Examples" table?
Thanks
It is possible! But you have to apply some workarounds.
plugins {
java
}
dependencies {
testImplementation(platform("org.junit:junit-bom:5.9.2"))
testImplementation(platform("io.cucumber:cucumber-bom:7.11.1"))
testImplementation("io.cucumber:cucumber-java")
testImplementation("io.cucumber:cucumber-junit-platform-engine")
testImplementation("org.junit.platform:junit-platform-suite")
testImplementation("org.junit.jupiter:junit-jupiter")
}
repositories {
mavenLocal()
mavenCentral()
}
tasks.withType<Test> {
useJUnitPlatform()
// Work around. Gradle does not include enough information to disambiguate
// between different examples and scenarios.
systemProperty("cucumber.junit-platform.naming-strategy", "long")
}
Because Gradle provides rather limited support for tests that aren't a in class file you have to use the junit-platform-suite to declare a suite.
package io.cucumber.skeleton;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
#Suite
#IncludeEngines("cucumber")
#SelectClasspathResource("io/cucumber/skeleton")
#ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "io.cucumber.skeleton")
public class RunCucumberTest {
}
This suite will target the cucumber TestEngine from cucumber-junit-platform-engine. The cucumber-java-skeleton contains a working (serial) example.
To make the tests execute in parallel you can add to junit-platform.properties:
cucumber.execution.parallel.enabled=true
That is it. For more config details check the JUnit 5 and Cucumber JUnit Platform Engine documentation.

Getting a "Using type x from an indirect dependency" in java_test_suite even when x is declared under dependency

My CustomTest.java has this import:
com.google.protobuf.Timestamp
I'm using java_test_suite to run tests in my BUILD file like so:
java_test_suite(
name = "all-tests",
srcs = glob(["src/test/java/**/*.java"]),
runner = "junit5",
test_suffixes = ["Test.java"],
runtime_deps = JUNIT5_DEPS,
deps = [
":mylib",
"#com_google_protobuf//:timestamp_proto",
artifact("org.junit.jupiter:junit-jupiter-api"),
artifact("org.junit.jupiter:junit-jupiter-params"),
] + deps,
)
However when I run tests on it using:
bazel test //:all-tests
I'm getting this error:
src/test/java/com/x/CustomTest.java:75: error: [strict] Using type com.google.protobuf.Timestamp from an indirect dependency (TOOL_INFO: "#com_google_protobuf//:timestamp_proto wrapped in java_proto_library"). See command below **
private static Timestamp timestampFromMilli(long milli) {
^
** Please add the following dependencies:
#com_google_protobuf//:timestamp_proto to //:src/test/java/com/x/CustomTest
** You can use the following buildozer command:
buildozer 'add deps #com_google_protobuf//:timestamp_proto' //:src/test/java/com/x/CustomTest
What do I need to do exactly? I tried using the buildozer command but all I got was:
rule 'src/test/java/com/x/CustomTest' not found
Where do I need to add this #com_google_protobuf//:timestamp_proto?
Looking at protobuf's build files, it looks like timestamp_proto is a plain proto_library:
https://github.com/protocolbuffers/protobuf/blob/main/BUILD.bazel#L70-L74
https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/BUILD.bazel#L64-L68
and so per the advice here:
https://github.com/protocolbuffers/protobuf/blob/main/BUILD.bazel#L19-L25
you might just need to use java_proto_library to make the java version of the proto:
java_proto_library(
name = "timestamp_java_proto",
deps = ["#com_google_protobuf//:timestamp_proto"],
)
and then use that in the deps of your java_test_suite instead of the timestamp_proto.
Just a guess, but the error message is not very helpful maybe because there happens to be a Timestamp java class in the deps of the plain proto library, and Strict deps is finding that one in the test's indirect dependencies. Might be worth filing a bug about it on https://github.com/bazelbuild/bazel/issues

Karate karate-config.js not a js function

I'm trying use karate for e2e tests and have started with a minimal setup. I want to create some config items in karate-config.js for use in the tests but karate is reporting that file is not a js function and hence the test fails trying to get the config:
Warning: Nashorn engine is planned to be removed from a future JDK release
12:16:35.264 [Test worker] WARN com.intuit.karate - not a js function or feature file: read('classpath:karate-config.js') - [type: NULL, value: null]
---------------------------------------------------------
feature: classpath:karate/insurer.feature
scenarios: 1 | passed: 0 | failed: 1 | time: 0.0163
---------------------------------------------------------
HTML report: (paste into browser to view) | Karate version: 0.9.1
file:/Users/srowatt/dev/repos/api/price-service/build/surefire-reports/karate.insurer.html
---------------------------------------------------------
-unknown-:4 - javascript evaluation failed: priceBaseUrl, ReferenceError: "priceBaseUrl" is not defined in <eval> at line number 1
org.opentest4j.AssertionFailedError: -unknown-:4 - javascript evaluation failed: priceBaseUrl, ReferenceError: "priceBaseUrl" is not defined in <eval> at line number 1
This is my karate-config.js:
function fn() {
return {
priceBaseUrl: "http://localhost:8080"
};
}
This is my insurer.feature test:
Feature: which creates insurers
Background:
* url priceBaseUrl
* configure logPrettyRequest = true
* configure logPrettyResponse = true
Scenario: basic roundtrip
# create a new insurer
Given path 'insurers'
And request { name: 'Sammy Insurance', companyCode: '99' }
When method post
Then status 201
And match response == { resourceId: '#number', version: 0, createdBy: 'anonymousUser' }
* def insurerId = response.resourceId
# get insurer by resource id
Given path 'insurers', insurerId
When method get
Then status 200
And match response == { id: '#(id)', name: 'Sammy Insurance', companyCode: '99' }
This is the InsurerTest.java test runner:
package karate;
import com.intuit.karate.junit5.Karate;
class InsurerTest {
#Karate.Test
public Karate testInsurer() {
return new Karate().feature("classpath:karate/insurer.feature");
}
}
Please use below code in the karate-config.js
function() {
return priceBaseUrl='http://localhost:8080';
}
When I see this:
Warning: Nashorn engine is planned to be removed from a future JDK release
I suspect you are on Java 9 or 11 ? To be honest, we haven't fully tested Karate on those versions of Java yet. Would it be possible for you to confirm that Java 8 (maybe 9 / 10 also) is OK.
That said, we are interested in resolving this as soon as possible, so if you can submit a sample project where we can replicate this, please do so: https://github.com/intuit/karate/wiki/How-to-Submit-an-Issue
EDIT: Karate 1.0 will use GraalVM instead of Nashorn and will run on even JDK 16: https://software-that-matters.com/2021/01/27/7-new-features-in-karate-test-automation-version-1_0/

python-behave:feature file : xpath issue

in feature file when I want to pass multiple condition in xpath then I am getting selenium.common.exceptions.InvalidSelectorException
Feature: showing off behave
Scenario Outline: run a simple test
Given we have behave installed <thing> and <good>
And Enter value in google <x_path> and <g_val>
Examples:
| thing | good | x_path | g_val |
| Red Tree Frog | Hello Automation | //input[#name='q' and #class='gsfi'] | hello world |
The StepDefination code is as below -
from behave import given
from selenium import webdriver
driver = webdriver.Ie("C:\\Users\\ksahu\\Downloads\\IEDriverServer_x64_3.3.0\\IEDriverServer.exe")
driver.implicitly_wait(15)
#given(u'we have behave installed {thing} and {good}')
def step_impl(context,thing,good):
print('============> '+thing+'===========> '+good)
#given(u'Enter value in google {x_pth} and {g_val}')
def step_impl(context,x_pth,g_val):
driver.get("http://www.google.com")
driver.maximize_window()
print(x_pth)
#driver.find_element_by_name(x_pth).send_keys(g_val)
driver.find_element_by_xpath(x_pth).send_keys(g_val)
The Exception is as below -
selenium.common.exceptions.InvalidSelectorException: Message: Unable to locate an element with the xpath expression //input[#name='q'\ and\ #class='gsfi'] because of the following error:
Error: Bad token, expected: ] got: \
Captured stdout:
=============> Red Tree Frog==========> Hello Automation
//input[#name='q'
The SPACE is not allowing in xpath.
Please let me know how to pass multiple condition in xpath in feature file
One quick workaround would be to specify multiple conditions this way:
//input[#name='q'][#class='gsfi']
Or, I think, putting the placeholder into backticks should solve the space escaping issue:
#given(u'Enter value in google `{x_pth}` and {g_val}')

Environment variables not detected when deploying to AppFog

It seems that when deploying my Spring app to AppFog, that the environment variables are not being detected.
I have the environment variables declared in my console:
And I try to reference these from within my app code, like so:
<context:property-placeholder
location="classpath:server.common.properties,
classpath:server.${concorde-env}.properties"/>
However, this generates an error:
Caused by: java.io.FileNotFoundException: class path resource
[server.${concorde-env}.properties] cannot be opened because it does
not exist
This approach works fine in other (non AppFog) environments.
I logged out the properties by calling:
log.info("Properties: " + System.getProperties().toString());
And it doesn't show those properties as available.
However, if I do a af env <<MY_APP_NAME>>, it shows the variables as present:
+--------------------------+------------------+
| Variable | Value |
+--------------------------+------------------+
| concorde-env | test |
| spring.profiles.active | runtime,test |
+--------------------------+------------------+
What am I missing to make these variables exposed to my app at runtime?
Try accessing the value like this: System.getenv("concorde-env") to see if the environment var can even be accessed in code.
Based on the error message "class path resource [server.${concorde-env}.properties] cannot be opened because it does not exist" it seems like ${concorde-env} is not even being evaluated or replaced even with empty string.
It looks like Spring has other ways of accessing env vars. Try #{systemEnvironment['concorde-env']} instead of ${concorde-env}

Categories