Diagnosis from the CI recording (build 1379, chrome / mysql latest / tomcat 11-jdk25)
Reading the archived VNC recording frame by frame gives the exact sequence around the failure:
clock
tab
state
00:55:23
2 (standalone WYSIWYG)
one two three four, caret at end
00:55:24-25
2
one two three four five, then Save & Continue succeeds
00:55:27
1 (in-place, #edit)
rejoined, shows one two three four + John's macro error
00:55:38
1
unchanged -> the 10s waitUntilTextContains("five") expires
So the in-place editor did load, and the browser itself never showed five. This is neither a stale page object nor a wait that was merely too short: nothing changed on screen for the whole 11 seconds.
The content shown is not the initial value the editor was given
inplace.js seeds the editor with config.document.renderedContent, which InplaceEditing.xml's load() re-fetches from the server (reload() then render(), both cache-busted with a timestamp). After the second tab's Save & Continue, that render contains five and the velocity macro executed as superadmin. The editor showed neither - it showed the revision the first tab had saved itself.
The editor is therefore rendering the Yjs fragment rather than its initial content, which is what BlockNoteViewWrapper.tsx does when the shared document already carries the initialContentLoaded flag:
So the first tab joined a session whose initialContentLoaded was already set - the room the second tab created - but whose replayed history stopped before the second tab's {{ five}} update.
One candidate mechanism ruled out
A plausible alternative was that AbstractCollaborationManager handed back a cached, already-disconnected Collaboration: XWikiBlockNote.vue calls join(docRef) with the editor's own reference but leave() with no argument, falling back to XWikiDocumentService.getCurrentDocumentReference(), so a key mismatch would leak the session and return a dead one on rejoin. It does not: XWikiModelReferenceSerializer.serialize() is getPrefix(type) + Model.serialize(toXWikiEntityReference(reference)), and the locale is not part of that serialization, so both calls compute the same key. inplace.js does destroy the Vue app on xwiki:actions:view, so onUnmounted really runs. The rejoin therefore builds a fresh provider and awaits sync.
What remains
The second tab's {{ five}} update had not reached the Yjs room when the first tab synced, and still had not 11 seconds later. Worth noting that clickSaveAndContinue() only proves the HTTP form save completed: the save goes through the edit form and is independent of the WebSocket, so the test moves on with no guarantee that the peer's edit reached the collaboration session.
This confirms the part of the description above that says the peer's edit never arrives, but it does not support the suggestion that the fix necessarily belongs in the page object or the rejoin helper - on this evidence a product-side fix in the propagation/sync path is equally likely. Deciding between the two needs the failure caught in the act.
Local reproduction attempt - not reproduced
The method cannot be run on its own: editPageTranslation (@Order(5)) makes the wiki multilingual and restrictScriptMacroExecution depends on that - run alone it fails systematically at CollaborationIT.java:560, where editInplace times out. Run as the pair editPageTranslation+restrictScriptMacroExecution with xwiki-it-repeat.mjs on chrome:
executions
passed
failed
pass rate
before
38
38
0
100.0%
That is 19 clean repetitions of the target method, with one further run excluded after hitting a 6 minute timeout with the Selenium node dropping out. 38 clean executions bound the local failure rate below 7.9% (95% confidence, rule of three) against a CI rate of about 6%, so the measurement was never powerful enough to conclude anything. The local environment also differs from the failing one: macOS/arm64, chromium 152, HSQLDB and Jetty standalone, versus Linux, Chrome, MySQL and Tomcat on CI.
Suggested next step
Land a temporary browser-console dump on the failing wait and let CI catch the next occurrence. LogType.BROWSER is already enabled for Chrome in Browser.buildChromeOptions(). The collaboration manager logs Joining realtime collaboration., Collaboration synchronized. and Setting initial content for realtime collaboration session., which would say whether the first tab created and seeded a fresh room or synced an out-of-date one - and that is the fork the fix depends on.
This message was sent by Atlassian Jira (v9.3.0#930000-sha1:287aeb6)
If image attachments aren't displayed, see this article.