Skip to content

cms config save path is brittle to automate (no explicit Save button; CKEditor 4 setData→submit race) #552

Description

@bjagg

Problem

src/main/webapp/WEB-INF/jsp/cms/configureContent.jsp (the cms portlet variant of SimpleContentPortlet) has no plain Save button on the form. Saving relies entirely on the Save plugin in the CKEditor 4 toolbar (<a class=\"cke_button__save\">), which submits the underlying form via an internal callback chain. The sibling webcomponent variant has an explicit <button type=\"submit\"> and saves cleanly.

Two consequences:

  1. Brittle for end-users. If anything strips or disables the CKE Save plugin (custom build, future major-version upgrade, plugin reorder, etc.), the form has no usable submit affordance. The visible "Return without saving" button is on a separate form whose action is ?pP_action=cancel.
  2. Brittle for automated tests. Any E2E flow that programmatically populates the editor and submits hits a CKEditor 4 race: editor.setData(html, callback) resolves before CKE finishes wiring its internal model back to the textarea, and the editor's submit-time hook re-syncs from a stale model. The form posts the previous content roughly half the time. This isn't theoretical — we hit this writing Playwright coverage for SimpleContent against uPortal-start (tests/ux/portlets/simple-content.spec.ts). The race never fired in isolated runs but reproduced ~50% in the full suite.

What we're shipping in tests as a workaround

const ck = window.CKEDITOR;
const id = Object.keys(ck.instances)[0];
ck.instances[id].destroy(true);   // \`true\` = updateElement on teardown

const textarea = document.querySelector(\"textarea[id$='content']\");
textarea.value = newContent;
textarea.form.submit();

Tearing the editor down before touching the textarea removes every CKE surface that could re-sync, so the form posts exactly what we set. We didn't find a setData-only sequence (event-listening on dataReady, double updateElement(), micro-task yields) that was reliable across full-suite runs.

Proposed product fix

Mirror the webcomponent variant in the cms variant: add an explicit submit button to cms/configureContent.jsp:

<form:form id=\"${n}contentForm\" modelAttribute=\"form\" action=\"${formUrl}\" method=\"post\">
    <form:textarea id=\"${n}content\" path=\"content\"/>
    <div class=\"btn-group\">
      <button type=\"submit\" name=\"update\" value=\"update\" class=\"btn-primary\">
        <spring:message code=\"configurationForm.save\"/>
      </button>
    </div>
</form:form>

Benefits:

  • Saving no longer depends on CKEditor's Save plugin being present and wired.
  • The toolbar Save can stay (it still works); this is an additive belt-and-suspenders.
  • Automated tests get a stable target. Click the visible button, done — no destroy(true) dance.

Repro for the test race (background, not required for the product fix)

  1. Open any cms-variant portlet in config mode (e.g. /p/what-is-uportal/max/render.uP?pCm=config).
  2. From DevTools / Playwright, run editor.setData('<p>NEW</p>', () => { editor.updateElement(); document.querySelector('form[id$=\"contentForm\"]').submit(); }).
  3. Inspect the POST body in the network tab. About half the time, content= is the previous HTML instead of <p>NEW</p>.
  4. Replace the inner sequence with editor.destroy(true); textarea.value = '<p>NEW</p>'; textarea.form.submit(); — the POST body is correct every time.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions