From 3d1ca00257c7d84afdf6e65cceec01d6353e82e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 11:33:03 +0000 Subject: [PATCH 1/2] ci(tests): run a Chromium browser test once more when it loses a test iframe The chrome and edge jobs of the Tests workflow sometimes lose a test iframe under the webdriverio provider. The run then either fails at once with "Cannot connect to the iframe" / "Failed to fetch dynamically imported module", or prints nothing more until the 20 minute job timeout. It is not tied to a test file: the stuck iframe was a different, trivial file each time, the same commit passes in firefox, safari, every Node, Deno and Bun, and main hit it too (run 35391927376, the merge of #555, which only added a verification tag). Over the last 40 PR runs 12 Chromium jobs hit it; firefox and safari never did. A whole browser run takes 1 to 3 minutes, so the step now wraps it in `timeout 300`, and runs it once more only when the first attempt timed out or failed with one of those two errors. Any other failure, a failing assertion included, fails the step on the first attempt, so no test is skipped or retried for its own sake. The matrix sets fail-fast: false, so a flake in one browser no longer cancels the others and hides their result. The browser name reaches the script through an env variable instead of an expression inside `run`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RLkm9YrtAifc6XCLFVEsdH --- .github/workflows/tests.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 867cfa08b..942e81404 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -107,6 +107,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 strategy: + # A flake in one browser must not cancel the others, or its cause is lost. + fail-fast: false matrix: browser: [edge, chrome, firefox] @@ -119,8 +121,30 @@ jobs: - name: Setup uses: ./.github/actions/setup + # Chromium under the webdriverio provider sometimes loses a test iframe: the run either fails + # at once with "Cannot connect to the iframe" / "Failed to fetch dynamically imported module", + # or prints nothing more until the job timeout. A whole run takes 1 to 3 minutes, so a first + # attempt that times out after 5 minutes, or fails with one of those two errors, is run once + # more. Any other failure, including a failing assertion, fails the step on the first attempt. - name: Run tests in ${{ matrix.browser }} - run: vp test --browser.enabled --browser.name=${{ matrix.browser }} + env: + BROWSER: ${{ matrix.browser }} + run: | + attempt() { + timeout 300 vp test --browser.enabled --browser.name="$BROWSER" 2>&1 | tee browser-tests.log + return "${PIPESTATUS[0]}" + } + + attempt && exit 0 + status=$? + + if [ "$status" -eq 124 ] || grep -qE "Cannot connect to the iframe|Failed to fetch dynamically imported module" browser-tests.log; then + echo "::warning::The $BROWSER test run lost a test iframe (exit $status); running it once more." + attempt + exit $? + fi + + exit "$status" test-safari: name: Test on Browsers (safari) From 6cd75fdef1a524f1327d4d3326d50b0da8a773cc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 02:40:21 +0000 Subject: [PATCH 2/2] ci(tests): retry a lost browser iframe twice, one test file at a time One retry was not enough: Chrome lost an iframe on both attempts in #600 and #604. The retries now run with --no-file-parallelism, since the iframes are lost when many files load at once. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RLkm9YrtAifc6XCLFVEsdH --- .github/workflows/tests.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 942e81404..bb2e52318 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -105,7 +105,7 @@ jobs: test-browsers: name: Test on Browsers (${{ matrix.browser }}) runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 25 strategy: # A flake in one browser must not cancel the others, or its cause is lost. fail-fast: false @@ -123,26 +123,32 @@ jobs: # Chromium under the webdriverio provider sometimes loses a test iframe: the run either fails # at once with "Cannot connect to the iframe" / "Failed to fetch dynamically imported module", - # or prints nothing more until the job timeout. A whole run takes 1 to 3 minutes, so a first - # attempt that times out after 5 minutes, or fails with one of those two errors, is run once - # more. Any other failure, including a failing assertion, fails the step on the first attempt. + # or prints nothing more until it is killed. A whole run takes 1 to 3 minutes, so an attempt + # that times out, or fails with one of those two errors, is run again, up to three attempts. + # The iframes are lost when many test files load at once, so the retries run one file at a + # time. Any other failure, including a failing assertion, fails the step at once. - name: Run tests in ${{ matrix.browser }} env: BROWSER: ${{ matrix.browser }} run: | attempt() { - timeout 300 vp test --browser.enabled --browser.name="$BROWSER" 2>&1 | tee browser-tests.log + timeout "$1" vp test --browser.enabled --browser.name="$BROWSER" "${@:2}" 2>&1 | tee browser-tests.log return "${PIPESTATUS[0]}" } - attempt && exit 0 + lost_iframe() { + [ "$1" -eq 124 ] || grep -qE "Cannot connect to the iframe|Failed to fetch dynamically imported module" browser-tests.log + } + + attempt 300 && exit 0 status=$? - if [ "$status" -eq 124 ] || grep -qE "Cannot connect to the iframe|Failed to fetch dynamically imported module" browser-tests.log; then - echo "::warning::The $BROWSER test run lost a test iframe (exit $status); running it once more." - attempt - exit $? - fi + for retry in 1 2; do + lost_iframe "$status" || exit "$status" + echo "::warning::The $BROWSER test run lost a test iframe (exit $status); retry $retry of 2, one file at a time." + attempt 420 --no-file-parallelism && exit 0 + status=$? + done exit "$status"