test: read Monaco content from the model, not the DOM - #3467
opensource-joe wants to merge 1 commit into
Conversation
) `plugin_metadata.crud-all-fields` was flaky on its final assertion, failing with `Received string: "{"` when reading the editor back after a save. It is a test-only race, and CI hides it because `playwright.config.ts` sets `retries: process.env.CI ? 2 : 0`. The spec had its own local `getMonacoEditorValue` that read the DOM through two paths, and neither is a reliable source for editor content: - the hidden `textarea` holds only a small buffer around the cursor for IME purposes, never the whole document - `.view-line` elements are virtualised, so only painted lines exist Sampling all three sources at the instant the drawer becomes visible, for a 219-character config: t+0ms textarea= 0 "" viewLines= 1 "{" model=219 t+50ms textarea= 0 "" viewLines=11 "{ \"log_format\"…" model=219 t+1000ms textarea= 0 "" viewLines=11 "{ \"log_format\"…" model=219 The textarea is empty at every offset, so the helper always fell through to the `.view-line` path, and that path returns exactly `{` until the editor paints. That is the observed failure value. The window is roughly 50ms wide, which is why it reproduces under load and passes on an idle machine. The model, meanwhile, is complete at t+0. There is no race to wait out, only the wrong source being read. Add `uiGetMonacoEditorValue` alongside the existing helpers and read `window.__monacoEditor__.getModel().getValue()`, which is the same source `uiFillMonacoEditor` and `uiClearMonacoEditor` already write to, and which `plugin-metadata.drawer-keeps-edits-on-failed-save` already reads. It polls for non-empty content because the drawer mounts its editor asynchronously and the global can briefly still point at a previously mounted instance. `routes.empty-plugin-config` had the same defect, reading `.view-lines` innerText. Its comment ("not the textarea") shows the textarea problem was already known; the fix reached for the other DOM source rather than the model. Converted too, so the pattern does not survive anywhere. Verified against a live APISIX instance: the affected specs pass 24/24 across 4 repeats, and lint and typecheck are clean.
|
Withdrawn: this PR is closed and its commit is folded into #3465. @LiteSun please disregard the workflow-approval ask below; it applies to #3465 only, where this change now lives as its own commit. Apologies for the extra notification. Leaving the rationale below, since it is the reasoning behind that commit. This is worth running properly because it is a test-only change and CI is the point of it. The clearest evidence that the DOM path was never viable rather than merely slow: the hidden textarea holds 0 characters at every time offset sampled, while the editor model holds the full 219. The fix reads from the model instead of the rendered DOM. |
|
Folding this into #3465 rather than keeping it separate, so there is one PR to review instead of two. Nothing is dropped: the commit here is cherry-picked onto #3465 unchanged, as its own commit rather than squashed, so the test fix stays independently reviewable. #3465's description now covers both halves separately, and its scope is unchanged otherwise. @LiteSun apologies for the churn, and please disregard my earlier ask on this one. The workflow-approval request now applies only to #3465. |
Please answer these questions before submitting a pull request, or your PR will get closed.
Why submit this pull request?
What changes will this PR take into?
Fixes #3466. Test-only change, no
src/code touched.The diagnosis changed once I could measure it
I originally suggested in #3466 that the helper should poll until its content parses. That treats it as a race to wait out. Measuring it showed the simpler story: the helper is reading sources that never hold the document.
plugin_metadata.crud-all-fields.spec.tsdefined its own localgetMonacoEditorValuethat tried the hiddentextareafirst and fell back to joining.view-lineelements. Neither is a source of editor content:textareaholds only a small buffer around the cursor, for IME purposes, never the whole document.view-lineelements are virtualised, so only painted lines existSampling all three sources at the instant the Edit Plugin drawer becomes visible, for a 219-character config:
Two things fall out of that table:
{until the editor paints, because the first view-line of the JSON is the opening brace. That is precisely theReceived string: "{"in the failure.The model is already complete at
t+0. There was never a race worth waiting out, just the wrong source being read. The paint window is roughly 50ms wide, which is why this reproduces on a loaded machine and passes on an idle one.The fix
Add
uiGetMonacoEditorValuenext to the existing Monaco helpers and readwindow.__monacoEditor__.getModel().getValue().That is not a new mechanism. It is the same source
uiFillMonacoEditoranduiClearMonacoEditoralready write to, and the same oneplugin-metadata.drawer-keeps-edits-on-failed-save.spec.tsalready reads. The flaky spec was the outlier for having a private DOM-scraping helper at all.It polls for non-empty content rather than reading once, because the drawer mounts its editor asynchronously and
window.__monacoEditor__can briefly still point at a previously mounted instance after a new drawer opens.routes.empty-plugin-config.spec.tshad the same defect, reading.view-linesinnerText. Its comment there ("Read Monaco editor content from the visible lines (not the textarea)") shows the textarea problem was already known, and the workaround reached for the other DOM source instead of the model. Converted as well, so the pattern does not survive anywhere ine2e/.Verification
Against a live APISIX instance from
e2e/server:plugin_metadata.crud-all-fields,plugin_metadata.crud-required-fields,plugin_metadata.list,routes.empty-plugin-config) pass 24/24 across 4 repeatsplugin_metadata.crud-all-fieldsalone passes 10/10 with--repeat-each=10pnpm lintclean at--max-warnings=0,tsc -bcleanOne honest caveat on the before/after. I cannot show a clean red-to-green locally, because on an idle machine the unpatched spec also passes (10/10 when I tried). The failure needs the paint to lose a roughly 50ms window, which happens under load. So the evidence that this is the right fix is the sampling table above, which is deterministic and reproducible, rather than a flip in pass rate. I also tried forcing it with CDP
Emulation.setCPUThrottlingRate; that does not work, because throttling slows the test driver along with the renderer and the relative timing barely moves.For the same reason, the earlier failure rates I quoted in #3466 (4 of 6 on
master) were measured while the machine was busy and should be read as "this fails regularly under load", not as a stable rate.Related issues
fixes #3466
Checklist: