diff --git a/pom.xml b/pom.xml
index d21681006..1d8b4bc46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,7 +39,7 @@
${maven.compiler.release}
4.0.4
25.2-SNAPSHOT
- 25.2-SNAPSHOT
+ 25.2.when-ready-SNAPSHOT
2.0.7
12.1.3
3.5.2
diff --git a/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java b/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java
index 188865b82..7389664b3 100644
--- a/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java
+++ b/vaadin-testbench-core-junit5/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java
@@ -11,6 +11,7 @@
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
+import java.time.Duration;
import java.util.Arrays;
import org.junit.jupiter.api.Assertions;
@@ -213,9 +214,7 @@ private WebDriver mockScreenshotDriver(int nrScreenshotsGrabbed,
"cursor-bottom-edge-off.png");
Mockito.when(driver.getScreenshotAs(OutputType.BYTES))
.thenReturn(screenshotBytes);
- Mockito.when(
- driver.executeScript(Mockito.contains("window.Vaadin.Flow")))
- .thenReturn(Boolean.TRUE);
+ mockWaitForVaadin(driver);
if (expectGetCapabilities) {
Capabilities mockedCapabilities = Mockito.mock(Capabilities.class);
Mockito.when(mockedCapabilities.getBrowserName())
@@ -283,6 +282,7 @@ public void testTotalTimeSpentServicingRequests() {
private FirefoxDriver mockJSExecutor(boolean forcesSync) {
FirefoxDriver jse = Mockito.mock(FirefoxDriver.class);
+ mockWaitForVaadin(jse);
Mockito.when(jse
.executeScript(Mockito.contains("window.Vaadin.Flow.client")))
.thenReturn(Boolean.TRUE);
@@ -290,4 +290,21 @@ private FirefoxDriver mockJSExecutor(boolean forcesSync) {
.thenReturn(Arrays.asList(1000L, 2000L, 3000L));
return jse;
}
+
+ private void mockWaitForVaadin(RemoteWebDriver driver) {
+ WebDriver.Options options = Mockito.mock(WebDriver.Options.class);
+ WebDriver.Timeouts timeouts = Mockito.mock(WebDriver.Timeouts.class);
+ Mockito.when(driver.manage()).thenReturn(options);
+ Mockito.when(options.timeouts()).thenReturn(timeouts);
+ Mockito.when(timeouts.getScriptTimeout())
+ .thenReturn(Duration.ofSeconds(30));
+ Mockito.when(timeouts.scriptTimeout(Mockito.any(Duration.class)))
+ .thenReturn(timeouts);
+ // Phase 1: sync check for Flow.ready returns true immediately
+ Mockito.when(driver.executeScript(
+ Mockito.contains("typeof window.Vaadin.Flow.ready")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(driver.executeAsyncScript(Mockito.contains("Flow.ready"),
+ Mockito.anyLong())).thenReturn(null);
+ }
}
diff --git a/vaadin-testbench-core/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java b/vaadin-testbench-core/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java
index 68ea1514b..d415f6182 100644
--- a/vaadin-testbench-core/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java
+++ b/vaadin-testbench-core/src/test/java/com/vaadin/testbench/commands/TestBenchCommandExecutorTest.java
@@ -11,6 +11,7 @@
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
+import java.time.Duration;
import java.util.Arrays;
import org.junit.Before;
@@ -216,9 +217,7 @@ private WebDriver mockScreenshotDriver(int nrScreenshotsGrabbed,
"cursor-bottom-edge-off.png");
Mockito.when(driver.getScreenshotAs(OutputType.BYTES))
.thenReturn(screenshotBytes);
- Mockito.when(
- driver.executeScript(Mockito.contains("window.Vaadin.Flow")))
- .thenReturn(Boolean.TRUE);
+ mockWaitForVaadin(driver);
if (expectGetCapabilities) {
Capabilities mockedCapabilities = Mockito.mock(Capabilities.class);
Mockito.when(mockedCapabilities.getBrowserName())
@@ -286,6 +285,7 @@ public void testTotalTimeSpentServicingRequests() {
private FirefoxDriver mockJSExecutor(boolean forcesSync) {
FirefoxDriver jse = Mockito.mock(FirefoxDriver.class);
+ mockWaitForVaadin(jse);
Mockito.when(jse
.executeScript(Mockito.contains("window.Vaadin.Flow.client")))
.thenReturn(Boolean.TRUE);
@@ -293,4 +293,21 @@ private FirefoxDriver mockJSExecutor(boolean forcesSync) {
.thenReturn(Arrays.asList(1000L, 2000L, 3000L));
return jse;
}
+
+ private void mockWaitForVaadin(RemoteWebDriver driver) {
+ WebDriver.Options options = Mockito.mock(WebDriver.Options.class);
+ WebDriver.Timeouts timeouts = Mockito.mock(WebDriver.Timeouts.class);
+ Mockito.when(driver.manage()).thenReturn(options);
+ Mockito.when(options.timeouts()).thenReturn(timeouts);
+ Mockito.when(timeouts.getScriptTimeout())
+ .thenReturn(Duration.ofSeconds(30));
+ Mockito.when(timeouts.scriptTimeout(Mockito.any(Duration.class)))
+ .thenReturn(timeouts);
+ // Phase 1: sync check for Flow.ready returns true immediately
+ Mockito.when(driver.executeScript(
+ Mockito.contains("typeof window.Vaadin.Flow.ready")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(driver.executeAsyncScript(Mockito.contains("Flow.ready"),
+ Mockito.anyLong())).thenReturn(null);
+ }
}
diff --git a/vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/WaitForVaadinIT.java b/vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/WaitForVaadinIT.java
index 75d0ff20c..87d43a444 100644
--- a/vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/WaitForVaadinIT.java
+++ b/vaadin-testbench-integration-tests-junit5/src/test/java/com/vaadin/tests/WaitForVaadinIT.java
@@ -8,10 +8,7 @@
*/
package com.vaadin.tests;
-import java.lang.reflect.Field;
-
import org.junit.jupiter.api.Assertions;
-import org.openqa.selenium.JavascriptExecutor;
import com.vaadin.flow.component.Component;
import com.vaadin.testUI.PageObjectView;
@@ -44,11 +41,11 @@ public void waitForVaadin_activeConnector_waits() {
@BrowserTest
public void waitForVaadin_activeConnector_waitsUtilReady() {
openTestURL();
- assertDevServerIsNotLoaded();
getCommandExecutor().executeScript(
- "window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => true};");
- setWaitForVaadinLoopHook(500,
- "window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => false};");
+ "window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => true};"
+ + "setTimeout(function() {"
+ + " window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => false};"
+ + "}, 500);");
getCommandExecutor().waitForVaadin();
assertClientIsActive();
}
@@ -61,41 +58,23 @@ public void waitForVaadin_noConnectors_returnsImmediately() {
assertExecutionNoLonger(() -> getCommandExecutor().waitForVaadin());
}
- @BrowserTest
- public void waitForVaadin_noFlow_returnsImmediately() {
- openTestURL();
-
- getCommandExecutor().executeScript("window.Vaadin.Flow = undefined;");
- assertExecutionNoLonger(() -> getCommandExecutor().waitForVaadin());
- }
-
@BrowserTest
public void waitForVaadin_devModeNotReady_waits() {
openTestURL();
- getCommandExecutor().executeScript(
- "window.Vaadin = {Flow: {devServerIsNotLoaded: true}};");
+ getCommandExecutor().executeScript("window.Vaadin.Flow.ready = false;");
assertExecutionBlocked(() -> getCommandExecutor().waitForVaadin());
}
@BrowserTest
public void waitForVaadin_devModeNotReady_waitsUntilReady() {
openTestURL();
- assertDevServerIsNotLoaded();
- getCommandExecutor().executeScript(
- "window.Vaadin = {Flow: {devServerIsNotLoaded: true}};");
- setWaitForVaadinLoopHook(500,
- "window.Vaadin.Flow.devServerIsNotLoaded = false;");
+ getCommandExecutor()
+ .executeScript("window._savedReady = window.Vaadin.Flow.ready;"
+ + "window.Vaadin.Flow.ready = false;"
+ + "setTimeout(function() {"
+ + " window.Vaadin.Flow.ready = window._savedReady;"
+ + "}, 500);");
getCommandExecutor().waitForVaadin();
- assertDevServerIsNotLoaded();
- }
-
- private void assertDevServerIsNotLoaded() {
- Object devServerIsNotLoaded = executeScript(
- "return window.Vaadin.Flow.devServerIsNotLoaded;");
- Assertions.assertTrue(
- devServerIsNotLoaded == null
- || devServerIsNotLoaded == Boolean.FALSE,
- "devServerIsNotLoaded should be null or false");
}
private void assertClientIsActive() {
@@ -123,28 +102,4 @@ private void assertExecutionBlocked(Runnable command) {
"Unexpected execution time, waiting time = " + timeout);
}
- private void setWaitForVaadinLoopHook(long timeout,
- String scriptToRunAfterTimeout) {
- long systemCurrentTimeMillis = System.currentTimeMillis();
- setWaitForVaadinLoopHook(() -> {
- if (System.currentTimeMillis()
- - systemCurrentTimeMillis > timeout) {
- ((JavascriptExecutor) getCommandExecutor().getDriver()
- .getWrappedDriver())
- .executeScript(scriptToRunAfterTimeout);
- setWaitForVaadinLoopHook(null);
- }
- });
- }
-
- private void setWaitForVaadinLoopHook(Runnable action) {
- try {
- Field field = getCommandExecutor().getClass()
- .getDeclaredField("waitForVaadinLoopHook");
- field.setAccessible(true);
- field.set(getCommandExecutor(), action);
- } catch (NoSuchFieldException | IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/WaitForVaadinIT.java b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/WaitForVaadinIT.java
index 7dbb88a5e..f066d31fb 100644
--- a/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/WaitForVaadinIT.java
+++ b/vaadin-testbench-integration-tests/src/test/java/com/vaadin/tests/WaitForVaadinIT.java
@@ -8,11 +8,8 @@
*/
package com.vaadin.tests;
-import java.lang.reflect.Field;
-
import org.junit.Assert;
import org.junit.Test;
-import org.openqa.selenium.JavascriptExecutor;
import com.vaadin.flow.component.Component;
import com.vaadin.testUI.PageObjectView;
@@ -44,11 +41,11 @@ public void waitForVaadin_activeConnector_waits() {
@Test
public void waitForVaadin_activeConnector_waitsUtilReady() {
openTestURL();
- assertDevServerIsNotLoaded();
getCommandExecutor().executeScript(
- "window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => true};");
- setWaitForVaadinLoopHook(500,
- "window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => false};");
+ "window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => true};"
+ + "setTimeout(function() {"
+ + " window.Vaadin.Flow.clients[\"blocker\"] = {isActive: () => false};"
+ + "}, 500);");
getCommandExecutor().waitForVaadin();
assertClientIsActive();
}
@@ -61,41 +58,23 @@ public void waitForVaadin_noConnectors_returnsImmediately() {
assertExecutionNoLonger(() -> getCommandExecutor().waitForVaadin());
}
- @Test
- public void waitForVaadin_noFlow_returnsImmediately() {
- openTestURL();
-
- getCommandExecutor().executeScript("window.Vaadin.Flow = undefined;");
- assertExecutionNoLonger(() -> getCommandExecutor().waitForVaadin());
- }
-
@Test
public void waitForVaadin_devModeNotReady_waits() {
openTestURL();
-
- getCommandExecutor().executeScript(
- "window.Vaadin = {Flow: {devServerIsNotLoaded: true}};");
+ getCommandExecutor().executeScript("window.Vaadin.Flow.ready = false;");
assertExecutionBlocked(() -> getCommandExecutor().waitForVaadin());
}
@Test
public void waitForVaadin_devModeNotReady_waitsUntilReady() {
openTestURL();
- assertDevServerIsNotLoaded();
- getCommandExecutor().executeScript(
- "window.Vaadin = {Flow: {devServerIsNotLoaded: true}};");
- setWaitForVaadinLoopHook(500,
- "window.Vaadin.Flow.devServerIsNotLoaded = false;");
+ getCommandExecutor()
+ .executeScript("window._savedReady = window.Vaadin.Flow.ready;"
+ + "window.Vaadin.Flow.ready = false;"
+ + "setTimeout(function() {"
+ + " window.Vaadin.Flow.ready = window._savedReady;"
+ + "}, 500);");
getCommandExecutor().waitForVaadin();
- assertDevServerIsNotLoaded();
- }
-
- private void assertDevServerIsNotLoaded() {
- Object devServerIsNotLoaded = executeScript(
- "return window.Vaadin.Flow.devServerIsNotLoaded;");
- Assert.assertTrue("devServerIsNotLoaded should be null or false",
- devServerIsNotLoaded == null
- || devServerIsNotLoaded == Boolean.FALSE);
}
private void assertClientIsActive() {
@@ -125,28 +104,4 @@ private void assertExecutionBlocked(Runnable command) {
timeout >= BLOCKING_EXECUTION_TIMEOUT);
}
- private void setWaitForVaadinLoopHook(long timeout,
- String scriptToRunAfterTimeout) {
- long systemCurrentTimeMillis = System.currentTimeMillis();
- setWaitForVaadinLoopHook(() -> {
- if (System.currentTimeMillis()
- - systemCurrentTimeMillis > timeout) {
- ((JavascriptExecutor) getCommandExecutor().getDriver()
- .getWrappedDriver())
- .executeScript(scriptToRunAfterTimeout);
- setWaitForVaadinLoopHook(null);
- }
- });
- }
-
- private void setWaitForVaadinLoopHook(Runnable action) {
- try {
- Field field = getCommandExecutor().getClass()
- .getDeclaredField("waitForVaadinLoopHook");
- field.setAccessible(true);
- field.set(getCommandExecutor(), action);
- } catch (NoSuchFieldException | IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/commands/TestBenchCommandExecutor.java b/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/commands/TestBenchCommandExecutor.java
index 744e4dde7..c75daeab9 100644
--- a/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/commands/TestBenchCommandExecutor.java
+++ b/vaadin-testbench-shared/src/main/java/com/vaadin/testbench/commands/TestBenchCommandExecutor.java
@@ -13,12 +13,14 @@
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.time.Duration;
import java.util.List;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Point;
+import org.openqa.selenium.ScriptTimeoutException;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.HttpCommandExecutor;
@@ -48,27 +50,19 @@ private static Logger getLogger() {
private boolean enableWaitForVaadin = true;
private boolean autoScrollIntoView = true;
// @formatter:off
- String WAIT_FOR_VAADIN_SCRIPT =
- "if (document.readyState != 'complete') {"
- + " return false;"
- + "}"
- + "if (window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.devServerIsNotLoaded) {"
- + " return false;"
- + "} else if (window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.clients) {"
- + " var clients = window.Vaadin.Flow.clients;"
- + " for (var client in clients) {"
- + " if (clients[client].isActive()) {"
- + " return false;"
- + " }"
- + " }"
- + " return true;"
- + "} else {"
- + " return true;"
- + "}";
+ private static final String READY_CHECK_SCRIPT =
+ "return typeof window.Vaadin !== 'undefined'"
+ + " && typeof window.Vaadin.Flow !== 'undefined'"
+ + " && typeof window.Vaadin.Flow.ready === 'function'";
+
+ private static final String WAIT_FOR_VAADIN_ASYNC_SCRIPT =
+ "var callback = arguments[arguments.length - 1];"
+ + "var timeout = arguments[0];"
+ + "window.Vaadin.Flow.ready({ timeout: timeout })"
+ + " .then(function() { callback(); })"
+ + " .catch(function() { callback(); });";
// @formatter:on
-
- // A hook for testing purposes
- private Runnable waitForVaadinLoopHook;
+ private static final long WAIT_FOR_VAADIN_TIMEOUT_MS = 40000;
public TestBenchCommandExecutor(ImageComparison imageComparison,
ReferenceNameGenerator referenceNameGenerator) {
@@ -109,31 +103,77 @@ public String getRemoteControlName() {
/**
* Block until Vaadin reports it has finished processing server messages.
+ *
+ * First waits for the dev server to start (if needed), then makes a single
+ * async call to {@code Flow.ready} which handles all remaining readiness
+ * checks.
*/
public void waitForVaadin() {
if (!enableWaitForVaadin) {
- // wait for vaadin is disabled, just return.
return;
}
- long timeoutTime = System.currentTimeMillis() + 40000;
- Boolean finished = false;
- while (System.currentTimeMillis() < timeoutTime && !finished) {
- if (waitForVaadinLoopHook != null) {
- waitForVaadinLoopHook.run();
+ // Must use the wrapped driver here to avoid calling waitForVaadin
+ // again
+ WebDriver wrappedDriver = getDriver().getWrappedDriver();
+ long deadline = System.currentTimeMillis() + WAIT_FOR_VAADIN_TIMEOUT_MS;
+
+ if (!waitForDevServer(wrappedDriver, deadline)) {
+ return;
+ }
+
+ // Single async call — Flow.ready handles all readiness checks
+ Duration originalTimeout = wrappedDriver.manage().timeouts()
+ .getScriptTimeout();
+ try {
+ long remaining = deadline - System.currentTimeMillis();
+ if (remaining <= 0) {
+ return;
}
- // Must use the wrapped driver here to avoid calling waitForVaadin
- // again
- finished = (Boolean) ((JavascriptExecutor) getDriver()
- .getWrappedDriver()).executeScript(WAIT_FOR_VAADIN_SCRIPT);
- if (finished == null) {
- // This should never happen but according to
- // https://dev.vaadin.com/ticket/19703, it happens
- getLogger().debug(
- "waitForVaadin returned null, this should never happen");
- finished = false;
+ // Allow a small grace period over the JS-side timeout so the
+ // promise can reject and call back before WebDriver bails
+ wrappedDriver.manage().timeouts()
+ .scriptTimeout(Duration.ofMillis(remaining + 1000));
+ ((JavascriptExecutor) wrappedDriver).executeAsyncScript(
+ WAIT_FOR_VAADIN_ASYNC_SCRIPT, remaining);
+ } catch (ScriptTimeoutException e) {
+ // Silent timeout
+ } finally {
+ wrappedDriver.manage().timeouts().scriptTimeout(originalTimeout);
+ }
+ }
+
+ /**
+ * Polls until {@code Vaadin.Flow.ready} is a function, indicating the dev
+ * server has started and Flow is loaded. Uses Java-side polling so it
+ * survives page reloads during dev server startup.
+ *
+ * @return {@code true} if ready became available, {@code false} if the
+ * deadline was reached
+ */
+ private boolean waitForDevServer(WebDriver wrappedDriver, long deadline) {
+ while (System.currentTimeMillis() < deadline) {
+ try {
+ Boolean ready = (Boolean) ((JavascriptExecutor) wrappedDriver)
+ .executeScript(READY_CHECK_SCRIPT);
+ if (Boolean.TRUE.equals(ready)) {
+ return true;
+ }
+ } catch (Exception e) {
+ // Page may be reloading, continue polling
+ }
+ long remaining = deadline - System.currentTimeMillis();
+ if (remaining <= 0) {
+ return false;
+ }
+ try {
+ Thread.sleep(Math.min(500, remaining));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
}
}
+ return false;
}
@Override