fix: wrap unprotected evaluate() calls with robust_evaluate() to handle navigation context loss (#6677)

* fix: wrap unprotected evaluate() calls with robust_evaluate() to handle navigation context loss

Fixes PR #5911 - Playwright UI test error: 'Execution context was destroyed'

The test had several direct page.evaluate() and locator.evaluate() calls that
weren't wrapped with robust_evaluate(), which retries when navigation destroys
the execution context mid-operation.

Changes:
- Wrap picker_visible_text() evaluate in robust_evaluate()
- Wrap _bubble_count() evaluate in robust_evaluate()
- Wrap assistant text query in robust_evaluate()
- Wrap theme_item click evaluation in robust_evaluate()
- Wrap background color/theme query in robust_evaluate()

This ensures all execution context losses from concurrent navigation are
properly caught and retried with exponential backoff, preventing transient
failures in the UI test suite.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: revert robust_evaluate on theme_item.evaluate per Codex review

The theme_item.evaluate('el => el.click()') is side-effecting — retrying
after a context loss could double-toggle the theme. It's already inside
a 3-attempt try/except loop that handles click failures gracefully.

The other 4 changes (all read-only queries) remain wrapped in
robust_evaluate() since retrying them is safe.

* fix: wrap remaining chat UI evaluate

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
This commit is contained in:
James Dawdy 2026-06-26 10:56:52 -05:00 committed by GitHub
parent 2ef394137a
commit b693ed7c91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -494,12 +494,15 @@ with sync_playwright() as p:
# typeahead actually filters (else an ignored-input regression
# would silently pass).
def picker_visible_text():
return page.evaluate("""() => {
return robust_evaluate(
page,
"""() => {
const el = document.querySelector(
'[role="dialog"], [role="listbox"], [role="menu"]'
);
return el ? (el.innerText || '').trim() : '';
}""")
}""",
)
search.fill("qwen")
page.wait_for_timeout(800)
@ -535,9 +538,12 @@ with sync_playwright() as p:
def _bubble_count():
"""Total [data-role='assistant'] elements (empty or not)."""
return page.evaluate("""() => {
return robust_evaluate(
page,
"""() => {
return document.querySelectorAll('[data-role="assistant"]').length;
}""")
}""",
)
def send_and_wait(prompt, idx):
# 1. Wait until the previous turn fully stopped: Send attached
@ -626,8 +632,11 @@ with sync_playwright() as p:
send_and_wait(p_, i)
shoot("04-after-five-turns")
texts = page.evaluate("""() => Array.from(document.querySelectorAll('[data-role="assistant"]'))
.map(e => (e.innerText || '').trim())""")
texts = robust_evaluate(
page,
"""() => Array.from(document.querySelectorAll('[data-role="assistant"]'))
.map(e => (e.innerText || '').trim())""",
)
if len(texts) < len(prompts):
fail(f"expected >= {len(prompts)} assistant bubbles, got {len(texts)}")
info(f"five turn lengths = {[len(t) for t in texts[:5]]}")
@ -840,7 +849,9 @@ with sync_playwright() as p:
# Settle. The ".dark" class on <html> is the ground truth
# (theme-store toggles only that); don't gate on ".light".
page.wait_for_timeout(700)
bg = page.evaluate("""() => {
bg = robust_evaluate(
page,
"""() => {
const root = document.documentElement;
return {
cls: root.className,
@ -848,7 +859,8 @@ with sync_playwright() as p:
bg: getComputedStyle(document.body).backgroundColor,
rbg: getComputedStyle(root).backgroundColor,
};
}""")
}""",
)
observed.append(bg)
shoot(f"10-theme-cycle-{cycle + 1}")
info(f" cycle {cycle + 1}: dark={bg['isDark']} body bg={bg['bg']!r}")
@ -1050,7 +1062,8 @@ with sync_playwright() as p:
shoot("15d-recent-clicked")
info(f"OK clicked recent entry: {t[:60]!r}")
# The landed thread must include at least one of our prompts.
turns_text = page.evaluate(
turns_text = robust_evaluate(
page,
"""() => {
const els = document.querySelectorAll(
'[data-role="user"], [data-role="assistant"]'
@ -1058,7 +1071,6 @@ with sync_playwright() as p:
return Array.from(els).map(e => (e.innerText || '')
.toLowerCase()).join(' ');
}""",
None,
)
clicked_recent = True
if any(k in turns_text for k in PROMPT_KEYWORDS):