Skip to content

fix(e2e): reliable UI delete-from-main for ephemeral prod canaries (#1771 follow-up) - #1804

Merged
Adam-S-Daniel merged 1 commit into
mainfrom
fix/1771-ui-delete-reliable
May 27, 2026
Merged

fix(e2e): reliable UI delete-from-main for ephemeral prod canaries (#1771 follow-up)#1804
Adam-S-Daniel merged 1 commit into
mainfrom
fix/1771-ui-delete-reliable

Conversation

@Adam-S-Daniel

Copy link
Copy Markdown
Owner

Problem

Issue #1771 step 4's ephemeral prod-canary specs (e2e/cms-publish-loop-prod-mutate.spec.js, e2e/cms-media-roundtrip.spec.js) create a born-published per-run _posts/2099-12-31-…-<runId>.md, serve it, then must DELETE it FROM MAIN via the Decap UI so the URL 404s. Run 26529125192 created+published+served fine, but the delete leg timed out at "Wait for the post to stop serving" — the deploy lane was quiescent and no delete-from-main PR was ever opened.

Root cause (proven)

#1801 added publishViaUi (Status:Ready → Publish Now) to push Decap into the PUBLISHED state. That is necessary but not sufficient:

  • "Publish Now" issues a synchronous merge PUT /pulls/{N}/merge. Branch protection 422s it (6 required checks still pending ~10 min). admin/publish-via-auto-merge.js catches the 422, labels cms/ready, and hands Decap a synthetic merged:true. The create PR auto-merges for real only ~5–15 min later, so its cms/posts/<slug> editorial branch lingers.
  • The delete leg re-navigated to the entry during that window. In editorial_workflow mode Decap overrides loadEntryloadUnpublishedEntry (withWorkflow.js). Because retrieveUnpublishedEntryData still finds the open editorial branch, the editor re-loads the entry as a DRAFT (currentStatus set, hasUnpublishedChanges=true).
  • EditorToolbar.js:654 then wires Delete to onClick={hasUnpublishedChanges ? onDeleteUnpublishedChanges : onDelete}. With hasUnpublishedChanges=true, the click calls onDeleteUnpublishedChanges → deletes only the draft branch, never the file on main. No delete-from-main PR opens; the URL never 404s.

The failure video confirms it: after publishViaUi, the editor toolbar still read "Status: Draft" + "Delete unpublished entry" (red), then ended on the Posts list.

The test_fixture hide-filter is NOT the cause: a test_fixture: true posts entry opens its editor fine by direct URL — the filter only hides LIST rows (verified, see below).

Why cms-delete-published works (and these didn't)

cms-delete-published.spec.js uses the same Publish-Now path but, crucially, by the time its URL serves the create PR has truly merged and its branch is gone — so the re-open lands a PUBLISHED entry → "Delete published entry" → onDelete (delete from main). The ephemeral specs raced ahead of the real merge.

Fix (mirrors the proven sequence)

Both specs now, before clicking delete:

  1. waitForMerge() on the captured create-PR number — the real merge, not the synthetic Publish-Now ack.
  2. reopenForPublishedDelete() (new shared helper in e2e/cms-editor-ui.js) poll-reloads the editor until Decap has dropped the now-merged editorial entry and re-loaded the published file: no Status chip and "Delete published entry" present. Only then is the delete clicked, so it hits onDelete (delete from main → a delete PR), which the existing cms/ready-label step picks up and the 404-wait confirms.

Create+publish+serve stay UI-driven. The afterAll Contents-API safety net is unchanged. No silent .catch() (silent-catch-lint clean).

Validation

  • Local editorial-backend repro (Decap test-repo, publish_mode: editorial_workflow, the same posts collection + test_fixture hide-filter): a temporary spec proved (a) a PUBLISHED test_fixture posts entry opens by direct URL and shows "Delete published entry" with no Status chip, and (b) the same entry seeded as an OPEN editorial draft shows "Delete unpublished entry". Both passed — confirming the affordance is driven purely by editorial state, and the hide-filter does not block the editor. (Temp spec removed before commit.)
  • TARGET=preview npx playwright test e2e/silent-catch-lint.test.js e2e/select-specs.test.js e2e/cms-recursion-churn.test.js e2e/cms-editor-ui.test.js --project chromium-desktop-108067 passed.
  • Both specs parse/list under --project chromium-desktop-3k --list.
  • npm run lint (0 errors) + npm run format:check clean.

Confidence: High on the root cause and the affordance state machine (proven against Decap source + a real editorial backend). The one thing the local backend cannot reproduce is the async merge→branch-removal timing of real GitHub auto-merge — waitForMerge + the poll-reload loop are designed precisely to absorb that lag, but the end-to-end timing only proves out on a real scheduled run.

Operational cruft observed (not addressed here — flagged for cleanup)

🤖 Generated with Claude Code

…1771 follow-up)

The ephemeral prod-canary specs (cms-publish-loop-prod-mutate,
cms-media-roundtrip) create a born-published per-run _posts/ entry,
serve it, then must DELETE it FROM MAIN via the Decap UI so the URL
404s. Run 26529125192 created+published+served fine, but the delete
leg never opened a delete-from-main PR and the 404-wait timed out.

Root cause (proven against Decap's source + a local editorial-backend
repro): "Publish Now" hits branch protection (checks pending), so
admin/publish-via-auto-merge.js catches the 422, labels cms/ready, and
hands Decap a SYNTHETIC merged:true. The create PR auto-merges for REAL
only ~5-15 min later, so its cms/posts/<slug> editorial branch lingers.
The delete leg re-navigated to the entry during that window; in
editorial_workflow mode Decap overrides loadEntry -> loadUnpublishedEntry
(withWorkflow.js), which re-loads the still-open editorial entry as a
DRAFT (currentStatus set, hasUnpublishedChanges=true). EditorToolbar.js
line 654 then wires Delete to onDeleteUnpublishedChanges -> drops only
the draft branch, never main. So no delete-from-main PR opens and the
URL never 404s. The failure video confirms it: after publishViaUi the
toolbar still read "Status: Draft" + "Delete unpublished entry".

The test_fixture hide-filter is NOT the cause: a test_fixture posts
entry opens its editor fine by direct URL (the filter only hides LIST
rows). Confirmed via a local test-repo editorial-backend repro: a
PUBLISHED test_fixture entry shows "Delete published entry"; the same
entry as an open editorial draft shows "Delete unpublished entry".

Fix (mirrors the proven cms-delete-published sequence, which works
because by the time its URL serves the create PR has truly merged and
its branch is gone): before deleting, both specs now
  1. waitForMerge() on the captured create PR number (real merge, not
     the synthetic Publish-Now ack), then
  2. reopenForPublishedDelete() (new shared helper) poll-reloads the
     editor until Decap drops the now-merged editorial entry and
     re-loads the published file -- no Status chip, "Delete published
     entry" present -- and only THEN clicks delete, so the click hits
     onDelete (delete from main -> a delete PR labelled cms/ready).

Create+publish+serve stay UI-driven; the afterAll Contents-API safety
net is unchanged; no silent .catch (silent-catch-lint clean).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions github-actions Bot added not-decap-created PR was not created by Decap CMS (head branch is not cms/<col>/<slug>) cms/draft Content draft — not ready for publish labels May 27, 2026
@Adam-S-Daniel
Adam-S-Daniel merged commit b535d39 into main May 27, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cms/draft Content draft — not ready for publish not-decap-created PR was not created by Decap CMS (head branch is not cms/<col>/<slug>)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant