From 8f2899f754bc31ab8349a5806021af124c71847f Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Sat, 6 Jul 2024 11:34:39 +0200 Subject: [PATCH] [JENKINS-71909]: Revert https://github.com/jenkinsci/active-choices-plugin/pull/79 The merge request mentions JENKINS-71365 (prototype.JS is removed from Jenkins core) but reverting this should not impact that. What we are doing is removing the async calls and promises, and instead using the synchronous code again. This is so that Active Choices parameters are rendering in order as they were before. Whilst rendering them asynchronously seemed like a good idea to provide a more responsive UI, in reality we oversaw an important aspect: users write their scripts relying on the order parameters are loaded. Users wrote their code using document.getElementId, for instance, which worked previously as users would put the parameter doing that after the parameter from which the ID was being used. There were several issues rendered, so this is not likely to be added back again any time soon. In order to have a more smart reactivity, we would need places to hook user callback code (like Vue or React do), but provided by Jenkins UI, or in a way that could take some time to create in the plug-in without breaking things, and that works well. --- CHANGES.md | 1 + pom.xml | 2 +- .../CascadeChoiceParameter/config.jelly | 6 +- .../CascadeChoiceParameter/index.jelly | 12 +- .../unochoice/ChoiceParameter/config.jelly | 4 +- .../DynamicReferenceParameter/config.jelly | 4 +- .../DynamicReferenceParameter/index.jelly | 12 +- .../unochoice/common/radioContent.jelly | 2 +- .../model/ScriptlerScript/config.jelly | 2 +- .../unochoice/stapler/unochoice/UnoChoice.es6 | 231 ++++++++++++------ .../java/org/biouno/unochoice/BaseUiTest.java | 195 +++++++++++++++ .../biouno/unochoice/UiAcceptanceTest.java | 154 +++--------- .../issue62835/TestForNodeLabelParameter.java | 44 ++-- .../TestRevertingAsynchronousProxy.java | 87 +++++++ .../unochoice/issue71909/package-info.java | 30 +++ .../test/jobs/test/config.xml | 133 ++++++++++ 16 files changed, 688 insertions(+), 231 deletions(-) create mode 100644 src/test/java/org/biouno/unochoice/BaseUiTest.java create mode 100644 src/test/java/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy.java create mode 100644 src/test/java/org/biouno/unochoice/issue71909/package-info.java create mode 100644 src/test/resources/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy/test/jobs/test/config.xml diff --git a/CHANGES.md b/CHANGES.md index 2510423d..8fb15d02 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ - Bump typescript from 5.4.3 to 5.6.2 - Bump webpack from 5.91.0 to 5.94.0 - Bump ws from 8.17.0 to 8.17.1 +- Reverted JENKINS-71365, pull request #79, making the Stapler proxy synchronous again, to ensure determinism in parameters resolution, fixing a regression - Update pom.xml to switch from node 18.16 to 18.18 (for eslint 9) - Update pom.xml to bump Jenkins version to Jenkins 2.462.2 (job-dsl requirement) diff --git a/pom.xml b/pom.xml index d8c490cc..d6f4173b 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ jenkinsci/active-choices-plugin 18.18.0 1.22.19 - PT60S + PT300S diff --git a/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/config.jelly b/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/config.jelly index 56a60eed..b1b89347 100644 --- a/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/config.jelly +++ b/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/config.jelly @@ -48,13 +48,13 @@ - + - + ${%Filterable} - + diff --git a/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/index.jelly b/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/index.jelly index 8c958bd9..280a5184 100644 --- a/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/index.jelly +++ b/src/main/resources/org/biouno/unochoice/CascadeChoiceParameter/index.jelly @@ -7,10 +7,18 @@ // source, references table var referencedParameters = Array(); - // add the element we want to monitor - referencedParameters.push("${value}"); + // add the element we want to monitor + referencedParameters.push("${value}"); + if (window.makeStaplerProxy) { + window.__old__makeStaplerProxy = window.makeStaplerProxy; + window.makeStaplerProxy = UnoChoice.makeStaplerProxy2; + } + var cascadeChoiceParameter = ; // Create Jenkins proxy + if (window.makeStaplerProxy) { + window.makeStaplerProxy = window.__old__makeStaplerProxy; + } UnoChoice.renderCascadeChoiceParameter('#${h.escape(paramName)}', ${it.filterable}, '${h.escape(it.getName())}', '${h.escape(it.getRandomName())}', ${it.getFilterLength()}, '${h.escape(paramName)}', referencedParameters, cascadeChoiceParameter); diff --git a/src/main/resources/org/biouno/unochoice/ChoiceParameter/config.jelly b/src/main/resources/org/biouno/unochoice/ChoiceParameter/config.jelly index f0391b32..5c03cc32 100644 --- a/src/main/resources/org/biouno/unochoice/ChoiceParameter/config.jelly +++ b/src/main/resources/org/biouno/unochoice/ChoiceParameter/config.jelly @@ -48,10 +48,10 @@ - + ${%Filterable} - + diff --git a/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/config.jelly b/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/config.jelly index c5edcfd5..d7ad4cba 100644 --- a/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/config.jelly +++ b/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/config.jelly @@ -56,11 +56,11 @@ - + - + ${%Omit value field} diff --git a/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/index.jelly b/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/index.jelly index 1309375c..b363f24d 100644 --- a/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/index.jelly +++ b/src/main/resources/org/biouno/unochoice/DynamicReferenceParameter/index.jelly @@ -60,10 +60,18 @@ // source, references table var referencedParameters = Array(); - // add the element we want to monitor - referencedParameters.push("${value}"); + // add the element we want to monitor + referencedParameters.push("${value}"); + if (window.makeStaplerProxy) { + window.__old__makeStaplerProxy = window.makeStaplerProxy; + window.makeStaplerProxy = UnoChoice.makeStaplerProxy2; + } + var dynamicReferenceParameter = ; // Create Jenkins proxy + if (window.makeStaplerProxy) { + window.makeStaplerProxy = window.__old__makeStaplerProxy; + } UnoChoice.renderDynamicRenderParameter('#${paramName}', '${h.escape(it.getName())}', '${h.escape(paramName)}', referencedParameters, dynamicReferenceParameter); // update spinner id diff --git a/src/main/resources/org/biouno/unochoice/common/radioContent.jelly b/src/main/resources/org/biouno/unochoice/common/radioContent.jelly index f73f6d2c..66ea1770 100644 --- a/src/main/resources/org/biouno/unochoice/common/radioContent.jelly +++ b/src/main/resources/org/biouno/unochoice/common/radioContent.jelly @@ -113,7 +113,7 @@ maxCount = ${it.visibleItemCount}; } - var refElement = document.getElementById("${id}"); + var refElement = document.getElementById("ecp_${h.escape(it.randomName)}_0"); if(maxCount > 0 && refElement && refElement.offsetHeight !=0) { for(var i=0; i< maxCount; i++) { height += refElement.offsetHeight + 3; diff --git a/src/main/resources/org/biouno/unochoice/model/ScriptlerScript/config.jelly b/src/main/resources/org/biouno/unochoice/model/ScriptlerScript/config.jelly index ca39aaa1..b048bd6c 100644 --- a/src/main/resources/org/biouno/unochoice/model/ScriptlerScript/config.jelly +++ b/src/main/resources/org/biouno/unochoice/model/ScriptlerScript/config.jelly @@ -3,7 +3,7 @@ This module depends on JQuery only.

* - * @param $ jQuery3 global var + * @param jQuery3 jQuery3 global var * @author Bruno P. Kinoshita * @since 0.20 */ -var UnoChoice = UnoChoice || ($ => { - let util = new Util($); +var UnoChoice = UnoChoice || (jQuery3 => { + let util = new Util(jQuery3); // The final public object let instance = {}; let SEPARATOR = '__LESEP__'; @@ -148,18 +148,18 @@ var UnoChoice = UnoChoice || ($ => { let parametersString = this.getReferencedParametersAsText(); // gets the array parameters, joined by , (e.g. a,b,c,d) console.log(`Values retrieved from Referenced Parameters: ${parametersString}`); // Update the CascadeChoiceParameter Map of parameters - await new Promise((resolve) => this.proxy.doUpdate(parametersString, t => resolve(t))); + await this.proxy.doUpdate(parametersString); let spinner, rootDiv; if (this.getRandomName()) { let spinnerId = this.getRandomName().split('_').pop(); - spinner = jQuery(`div#${spinnerId}-spinner`); + spinner = jQuery3(`div#${spinnerId}-spinner`); // Show spinner if (spinner) { spinner.show(); } // Disable DIV changes - rootDiv = jQuery(`div#${spinnerId}`); + rootDiv = jQuery3(`div#${spinnerId}`); if (rootDiv) { rootDiv.css('pointer-events', 'none'); } @@ -169,7 +169,7 @@ var UnoChoice = UnoChoice || ($ => { // The inner function is called with the response provided by Stapler. Then we update the HTML elements. let _self = this; // re-reference this to use within the inner function console.log('Calling Java server code to update HTML elements...'); - await new Promise((resolve) => this.proxy.getChoicesForUI(t => { + await this.proxy.getChoicesForUI(t => { let data = t.responseObject(); console.log(`Values returned from server: ${data}`); let newValues = data[0]; @@ -240,7 +240,7 @@ var UnoChoice = UnoChoice || ($ => { } else if (parameterElement.tagName === 'DIV' || parameterElement.tagName === 'SPAN') { if (parameterElement.children.length > 0 && (parameterElement.children[0].tagName === 'DIV' || parameterElement.children[0].tagName === 'SPAN')) { let tbody = parameterElement.children[0]; - $(tbody).empty(); + jQuery3(tbody).empty(); let originalArray = []; // Check whether it is a radio or checkbox element if (parameterElement.className === 'dynamic_checkbox') { @@ -311,12 +311,11 @@ var UnoChoice = UnoChoice || ($ => { parameterElement.style.height = newValues.length > 10 ? '230px' : 'auto'; } // if (parameterElement.children.length > 0 && parameterElement.children[0].tagName === 'DIV') { } // if (parameterElement.tagName === 'SELECT') { // } else if (parameterElement.tagName === 'DIV') { - resolve(t) - })); + }); // propagate change // console.log('Propagating change event from ' + this.getParameterName()); // let e1 = $.Event('change', {parameterName: this.getParameterName()}); - // $(this.getParameterElement()).trigger(e1); + // jQuery3(this.getParameterElement()).trigger(e1); if (!avoidRecursion) { if (cascadeParameters && cascadeParameters.length > 0) { for (let i = 0; i < cascadeParameters.length; i++) { @@ -342,7 +341,7 @@ var UnoChoice = UnoChoice || ($ => { /** * Returns true iff the given parameter is not null, and one of its * reference parameters is the same parameter as this. In other words, - * it returns whether or not the given parameter references this parameter. + * it returns whether the given parameter references this parameter. * * @since 0.22 * @param cascadeParameter {CascadeParameter} a given parameter @@ -377,18 +376,18 @@ var UnoChoice = UnoChoice || ($ => { this.cascadeParameter = cascadeParameter; // Add event listener let _self = this; - $(this.paramElement).change(e => { + jQuery3(this.paramElement).change(e => { if (e.parameterName === _self.paramName) { console.log('Skipping self reference to avoid infinite loop!'); e.stopImmediatePropagation(); } else { console.log(`Cascading changes from parameter ${_self.paramName}...`); //_self.cascadeParameter.loading(true); - $(".behavior-loading").show(); + jQuery3(".behavior-loading").show(); // start updating in separate async function so browser will be able to repaint and show 'loading' animation , see JENKINS-34487 setTimeout(async () => { - await _self.cascadeParameter.update(false); - $(".behavior-loading").hide(); + await _self.cascadeParameter.update(false); + jQuery3(".behavior-loading").hide(); }, 0); } }); @@ -441,18 +440,18 @@ var UnoChoice = UnoChoice || ($ => { let parametersString = this.getReferencedParametersAsText(); // gets the array parameters, joined by , (e.g. a,b,c,d) console.log(`Values retrieved from Referenced Parameters: ${parametersString}`); // Update the Map of parameters - await new Promise((resolve) => this.proxy.doUpdate(parametersString, t => resolve(t))); + await this.proxy.doUpdate(parametersString); let parameterElement = this.getParameterElement(); let spinner, rootDiv; if (parameterElement.id) { let spinnerId = parameterElement.id.split('_').pop(); - spinner = jQuery(`div#${spinnerId}-spinner`); + spinner = jQuery3(`div#${spinnerId}-spinner`); // Show spinner if (spinner) { spinner.show(); } - rootDiv = jQuery(`div#${spinnerId}`); + rootDiv = jQuery3(`div#${spinnerId}`); // Disable DIV changes if (rootDiv) { rootDiv.css('pointer-events', 'none'); @@ -462,9 +461,9 @@ var UnoChoice = UnoChoice || ($ => { // or maybe call a string to put as value in a INPUT. if (parameterElement.tagName === 'OL') { // handle OL's console.log('Calling Java server code to update HTML elements...'); - await new Promise((resolve) => this.proxy.getChoicesForUI(t => { - $(parameterElement).empty(); // remove all children elements - let data = t.responseObject(); + await this.proxy.getChoicesForUI(t => { + jQuery3(parameterElement).empty(); // remove all children elements + const data = t.responseObject(); console.log(`Values returned from server: ${data}`); let newValues = data[0]; // let newKeys = data[1]; @@ -473,13 +472,12 @@ var UnoChoice = UnoChoice || ($ => { li.innerHTML = newValues[i]; parameterElement.appendChild(li); // append new elements } - resolve(t) - })); + }); } else if (parameterElement.tagName === 'UL') { // handle OL's - $(parameterElement).empty(); // remove all children elements + jQuery3(parameterElement).empty(); // remove all children elements console.log('Calling Java server code to update HTML elements...'); - await new Promise(resolve => this.proxy.getChoicesForUI(t => { - let data = t.responseObject(); + await this.proxy.getChoicesForUI(t => { + const data = t.responseObject(); console.log(`Values returned from server: ${data}`); let newValues = data[0]; // let newKeys = data[1]; @@ -488,23 +486,20 @@ var UnoChoice = UnoChoice || ($ => { li.innerHTML = newValues[i]; parameterElement.appendChild(li); // append new elements } - resolve(t) - })); + }); } else if (parameterElement.id.indexOf('inputElement_') > -1) { // handle input text boxes - await new Promise(resolve => this.proxy.getChoicesAsStringForUI(t => { - parameterElement.value = t.responseObject(); - resolve(t) - })); + await this.proxy.getChoicesAsStringForUI(t => { + parameterElement.value = JSON.stringify(t.responseObject()); + }); } else if (parameterElement.id.indexOf('formattedHtml_') > -1) { // handle formatted HTML - await new Promise(resolve => this.proxy.getChoicesAsStringForUI(t => { + await this.proxy.getChoicesAsStringForUI(t => { parameterElement.innerHTML = t.responseObject(); - resolve(t) - })); + }); } // propagate change // console.log('Propagating change event from ' + this.getParameterName()); // let e1 = $.Event('change', {parameterName: this.getParameterName()}); - // $(this.getParameterElement()).trigger(e1); + // jQuery3(this.getParameterElement()).trigger(e1); if (!avoidRecursion) { if (cascadeParameters && cascadeParameters.length > 0) { for (let i = 0; i < cascadeParameters.length; i++) { @@ -542,21 +537,21 @@ var UnoChoice = UnoChoice || ($ => { this.originalArray = []; // push existing values into originalArray array if (this.paramElement.tagName === 'SELECT') { // handle SELECTS - let options = $(paramElement).children().toArray(); + let options = jQuery3(paramElement).children().toArray(); for (let i = 0; i < options.length; ++i) { this.originalArray.push(options[i]); } } else if (paramElement.tagName === 'DIV' || paramElement.tagName === 'SPAN') { // handle CHECKBOXES - if ($(paramElement).children().length > 0 && (paramElement.children[0].tagName === 'DIV' || paramElement.children[0].tagName === 'SPAN')) { + if (jQuery3(paramElement).children().length > 0 && (paramElement.children[0].tagName === 'DIV' || paramElement.children[0].tagName === 'SPAN')) { let tbody = paramElement.children[0]; - let trs = $(tbody).find('div'); + let trs = jQuery3(tbody).find('div'); for (let i = 0; i < trs.length ; ++i) { - let tds = $(trs[i]).find('div'); - let inputs = $(tds[0]).find('input'); + let tds = jQuery3(trs[i]).find('div'); + let inputs = jQuery3(tds[0]).find('input'); let input = inputs[0]; this.originalArray.push(input); } - } // if ($(paramElement).children().length > 0 && paramElement.children[0].tagName === 'DIV') { + } // if (jQuery3(paramElement).children().length > 0 && paramElement.children[0].tagName === 'DIV') { } this.initEventHandler(); } @@ -615,7 +610,7 @@ var UnoChoice = UnoChoice || ($ => { */ FilterElement.prototype.initEventHandler = function() { let _self = this; - $(_self.filterElement).keyup(e => { + jQuery3(_self.filterElement).keyup(e => { //let filterElement = e.target; let filterElement = _self.getFilterElement(); let filteredElement = _self.getParameterElement(); @@ -646,17 +641,17 @@ var UnoChoice = UnoChoice || ($ => { let tagName = filteredElement.tagName; if (tagName === 'SELECT') { // handle SELECT's - $(filteredElement).children().remove(); + jQuery3(filteredElement).children().remove(); for (let i = 0; i < newOptions.length ; ++i) { let opt = document.createElement('option'); opt.value = newOptions[i].value; opt.innerHTML = newOptions[i].innerHTML; - $(filteredElement).append(opt); + jQuery3(filteredElement).append(opt); } } else if (tagName === 'DIV' || tagName === 'SPAN') { // handle CHECKBOXES, RADIOBOXES and other elements (Jenkins renders them as tables) - if ($(filteredElement).children().length > 0 && ($(filteredElement).children()[0].tagName === 'DIV' || $(filteredElement).children()[0].tagName === 'SPAN')) { + if (jQuery3(filteredElement).children().length > 0 && (jQuery3(filteredElement).children()[0].tagName === 'DIV' || jQuery3(filteredElement).children()[0].tagName === 'SPAN')) { let tbody = filteredElement.children[0]; - $(tbody).empty(); + jQuery3(tbody).empty(); if (filteredElement.className === 'dynamic_checkbox') { for (let i = 0; i < newOptions.length; i++) { let entry = newOptions[i]; @@ -707,12 +702,12 @@ var UnoChoice = UnoChoice || ($ => { tbody.appendChild(tr); } } - } // if ($(filteredElement).children().length > 0 && $(filteredElement).children()[0].tagName === 'DIV') { + } // if (jQuery3(filteredElement).children().length > 0 && jQuery3(filteredElement).children()[0].tagName === 'DIV') { } // if (tagName === 'SELECT') { // } else if (tagName === 'DIV') { // Propagate the changes made by the filter console.log('Propagating change event after filtering'); let e1 = $.Event('change', {parameterName: 'Filter Element Event'}); - $(filteredElement).trigger(e1); + jQuery3(filteredElement).trigger(e1); }); } // HTML utility methods @@ -736,11 +731,11 @@ var UnoChoice = UnoChoice || ($ => { * @see issue #21 in GitHub - github.com/biouno/uno-choice-plugin/issues */ function fakeSelectRadioButton(clazzName, id) { - let element = $(`#${id}`).get(0); + let element = jQuery3(`#${id}`).get(0); // deselect all radios with the class=clazzName - let radios = $(`input[class="${clazzName}"]`); + let radios = jQuery3(`input[class="${clazzName}"]`); radios.each(function(index) { - $(this).attr('name', ''); + jQuery3(this).attr('name', ''); }); // select the radio with the id=id let parent = element.parentNode; @@ -768,7 +763,7 @@ var UnoChoice = UnoChoice || ($ => { * @return {string} the value of the HTML element used as parameter value in Jenkins, as a string */ function getParameterValue(htmlParameter) { - let e = $(htmlParameter); + let e = jQuery3(htmlParameter); let value = ''; if (e.attr('name') === 'value') { value = util.getElementValue(e); @@ -777,7 +772,7 @@ var UnoChoice = UnoChoice || ($ => { if (subElements) { let valueBuffer = Array(); subElements.each(function() { - let tempValue = util.getElementValue($(this)); + let tempValue = util.getElementValue(jQuery3(this)); if (tempValue) valueBuffer.push(tempValue); }); @@ -795,8 +790,101 @@ var UnoChoice = UnoChoice || ($ => { return value; } + // Hacks in Jenkins core + /** + *

This function is the same as makeStaplerProxy available in Jenkins core, but executes calls + * synchronously. Since many parameters must be filled only after other parameters have been + * updated, calling Jenkins methods asynchronously causes several unpredictable errors.

+ * + *

JENKINS-71909: Stapler had to be updated when Prototype and jQuery dependencies + * were removed from Jenkins. This means that we also had to update this function to + * match what was done there - thanks asc3ns10n (GH).

+ * + * @param url {string} The URL + * @param staplerCrumb {string} The crumb + * @param methods {Array} The methods + */ + function makeStaplerProxy2(url, staplerCrumb, methods) { + if (url.substring(url.length - 1) !== '/') url+='/'; + let proxy = {}; + let stringify; + if (Object.toJSON) // needs to use Prototype.js if it's present. See commit comment for discussion + stringify = Object.toJSON; // from prototype + else if (typeof(JSON)=="object" && JSON.stringify) + stringify = JSON.stringify; // standard + let genMethod = methodName => { + proxy[methodName] = async function() { + let args = arguments; + // the final argument can be a callback that receives the return value + let callback = (() => { + if (args.length === 0) return null; + let tail = args[args.length-1]; + return (typeof(tail)=='function') ? tail : null; + })(); + // 'arguments' is not an array, so we convert it into an array + let a = []; + for (let i=0; i { async function renderCascadeChoiceParameter(parentDivRef, filterable, name, randomName, filterLength, paramName, referencedParameters, cascadeChoiceParameter) { // find the cascade parameter element - let parentDiv = jQuery(parentDivRef); + let parentDiv = jQuery3(parentDivRef); let parameterHtmlElement = parentDiv.find('DIV'); if (!parameterHtmlElement || parameterHtmlElement.length === 0) { console.log('Could not find element by name, perhaps it is a DIV?'); @@ -838,10 +926,10 @@ var UnoChoice = UnoChoice || ($ => { for (let i = 0; i < referencedParameters.length ; ++i) { let parameterElement = null; // FIXME: review the block below - let divs = jQuery('div[name="parameter"]'); + let divs = jQuery3('div[name="parameter"]'); for (let j = 0; j < divs.length ; j++) { let div = divs[j]; - let hiddenNames = jQuery(div).find('input[name="name"]'); + let hiddenNames = jQuery3(div).find('input[name="name"]'); if (hiddenNames[0].value === referencedParameters[i]) { let children = div.children; for (let k = 0; k < children.length; ++k) { @@ -850,7 +938,7 @@ var UnoChoice = UnoChoice || ($ => { parameterElement = child; break; } else if (child.tagName === 'DIV' || child.tagName === 'SPAN') { - let subValues = jQuery(child).find('input[name="value"]'); + let subValues = jQuery3(child).find('input[name="value"]'); if (subValues && subValues.get(0)) { parameterElement = child; break; @@ -882,36 +970,36 @@ var UnoChoice = UnoChoice || ($ => { async function renderDynamicRenderParameter(parentDivRef, name, paramName, referencedParameters, dynamicReferenceParameter) { // find the cascade parameter element - let parentDiv = jQuery(parentDivRef); + let parentDiv = jQuery3(parentDivRef); // if the parameter class has been set to hidden, then we hide it now if (parentDiv.get(0).getAttribute('class') === 'hidden_uno_choice_parameter') { - let parentTbody = jQuery(parentDiv.get(0)).parents('tbody'); + let parentTbody = jQuery3(parentDiv.get(0)).parents('tbody'); // FIXME: temporary fix to support both TABLE and DIV in the Jenkins UI // remove after most users have migrated to newer versions with DIVs if (!parentTbody || parentTbody.length === 0) { - parentTbody = jQuery(parentDiv.get(0)).parents('div > div.tr'); + parentTbody = jQuery3(parentDiv.get(0)).parents('div > div.tr'); } if (parentTbody && parentTbody.length > 0) { - jQuery(parentTbody.get(0)).attr('style', 'visibility:hidden;position:absolute;'); + jQuery3(parentTbody.get(0)).attr('style', 'visibility:hidden;position:absolute;'); } } let parameterHtmlElement = null; for(let i = 0; i < parentDiv.children().length; i++) { let child = parentDiv.children()[i]; if (child.getAttribute('name') === 'value' || child.id.indexOf('ecp_') > -1) { - parameterHtmlElement = jQuery(child); + parameterHtmlElement = jQuery3(child); break; } if (child.id.indexOf('inputElement_') > -1) { - parameterHtmlElement = jQuery(child); + parameterHtmlElement = jQuery3(child); break; } if (child.id.indexOf('formattedHtml_') > -1) { - parameterHtmlElement = jQuery(child); + parameterHtmlElement = jQuery3(child); break; } if (child.id.indexOf('imageGallery_') > -1) { - parameterHtmlElement = jQuery(child); + parameterHtmlElement = jQuery3(child); break; } } @@ -921,10 +1009,10 @@ var UnoChoice = UnoChoice || ($ => { for (let i = 0; i < referencedParameters.length ; ++i) { let parameterElement = null; // FIXME: review the block below - let divs = jQuery('div[name="parameter"]'); + let divs = jQuery3('div[name="parameter"]'); for (let j = 0; j < divs.length ; j++) { let div = divs[j]; - let hiddenNames = jQuery(div).find('input[name="name"]'); + let hiddenNames = jQuery3(div).find('input[name="name"]'); if (hiddenNames[0].value === referencedParameters[i]) { let children = div.children; for (let k = 0; k < children.length; ++k) { @@ -933,7 +1021,7 @@ var UnoChoice = UnoChoice || ($ => { parameterElement = child; break; } else if (child.tagName === 'DIV' || child.tagName === 'SPAN') { - let subValues = jQuery(child).find('input[name="value"]'); + let subValues = jQuery3(child).find('input[name="value"]'); if (subValues && subValues.get(0)) { parameterElement = child; break; @@ -970,6 +1058,7 @@ var UnoChoice = UnoChoice || ($ => { instance.DynamicReferenceParameter = DynamicReferenceParameter; instance.ReferencedParameter = ReferencedParameter; instance.FilterElement = FilterElement; + instance.makeStaplerProxy2 = makeStaplerProxy2; instance.cascadeParameters = cascadeParameters; instance.renderChoiceParameter = renderChoiceParameter; instance.renderCascadeChoiceParameter = renderCascadeChoiceParameter; diff --git a/src/test/java/org/biouno/unochoice/BaseUiTest.java b/src/test/java/org/biouno/unochoice/BaseUiTest.java new file mode 100644 index 00000000..95b9b966 --- /dev/null +++ b/src/test/java/org/biouno/unochoice/BaseUiTest.java @@ -0,0 +1,195 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2024 Ioannis Moutsatsos, Bruno P. Kinoshita + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.biouno.unochoice; + +import io.github.bonigarcia.wdm.WebDriverManager; +import org.apache.commons.lang3.StringUtils; +import org.htmlunit.ElementNotFoundException; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.jvnet.hudson.test.JenkinsRule; +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.text.MessageFormat; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public abstract class BaseUiTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + protected WebDriver driver; + protected WebDriverWait wait; + + protected static boolean isCi() { + return StringUtils.isNotBlank(System.getenv("CI")); + } + + protected static final Duration MAX_WAIT = Duration.parse(System.getProperty("ui.loading.timeout", "PT300S")); + + @BeforeClass + public static void setUpClass() { + if (isCi()) { + // The browserVersion needs to match what is provided by the Jenkins Infrastructure + // If you see an exception like this: + // + // org.openqa.selenium.SessionNotCreatedException: Could not start a new session. Response code 500. Message: session not created: This version of ChromeDriver only supports Chrome version 114 + // Current browser version is 112.0.5615.49 with binary path /usr/bin/chromium-browser + // + // Then that means you need to update the version here to match the current browser version. + WebDriverManager.chromedriver().browserVersion("112").setup(); + } else { + WebDriverManager.chromedriver().setup(); + } + } + + @Before + public void setUp() throws Exception { + if (isCi()) { + driver = new ChromeDriver(new ChromeOptions().addArguments("--headless", "--disable-dev-shm-usage", "--no-sandbox")); + } else { + driver = new ChromeDriver(new ChromeOptions()); + } + wait = new WebDriverWait(driver, MAX_WAIT); + driver.manage().window().setSize(new Dimension(2560, 1440)); + } + + @After + public void tearDown() { + driver.quit(); + } + + protected static By radios(String paramName) { + return By.cssSelector("div.active-choice:has([name='name'][value='" + paramName + "']) input[type='radio']"); + } + + protected List findRadios(String paramName) { + return findRadios(paramName, null); + } + + protected List findRadios(String paramName, Map attributes) { + return driver.findElements(radios(paramName)); + } + + protected static By checkboxes(String paramName) { + return By.cssSelector("div.active-choice:has([name='name'][value='" + paramName + "']) input[type='checkbox']"); + } + + protected List findCheckboxes(String paramName) { + return driver.findElements(checkboxes(paramName)); + } + + protected static By selects(String paramName) { + return By.cssSelector("div.active-choice:has([name='name'][value='" + paramName + "']) > select"); + } + + protected WebElement findSelect(String paramName) { + return driver.findElement(selects(paramName)); + } + + protected WebElement findParamDiv(String paramName) { + final WebElement paramValueInput = driver.findElement(By.cssSelector("input[name='parameter.name'][value='" + paramName + "']")); + // Up to how many parent levels to we want to search for the help button? + // At the moment it's 3 levels up, so let's give it some room, use 7. + final int parentsLimit = 7; + WebElement parentElement = paramValueInput.findElement(By.xpath("./..")); + for (int i = 0; i < parentsLimit; i++) { + if (parentElement.getAttribute("name") != null && parentElement.getAttribute("name").equals("parameterDefinitions")) { + return parentElement; + } + parentElement = parentElement.findElement(By.xpath("./..")); + } + throw new ElementNotFoundException("div", "parameterDefinitions", ""); + } + + protected void checkOptions(Supplier param1Input, String... options) { + wait.withMessage(() -> { + List optionElements = param1Input.get().findElements(By.cssSelector("option")); + List optionValues = optionElements.stream().map(WebElement::getText).collect(Collectors.toList()); + return MessageFormat.format("{0} should have had {1}. Had {2}", param1Input, Arrays.asList(options), optionValues); + }) + .until(d -> { + try { + List optionElements = param1Input.get().findElements(By.cssSelector("option")); + List optionValues = optionElements.stream().map(WebElement::getText).collect(Collectors.toList()); + return optionValues.equals(Arrays.asList(options)); + } catch (StaleElementReferenceException e) { + return false; + } + }); + } + + /** + * This function receives a {@code By} selector to avoid stale elements - it will repeatedly + * query the driver for a new element. + * + * @param selector selector + * @param options expected options + */ + protected void checkRadios(By selector, String... options) { + wait.withMessage(() -> { + final List radios = driver.findElements(selector); + List optionValues = radios.stream().map(it -> it.getAttribute("value")).collect(Collectors.toList()); + return MessageFormat.format("{0} should have had {1}. Had {2}", radios, Arrays.asList(options), optionValues); + }) + .until(d -> { + try { + final List radios = driver.findElements(selector); + List optionValues = radios.stream().map(it -> it.getAttribute("value")).collect(Collectors.toList()); + return optionValues.equals(Arrays.asList(options)); + } catch (StaleElementReferenceException e) { + return false; + } + }); + } + + protected void checkRadios(Supplier> radios, String... options) { + wait.withMessage(() -> { + List optionValues = radios.get().stream().map(it -> it.getAttribute("value")).collect(Collectors.toList()); + return MessageFormat.format("{0} should have had {1}. Had {2}", radios, Arrays.asList(options), optionValues); + }) + .until(d -> { + try { + List optionValues = radios.get().stream().map(it -> it.getAttribute("value")).collect(Collectors.toList()); + return optionValues.equals(Arrays.asList(options)); + } catch (StaleElementReferenceException e) { + return false; + } + }); + } +} diff --git a/src/test/java/org/biouno/unochoice/UiAcceptanceTest.java b/src/test/java/org/biouno/unochoice/UiAcceptanceTest.java index 94cd6c21..1ff27974 100644 --- a/src/test/java/org/biouno/unochoice/UiAcceptanceTest.java +++ b/src/test/java/org/biouno/unochoice/UiAcceptanceTest.java @@ -1,82 +1,43 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2024 Ioannis Moutsatsos, Bruno P. Kinoshita + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package org.biouno.unochoice; -import io.github.bonigarcia.wdm.WebDriverManager; -import org.apache.commons.lang3.StringUtils; -import org.htmlunit.ElementNotFoundException; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.recipes.LocalData; import org.openqa.selenium.By; -import org.openqa.selenium.Dimension; -import org.openqa.selenium.StaleElementReferenceException; -import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.Select; -import org.openqa.selenium.support.ui.WebDriverWait; -import java.text.MessageFormat; -import java.time.Duration; -import java.util.Arrays; import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class UiAcceptanceTest { - - private static final Duration MAX_WAIT = Duration.parse(System.getProperty("ui.loading.timeout", "PT60S")); - - @Rule - public JenkinsRule j = new JenkinsRule(); - - private WebDriver driver; - private WebDriverWait wait; - - @BeforeClass - public static void setUpClass() { - if (isCi()) { - // The browserVersion needs to match what is provided by the Jenkins Infrastructure - // If you see an exception like this: - // - // org.openqa.selenium.SessionNotCreatedException: Could not start a new session. Response code 500. Message: session not created: This version of ChromeDriver only supports Chrome version 114 - // Current browser version is 112.0.5615.49 with binary path /usr/bin/chromium-browser - // - // Then that means you need to update the version here to match the current browser version. - WebDriverManager.chromedriver().browserVersion("112").setup(); - } else { - WebDriverManager.chromedriver().setup(); - } - } - - private static boolean isCi() { - return StringUtils.isNotBlank(System.getenv("CI")); - } - - @Before - public void setUp() throws Exception { - if (isCi()) { - driver = new ChromeDriver(new ChromeOptions().addArguments("--headless", "--disable-dev-shm-usage", "--no-sandbox")); - } else { - driver = new ChromeDriver(new ChromeOptions()); - } - wait = new WebDriverWait(driver, MAX_WAIT); - driver.manage().window().setSize(new Dimension(2560, 1440)); - } - - @After - public void tearDown() { - driver.quit(); - } +public class UiAcceptanceTest extends BaseUiTest { @LocalData("test") @Test @@ -92,7 +53,11 @@ public void testHelpFiles() throws Exception { final WebElement helpIcon = param1ParamDiv.findElement(By.cssSelector("a.jenkins-help-button")); wait.until(ExpectedConditions.elementToBeClickable(helpIcon)); - helpIcon.click(); + Actions actions = new Actions(driver); + actions + .moveToElement(helpIcon) + .click() + .perform(); wait.withMessage(() -> "The help text should have been displayed").until(d -> helpTextDiv.isDisplayed()); @@ -215,63 +180,4 @@ public void test() throws Exception { checkOptions(() -> findSelect("PARAM4A")); } - - private List findRadios(String paramName) { - return driver.findElements(By.cssSelector("div.active-choice:has([name='name'][value='" + paramName + "']) input[type='radio']")); - } - - private List findCheckboxes(String paramName) { - return driver.findElements(By.cssSelector("div.active-choice:has([name='name'][value='" + paramName + "']) input[type='checkbox']")); - } - - private WebElement findSelect(String paramName) { - return driver.findElement(By.cssSelector("div.active-choice:has([name='name'][value='" + paramName + "']) > select")); - } - - private WebElement findParamDiv(String paramName) { - final WebElement paramValueInput = driver.findElement(By.cssSelector("input[name='parameter.name'][value='" + paramName + "']")); - // Up to how many parent levels to we want to search for the help button? - // At the moment it's 3 levels up, so let's give it some room, use 7. - final int parentsLimit = 7; - WebElement parentElement = paramValueInput.findElement(By.xpath("./..")); - for (int i = 0; i < parentsLimit; i++) { - if (parentElement.getAttribute("name") != null && parentElement.getAttribute("name").equals("parameterDefinitions")) { - return parentElement; - } - parentElement = parentElement.findElement(By.xpath("./..")); - } - throw new ElementNotFoundException("div", "parameterDefinitions", ""); - } - - private void checkOptions(Supplier param1Input, String... options) { - wait.withMessage(() -> { - List optionElements = param1Input.get().findElements(By.cssSelector("option")); - List optionValues = optionElements.stream().map(WebElement::getText).collect(Collectors.toList()); - return MessageFormat.format("{0} should have had {1}. Had {2}", param1Input, Arrays.asList(options), optionValues); - }) - .until(d -> { - try { - List optionElements = param1Input.get().findElements(By.cssSelector("option")); - List optionValues = optionElements.stream().map(WebElement::getText).collect(Collectors.toList()); - return optionValues.equals(Arrays.asList(options)); - } catch (StaleElementReferenceException e) { - return false; - } - }); - } - - private void checkRadios(Supplier> radios, String... options) { - wait.withMessage(() -> { - List optionValues = radios.get().stream().map(it -> it.getAttribute("value")).collect(Collectors.toList()); - return MessageFormat.format("{0} should have had {1}. Had {2}", radios, Arrays.asList(options), optionValues); - }) - .until(d -> { - try { - List optionValues = radios.get().stream().map(it -> it.getAttribute("value")).collect(Collectors.toList()); - return optionValues.equals(Arrays.asList(options)); - } catch (StaleElementReferenceException e) { - return false; - } - }); - } } diff --git a/src/test/java/org/biouno/unochoice/issue62835/TestForNodeLabelParameter.java b/src/test/java/org/biouno/unochoice/issue62835/TestForNodeLabelParameter.java index 14bd8b68..4f2d712c 100644 --- a/src/test/java/org/biouno/unochoice/issue62835/TestForNodeLabelParameter.java +++ b/src/test/java/org/biouno/unochoice/issue62835/TestForNodeLabelParameter.java @@ -57,7 +57,6 @@ import com.google.common.collect.Lists; import hudson.model.FreeStyleProject; -import hudson.model.ParameterDefinition; import hudson.model.ParametersDefinitionProperty; import hudson.model.labels.LabelAtom; import hudson.slaves.DumbSlave; @@ -117,30 +116,31 @@ public void testNodeLabelParameterValueFound() throws IOException, SAXException true, 1); - project.addProperty(new ParametersDefinitionProperty(Arrays.asList(nodeLabelParameter, reactsToNodeLabelParameter))); + project.addProperty(new ParametersDefinitionProperty(Arrays.asList(nodeLabelParameter, reactsToNodeLabelParameter))); project.save(); - WebClient wc = j.createWebClient(); - wc.setThrowExceptionOnFailingStatusCode(false); - HtmlPage configPage = wc.goTo("job/" + project.getName() + "/build?delay=0sec"); - DomElement renderedParameterElement = configPage.getElementById("random-name"); - HtmlSelect select = null; - for (DomNode node: renderedParameterElement.getChildren()) { - if (node instanceof HtmlSelect) { - select = (HtmlSelect) node; - break; + try (WebClient wc = j.createWebClient()) { + wc.setThrowExceptionOnFailingStatusCode(false); + HtmlPage configPage = wc.goTo("job/" + project.getName() + "/build?delay=0sec"); + DomElement renderedParameterElement = configPage.getElementById("random-name"); + HtmlSelect select = null; + for (DomNode node: renderedParameterElement.getChildren()) { + if (node instanceof HtmlSelect) { + select = (HtmlSelect) node; + break; + } } + if (select == null) { + fail("Missing cascade parameter select HTML node element!"); + } + List htmlOptions = select.getOptions(); + final List options = htmlOptions + .stream() + .map(HtmlOption::getText) + .collect(Collectors.toList()); + final List expected = new LinkedList<>(Collections.singletonList(nodeName)); + assertEquals("Wrong number of HTML options rendered", expected.size(), options.size()); + assertEquals("Wrong HTML options rendered (or out of order)", expected, options); } - if (select == null) { - fail("Missing cascade parameter select HTML node element!"); - } - List htmlOptions = select.getOptions(); - final List options = htmlOptions - .stream() - .map(HtmlOption::getText) - .collect(Collectors.toList()); - final List expected = new LinkedList<>(Collections.singletonList(nodeName)); - assertEquals("Wrong number of HTML options rendered", expected.size(), options.size()); - assertEquals("Wrong HTML options rendered (or out of order)", expected, options); } } diff --git a/src/test/java/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy.java b/src/test/java/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy.java new file mode 100644 index 00000000..63576335 --- /dev/null +++ b/src/test/java/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy.java @@ -0,0 +1,87 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2024 Ioannis Moutsatsos, Bruno P. Kinoshita + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.biouno.unochoice.issue71909; + +import org.biouno.unochoice.BaseUiTest; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.recipes.LocalData; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.Select; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * See JENKINS-71909. + * + *

JENKINS-71365 made Stapler proxy asynchronous and used JavaScript promises. This + * caused a regression where users could no longer render parameters in a deterministic + * way.

+ * + *

To have proper asynchronous reactivity, we would have to either implement something + * akin to Vue or React's reactivity engine, since users would have to be able to declare + * how parameters react based on more elaborated constraints.

+ * + *

This test uses the example from JENKINS-71909 to reproduce the bug.

+ * + * @since 2.8.4 + */ +@Issue("JENKINS-71909") +public class TestRevertingAsynchronousProxy extends BaseUiTest { + + @LocalData("test") + @Test + public void test() throws Exception { + // Load the page + driver.get(j.getURL().toString() + "job/test/build"); + + // From OP: + // + // The issue is when "Item2" is selected, then the 4) active-choice + // elements returns as selected "buster" instead of "bullseye", this + // because, when first called the wrong server list is returned from + // 3), because 3) is called with "a1" as parameter instead of "a2" + // as should be after the 2) gets executed. + + WebElement targetParam = findSelect("TARGET"); + assertTrue(targetParam.isDisplayed()); + assertTrue(targetParam.isEnabled()); + new Select(targetParam).selectByValue("Item2"); + + wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".jenkins-spinner"))); + + List dockerBaseImageParam = findRadios("DOCKER_BASE_IMAGE"); + assertEquals(2, dockerBaseImageParam.size()); + + checkRadios(radios("DOCKER_BASE_IMAGE"), "buster", "bullseye"); + + assertEquals("bullseye", findRadios("DOCKER_BASE_IMAGE").get(1).getAttribute("value")); + assertEquals("true", findRadios("DOCKER_BASE_IMAGE").get(1).getAttribute("checked")); + } +} diff --git a/src/test/java/org/biouno/unochoice/issue71909/package-info.java b/src/test/java/org/biouno/unochoice/issue71909/package-info.java new file mode 100644 index 00000000..ec96e2ba --- /dev/null +++ b/src/test/java/org/biouno/unochoice/issue71909/package-info.java @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2024 Ioannis Moutsatsos, Bruno P. Kinoshita + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * See JENKINS-71909. + * + * @since 2.8.4 + */ +package org.biouno.unochoice.issue71909; diff --git a/src/test/resources/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy/test/jobs/test/config.xml b/src/test/resources/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy/test/jobs/test/config.xml new file mode 100644 index 00000000..c24ea601 --- /dev/null +++ b/src/test/resources/org/biouno/unochoice/issue71909/TestRevertingAsynchronousProxy/test/jobs/test/config.xml @@ -0,0 +1,133 @@ + + + + + false + + + + + TARGET + choice-parameter-19685286034459 + 1 + + false + + + + false + + + JENKINS-71909 + JENKINS-71909 + PT_SINGLE_SELECT + false + 1 + + + VARIANT + choice-parameter-19685291017751 + 1 + + false + + + + false + + + JENKINS-71909 + JENKINS-71909 + + TARGET + PT_RADIO + false + 1 + + + MACHINES + choice-parameter-19685294460278 + 1 + + false + + + + false + + + JENKINS-71909 + JENKINS-71909 + + TARGET,VARIANT + PT_CHECKBOX + false + 1 + + + DOCKER_BASE_IMAGE + choice-parameter-19685297716936 + 1 + + false + + + + false + + + JENKINS-71909 + JENKINS-71909 + + MACHINES + PT_RADIO + false + 1 + + + + + + true + false + false + false + + false + + + +