Selenium test with SAC

This commit is contained in:
Marcial Rosales 2025-05-26 17:15:02 +02:00
parent 4c34155886
commit 70ec8dffdd
11 changed files with 244 additions and 35 deletions

View File

@ -1,5 +1,5 @@
<% if (consumers.length > 0) { %>
<table class="list">
<table class="list" id="consumers">
<thead>
<tr>
<% if (mode == 'queue') { %>

View File

@ -11,10 +11,10 @@
<% } %>
<h3>Details</h3>
<table class="facts facts-l">
<table class="facts facts-l" id="details-queue-table">
<tr>
<th>Features</th>
<td><%= fmt_features(queue) %></td>
<td id="details-queue-features"><%= fmt_features(queue) %></td>
</tr>
<% if(!disable_stats) { %>
<tr>
@ -77,7 +77,7 @@
<% } %>
<% if(!disable_stats) { %>
<div class="section-hidden">
<div class="section-hidden" id="queue-consumers-section">
<h2 class="updatable">Consumers (<%=(queue.consumer_details.length)%>) </h2>
<div class="hider updatable">
<%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %>

View File

@ -1,4 +1,5 @@
<table class="facts facts-l">
<table class="facts facts-l" id="details-queue-stats-table">
<tbody>
<tr>
<th>State</th>
<td><%= fmt_object_state(queue) %></td>
@ -6,18 +7,18 @@
<% if(queue.consumers) { %>
<tr>
<th>Consumers</th>
<td><%= fmt_string(queue.consumers) %></td>
<td id="consumers"><%= fmt_string(queue.consumers) %></td>
</tr>
<% } else if(queue.hasOwnProperty('consumer_details')) { %>
<tr>
<th>Consumers</th>
<td><%= fmt_string(queue.consumer_details.length) %></td>
<td id="consumers"><%= fmt_string(queue.consumer_details.length) %></td>
</tr>
<% } %>
<% if(queue.hasOwnProperty('publishers')) { %>
<tr>
<th>Publishers</th>
<td><%= fmt_string(queue.publishers) %></td>
<td id="publishers"><%= fmt_string(queue.publishers) %></td>
</tr>
<% } %>
<tr>
@ -30,6 +31,7 @@
<td><%= fmt_string(queue.delivery_limit) %></td>
</tr>
<% } %>
</tbody>
</table>
<table class="facts">

View File

@ -1,5 +1,4 @@
# syntax=docker/dockerfile:1
FROM atools/jdk-maven-node:mvn3-jdk11-node16 as base
FROM node:18 as base
WORKDIR /code

View File

@ -40,20 +40,22 @@ function getConnectionOptions() {
}
module.exports = {
open: () => {
open: (queueName = "my-queue") => {
let promise = new Promise((resolve, reject) => {
container.on('connection_open', function(context) {
resolve()
})
})
console.log("Opening amqp connection using " + JSON.stringify(connectionOptions))
let connection = container.connect(connectionOptions)
let receiver = connection.open_receiver({
source: 'my-queue',
source: queueName,
target: 'receiver-target',
name: 'receiver-link'
})
let sender = connection.open_sender({
target: 'my-queue',
target: queueName,
source: 'sender-source',
name: 'sender-link'
})
@ -64,6 +66,13 @@ module.exports = {
'sender' : sender
}
},
openReceiver: (handler, queueName = "my-queue") => {
return handler.connection.open_receiver({
source: queueName,
target: 'receiver-target',
name: 'receiver-link'
})
},
close: (connection) => {
if (connection != null) {
connection.close()

View File

@ -114,7 +114,7 @@ module.exports = {
throw new Error(req.responseText)
}
},
createVhost: (url, name, description = "", tags = []) => {
createVhost: (url, authorization, name, description = "", tags = []) => {
let vhost = {
"description": description,
"tags": tags
@ -122,10 +122,9 @@ module.exports = {
log("Create vhost " + JSON.stringify(vhost)
+ " with name " + name + " on " + url)
const req = new XMLHttpRequest()
let base64Credentials = btoa('administrator-only' + ":" + 'guest')
let finalUrl = url + "/api/vhosts/" + encodeURIComponent(name)
req.open('PUT', finalUrl, false)
req.setRequestHeader("Authorization", "Basic " + base64Credentials)
req.setRequestHeader("Authorization", authorization)
req.setRequestHeader('Content-Type', 'application/json')
req.send(JSON.stringify(vhost))
@ -158,13 +157,12 @@ module.exports = {
throw new Error(req.responseText)
}
},
deleteVhost: (url, vhost) => {
deleteVhost: (url, authorization, vhost) => {
log("Deleting vhost " + vhost)
const req = new XMLHttpRequest()
let base64Credentials = btoa('administrator-only' + ":" + 'guest')
let finalUrl = url + "/api/vhosts/" + encodeURIComponent(vhost)
req.open('DELETE', finalUrl, false)
req.setRequestHeader("Authorization", "Basic " + base64Credentials)
req.setRequestHeader("Authorization", authorization)
req.send()
if (req.status == 200 || req.status == 204) {
@ -194,21 +192,18 @@ module.exports = {
throw new Error(req.responseText)
}
},
createQueue: (url, name, vhost, queueType = "quorum") => {
createQueue: (url, authorization, vhost, name, arguments = {}) => {
log("Create queue " + JSON.stringify(name)
+ " in vhost " + vhost + " on " + url)
const req = new XMLHttpRequest()
let base64Credentials = btoa('administrator-only' + ":" + 'guest')
let finalUrl = url + "/api/queues/" + encodeURIComponent(vhost) + "/"
+ encodeURIComponent(name)
req.open('PUT', finalUrl, false)
req.setRequestHeader("Authorization", "Basic " + base64Credentials)
req.setRequestHeader("Authorization", authorization)
req.setRequestHeader('Content-Type', 'application/json')
let payload = {
"durable": true,
"arguments":{
"x-queue-type" : queueType
}
"arguments": arguments
}
req.send(JSON.stringify(payload))
if (req.status == 200 || req.status == 204 || req.status == 201) {
@ -219,14 +214,13 @@ module.exports = {
throw new Error(req.responseText)
}
},
deleteQueue: (url, name, vhost) => {
deleteQueue: (url, authorization, vhost, name) => {
log("Deleting queue " + name + " on vhost " + vhost)
const req = new XMLHttpRequest()
let base64Credentials = btoa('administrator-only' + ":" + 'guest')
let finalUrl = url + "/api/queues/" + encodeURIComponent(vhost) + "/"
+ encodeURIComponent(name)
req.open('DELETE', finalUrl, false)
req.setRequestHeader("Authorization", "Basic " + base64Credentials)
req.setRequestHeader("Authorization", authorization)
req.send()
if (req.status == 200 || req.status == 204) {

View File

@ -34,7 +34,12 @@ module.exports = class BasePage {
this.interactionDelay = parseInt(process.env.SELENIUM_INTERACTION_DELAY) || 0 // slow down interactions (when rabbit is behind a http proxy)
}
async goTo(path) {
return driver.get(d.baseUrl + path)
}
async refresh() {
return this.driver.navigate().refresh()
}
async isLoaded () {
return this.waitForDisplayed(MENU_TABS)
}
@ -147,6 +152,39 @@ module.exports = class BasePage {
const select = await new Select(selectable)
return select.selectByValue(vhost)
}
async getTableMini(tableLocator) {
const table = await this.waitForDisplayed(tableLocator)
return this.getTableMiniUsingTableElement(table)
}
async getTableMiniUsingTableElement(table) {
let tbody = await table.findElement(By.css('tbody'))
let rows = await tbody.findElements(By.xpath("./child::*"))
let table_model = []
for (let row of rows) {
let columnName = await row.findElement(By.css('th')).getText()
let columnValue = await row.findElement(By.css('td'))
let columnContent = await columnValue.findElement(By.xpath("./child::*"))
let columnType = await columnContent.getTagName()
switch (columnType) {
case "table":
table_model.push({
"name": columnName,
"value" : await this.getTableMiniUsingTableElement(columnValue)
})
break
default:
table_model.push({
"name" : columnName,
"value" : await columnContent.getText()
})
}
}
return table_model
}
async getTable(tableLocator, firstNColumns, rowClass) {
const table = await this.waitForDisplayed(tableLocator)
const rows = await table.findElements(rowClass == undefined ?

View File

@ -7,14 +7,36 @@ const QUEUE_NAME = By.css('div#main h1 b')
const DELETE_SECTION = By.css('div#main div#delete')
const DELETE_BUTTON = By.css('div#main div#delete input[type=submit]')
const FEATURES_TABLE = By.css('table#details-queue-table td#details-queue-features table.mini')
const STATS_CONSUMER_COUNT = By.css('table#details-queue-stats-table td#consumers')
const CONSUMERS_SECTION = By.css('div#queue-consumers-section')
const CONSUMERS_SECTION_TITLE = By.css('div#queue-consumers-section h2')
const CONSUMERS_TABLE = By.css('div#queue-consumers-section table.list#consumers')
module.exports = class QueuePage extends BasePage {
async isLoaded() {
return this.waitForDisplayed(QUEUE_NAME)
}
async getName() {
return this.getText(QUEUE_NAME)
}
async getConsumerCount() {
return this.getText(STATS_CONSUMER_COUNT)
}
async getFeatures() {
return this.getTableMini(FEATURES_TABLE)
}
async getConsumersSectionTitle() {
return this.getText(CONSUMERS_SECTION_TITLE)
}
async clickOnConsumerSection() {
return this.click(CONSUMERS_SECTION)
}
async getConsumersTable() {
return this.getTable(CONSUMERS_TABLE)
}
async ensureDeleteQueueSectionIsVisible() {
await this.click(DELETE_SECTION)
return this.driver.findElement(DELETE_SECTION).isDisplayed()

View File

@ -0,0 +1,143 @@
const { By, Key, until, Builder } = require('selenium-webdriver')
require('chromedriver')
const assert = require('assert')
const { buildDriver, goToHome, captureScreensFor, teardown, doWhile, goToQueue } = require('../utils')
const { createQueue, deleteQueue, getManagementUrl, basicAuthorization } = require('../mgt-api')
const { open: openAmqp, once: onceAmqp, on: onAmqp, close: closeAmqp,
openReceiver : openReceiver} = require('../amqp')
const LoginPage = require('../pageobjects/LoginPage')
const OverviewPage = require('../pageobjects/OverviewPage')
const QueuesAndStreamsPage = require('../pageobjects/QueuesAndStreamsPage')
const QueuePage = require('../pageobjects/QueuePage')
const StreamPage = require('../pageobjects/StreamPage')
var untilConnectionEstablished = new Promise((resolve, reject) => {
onAmqp('connection_open', function(context) {
console.log("Amqp connection opened")
resolve()
})
})
describe('Given a quorum queue configured with SAC', function () {
let login
let queuesAndStreams
let queuePage
let queueName
let stream
let overview
let captureScreen
before(async function () {
driver = buildDriver()
await goToHome(driver)
login = new LoginPage(driver)
overview = new OverviewPage(driver)
queuesAndStreams = new QueuesAndStreamsPage(driver)
queuePage = new QueuePage(driver)
stream = new StreamPage(driver)
captureScreen = captureScreensFor(driver, __filename)
await login.login('management', 'guest')
if (!await overview.isLoaded()) {
throw new Error('Failed to login')
}
await overview.selectRefreshOption("Do not refresh")
await overview.clickOnQueuesTab()
queueName = "test_" + Math.floor(Math.random() * 1000)
createQueue(getManagementUrl(), basicAuthorization("management", "guest"),
"/", queueName, {
"x-queue-type": "quorum",
"x-single-active-consumer": true
})
await goToQueue(driver, "/", queueName)
await queuePage.isLoaded()
assert.equal(queueName, await queuePage.getName())
})
it('it must display its queue-type and durability', async function () {
let table = await queuePage.getFeatures()
assert.equal(table[0].name, "arguments:")
let expectedArguments = [
{"name":"x-queue-type:","value":"quorum"}
]
assert.equal(JSON.stringify(table[0].value), JSON.stringify(expectedArguments))
assert.equal(table[1].name, "x-single-active-consumer:")
assert.equal(table[1].value, "true")
assert.equal(table[2].name, "durable:")
assert.equal(table[2].value, "true")
})
it('it should not have any consumers', async function() {
assert.equal("0", await queuePage.getConsumerCount())
assert.equal("Consumers (0)", await queuePage.getConsumersSectionTitle())
})
describe("given there is a consumer attached to the queue", function () {
let amqp
before(async function() {
amqp = openAmqp(queueName)
await untilConnectionEstablished
})
it('it should have one consumer', async function() {
await doWhile(async function() {
await queuePage.refresh()
await queuePage.isLoaded()
return queuePage.getConsumerCount()
}, function(count) {
return count.localeCompare("0") == 1
}, 5000)
assert.equal("1", await queuePage.getConsumerCount())
assert.equal("Consumers (1)", await queuePage.getConsumersSectionTitle())
await queuePage.clickOnConsumerSection()
let consumerTable = await queuePage.getConsumersTable()
console.log("consumer table: " + JSON.stringify(consumerTable))
assert.equal("single active", consumerTable[0][6])
assert.equal("●", consumerTable[0][5])
})
it('it should have two consumers, after adding a second subscriber', async function() {
openReceiver(amqp, queueName)
await doWhile(async function() {
await queuePage.refresh()
await queuePage.isLoaded()
return queuePage.getConsumerCount()
}, function(count) {
return count.localeCompare("2") == 0
}, 5000)
assert.equal("2", await queuePage.getConsumerCount())
assert.equal("Consumers (2)", await queuePage.getConsumersSectionTitle())
await queuePage.clickOnConsumerSection()
let consumerTable = await queuePage.getConsumersTable()
console.log("consumer table: " + JSON.stringify(consumerTable))
let activeConsumer = consumerTable[1][6].localeCompare("single active") == 0 ?
1 : 0
let nonActiveConsumer = activeConsumer == 1 ? 0 : 1
assert.equal("waiting", consumerTable[nonActiveConsumer][6])
assert.equal("○", consumerTable[nonActiveConsumer][5])
assert.equal("single active", consumerTable[activeConsumer][6])
assert.equal("●", consumerTable[activeConsumer][5])
})
after(function() {
try {
if (amqp != null) {
closeAmqp(amqp.connection)
}
} catch (error) {
error("Failed to close amqp10 connection due to " + error);
}
})
})
after(async function () {
await teardown(driver, this, captureScreen)
deleteQueue(getManagementUrl(), basicAuthorization("management", "guest"),
"/", queueName)
})
})

View File

@ -129,9 +129,9 @@ module.exports = {
goToExchanges: (d) => {
return d.driver.get(d.baseUrl + '#/exchanges')
},
goTo: (d, address) => {
return d.get(address)
goToQueue(d, vhost, queue) {
return d.driver.get(d.baseUrl + '#/queues/' + encodeURIComponent(vhost) + '/' + encodeURIComponent(queue))
},
delay: async (msec, ref) => {

View File

@ -2,7 +2,7 @@ const { By, Key, until, Builder } = require('selenium-webdriver')
require('chromedriver')
const assert = require('assert')
const { buildDriver, goToHome, captureScreensFor, teardown, doWhile, log, delay } = require('../utils')
const { getManagementUrl, createVhost, deleteVhost } = require('../mgt-api')
const { getManagementUrl, basicAuthorization, createVhost, deleteVhost } = require('../mgt-api')
const LoginPage = require('../pageobjects/LoginPage')
const OverviewPage = require('../pageobjects/OverviewPage')
@ -107,7 +107,8 @@ describe('Virtual Hosts in Admin tab', function () {
let vhost = "test_" + Math.floor(Math.random() * 1000)
before(async function() {
log("Creating vhost")
createVhost(getManagementUrl(), vhost, "selenium", "selenium-tag")
createVhost(getManagementUrl(), basicAuthorization('administraotor', 'guest'),
vhost, "selenium", "selenium-tag")
// await overview.clickOnOverviewTab()
await overview.clickOnAdminTab()
await adminTab.clickOnVhosts()
@ -131,7 +132,8 @@ describe('Virtual Hosts in Admin tab', function () {
})
after(async function () {
log("Deleting vhost")
deleteVhost(getManagementUrl(), vhost)
deleteVhost(getManagementUrl(), basicAuthorization("administrator", "guest"),
vhost)
})
})