diff --git a/src/__tests__/fixtures/github_dom_2026_07/github_pr_batch_open.html b/src/__tests__/fixtures/github_dom_2026_07/github_pr_batch_open.html new file mode 100644 index 0000000..0518090 --- /dev/null +++ b/src/__tests__/fixtures/github_dom_2026_07/github_pr_batch_open.html @@ -0,0 +1,4015 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + merge queue: checking main (4e28f16) and #1 together by mergify[bot] · Pull Request #4 · mergify-sandbox/browser-extension-fixtures + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + +
+
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ Open in github.dev + Open in a new github.dev tab + Open in codespace + + + + + + + + + + + + + + + + + + + + +

merge queue: checking main (4e28f16) and #1 together#4

Loading merge status
Draft
mergify[bot] wants to merge 4 commits into
mainfrom
mergify/merge-queue/c5dbc503b7
Draft

merge queue: checking main (4e28f16) and #1 together#4
mergify[bot] wants to merge 4 commits into
mainfrom
mergify/merge-queue/c5dbc503b7

Conversation

+ + + @mergify +
+
+
+

+
+ + @mergify + + + + mergify + Bot + + + + + + commented + + + Jul 17, 2026 + + + + + + + + + +
+ +

+ +
+
+
+ + + + + + + + + + +

+ + + + + +

+ + + + +
+
+
+ +
+ + + + + + +
+
+
+ +
+
+ + +
+ +

⏳ The pull request #1 is queued for merge and currently being checked. ⏳

+

Branch main (4e28f16) and #1 are queued together for merge.

+

This pull request has been created by Mergify to check the mergeability of #1.
+You don't need to do anything. Mergify will close this pull request automatically when it is complete.

+

Required conditions of queue rule fixtures for merge:

+
    +
  • status-success=fixture-ci
  • +
+

Required conditions to stay in the queue:

+ +
---
+checking_base_sha: 4e28f162826eb5f2c44b33a67828e09bd7fb3e28
+previous_check_retries: []
+previous_failed_batches: []
+pull_requests:
+  - number: 1
+    scopes: []
+scopes: []
+...
+
+ + + + +
+
+
+ +
+ +
+ +
+
+
+ + + +
+
+ + +
+ + + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + +
+ + + +
+
+
+

+ Select a reply +

+ +
+
+ +
+
+ +
+ +
+ + + Loading + + + +
+
+ +
+ + + + + + +
+
+ + + +
+ + +
+ +

+ The content you are editing has changed. + Please copy your edits and refresh the page. +

+ + + +
+ + + + + + + + +
+
+
+
+ + + Slash commands +
+ +
+ Preview +
+ +
+
+ + Loading + +
+
+
+ +
+
+
+
+ + + Slash commands +
+ +
+ Preview +
+ +
+
+
+ +

An unexpected error has occurred

+
+
+
+
+ + +
+ +
+ +
+
+ +
+ + + + + + + + + + + + +
+
+
+ +
+
+
+

Nothing to preview

+
+
+ +
+
+
+ +
+ + + + + + + +
+
+ + +
+ + +
+ +
+ + + +
+ + + +
+
+ + +
+ +
+
+ +
+ + +
+
+
+
+ +
+ + + +
+ + Mention @copilot in a comment to make changes to this pull request. + +
+
+ +
+ + +
+ +
+
+ +
+
fixture-author + +and others + added 4 commits + July 17, 2026 15:55
+
+
+ +
+
+
+
+ +
+
+ + @fixture-author +
+
+ +
+ + feat: change one + + +
+ +
+ + + + + + +
+
+
+

+ Verified +

+
+
+ +
+
+ This commit was signed with the committer’s verified signature. + The key has expired. +
+
+ +
+
+ +
+
+ +
+ +
+ + + + + fixture-author + Julian Maurin + +
+ +
+
+ +
+ +
+ +
+
+ + +
+
+ +
+ + 5706423 + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + @fixture-author +
+
+ + + +
+ + + + + + +
+
+
+

+ Verified +

+
+
+ +
+
+ This commit was signed with the committer’s verified signature. + The key has expired. +
+
+ +
+
+ +
+
+ +
+ +
+ + + + + fixture-author + Julian Maurin + +
+ +
+
+ +
+ +
+ +
+
+ + +
+
+ +
+ + 75d9837 + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + @fixture-author +
+
+ + + +
+ + + + + + +
+
+
+

+ Verified +

+
+
+ +
+
+ This commit was signed with the committer’s verified signature. + The key has expired. +
+
+ +
+
+ +
+
+ +
+ +
+ + + + + fixture-author + Julian Maurin + +
+ +
+
+ +
+ +
+ +
+
+ + +
+ + + + + + +
+ +
+
+ +
+ + 521e34a + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + @mergify +
+
+ +
+ + Merge of #1 + + +
+ +
+ + + +
+ +
+
+ + +
+
+ +
+ + a9fc458 + +
+
+
+
+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+ @mergify +mergify +Bot + + + mentioned this pull request + + Jul 17, 2026 + +
+ +
+ + + +
+ + Open + +
+
+ + + + +
+
+ + + +
+ + + + + + +
+
+ +
+ + + +
+ + +
+ +
+ + + + +
Loading
+ + +
+
+ @fixture-author +
+
+
+ + +
+ +

Add a comment

+ + + +
+
+ + +
+ + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + +
+ + + +
+
+
+

+ Select a reply +

+ +
+
+ +
+
+ +
+ +
+ + + Loading + + + +
+
+ +
+ + + + + + +
+
+ + + +
+ + + + +
+ + + + +
+
+
+
+ + + Slash commands +
+ +
+ Preview +
+ +
+
+ + Loading + +
+
+
+ +
+
+
+
+ + + Slash commands +
+ +
+ Preview +
+ +
+
+
+ +

An unexpected error has occurred

+
+
+
+
+ + + +
+ + + +
+ +
+ + + + + +
+
+ + + +
+
+ +
+ + + + + + + + + + + + +
+
+ +
+ +
+
+ + + +
+
+
+ +
+ +
+ +
+ + + + + + +
+
+ + +
+ +
+ +
+ +
+
+ + + +
+
+
+
+
+ +
+
+
+
+ + + Remember, contributions to this repository should follow + our + GitHub Community Guidelines. + +
+ + +
+ + ProTip! + Add .patch or .diff to the end of URLs for Git’s plaintext views. +
+
+ + +
+
+
+ + + + + + + + +
+ + + +
+ + + + Labels + + + +
+ Apply labels to this pull request + +
+
+
+ + + Loading + +
+
+ + + + +
+
+ + +
+ None yet +
+ +
+ + + + +
+
+
+ + + + Projects + + + +
+ Projects + +
+ + + + +
+
+ +
+
+ + None yet + + + +
+ + + + + + + + + +
+
+
+ +
+ +
+ + + + Development + + + +
+ Link an issue from this repository + +
+ + + + +
+
+ + + + +

Successfully merging this pull request may close these issues.

+ + + +
+

None yet

+
+ + +
+
+
+
+ +
+ + + + + +
+
+

+ 1 participant +

+
+ + @fixture-author +
+
+
+ + + + + +
+
+ + + Lock conversation + + +
+
+ +

+ Lock conversation on this pull request +

+
+
+
    +
  • Other users can’t add new comments to this pull request.
  • +
  • + You and other members of teams with + write access + to this repository can still leave comments that others can see. +
  • +
  • You can always unlock this pull request again in the future.
  • +
+ +
+
+ +
+
+ +

+ Optionally, choose a reason for locking that others can see. Learn more about when + it’s appropriate to lock conversations. +

+
+
+
+ +
+
+
+ + + + + + + + +
+
+ + + Archive pull request + + + +
+
+ +

+ Archive this pull request +

+
+
+

Are you sure you want to archive this pull request?

+
    +
  • The pull request will be closed and locked.
  • +
  • Only repository administrators will be able to see it.
  • +
  • A repository administrator can unarchive this pull request at any time.
  • +
+
+ +
+
+
+ + +
+ +
+
+ + + + +
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + \ No newline at end of file diff --git a/src/__tests__/queue.test.js b/src/__tests__/queue.test.js index 3de9fb1..a6c93be 100644 --- a/src/__tests__/queue.test.js +++ b/src/__tests__/queue.test.js @@ -672,7 +672,7 @@ const { isPullRequestDraft, getMergeQueueLink, } = require("../queue"); -const { injectFixtureInDOM, LEGACY_ERA } = require("./utils"); +const { injectFixtureInDOM, LEGACY_ERA, CURRENT_ERA } = require("./utils"); // Minimal PR-page DOM: a status pill (draft/closed), the header-meta author // link, and the classic base-ref span. Any argument left undefined is omitted @@ -869,10 +869,26 @@ describe("batch-PR row treatment", () => { }); }); + test("labels the button-less row with a 'Merge-queue batch PR' pill", () => { + setPrDom({ draft: true, author: "mergify", baseRef: "main" }); + for (const variant of ["default", "sidebar"]) { + const row = buildMergifyRow(variant); + const pill = row.querySelector( + '[data-mergify-state-pill][data-state="batch"]', + ); + expect(pill).not.toBeNull(); + expect(pill.textContent.trim()).toBe("Merge-queue batch PR"); + } + }); + test("a normal draft PR keeps its queue and command buttons", () => { setPrDom({ draft: true, author: "octocat", baseRef: "main" }); const row = buildMergifyRow(); expect(row.querySelector("[data-mergify-queue-btn]")).not.toBeNull(); + // ... and shows no batch pill: the pill is exclusive to batch PRs. + expect( + row.querySelector('[data-mergify-state-pill][data-state="batch"]'), + ).toBeNull(); const buttons = commandButtonsOf(row); expect(buttons.map((b) => b.textContent)).toEqual([ "Refresh", @@ -943,3 +959,82 @@ describe("batch-PR informational links", () => { expect(linkLabels(row)).toEqual(["queue", "logs"]); }); }); + +describe("batch-PR real fixtures (current GitHub DOM)", () => { + beforeEach(() => { + document.body.innerHTML = ""; + window.history.replaceState( + {}, + "", + "/mergify-sandbox/browser-extension-fixtures/pull/4", + ); + }); + afterEach(() => { + document.body.innerHTML = ""; + }); + + // Guards against selector drift: detection reads the current React PR header + // (pull-body author marker + branch-name links), not the retired + // .gh-header-meta / .commit-ref.base-ref the earlier synthetic tests build. + test("detects the batch PR and reads its base ref on the current DOM", () => { + injectFixtureInDOM("github_pr_batch_open", CURRENT_ERA); + expect(isMergeQueueBatchPr()).toBe(true); + expect(getBaseRef()).toBe("main"); + expect(deriveQueueButtonState()).toBe("batch"); + }); + + test("renders the batch row: pill, no buttons, live queue/logs links", () => { + injectFixtureInDOM("github_pr_batch_open", CURRENT_ERA); + const row = buildMergifyRow(); + const pill = row.querySelector( + '[data-mergify-state-pill][data-state="batch"]', + ); + expect(pill?.textContent.trim()).toBe("Merge-queue batch PR"); + expect(row.querySelectorAll("button").length).toBe(0); + const links = [...row.querySelectorAll("a")].map((a) => + a.textContent.trim(), + ); + expect(links).toContain("queue"); + expect(links).toContain("logs"); + // Queue link deep-links to the batch drawer, branch = the base ref. + const queueHref = [...row.querySelectorAll("a")].find( + (a) => a.textContent.trim() === "queue", + )?.href; + expect(queueHref).toContain("batch_pr=4"); + expect(queueHref).toContain("branch=main"); + }); + + // Regression for the dequeue-button hazard. Real merge-queue batch PRs carry + // NO "Mergify Merge Queue" check (verified against production batch PRs — the + // check lives on the embarked PRs), so the fixture has none; the batch guard + // is what guarantees no live "Remove from merge queue" button here. The + // complementary "even when a queued signal IS present" case is covered by the + // rich-payload ("checking") test in batch-PR row treatment. + test("shows no live dequeue button", () => { + injectFixtureInDOM("github_pr_batch_open", CURRENT_ERA); + const row = buildMergifyRow(); + const dequeue = [...row.querySelectorAll("button")].find((b) => + /remove from merge queue/i.test(b.textContent), + ); + expect(dequeue).toBeUndefined(); + expect(row.querySelector("[data-mergify-queue-btn]")).toBeNull(); + }); + + // A closed batch PR loses its draft pill, so it's no longer detected as an + // open batch (isMergeQueueBatchPr) — but Mergify authorship still marks it as + // batch context, so the row drops the (now pointless) queue link and keeps + // the batch-scoped activity log. Proves isPullRequestAuthoredByMergify reads + // the Mergify author on a *closed* PR's current DOM too. + test("closed batch PR: no queue link, batch-scoped logs link, no buttons", () => { + injectFixtureInDOM("github_pr_batch_closed", CURRENT_ERA); + const row = buildMergifyRow(); + const labels = [...row.querySelectorAll("a")] + .map((a) => a.textContent.trim()) + .filter((t) => t === "queue" || t === "logs"); + expect(labels).toEqual(["logs"]); + const logsHref = [...row.querySelectorAll("a")].find( + (a) => a.textContent.trim() === "logs", + )?.href; + expect(logsHref).toContain("batch_pull=4"); + }); +}); diff --git a/src/queue.js b/src/queue.js index efec891..412bd37 100644 --- a/src/queue.js +++ b/src/queue.js @@ -151,9 +151,14 @@ export function isPullRequestDraft() { } // True when the PR opener is the Mergify GitHub App. Scope the mergify-author -// check to the PR header meta so a Mergify bot *comment* further down the -// timeline can't be mistaken for the PR author. +// check to the PR description (the opening comment) so a Mergify bot *comment* +// further down the timeline can't be mistaken for the PR author. export function isPullRequestAuthoredByMergify() { + // Current PR page: the opening comment carries the pull-body marker; its + // author is the PR opener. Present across DOM eras, so it's the primary + // scope. Falls back to the classic header meta for the minimal/legacy DOM. + const prBody = document.querySelector(".js-command-palette-pull-body"); + if (prBody) return isMergifyBotComment(prBody); const header = document.querySelector(".gh-header-meta"); return header ? isMergifyBotComment(header) : false; } @@ -586,11 +591,19 @@ export function getBatchActivityLogLink() { return getActivityLogLink("batch_pull"); } -// Base branch of the current PR, read from the classic PR UI. The -// `.commit-ref.base-ref` span is titled "owner/repo:branch"; ref names can't -// contain ":", so everything after the first colon is the branch (which may -// itself contain "/"). +// Base branch of the current PR. The header reads "into from ", so +// the first branch-name link is the base; read the branch off its /tree/ +// href so a slashed ref survives. Falls back to the classic PR UI's +// `.commit-ref.base-ref` span (titled "owner/repo:branch"; ref names can't +// contain ":", so everything after the first colon is the branch). export function getBaseRef() { + const href = document + .querySelector('a[class*="BranchName"]') + ?.getAttribute("href"); + const marker = "/tree/"; + const idx = href ? href.indexOf(marker) : -1; + if (idx !== -1) return href.slice(idx + marker.length) || null; + const title = document .querySelector(".commit-ref.base-ref") ?.getAttribute("title"); @@ -648,6 +661,10 @@ function buildLegacyDefaultRow() { row.appendChild(buttons); } + // Batch PRs carry no action buttons, so the pill is what tells the reader + // the row belongs to a merge-queue batch draft rather than being empty. + if (state === "batch") row.appendChild(buildStatePill("batch")); + const info = buildBrandAndLinks(); if (state === "merged") { @@ -687,6 +704,9 @@ function buildLegacySidebarRow() { row.appendChild(buttons); } + // See buildLegacyDefaultRow: the pill labels the button-less batch row. + if (state === "batch") row.appendChild(buildStatePill("batch")); + const secondLine = document.createElement("div"); secondLine.style.cssText = "display:flex;align-items:center;justify-content:space-between;gap:8px;"; @@ -905,6 +925,7 @@ const STATE_PILL_COPY = { }, merged: { label: "Merged", note: "" }, dequeued: { label: "Dequeued", note: "removed from queue" }, + batch: { label: "Merge-queue batch PR", note: "" }, }; const STATE_PILL_COLORS = { @@ -945,6 +966,14 @@ const STATE_PILL_COLORS = { border: "#da363366", pulse: false, }, + // Informational, not a live queue state: a calm accent-blue that reads as + // "for your information" rather than pending/error. + batch: { + bg: "#1f6feb22", + fg: "#58a6ff", + border: "#1f6feb55", + pulse: false, + }, }; let _mqpStyleInjected = false; @@ -1045,6 +1074,8 @@ function buildButtonsRow(queueButtonState) { appendCommandButtons(buttons); row.appendChild(buttons); } + // See buildLegacyDefaultRow: the pill labels the button-less batch row. + if (queueButtonState === "batch") row.appendChild(buildStatePill("batch")); row.appendChild(buildBrandAndLinks()); return row; }