From 4bc4a9ec47f30987b8481bcfc10e4983d4d9a2ef Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Thu, 28 Aug 2025 10:51:09 +0200 Subject: [PATCH 1/4] Test feature flags --- selenium/full-suite-management-ui | 1 + selenium/package.json | 2 +- selenium/short-suite-management-ui | 3 +- selenium/suites/mgt/feature-flags.sh | 9 +++ selenium/test/feature-flags/feature-flags.js | 52 +++++++++++++++++ selenium/test/pageobjects/BasePage.js | 9 ++- .../test/pageobjects/FeatureFlagsAdminTab.js | 56 +++++++++++++++++++ 7 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 selenium/suites/mgt/feature-flags.sh create mode 100644 selenium/test/feature-flags/feature-flags.js create mode 100644 selenium/test/pageobjects/FeatureFlagsAdminTab.js diff --git a/selenium/full-suite-management-ui b/selenium/full-suite-management-ui index ceec03793e34..0c39f9278b92 100644 --- a/selenium/full-suite-management-ui +++ b/selenium/full-suite-management-ui @@ -22,3 +22,4 @@ mgt/exchanges.sh mgt/limits.sh mgt/mgt-only-exchanges.sh mgt/queuesAndStreams.sh +mgt/feature-flags.sh \ No newline at end of file diff --git a/selenium/package.json b/selenium/package.json index c79d91274d10..b84708617f62 100644 --- a/selenium/package.json +++ b/selenium/package.json @@ -12,7 +12,7 @@ "author": "", "license": "ISC", "dependencies": { - "chromedriver": "^137.0", + "chromedriver": "^139.0", "ejs": "^3.1.8", "express": "^4.18.2", "geckodriver": "^3.0.2", diff --git a/selenium/short-suite-management-ui b/selenium/short-suite-management-ui index a0d4a3a86c38..03828544c99e 100644 --- a/selenium/short-suite-management-ui +++ b/selenium/short-suite-management-ui @@ -8,4 +8,5 @@ mgt/exchanges.sh mgt/queuesAndStreams.sh mgt/limits.sh mgt/amqp10-connections.sh -mgt/mqtt-connections.sh \ No newline at end of file +mgt/mqtt-connections.sh +mgt/feature-flags.sh \ No newline at end of file diff --git a/selenium/suites/mgt/feature-flags.sh b/selenium/suites/mgt/feature-flags.sh new file mode 100644 index 000000000000..87cd1082aa01 --- /dev/null +++ b/selenium/suites/mgt/feature-flags.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +TEST_CASES_PATH=/feature-flags +TEST_CONFIG_PATH=/basic-auth + +source $SCRIPT/../../bin/suite_template $@ +run diff --git a/selenium/test/feature-flags/feature-flags.js b/selenium/test/feature-flags/feature-flags.js new file mode 100644 index 000000000000..736cbfb33e8c --- /dev/null +++ b/selenium/test/feature-flags/feature-flags.js @@ -0,0 +1,52 @@ +const { By, Key, until, Builder } = require('selenium-webdriver') +require('chromedriver') +const assert = require('assert') +const { buildDriver, goToHome, captureScreensFor, teardown, findTableRow, delay } = require('../utils') + +const LoginPage = require('../pageobjects/LoginPage') +const OverviewPage = require('../pageobjects/OverviewPage') +const AdminTab = require('../pageobjects/AdminTab') +const FeatureFlagsAdminTab = require('../pageobjects/FeatureFlagsAdminTab') + +describe('Feature flags in Admin tab', function () { + let login + let overview + let ffTab + let captureScreen + + before(async function () { + driver = buildDriver() + await goToHome(driver) + login = new LoginPage(driver) + overview = new OverviewPage(driver) + adminTab = new AdminTab(driver) + ffTab = new FeatureFlagsAdminTab(driver) + captureScreen = captureScreensFor(driver, __filename) + + await login.login('guest', 'guest') + if (!await overview.isLoaded()) { + throw new Error('Failed to login') + } + await overview.selectRefreshOption("Do not refresh") + }) + + it('it has at least one feature flag', async function () { + await overview.clickOnAdminTab() + await adminTab.clickOnFeatureFlags() + let ffTable = await ffTab.getAll() + assert(ffTable.length > 0) + }) + it('it has khepri_db feature flag', async function () { + await overview.clickOnAdminTab() + await adminTab.clickOnFeatureFlags() + let ffTable = await ffTab.getAll() + assert(findTableRow(ffTable, function(row) { + return row[0] === 'khepri_db' + })) + }) + + + after(async function () { + await teardown(driver, this, captureScreen) + }) +}) diff --git a/selenium/test/pageobjects/BasePage.js b/selenium/test/pageobjects/BasePage.js index 36bad7c4e0b4..63106a1fcbe8 100644 --- a/selenium/test/pageobjects/BasePage.js +++ b/selenium/test/pageobjects/BasePage.js @@ -33,7 +33,14 @@ module.exports = class BasePage { this.polling = parseInt(process.env.SELENIUM_POLLING) || 500 // how frequent selenium searches for an element this.interactionDelay = parseInt(process.env.SELENIUM_INTERACTION_DELAY) || 0 // slow down interactions (when rabbit is behind a http proxy) } - + async ensureSectionIsVisible(section) { + let classes = await this.driver.findElement(section).getAttribute("class") + if (classes.search('section-visible') < 0) { + return this.click(section) + } else { + return Promise.resolve(true) + } + } async goTo(path) { return driver.get(d.baseUrl + path) } diff --git a/selenium/test/pageobjects/FeatureFlagsAdminTab.js b/selenium/test/pageobjects/FeatureFlagsAdminTab.js new file mode 100644 index 000000000000..71f21f9e5d70 --- /dev/null +++ b/selenium/test/pageobjects/FeatureFlagsAdminTab.js @@ -0,0 +1,56 @@ +const { By, Key, until, Builder } = require('selenium-webdriver') +const { delay } = require('../utils') + +const AdminTab = require('./AdminTab') + + +const FEATURE_FLAGS_SECTION = By.css('div#main div#feature-flags') +const FEATURE_FLAGS_TABLE = By.css('div#main div#feature-flags div#ff-table-section table') + +const ACCEPT_ENABLE_EXPERIMENTAL_FEATURE_FLAG = By.css('p#ff-exp-ack-supported') +const CONFIRM_ENABLE_EXPERIMENTAL_FEATURE_FLAG = By.css('button#ff-exp-confirm') + + +module.exports = class FeatureFlagsAdminTab extends AdminTab { + async isLoaded () { + await this.waitForDisplayed(FEATURE_FLAGS_SECTION) + } + + async getAll() { + await this.ensureSectionIsVisible(FEATURE_FLAGS_SECTION) + try + { + return this.getTable(FEATURE_FLAGS_TABLE, 4) + } catch (NoSuchElement) { + return Promise.resolve([]) + } + } + + async enable(name) { + let state = await this.getState(name) + if (!await state.isSelected()) { + await this.driver.findElement(this.getParentCheckboxLocator(name)).click() + await delay(1000) + const dialog = await this.driver.wait( + until.elementLocated(By.css('dialog#ff-exp-dialog[open]')), + 10000 // 10 seconds timeout + ); + + await dialog.findElement(ACCEPT_ENABLE_EXPERIMENTAL_FEATURE_FLAG).click() + await dialog.findElement(CONFIRM_ENABLE_EXPERIMENTAL_FEATURE_FLAG).click() + return delay(1000) + }else { + return Promise.resolve() + } + } + getCheckboxLocator(name) { + return By.css('div#ff-table-section table input#ff-checkbox-' + name) + } + getParentCheckboxLocator(name) { + return By.css('div#ff-table-section table td#ff-td-' + name) + } + async getState(name) { + return this.driver.findElement(this.getCheckboxLocator(name)) + } + +} From a809c3000ead685dee8002a30ab7f1a7ca211d53 Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Thu, 28 Aug 2025 11:02:06 +0200 Subject: [PATCH 2/4] Test feature flags in management ui with selenium --- deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs | 2 +- selenium/suites/mgt/feature-flags.sh | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 selenium/suites/mgt/feature-flags.sh diff --git a/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs b/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs index 0ab4d6a16f55..4d3a78d43d7b 100644 --- a/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs +++ b/deps/rabbitmq_management/priv/www/js/tmpl/feature-flags.ejs @@ -20,7 +20,7 @@

-
+

Feature Flags

<%= filter_ui(nonreq_feature_flags) %> diff --git a/selenium/suites/mgt/feature-flags.sh b/selenium/suites/mgt/feature-flags.sh old mode 100644 new mode 100755 From fbd83663e332a9487105cb8a43a46af1ddf2e1eb Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Thu, 28 Aug 2025 11:03:31 +0200 Subject: [PATCH 3/4] Check that since 4.2 onwards khepri_db is enabled --- selenium/test/feature-flags/feature-flags.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selenium/test/feature-flags/feature-flags.js b/selenium/test/feature-flags/feature-flags.js index 736cbfb33e8c..a64afbf5d203 100644 --- a/selenium/test/feature-flags/feature-flags.js +++ b/selenium/test/feature-flags/feature-flags.js @@ -42,7 +42,9 @@ describe('Feature flags in Admin tab', function () { let ffTable = await ffTab.getAll() assert(findTableRow(ffTable, function(row) { return row[0] === 'khepri_db' - })) + })) + let state = await ffTab.getState('khepri_db') + assert(await state.isSelected()) }) From 4aa666e08a7d3bf1bbc5ec808606dd664ada3a34 Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Thu, 28 Aug 2025 11:40:22 +0200 Subject: [PATCH 4/4] Take into account experimental ff --- .../test/pageobjects/FeatureFlagsAdminTab.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/selenium/test/pageobjects/FeatureFlagsAdminTab.js b/selenium/test/pageobjects/FeatureFlagsAdminTab.js index 71f21f9e5d70..9e871a1cd7d3 100644 --- a/selenium/test/pageobjects/FeatureFlagsAdminTab.js +++ b/selenium/test/pageobjects/FeatureFlagsAdminTab.js @@ -26,19 +26,22 @@ module.exports = class FeatureFlagsAdminTab extends AdminTab { } } - async enable(name) { + async enable(name, isExperimental = false) { let state = await this.getState(name) if (!await state.isSelected()) { await this.driver.findElement(this.getParentCheckboxLocator(name)).click() - await delay(1000) - const dialog = await this.driver.wait( - until.elementLocated(By.css('dialog#ff-exp-dialog[open]')), - 10000 // 10 seconds timeout - ); - - await dialog.findElement(ACCEPT_ENABLE_EXPERIMENTAL_FEATURE_FLAG).click() - await dialog.findElement(CONFIRM_ENABLE_EXPERIMENTAL_FEATURE_FLAG).click() - return delay(1000) + if (isExperimental) { + await delay(1000) + const dialog = await this.driver.wait( + until.elementLocated(By.css('dialog#ff-exp-dialog[open]')), + 10000 // 10 seconds timeout + ) + await dialog.findElement(ACCEPT_ENABLE_EXPERIMENTAL_FEATURE_FLAG).click() + await dialog.findElement(CONFIRM_ENABLE_EXPERIMENTAL_FEATURE_FLAG).click() + return delay(1000) + }else { + return Promise.resolve() + } }else { return Promise.resolve() }