There is 1 update, 1 comment.
 
 
XWiki Platform / cid:jira-generated-image-avatar-242f3b13-0535-4497-b0d8-946b71d2147c XWIKI-22619 Open

RealtimeWYSIWYGEditorIT#dragAndDropFilesAtTheSameTime is flickering

 
View issue   ยท   Add comment
 

1 update

 
cid:jira-generated-image-avatar-834e3fb0-75b7-4c8d-acf8-ec3e9fbf5286 Changes by Michael Hamann on 01/Oct/26 15:56
 
Assignee: Michael Hamann
 
 

1 comment

 
cid:jira-generated-image-avatar-834e3fb0-75b7-4c8d-acf8-ec3e9fbf5286 Michael Hamann on 01/Oct/26 15:53
 

Analysis based on two Claude Code sessions with Opus 5 first and then a later session with Opus 5.5:

Summary

org.xwiki.realtime.wysiwyg.test.ui.AllIT$NestedRealtimeWYSIWYGEditorIT#dragAndDropFilesAtTheSameTime fails regularly on CI, on every branch. Over the 28 days from 2026-08-25 to 2026-09-22 it ran 559 times and failed 94 times (16.8%), but those failures are seven distinct problems, only one of which is a genuine flicker of this test:

# Failures Fails in Verdict
F1 57 RealtimeWYSIWYGEditPage.gotoPage -> RealtimeEditToolbar.waitUntilConnected Not this test. master only, 2026-09-04 to 2026-09-14, the whole RealtimeWYSIWYGEditorIT class failed in those builds (median 27 other test failures per build). Gone since 2026-09-14.
F2 + F3 29 RichTextAreaElement.dropFile -> waitUntilWidgetSelected The actual flicker. 28 of 29 on Chrome, all branches, still happening (last occurrence 2026-09-22).
F4 4 RichTextAreaElement.waitForUploadsToFinish Realtime product bug, see XWIKI-25071.
F5 2 the final assertEquals Realtime product bug, see XWIKI-25071.
F6 1 waitUntilTextContains("yellow") Same realtime product bug, see XWIKI-25071.
F7 1 page load timeout Infrastructure noise.

F2/F3 - the test waits for a state that has already expired

dropFile(path, false) calls waitUntilWidgetSelected(), which polls the edited content for the cke_widget_selected CSS class. That state is transient: as soon as the upload completes, xwiki-upload's overrideUploadWidget replaces the upload widget with the final <a>/<img>, and cke_widget_selected is gone for good.

The content is polled every 500 ms, and RealtimeRichTextAreaElement.repeatedWait retries that 5 times for 2 s each. If a single poll gap straddles the widget's whole lifetime, the wait can never succeed and burns the full 10 s (switching browser tabs 4 times for nothing).

Evidence, from the REALTIME_DEBUG dump of build master#1381 (2026-09-22 01:12, chrome / mysql latest / tomcat 11-jdk25), second tab:

01:52:15.300  Push local content: ... ["P",["second ", SPAN.xwiki-widget-placeholder-uploadfile]]  <- drop processed
01:52:15.570  Push local content: ... ["P",["second ", <attach link source-button.mp4>]]           <- upload finished
01:52:16.067  (last remote content received)
01:52:17.6 .. 01:52:27.7   only Saver heartbeats - the test polled for 10 s, then gave up

The upload widget existed for 270 ms (377 ms for the first tab in the same run). No Received remote content happened in between, so nothing remote disturbed the widget: the local upload simply finished faster than the poller looked. The archived screenshot confirms it - source-button.mp4 is already a finished, selected attachment link with the link balloon open.

This also explains why 28 of the 29 occurrences are on Chrome: Chrome finishes these uploads faster than the poll interval more often. The mysql latest / tomcat 11-jdk25 / jdk25 correlations are just where Chrome runs.

Fix

Stop polling the DOM for an expiring state, and latch the event in the browser instead. The script that dispatches the drop in RichTextAreaElement.dropFile(WebElement) now also registers a one-shot editor.widgets.on('instanceCreated') listener for the uploadfile/uploadimage widgets and, from it, widget.once('select', ...), which sets a flag on the editor instance:

editor._testUploadWidgetSelected = false;
const widgetListener = editor.widgets.on('instanceCreated', function(event) {
  const widget = event.data;
  if (widget.name === 'uploadfile' || widget.name === 'uploadimage') {
    widgetListener.removeListener();
    widget.once('select', function() {
      editor._testUploadWidgetSelected = true;
    });
  }
});

dropFile then waits on that flag, which stays set after the upload finishes. The public waitUntilWidgetSelected() is unchanged - its other callers (ImageIT, MacroIT) wait on widget selections that are not transient.

Validation

F2 does not reproduce locally, because local WebDriver round trips are about 10 ms, so the first poll always lands inside the widget's ~300 ms lifetime. Injecting a temporary 1 s sleep right after the drop is dispatched stands in for CI's slower first look:

Variant Result
current code + 1 s delay 3/3 fail inside dropFile, with exactly the CI stack trace
fixed code + 1 s delay 3/3 pass
current code, 15 repetitions, no delay 6 pass / 9 fail - 7 x F4, 2 x F5, 0 inside dropFile
fixed code, 15 repetitions, no delay 5 pass / 10 fail - 6 x F4, 2 x F5, 2 x F6, 0 inside dropFile

Other failures of this test

F4, F5 and F6 are caused by realtime synchronization bugs that also affect users, not by the test. They are tracked separately in XWIKI-25071. With the fixes from both issues the test passed 15 times out of 15 locally on Chrome and on Firefox (versus 10 failures out of 15 with only the test fix).

How to reproduce locally

Temporarily turn the test into a @RepeatedTest(15) (the environment startup dominates: about 3.5 min per Maven run versus about 20 s per repetition), then:

LANG=C.UTF-8 xmvn clean install -pl :xwiki-platform-realtime-wysiwyg-test-docker \
  -Dit.test='AllIT$NestedRealtimeWYSIWYGEditorIT#dragAndDropFilesAtTheSameTime' \
  -Dxwiki.test.ui.browser=chrome

Group the failures by the line number of the deepest RealtimeWYSIWYGEditorIT stack frame - several distinct races hide behind this one test. On CI, the RealtimeTestDebugger output (browser console logs plus window.REALTIME_DEBUG) is in the Jenkins console log, not in the archived artifacts.