Skip to content

fix(test): increase Selenium UI test timeouts#2743

Closed
anusshukla wants to merge 1 commit into
mainfrom
fix/ui-selenium-timeouts
Closed

fix(test): increase Selenium UI test timeouts#2743
anusshukla wants to merge 1 commit into
mainfrom
fix/ui-selenium-timeouts

Conversation

@anusshukla
Copy link
Copy Markdown
Contributor

@anusshukla anusshukla commented Apr 16, 2026

Timeouts calibrated for local dev were too aggressive for CI runners, causing cascading TimeoutExceptions across all three non-Lightspeed UI tests.

related: #2741

Increases Selenium UI test timeouts and retries to prevent CI flakiness caused by CI being slower than local runs; approach: raise waits, poll intervals, retry counts, and add post-healthcheck delay.

  • test/ui/test_00_commands.py: longer terminal focus delay, WebDriverWait timeout/poll, “Untitled” tab wait, editor read delay
  • test/ui/test_01_dev_webviews.py: iframe lookup retries increased (10→20)
  • test/ui/utils/ui_utils.py: command palette input timeout increased (2s→5s)
  • docker-compose.yml & fixtures: longer healthcheck/start delays and post-healthcheck sleep

related: #2741

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Timing and retry parameters were increased across UI tests, a browser setup delay was added, and the Selenium service healthcheck now requires two endpoints to be healthy before proceeding.

Changes

Cohort / File(s) Summary
Command Test Timing Adjustments
test/ui/test_00_commands.py
Increased terminal focus pre-input delay (3s→5s); extended WebDriverWait overall timeout (10s→30s) and poll frequency (0.5s→1s); extended “Untitled” tab wait (2s→10s) and editor read sleep (1s→2s).
Webview Form Lookup Retries
test/ui/test_01_dev_webviews.py
Increased iframe lookup retry counts for //form[@id='devfile-form'] and //form[@id='devcontainer-form'] (10→20 retries).
VSCode Ready & Command Palette
test/ui/utils/ui_utils.py
Replaced single wait with retry loop for VSCode readiness (max_nav_attempts=3, per-attempt timeout increased to 90s); increased command palette input wait timeout (2s→5s) inside existing retry logic.
Selenium Healthcheck & Startup Delay
docker-compose.yml, test/ui/fixtures/ui_fixtures.py
Healthcheck changed to CMD-SHELL requiring both http://localhost:4444 and http://localhost:8080 to respond; start_period increased (10s→30s). After container healthy, added an extra 30s sleep before creating the WebDriver.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hopped through waits and gentle retries,
Tucked seconds in corners, gave flaps more skies.
With curls of patience and tidy surprise,
Tests breathe easier beneath my long ears' rise. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'fix(test): increase Selenium UI test timeouts' accurately summarizes the main change—increasing timeout values across Selenium UI tests to address CI failures.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ui-selenium-timeouts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
test/ui/test_01_dev_webviews.py (1)

27-27: Consider centralizing this retry budget instead of hard-coding 20 twice.

These are valid CI-hardening changes, but duplicating retry counts makes later tuning harder (and worst-case misses now wait much longer). A shared constant (or timeout-based helper wrapper) would keep both tests consistent and easier to calibrate.

♻️ Suggested small refactor
+FORM_READY_RETRIES = 20
...
-    find_element_across_iframes(driver, "//form[`@id`='devfile-form']", retries=20)
+    find_element_across_iframes(driver, "//form[`@id`='devfile-form']", retries=FORM_READY_RETRIES)
...
-        retries=20,
+        retries=FORM_READY_RETRIES,

Also applies to: 73-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/ui/test_01_dev_webviews.py` at line 27, The test hard-codes the retry
count (20) when calling find_element_across_iframes in test_01_dev_webviews.py
(lines showing calls to find_element_across_iframes at start and around line
73); centralize this retry budget by introducing a shared constant (e.g.,
DEFAULT_IFRAME_RETRIES or IFRAME_FIND_RETRIES) or a timeout-based helper used by
both call sites, replace the literal 20 with that constant/helper in both calls,
and update any related tests to import/use the new constant so tuning happens in
one place.
test/ui/test_00_commands.py (1)

46-46: Prefer condition-based waits over longer fixed sleeps here.

These increases will help CI, but hard sleeps can still be flaky and slow. Consider waiting on a concrete UI condition (terminal prompt/output ready, or editor lines present) instead of unconditional sleeps.

Also applies to: 91-91

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/ui/test_00_commands.py` at line 46, Replace the fixed time.sleep(5) (and
the similar sleep at the later occurrence) with a condition-based wait: poll the
actual UI/terminal readiness (e.g., loop until terminal prompt/output appears,
editor buffer has expected lines, or a helper like
terminal.read_until()/expect()/get_lines() returns the expected content) with a
short sleep interval and a total timeout, then assert readiness; update calls to
time.sleep(...) in test_00_commands.py (the explicit time.sleep lines) to use
this loop/utility so CI waits on a concrete condition rather than an
unconditional long sleep.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/ui/test_00_commands.py`:
- Line 46: Replace the fixed time.sleep(5) (and the similar sleep at the later
occurrence) with a condition-based wait: poll the actual UI/terminal readiness
(e.g., loop until terminal prompt/output appears, editor buffer has expected
lines, or a helper like terminal.read_until()/expect()/get_lines() returns the
expected content) with a short sleep interval and a total timeout, then assert
readiness; update calls to time.sleep(...) in test_00_commands.py (the explicit
time.sleep lines) to use this loop/utility so CI waits on a concrete condition
rather than an unconditional long sleep.

In `@test/ui/test_01_dev_webviews.py`:
- Line 27: The test hard-codes the retry count (20) when calling
find_element_across_iframes in test_01_dev_webviews.py (lines showing calls to
find_element_across_iframes at start and around line 73); centralize this retry
budget by introducing a shared constant (e.g., DEFAULT_IFRAME_RETRIES or
IFRAME_FIND_RETRIES) or a timeout-based helper used by both call sites, replace
the literal 20 with that constant/helper in both calls, and update any related
tests to import/use the new constant so tuning happens in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 508ceedc-539f-4a74-bb1e-1d510bbb9097

📥 Commits

Reviewing files that changed from the base of the PR and between becf350 and bfafc33.

📒 Files selected for processing (3)
  • test/ui/test_00_commands.py
  • test/ui/test_01_dev_webviews.py
  • test/ui/utils/ui_utils.py

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The container healthcheck only verified Selenium (port 4444) but
not code-server (port 8080), so tests could start before VS Code
was ready. Test-level timeouts were also too aggressive for CI.

- Check both Selenium and code-server in healthcheck
- Wait for extension activation after container becomes healthy
- Retry page load in ensure_vscode_ready on timeout
- Increase timeouts: command palette 2s->5s, tab wait 2s->10s,
  terminal output 10s->30s, webview form retries 10->20

related: #2741
Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/ui/fixtures/ui_fixtures.py (1)

92-95: Make the post-healthcheck wait configurable instead of hard-coded.

This unconditional 30s pause adds fixed latency to every session and may still be too short/long depending on runner speed. Prefer an env-configurable delay.

Proposed change
             log.info(
                 "Container healthy, waiting for code-server extension activation..."
             )
-            time.sleep(30)
+            activation_wait_seconds = int(
+                os.environ.get("UI_EXTENSION_ACTIVATION_WAIT_SECONDS", "30")
+            )
+            if activation_wait_seconds > 0:
+                time.sleep(activation_wait_seconds)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/ui/fixtures/ui_fixtures.py` around lines 92 - 95, Replace the hard-coded
time.sleep(30) in test/ui/fixtures/ui_fixtures.py with a configurable delay
sourced from an environment variable (e.g., UI_POST_HEALTHCHECK_DELAY) and a
sensible default of 30s; parse and validate the value (int, non-negative) into a
variable like post_healthcheck_delay and call
time.sleep(post_healthcheck_delay), and update the log.info message (or add a
new log.debug) to include the chosen delay so the behavior is explicit at
runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/ui/utils/ui_utils.py`:
- Around line 81-90: The loop in ensure_vscode_ready uses fixed 90s waits and
fixed 10s sleeps, which can exceed the caller-supplied timeout; change it to
track elapsed time (e.g. via time.monotonic()) and compute a remaining_time
budget for each iteration when calling wait_displayed(driver,
"//a[`@aria-label`='Ansible']", timeout=remaining_time) and for any sleep (sleep
only min(10, remaining_time)), stop retrying and raise if remaining_time <= 0;
update handling around exceptions (TimeoutException, TimeOutError) and the
driver.refresh() call so they respect the remaining timeout budget.

---

Nitpick comments:
In `@test/ui/fixtures/ui_fixtures.py`:
- Around line 92-95: Replace the hard-coded time.sleep(30) in
test/ui/fixtures/ui_fixtures.py with a configurable delay sourced from an
environment variable (e.g., UI_POST_HEALTHCHECK_DELAY) and a sensible default of
30s; parse and validate the value (int, non-negative) into a variable like
post_healthcheck_delay and call time.sleep(post_healthcheck_delay), and update
the log.info message (or add a new log.debug) to include the chosen delay so the
behavior is explicit at runtime.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 06fb6469-5d4d-4f8d-a255-5471c810b78a

📥 Commits

Reviewing files that changed from the base of the PR and between bfafc33 and 6e90bd6.

📒 Files selected for processing (5)
  • docker-compose.yml
  • test/ui/fixtures/ui_fixtures.py
  • test/ui/test_00_commands.py
  • test/ui/test_01_dev_webviews.py
  • test/ui/utils/ui_utils.py
✅ Files skipped from review due to trivial changes (1)
  • docker-compose.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/ui/test_01_dev_webviews.py
  • test/ui/test_00_commands.py

Comment thread test/ui/utils/ui_utils.py
Comment on lines +81 to +90
max_nav_attempts = 3
for attempt in range(max_nav_attempts):
try:
wait_displayed(driver, "//a[@aria-label='Ansible']", timeout=90)
break
except (TimeoutException, TimeOutError):
if attempt == max_nav_attempts - 1:
raise
driver.refresh()
time.sleep(10)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

ensure_vscode_ready currently bypasses its timeout budget.

The new loop uses fixed 90s attempts and 10s sleeps, so total wait can exceed the function’s timeout argument. Use a remaining-time budget tied to timeout.

Proposed change
-    max_nav_attempts = 3
-    for attempt in range(max_nav_attempts):
+    max_nav_attempts = 3
+    deadline = time.monotonic() + timeout
+    for attempt in range(max_nav_attempts):
+        remaining = int(deadline - time.monotonic())
+        if remaining <= 0:
+            raise TimeOutError("timeout waiting for VSCode Ansible icon")
         try:
-            wait_displayed(driver, "//a[`@aria-label`='Ansible']", timeout=90)
+            wait_displayed(
+                driver,
+                "//a[`@aria-label`='Ansible']",
+                timeout=min(remaining, 90),
+            )
             break
         except (TimeoutException, TimeOutError):
             if attempt == max_nav_attempts - 1:
                 raise
             driver.refresh()
-            time.sleep(10)
+            time.sleep(min(10, max(0, deadline - time.monotonic())))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
max_nav_attempts = 3
for attempt in range(max_nav_attempts):
try:
wait_displayed(driver, "//a[@aria-label='Ansible']", timeout=90)
break
except (TimeoutException, TimeOutError):
if attempt == max_nav_attempts - 1:
raise
driver.refresh()
time.sleep(10)
max_nav_attempts = 3
deadline = time.monotonic() + timeout
for attempt in range(max_nav_attempts):
remaining = int(deadline - time.monotonic())
if remaining <= 0:
raise TimeOutError("timeout waiting for VSCode Ansible icon")
try:
wait_displayed(
driver,
"//a[`@aria-label`='Ansible']",
timeout=min(remaining, 90),
)
break
except (TimeoutException, TimeOutError):
if attempt == max_nav_attempts - 1:
raise
driver.refresh()
time.sleep(min(10, max(0, deadline - time.monotonic())))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/ui/utils/ui_utils.py` around lines 81 - 90, The loop in
ensure_vscode_ready uses fixed 90s waits and fixed 10s sleeps, which can exceed
the caller-supplied timeout; change it to track elapsed time (e.g. via
time.monotonic()) and compute a remaining_time budget for each iteration when
calling wait_displayed(driver, "//a[`@aria-label`='Ansible']",
timeout=remaining_time) and for any sleep (sleep only min(10, remaining_time)),
stop retrying and raise if remaining_time <= 0; update handling around
exceptions (TimeoutException, TimeOutError) and the driver.refresh() call so
they respect the remaining timeout budget.

@github-project-automation github-project-automation Bot moved this from In Progress to Done in 🧰 devtools project board Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant