From ac87fa1969371f9ff389761e88eadb8a9a1b8982 Mon Sep 17 00:00:00 2001 From: leonace924 Date: Mon, 5 Jan 2026 19:58:52 -0500 Subject: [PATCH 1/8] feat: add rss title field and handle rss link from request --- db/knex_init_db.js | 1 + .../2026-01-05-0000-add-rss-title.js | 12 +++ server/model/status_page.js | 76 +++++++++++++++---- server/routers/status-page-router.js | 2 +- .../status-page-socket-handler.js | 1 + src/lang/en.json | 2 + src/pages/StatusPage.vue | 9 +++ 7 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 db/knex_migrations/2026-01-05-0000-add-rss-title.js diff --git a/db/knex_init_db.js b/db/knex_init_db.js index 46bff4bfa..95a6fb55f 100644 --- a/db/knex_init_db.js +++ b/db/knex_init_db.js @@ -202,6 +202,7 @@ async function createTables() { table.text("footer_text"); table.text("custom_css"); table.boolean("show_powered_by").notNullable().defaultTo(true); + table.string("rss_title", 255); table.string("google_analytics_tag_id"); }); diff --git a/db/knex_migrations/2026-01-05-0000-add-rss-title.js b/db/knex_migrations/2026-01-05-0000-add-rss-title.js new file mode 100644 index 000000000..33f775c65 --- /dev/null +++ b/db/knex_migrations/2026-01-05-0000-add-rss-title.js @@ -0,0 +1,12 @@ +exports.up = function (knex) { + return knex.schema + .alterTable("status_page", function (table) { + table.string("rss_title", 255); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("status_page", function (table) { + table.dropColumn("rss_title"); + }); +}; diff --git a/server/model/status_page.js b/server/model/status_page.js index 500473587..ecb73088b 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -7,6 +7,7 @@ const analytics = require("../analytics/analytics"); const { marked } = require("marked"); const { Feed } = require("feed"); const config = require("../config"); +const { setting } = require("../util-server"); const { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE, DOWN } = require("../../src/util"); @@ -22,16 +23,17 @@ class StatusPage extends BeanModel { * Handle responses to RSS pages * @param {Response} response Response object * @param {string} slug Status page slug + * @param {Request} request Request object * @returns {Promise} */ - static async handleStatusPageRSSResponse(response, slug) { + static async handleStatusPageRSSResponse(response, slug, request) { let statusPage = await R.findOne("status_page", " slug = ? ", [ slug ]); if (statusPage) { response.type("application/rss+xml"); - response.send(await StatusPage.renderRSS(statusPage, slug)); + response.send(await StatusPage.renderRSS(statusPage, slug, request)); } else { response.status(404).send(UptimeKumaServer.getInstance().indexHTML); } @@ -64,20 +66,29 @@ class StatusPage extends BeanModel { /** * SSR for RSS feed - * @param {statusPage} statusPage object - * @param {slug} slug from router - * @returns {Promise} the rendered html + * @param {StatusPage} statusPage Status page object + * @param {string} slug Status page slug + * @param {Request} request Express request object + * @returns {Promise} The rendered RSS XML */ - static async renderRSS(statusPage, slug) { + static async renderRSS(statusPage, slug, request) { const { heartbeats, statusDescription } = await StatusPage.getRSSPageData(statusPage); - let proto = config.isSSL ? "https" : "http"; - let host = `${proto}://${config.hostname || "localhost"}:${config.port}/status/${slug}`; + // Build the feed URL, respecting proxy headers if trustProxy is enabled + const feedUrl = await StatusPage.buildRSSUrl(slug, request); + + // Use custom RSS title if set, otherwise fall back to status page title + let feedTitle = "Uptime Kuma RSS Feed"; + if (statusPage.rss_title) { + feedTitle = statusPage.rss_title; + } else if (statusPage.title) { + feedTitle = `${statusPage.title} RSS Feed`; + } const feed = new Feed({ - title: "uptime kuma rss feed", - description: `current status: ${statusDescription}`, - link: host, + title: feedTitle, + description: `Current status: ${statusDescription}`, + link: feedUrl, language: "en", // optional, used only in RSS 2.0, possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes updated: new Date(), // optional, default = today }); @@ -85,8 +96,9 @@ class StatusPage extends BeanModel { heartbeats.forEach(heartbeat => { feed.addItem({ title: `${heartbeat.name} is down`, - description: `${heartbeat.name} has been down since ${heartbeat.time}`, - id: heartbeat.monitorID, + description: `${heartbeat.name} has been down since ${heartbeat.time} UTC`, + id: `${heartbeat.monitorID}-${heartbeat.time}`, + link: feedUrl, date: new Date(heartbeat.time), }); }); @@ -94,6 +106,38 @@ class StatusPage extends BeanModel { return feed.rss2(); } + /** + * Build RSS feed URL, handling proxy headers + * @param {string} slug Status page slug + * @param {Request} request Express request object + * @returns {Promise} The full URL for the RSS feed + */ + static async buildRSSUrl(slug, request) { + if (request) { + const trustProxy = await setting("trustProxy"); + + // Determine protocol (check X-Forwarded-Proto if behind proxy) + let proto = request.protocol; + if (trustProxy && request.headers["x-forwarded-proto"]) { + proto = request.headers["x-forwarded-proto"].split(",")[0].trim(); + } + + // Determine host (check X-Forwarded-Host if behind proxy) + let host = request.get("host"); + if (trustProxy && request.headers["x-forwarded-host"]) { + host = request.headers["x-forwarded-host"]; + } + + return `${proto}://${host}/status/${slug}`; + } + + // Fallback to config values + const proto = config.isSSL ? "https" : "http"; + const host = config.hostname || "localhost"; + const port = config.port; + return `${proto}://${host}:${port}/status/${slug}`; + } + /** * SSR for status pages * @param {string} indexHTML HTML page to render @@ -415,7 +459,8 @@ class StatusPage extends BeanModel { analyticsScriptUrl: this.analytics_script_url, analyticsType: this.analytics_type, showCertificateExpiry: !!this.show_certificate_expiry, - showOnlyLastHeartbeat: !!this.show_only_last_heartbeat + showOnlyLastHeartbeat: !!this.show_only_last_heartbeat, + rssTitle: this.rss_title, }; } @@ -441,7 +486,8 @@ class StatusPage extends BeanModel { analyticsScriptUrl: this.analytics_script_url, analyticsType: this.analytics_type, showCertificateExpiry: !!this.show_certificate_expiry, - showOnlyLastHeartbeat: !!this.show_only_last_heartbeat + showOnlyLastHeartbeat: !!this.show_only_last_heartbeat, + rssTitle: this.rss_title, }; } diff --git a/server/routers/status-page-router.js b/server/routers/status-page-router.js index 6e57451f1..78a000be5 100644 --- a/server/routers/status-page-router.js +++ b/server/routers/status-page-router.js @@ -22,7 +22,7 @@ router.get("/status/:slug", cache("5 minutes"), async (request, response) => { router.get("/status/:slug/rss", cache("5 minutes"), async (request, response) => { let slug = request.params.slug; slug = slug.toLowerCase(); - await StatusPage.handleStatusPageRSSResponse(response, slug); + await StatusPage.handleStatusPageRSSResponse(response, slug, request); }); router.get("/status", cache("5 minutes"), async (request, response) => { diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index e42b13607..76f6775df 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -164,6 +164,7 @@ module.exports.statusPageSocketHandler = (socket) => { statusPage.footer_text = config.footerText; statusPage.custom_css = config.customCSS; statusPage.show_powered_by = config.showPoweredBy; + statusPage.rss_title = config.rssTitle; statusPage.show_only_last_heartbeat = config.showOnlyLastHeartbeat; statusPage.show_certificate_expiry = config.showCertificateExpiry; statusPage.modified_date = R.isoDateTime(); diff --git a/src/lang/en.json b/src/lang/en.json index 5bc546dd7..b7763bc61 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -388,6 +388,8 @@ "Proxy": "Proxy", "Date Created": "Date Created", "Footer Text": "Footer Text", + "RSS Title": "RSS Title", + "Leave blank to use status page title": "Leave blank to use status page title", "Refresh Interval": "Refresh Interval", "Refresh Interval Description": "The status page will do a full site refresh every {0} seconds", "Show Powered By": "Show Powered By", diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index eb7b7f780..14a20a931 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -34,6 +34,15 @@ + +
+ + +
+ {{ $t("Leave blank to use status page title") }} +
+
+
From c43ba1c8af89c73c58f5f66167e4db8e3678b4dc Mon Sep 17 00:00:00 2001 From: leonace924 Date: Mon, 5 Jan 2026 20:04:58 -0500 Subject: [PATCH 2/8] feat: add rss testing --- test/e2e/specs/status-page.spec.js | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js index 360fa464a..456322817 100644 --- a/test/e2e/specs/status-page.spec.js +++ b/test/e2e/specs/status-page.spec.js @@ -283,4 +283,78 @@ test.describe("Status Page", () => { expect(rssContent).toContain(""); }); + test("RSS feed uses custom title and status page title", async ({ page }, testInfo) => { + test.setTimeout(60000); + + const statusPageTitle = "My Service Status"; + const customRssTitle = "Custom RSS Feed Title"; + + await page.goto("./add"); + await login(page); + + // Create a monitor + await expect(page.getByTestId("monitor-type-select")).toBeVisible(); + await page.getByTestId("monitor-type-select").selectOption("http"); + await page.getByTestId("friendly-name-input").fill("Test Monitor"); + await page.getByTestId("url-input").fill("https://example.com"); + await page.getByTestId("save-button").click(); + await page.waitForURL("/dashboard/*"); + + // Create a status page with a title + await page.goto("./add-status-page"); + await page.getByTestId("name-input").fill(statusPageTitle); + await page.getByTestId("slug-input").fill("rss-test"); + await page.getByTestId("submit-button").click(); + await page.waitForURL("/status/rss-test?edit"); + + // Add a group and monitor + await page.getByTestId("add-group-button").click(); + await page.getByTestId("group-name").fill("Test Group"); + await page.getByTestId("monitor-select").click(); + await page.getByTestId("monitor-select").getByRole("option", { name: "Test Monitor" }).click(); + + // Save without custom RSS title first + await page.getByTestId("save-button").click(); + await expect(page.getByTestId("edit-sidebar")).toHaveCount(0); + + // Fetch RSS feed - should use status page title + let rssResponse = await page.request.get("/status/rss-test/rss"); + expect(rssResponse.status()).toBe(200); + let rssContent = await rssResponse.text(); + + // Verify RSS feed uses status page title as fallback + expect(rssContent).toContain(`<![CDATA[${statusPageTitle} RSS Feed]]>`); + + // Verify RSS link uses the correct domain (not localhost hardcoded) + expect(rssContent).toContain("http://"); + expect(rssContent).toContain("/status/rss-test"); + + await testInfo.attach("rss-feed-default-title.xml", { + body: rssContent, + contentType: "application/xml" + }); + + // Now set a custom RSS title + await page.getByTestId("edit-button").click(); + await expect(page.getByTestId("edit-sidebar")).toHaveCount(1); + await page.getByTestId("rss-title-input").fill(customRssTitle); + await page.getByTestId("save-button").click(); + await expect(page.getByTestId("edit-sidebar")).toHaveCount(0); + + // Fetch RSS feed again - should use custom RSS title + rssResponse = await page.request.get("/status/rss-test/rss"); + expect(rssResponse.status()).toBe(200); + rssContent = await rssResponse.text(); + + // Verify RSS feed uses custom title + expect(rssContent).toContain(`<![CDATA[${customRssTitle}]]>`); + + await testInfo.attach("rss-feed-custom-title.xml", { + body: rssContent, + contentType: "application/xml" + }); + + await screenshot(testInfo, page); + }); + }); From 94e9005002f860bb9ce1ac5650dec5e136f3c4cf Mon Sep 17 00:00:00 2001 From: leonace924 Date: Mon, 5 Jan 2026 20:12:37 -0500 Subject: [PATCH 3/8] fix: update the migration js --- db/knex_migrations/2026-01-05-0000-add-rss-title.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/db/knex_migrations/2026-01-05-0000-add-rss-title.js b/db/knex_migrations/2026-01-05-0000-add-rss-title.js index 33f775c65..f0d947eba 100644 --- a/db/knex_migrations/2026-01-05-0000-add-rss-title.js +++ b/db/knex_migrations/2026-01-05-0000-add-rss-title.js @@ -1,8 +1,11 @@ -exports.up = function (knex) { - return knex.schema - .alterTable("status_page", function (table) { +exports.up = async function (knex) { + // Check if column already exists (created by knex_init_db.js for fresh installs) + const hasColumn = await knex.schema.hasColumn("status_page", "rss_title"); + if (!hasColumn) { + await knex.schema.alterTable("status_page", function (table) { table.string("rss_title", 255); }); + } }; exports.down = function (knex) { From 0b0f0175080e94d6ce048a5ebeaf9db20b9cbe8e Mon Sep 17 00:00:00 2001 From: leonace924 Date: Mon, 5 Jan 2026 20:19:10 -0500 Subject: [PATCH 4/8] fix: update e2e testing --- test/e2e/specs/status-page.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js index 456322817..12211bd28 100644 --- a/test/e2e/specs/status-page.spec.js +++ b/test/e2e/specs/status-page.spec.js @@ -323,7 +323,7 @@ test.describe("Status Page", () => { let rssContent = await rssResponse.text(); // Verify RSS feed uses status page title as fallback - expect(rssContent).toContain(`<![CDATA[${statusPageTitle} RSS Feed]]>`); + expect(rssContent).toContain(`${statusPageTitle} RSS Feed`); // Verify RSS link uses the correct domain (not localhost hardcoded) expect(rssContent).toContain("http://"); @@ -347,7 +347,7 @@ test.describe("Status Page", () => { rssContent = await rssResponse.text(); // Verify RSS feed uses custom title - expect(rssContent).toContain(`<![CDATA[${customRssTitle}]]>`); + expect(rssContent).toContain(`${customRssTitle}`); await testInfo.attach("rss-feed-custom-title.xml", { body: rssContent, From ad1b0c10a03f61ab274871182ac616f8219a2d85 Mon Sep 17 00:00:00 2001 From: leonace924 Date: Mon, 5 Jan 2026 20:26:15 -0500 Subject: [PATCH 5/8] fix: address the comments --- db/knex_init_db.js | 1 - server/model/status_page.js | 11 ++++------- src/pages/StatusPage.vue | 18 +++++++++--------- test/e2e/specs/status-page.spec.js | 3 +-- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/db/knex_init_db.js b/db/knex_init_db.js index 95a6fb55f..46bff4bfa 100644 --- a/db/knex_init_db.js +++ b/db/knex_init_db.js @@ -202,7 +202,6 @@ async function createTables() { table.text("footer_text"); table.text("custom_css"); table.boolean("show_powered_by").notNullable().defaultTo(true); - table.string("rss_title", 255); table.string("google_analytics_tag_id"); }); diff --git a/server/model/status_page.js b/server/model/status_page.js index ecb73088b..e43fbe2ae 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -32,8 +32,9 @@ class StatusPage extends BeanModel { ]); if (statusPage) { + const feedUrl = await StatusPage.buildRSSUrl(slug, request); response.type("application/rss+xml"); - response.send(await StatusPage.renderRSS(statusPage, slug, request)); + response.send(await StatusPage.renderRSS(statusPage, feedUrl)); } else { response.status(404).send(UptimeKumaServer.getInstance().indexHTML); } @@ -67,16 +68,12 @@ class StatusPage extends BeanModel { /** * SSR for RSS feed * @param {StatusPage} statusPage Status page object - * @param {string} slug Status page slug - * @param {Request} request Express request object + * @param {string} feedUrl The URL for the RSS feed * @returns {Promise} The rendered RSS XML */ - static async renderRSS(statusPage, slug, request) { + static async renderRSS(statusPage, feedUrl) { const { heartbeats, statusDescription } = await StatusPage.getRSSPageData(statusPage); - // Build the feed URL, respecting proxy headers if trustProxy is enabled - const feedUrl = await StatusPage.buildRSSUrl(slug, request); - // Use custom RSS title if set, otherwise fall back to status page title let feedTitle = "Uptime Kuma RSS Feed"; if (statusPage.rss_title) { diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index 14a20a931..81ae55e10 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -34,15 +34,6 @@
- -
- - -
- {{ $t("Leave blank to use status page title") }} -
-
-
@@ -125,6 +116,15 @@
+ +
+ + +
+ {{ $t("Leave blank to use status page title") }} +
+
+
{{ $t("Custom CSS") }}
diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js index 12211bd28..183709cad 100644 --- a/test/e2e/specs/status-page.spec.js +++ b/test/e2e/specs/status-page.spec.js @@ -326,8 +326,7 @@ test.describe("Status Page", () => { expect(rssContent).toContain(`${statusPageTitle} RSS Feed`); // Verify RSS link uses the correct domain (not localhost hardcoded) - expect(rssContent).toContain("http://"); - expect(rssContent).toContain("/status/rss-test"); + expect(rssContent).toMatch(/https?:\/\/[^<]+\/status\/rss-test<\/link>/); await testInfo.attach("rss-feed-default-title.xml", { body: rssContent, From d22f20175e1bd378991afc4fdd20f49f93a14834 Mon Sep 17 00:00:00 2001 From: Pegasus <42954461+leonace924@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:45:20 -0500 Subject: [PATCH 6/8] Update db/knex_migrations/2026-01-05-0000-add-rss-title.js Co-authored-by: Frank Elsinga --- db/knex_migrations/2026-01-05-0000-add-rss-title.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/db/knex_migrations/2026-01-05-0000-add-rss-title.js b/db/knex_migrations/2026-01-05-0000-add-rss-title.js index f0d947eba..0d1372d6b 100644 --- a/db/knex_migrations/2026-01-05-0000-add-rss-title.js +++ b/db/knex_migrations/2026-01-05-0000-add-rss-title.js @@ -1,11 +1,7 @@ exports.up = async function (knex) { - // Check if column already exists (created by knex_init_db.js for fresh installs) - const hasColumn = await knex.schema.hasColumn("status_page", "rss_title"); - if (!hasColumn) { - await knex.schema.alterTable("status_page", function (table) { - table.string("rss_title", 255); - }); - } + await knex.schema.alterTable("status_page", function (table) { + table.string("rss_title", 255); + }); }; exports.down = function (knex) { From e4cd30b4bfb4adeee69a42560d1b496860e7b2d4 Mon Sep 17 00:00:00 2001 From: leonace924 Date: Mon, 5 Jan 2026 20:52:26 -0500 Subject: [PATCH 7/8] fix: remove the test --- test/e2e/specs/status-page.spec.js | 73 ------------------------------ 1 file changed, 73 deletions(-) diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js index 183709cad..360fa464a 100644 --- a/test/e2e/specs/status-page.spec.js +++ b/test/e2e/specs/status-page.spec.js @@ -283,77 +283,4 @@ test.describe("Status Page", () => { expect(rssContent).toContain(""); }); - test("RSS feed uses custom title and status page title", async ({ page }, testInfo) => { - test.setTimeout(60000); - - const statusPageTitle = "My Service Status"; - const customRssTitle = "Custom RSS Feed Title"; - - await page.goto("./add"); - await login(page); - - // Create a monitor - await expect(page.getByTestId("monitor-type-select")).toBeVisible(); - await page.getByTestId("monitor-type-select").selectOption("http"); - await page.getByTestId("friendly-name-input").fill("Test Monitor"); - await page.getByTestId("url-input").fill("https://example.com"); - await page.getByTestId("save-button").click(); - await page.waitForURL("/dashboard/*"); - - // Create a status page with a title - await page.goto("./add-status-page"); - await page.getByTestId("name-input").fill(statusPageTitle); - await page.getByTestId("slug-input").fill("rss-test"); - await page.getByTestId("submit-button").click(); - await page.waitForURL("/status/rss-test?edit"); - - // Add a group and monitor - await page.getByTestId("add-group-button").click(); - await page.getByTestId("group-name").fill("Test Group"); - await page.getByTestId("monitor-select").click(); - await page.getByTestId("monitor-select").getByRole("option", { name: "Test Monitor" }).click(); - - // Save without custom RSS title first - await page.getByTestId("save-button").click(); - await expect(page.getByTestId("edit-sidebar")).toHaveCount(0); - - // Fetch RSS feed - should use status page title - let rssResponse = await page.request.get("/status/rss-test/rss"); - expect(rssResponse.status()).toBe(200); - let rssContent = await rssResponse.text(); - - // Verify RSS feed uses status page title as fallback - expect(rssContent).toContain(`${statusPageTitle} RSS Feed`); - - // Verify RSS link uses the correct domain (not localhost hardcoded) - expect(rssContent).toMatch(/https?:\/\/[^<]+\/status\/rss-test<\/link>/); - - await testInfo.attach("rss-feed-default-title.xml", { - body: rssContent, - contentType: "application/xml" - }); - - // Now set a custom RSS title - await page.getByTestId("edit-button").click(); - await expect(page.getByTestId("edit-sidebar")).toHaveCount(1); - await page.getByTestId("rss-title-input").fill(customRssTitle); - await page.getByTestId("save-button").click(); - await expect(page.getByTestId("edit-sidebar")).toHaveCount(0); - - // Fetch RSS feed again - should use custom RSS title - rssResponse = await page.request.get("/status/rss-test/rss"); - expect(rssResponse.status()).toBe(200); - rssContent = await rssResponse.text(); - - // Verify RSS feed uses custom title - expect(rssContent).toContain(`${customRssTitle}`); - - await testInfo.attach("rss-feed-custom-title.xml", { - body: rssContent, - contentType: "application/xml" - }); - - await screenshot(testInfo, page); - }); - }); From b9a257df97b658c5a3140ca4f168fbb2d176f58b Mon Sep 17 00:00:00 2001 From: leonace924 Date: Mon, 5 Jan 2026 23:12:02 -0500 Subject: [PATCH 8/8] fix: update the test --- test/e2e/specs/status-page.spec.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/e2e/specs/status-page.spec.js b/test/e2e/specs/status-page.spec.js index 360fa464a..111a7e71d 100644 --- a/test/e2e/specs/status-page.spec.js +++ b/test/e2e/specs/status-page.spec.js @@ -281,6 +281,35 @@ test.describe("Status Page", () => { expect(rssContent).toContain(""); + + // Verify RSS feed uses status page title as fallback (from issue #6217) + expect(rssContent).toContain("Security Test RSS Feed"); + + // Verify RSS link uses the correct domain (not localhost hardcoded) + expect(rssContent).toMatch(/https?:\/\/[^<]+\/status\/security-test<\/link>/); + + // Test custom RSS title functionality + const customRssTitle = "Custom RSS Feed Title"; + await page.getByTestId("edit-button").click(); + await expect(page.getByTestId("edit-sidebar")).toHaveCount(1); + await page.getByTestId("rss-title-input").fill(customRssTitle); + await page.getByTestId("save-button").click(); + await expect(page.getByTestId("edit-sidebar")).toHaveCount(0); + + // Fetch RSS feed again - should use custom RSS title + const rssResponseCustom = await page.request.get("/status/security-test/rss"); + expect(rssResponseCustom.status()).toBe(200); + const rssContentCustom = await rssResponseCustom.text(); + + // Verify RSS feed uses custom title + expect(rssContentCustom).toContain(`${customRssTitle}`); + + await testInfo.attach("rss-feed-custom-title.xml", { + body: rssContentCustom, + contentType: "application/xml" + }); + + await screenshot(testInfo, page); }); });