fix(e2e): reliable UI delete-from-main for ephemeral prod canaries (#1771 follow-up) - #1804
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)
#1801addedpublishViaUi(Status:Ready → Publish Now) to push Decap into the PUBLISHED state. That is necessary but not sufficient:PUT /pulls/{N}/merge. Branch protection 422s it (6 required checks still pending ~10 min).admin/publish-via-auto-merge.jscatches the 422, labelscms/ready, and hands Decap a syntheticmerged:true. The create PR auto-merges for real only ~5–15 min later, so itscms/posts/<slug>editorial branch lingers.editorial_workflowmode Decap overridesloadEntry→loadUnpublishedEntry(withWorkflow.js). BecauseretrieveUnpublishedEntryDatastill finds the open editorial branch, the editor re-loads the entry as a DRAFT (currentStatusset,hasUnpublishedChanges=true).EditorToolbar.js:654then wires Delete toonClick={hasUnpublishedChanges ? onDeleteUnpublishedChanges : onDelete}. WithhasUnpublishedChanges=true, the click callsonDeleteUnpublishedChanges→ 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_fixturehide-filter is NOT the cause: atest_fixture: trueposts entry opens its editor fine by direct URL — the filter only hides LIST rows (verified, see below).Why
cms-delete-publishedworks (and these didn't)cms-delete-published.spec.jsuses 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:
waitForMerge()on the captured create-PR number — the real merge, not the synthetic Publish-Now ack.reopenForPublishedDelete()(new shared helper ine2e/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 hitsonDelete(delete from main → a delete PR), which the existingcms/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
test-repo,publish_mode: editorial_workflow, the samepostscollection +test_fixturehide-filter): a temporary spec proved (a) a PUBLISHEDtest_fixtureposts 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-1080→ 67 passed.--project chromium-desktop-3k --list.npm run lint(0 errors) +npm run format:checkclean.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)
_posts/2099-01-03-e2e-media-roundtrip.md, which is 404/gone from main (the old persistent fixture retired in the step-4 refactor). Stale — should be closed.cms/draft+cms/readylabels; stuck._e2ecanary-delete create PR — both stuck.test_fixtureephemeral posts accumulated on main (per the failure screenshot's "Show automated-test posts (5)" toggle).cms/draftonly) — leave it.🤖 Generated with Claude Code