Skip to content

Commit 3ae2a20

Browse files
committed
Include retries tests for JUnit and TestNG
1 parent 0c4717f commit 3ae2a20

File tree

6 files changed

+251
-0
lines changed

6 files changed

+251
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* (C) Copyright 2025 Boni Garcia (https://bonigarcia.github.io/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.github.bonigarcia.junit.selenium;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.openqa.selenium.By;
24+
25+
@ExtendWith(RetryExtension.class)
26+
class RandomCalculatorJUnitTest extends BrowserParent {
27+
28+
@Test
29+
@Retry(4)
30+
void testRandomCalculator() {
31+
driver.get(
32+
"https://bonigarcia.dev/selenium-webdriver-java/random-calculator.html");
33+
// 1 + 3
34+
driver.findElement(By.xpath("//span[text()='1']")).click();
35+
driver.findElement(By.xpath("//span[text()='+']")).click();
36+
driver.findElement(By.xpath("//span[text()='3']")).click();
37+
driver.findElement(By.xpath("//span[text()='=']")).click();
38+
39+
// ... should be 4
40+
String result = driver.findElement(By.className("screen")).getText();
41+
assertThat(result).isEqualTo("4");
42+
}
43+
44+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* (C) Copyright 2025 Boni Garcia (https://bonigarcia.github.io/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.github.bonigarcia.junit.selenium;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
@Retention(RetentionPolicy.RUNTIME)
23+
public @interface Retry {
24+
int value() default 3;
25+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* (C) Copyright 2025 Boni Garcia (https://bonigarcia.github.io/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.github.bonigarcia.junit.selenium;
18+
19+
import java.util.concurrent.atomic.AtomicInteger;
20+
21+
import org.junit.jupiter.api.extension.ExtensionContext;
22+
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
23+
24+
public class RetryExtension implements TestExecutionExceptionHandler {
25+
26+
static final int DEFAULT_MAX_RETRIES = 3;
27+
28+
final AtomicInteger retryCount = new AtomicInteger(1);
29+
30+
@Override
31+
public void handleTestExecutionException(ExtensionContext extensionContext,
32+
Throwable throwable) throws Throwable {
33+
logError(throwable);
34+
35+
extensionContext.getTestMethod().ifPresent(method -> {
36+
int maxRetries = method.getAnnotation(Retry.class) != null
37+
? method.getAnnotation(Retry.class).value()
38+
: DEFAULT_MAX_RETRIES;
39+
40+
while (retryCount.incrementAndGet() <= maxRetries) {
41+
try {
42+
extensionContext.getExecutableInvoker().invoke(method,
43+
extensionContext.getRequiredTestInstance());
44+
return;
45+
} catch (Throwable t) {
46+
logError(t);
47+
48+
if (retryCount.get() >= maxRetries) {
49+
throw t;
50+
}
51+
}
52+
}
53+
});
54+
}
55+
56+
private void logError(Throwable e) {
57+
System.err.println("Attempt test execution #" + retryCount.get()
58+
+ " failed (" + e.getClass().getName() + "thrown): "
59+
+ e.getMessage());
60+
}
61+
62+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* (C) Copyright 2025 Boni Garcia (https://bonigarcia.github.io/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.github.bonigarcia.testng.selenium;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import org.openqa.selenium.By;
22+
import org.testng.annotations.Test;
23+
24+
public class RandomCalculatorNGTest extends BrowserParent {
25+
26+
@Test(retryAnalyzer = RetryAnalyzer.class)
27+
@Retry(5)
28+
public void testRandomCalculator() {
29+
driver.get(
30+
"https://bonigarcia.dev/selenium-webdriver-java/random-calculator.html");
31+
// 1 + 3
32+
driver.findElement(By.xpath("//span[text()='1']")).click();
33+
driver.findElement(By.xpath("//span[text()='+']")).click();
34+
driver.findElement(By.xpath("//span[text()='3']")).click();
35+
driver.findElement(By.xpath("//span[text()='=']")).click();
36+
37+
// ... should be 4
38+
String result = driver.findElement(By.className("screen")).getText();
39+
assertThat(result).isEqualTo("4");
40+
}
41+
42+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* (C) Copyright 2025 Boni Garcia (https://bonigarcia.github.io/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.github.bonigarcia.testng.selenium;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
@Retention(RetentionPolicy.RUNTIME)
23+
public @interface Retry {
24+
int value() default 3;
25+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* (C) Copyright 2021 Boni Garcia (https://bonigarcia.github.io/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.github.bonigarcia.testng.selenium;
18+
19+
import java.lang.reflect.Method;
20+
import java.util.concurrent.atomic.AtomicInteger;
21+
22+
import org.testng.IRetryAnalyzer;
23+
import org.testng.ITestResult;
24+
25+
public class RetryAnalyzer implements IRetryAnalyzer {
26+
27+
static final int DEFAULT_MAX_RETRIES = 3;
28+
29+
final AtomicInteger retryCount = new AtomicInteger(1);
30+
31+
@Override
32+
public boolean retry(ITestResult result) {
33+
Method method = result.getMethod().getConstructorOrMethod().getMethod();
34+
int maxRetries = DEFAULT_MAX_RETRIES;
35+
if (method.isAnnotationPresent(Retry.class)) {
36+
Retry retry = method.getAnnotation(Retry.class);
37+
maxRetries = retry.value();
38+
}
39+
if (retryCount.get() <= maxRetries) {
40+
logError(result.getThrowable());
41+
retryCount.incrementAndGet();
42+
return true;
43+
}
44+
return false;
45+
}
46+
47+
private void logError(Throwable e) {
48+
System.err.println("Attempt test execution #" + retryCount.get()
49+
+ " failed (" + e.getClass().getName() + "thrown): "
50+
+ e.getMessage());
51+
}
52+
53+
}

0 commit comments

Comments
 (0)