Skip to content

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

@bjagg

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions