Repository navigation
Expand file tree
/
Copy pathbuild.gradle
More file actions
306 lines (270 loc) · 9.08 KB
/
Copy pathbuild.gradle
File metadata and controls
306 lines (270 loc) · 9.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
defaultTasks 'clean','test'
buildscript {
repositories {
mavenLocal()
gradlePluginPortal()
mavenCentral()
}
dependencies {
classpath 'net.masterthought:cucumber-reporting:5.12.0'
}
}
apply plugin: 'java'
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
testImplementation libs.bundles.cucumber
testImplementation libs.bundles.apache.commons
testImplementation libs.bundles.appium
testImplementation libs.bundles.selenium
testImplementation libs.bundles.http.client
testImplementation libs.bundles.jackson
testImplementation libs.bundles.json
testImplementation libs.bundles.assertions
testImplementation libs.bundles.logging
testImplementation libs.bundles.utilities
}
/**
* Cucumber report paths.
*
* JSON reports:
* target/cucumber-reports/json/
*
* Cucumber rerun files:
* target/cucumber-reports/rerun/
*/
def cucumberReportsDir = file("target/cucumber-reports")
def cucumberJsonDir = file("${cucumberReportsDir}/json")
def cucumberRerunDir = file("${cucumberReportsDir}/rerun")
/**
* Gradle HTML report paths.
*
* Main execution:
* target/gradle-test-reports/main/
*
* First rerun:
* target/gradle-test-reports/rerun-1/
*
* Second rerun:
* target/gradle-test-reports/rerun-2/
*/
def gradleTestReportsDir = file("target/gradle-test-reports")
/**
* Common system properties for all Cucumber/Appium executions.
*
* These values are read from the Gradle JVM system properties and forwarded
* to each Test task JVM.
*/
def configureCommonCucumberSystemProperties = { Test task ->
task.systemProperty "cucumber.filter.tags", System.getProperty("cucumber.filter.tags")
task.systemProperty "appium", System.getProperty("appium")
task.systemProperty "device", System.getProperty("device")
task.systemProperty "udid", System.getProperty("udid")
task.systemProperty "server", System.getProperty("server")
task.systemProperty "host", System.getProperty("host")
task.systemProperty "backend", System.getProperty("backend")
}
/**
* Configures a Cucumber Test task.
*
* Parameters:
* - task: Gradle Test task to configure.
* - features: feature directory or Cucumber rerun file.
* - jsonFileName: output Cucumber JSON report file name.
* - rerunOutputFileName: optional Cucumber rerun output file name.
* - ignoreFailuresValue: whether this task should allow the build to continue after failures.
*
* Important for Gradle 9:
* Custom Test tasks such as cucumberRerun1 and cucumberRerun2 must explicitly
* declare the compiled test classes and runtime classpath. Otherwise Gradle may
* consider them NO-SOURCE and skip them, even when the Cucumber rerun file exists.
*/
def configureCucumberTask = { Test task, String features, String jsonFileName, String rerunOutputFileName = null,
boolean ignoreFailuresValue = false ->
task.useJUnit()
/*
* Gradle 9 compatibility.
*
* The default 'test' task already knows its test classes and classpath, but
* custom Test tasks do not safely inherit those values anymore. Setting them
* explicitly prevents cucumberRerun1/cucumberRerun2 from being skipped as
* NO-SOURCE after the main execution generates a rerun file.
*/
task.include '**/RunCucumberTest.class'
task.testClassesDirs = sourceSets.test.output.classesDirs
task.classpath = sourceSets.test.runtimeClasspath
/*
* Cucumber is launched through the JUnit runner and receives the feature
* location through cucumber.features. Disabling class scanning avoids Gradle
* trying to discover test classes on its own.
*/
task.scanForTestClasses = false
task.testLogging.showStandardStreams = true
task.ignoreFailures = ignoreFailuresValue
task.systemProperty "cucumber.features", features
def plugins = [
"pretty",
"json:${cucumberJsonDir}/${jsonFileName}"
]
if (rerunOutputFileName != null) {
plugins << "rerun:${cucumberRerunDir}/${rerunOutputFileName}"
}
task.systemProperty "cucumber.plugin", plugins.join(",")
configureCommonCucumberSystemProperties(task)
}
/**
* Main execution.
*
* Runs the full Cucumber suite.
*
* This task is allowed to fail without failing the build because failed
* scenarios are written to rerun.txt and retried by cucumberRerun1.
*
* Generates:
* - target/cucumber-reports/json/cucumber-main.json
* - target/cucumber-reports/rerun/rerun.txt
* - target/gradle-test-reports/main/index.html
*/
test {
doFirst {
delete cucumberReportsDir
delete gradleTestReportsDir
cucumberJsonDir.mkdirs()
cucumberRerunDir.mkdirs()
}
configureCucumberTask(
delegate,
"src/test/resources/io/cucumber",
"cucumber-main.json",
"rerun.txt",
true
)
reports {
html.required.set(true)
html.outputLocation.set(file("${gradleTestReportsDir}/main"))
junitXml.required.set(false)
}
}
/**
* First rerun.
*
* Runs only if the main execution generated a non-empty rerun.txt file.
* That file is produced by Cucumber and contains the scenarios that failed
* in the main execution.
*
* This task is allowed to fail without failing the build because remaining
* failed scenarios are written to rerun-1.txt and retried by cucumberRerun2.
*
* Generates:
* - target/cucumber-reports/json/cucumber-rerun-1.json
* - target/cucumber-reports/rerun/rerun-1.txt
* - target/gradle-test-reports/rerun-1/index.html
*/
tasks.register('cucumberRerun1', Test) {
onlyIf {
def rerunFile = file("${cucumberRerunDir}/rerun.txt")
rerunFile.exists() && rerunFile.text.trim()
}
configureCucumberTask(
delegate,
"@${cucumberRerunDir}/rerun.txt",
"cucumber-rerun-1.json",
"rerun-1.txt",
true
)
reports {
html.required.set(true)
html.outputLocation.set(file("${gradleTestReportsDir}/rerun-1"))
junitXml.required.set(false)
}
}
/**
* Second rerun.
*
* Runs only if cucumberRerun1 generated a non-empty rerun-1.txt file.
* That file contains the scenarios that failed again in the first rerun.
*
* This task is NOT allowed to ignore failures. If any scenario still fails here,
* the whole build fails.
*
* Generates:
* - target/cucumber-reports/json/cucumber-rerun-2.json
* - target/gradle-test-reports/rerun-2/index.html
*/
tasks.register('cucumberRerun2', Test) {
onlyIf {
def rerunFile = file("${cucumberRerunDir}/rerun-1.txt")
rerunFile.exists() && rerunFile.text.trim()
}
configureCucumberTask(
delegate,
"@${cucumberRerunDir}/rerun-1.txt",
"cucumber-rerun-2.json",
null,
false
)
reports {
html.required.set(true)
html.outputLocation.set(file("${gradleTestReportsDir}/rerun-2"))
junitXml.required.set(false)
}
}
/**
* Execution chain.
*
* A single './gradlew clean test' may execute:
*
* 1. test
* Runs the full suite.
*
* 2. cucumberRerun1
* Runs only scenarios that failed in the main execution, if any.
*
* 3. cucumberRerun2
* Runs only scenarios that failed again in cucumberRerun1, if any.
*
* Failure policy:
* - test may fail without failing the build, because cucumberRerun1 retries failed scenarios.
* - cucumberRerun1 may fail without failing the build, because cucumberRerun2 retries remaining failures.
* - cucumberRerun2 does not ignore failures. If any scenario still fails here, the build fails.
*
* Therefore, the build only fails if a scenario fails in the main execution
* and keeps failing after all configured reruns.
*
* The chain is expressed with finalizedBy so that reruns are attempted after
* the previous execution, even when that previous execution contains failing tests.
*/
test.finalizedBy cucumberRerun1
cucumberRerun1.finalizedBy cucumberRerun2
cucumberRerun1.mustRunAfter test
cucumberRerun2.mustRunAfter cucumberRerun1
/**
* HTML report.
*
* Reads all Cucumber JSON files produced by test + reruns and generates
* an interactive HTML report under target/cucumber-html-report/.
*
* Entry point: target/cucumber-html-report/overview-features.html
*/
tasks.register('cucumberReport') {
mustRunAfter cucumberRerun2
doLast {
def jsonFiles = fileTree(cucumberJsonDir)
.matching { include '*.json' }
.files
.collect { it.absolutePath }
if (jsonFiles.isEmpty()) {
logger.warn('No Cucumber JSON files found — skipping HTML report generation.')
return
}
def reportDir = file('target/cucumber-html-report')
reportDir.mkdirs()
def config = new net.masterthought.cucumber.Configuration(reportDir, 'ownCloud Android E2E')
config.addClassifications('Platform', 'Android')
new net.masterthought.cucumber.ReportBuilder(jsonFiles, config).generateReports()
logger.lifecycle("Cucumber HTML report → file://${reportDir}/cucumber-html-reports/overview-features.html")
}
}
cucumberRerun2.finalizedBy cucumberReport