Skip to content
Permalink
Browse files

[FIX JENKINS-39339] contextual try Blue Ocean button (#65)

* Add "Open Blue Ocean" link to footer

need it because we removed the header

* Added a few custom assertions

* Added some pipeline activity page empty state assertions

* Added openBlueOcean custom command

* Add smoke test for the "Open Blue Ocean" button

* remove urlMatches - unused

* Fix url encoding in urlEndsWith

* Make pipeline activity basic layout testing less fragile

* A test for "Open Blue Ocean" on a freestyle down in a folder

* Fix urlEndsWith assertion to handle weird characters in a more human way

* A test for "Open Blue Ocean" on a multibranch run down in a folder
  • Loading branch information
tfennelly committed Nov 4, 2016
1 parent 92a7a0c commit cdf8d0d9c1e5db06951f9de01bf7498a84c7d254
@@ -0,0 +1,56 @@
/**
* Checks if the current url ends with the given value.
*
* ```
* this.demoTest = function (client) {
* browser.assert.urlEndsWith('/blue/organizations/jenkins/my-pipeline/activity');
* };
* ```
*
* @method urlEndsWith
* @param {string} expected The expected url.
* @param {string} [message] Optional log message to display in the output. If missing, one is displayed by default.
* @api assertions
*/

var util = require('util');
exports.assertion = function(expected, msg) {

this.message = msg || util.format('Testing if the URL ends with "%s".', expected);
this.expected = expected;

this.pass = function(value) {
if (humanize(value).endsWith(expected)) {
return true;
} else {
console.log("urlEndsWith assert failed. The assert log (see below) shows the dehumanized URLs as seen by the browser. Here's an attempt at decoding/humanizing that URL:");
console.log("\t" + humanize(value));
return false;
}
};

this.value = function(result) {
return result.value;
};

this.command = function(callback) {
this.api.url(callback);
return this;
};

};

function humanize(url) {
var urlTokens = url.split('/');
var decodedUrlTokens = urlTokens.map(function(token) {
return decodeURIComponent(token).replace(/\//g, '%2F');
});

// pop off the last token if it's empty i.e. there
// was a trailing slash.
if (decodedUrlTokens[decodedUrlTokens.length - 1] === '') {
decodedUrlTokens.pop();
}

return decodedUrlTokens.join('/');
}
@@ -0,0 +1,60 @@
/**
* @module addOpenBlueOceanLinkToFooter
* @memberof custom_commands
* @description Nightwatch command to add a link to Blue Ocean in the footer. Only relevant for classic Jenkins.
* See http://nightwatchjs.org/guide#writing-custom-commands
*/

const util = require('util');
const events = require('events');

function Cmd() {
events.EventEmitter.call(this);
}
util.inherits(Cmd, events.EventEmitter);

Cmd.prototype.command = function () {
var self = this;

this.api.execute(function() {
(function () {
function waitForJQuery() {
try {
// In classic Jenkins, we can try dipping into the js-modules
// and get jQuery. If it's not there, then we're not in classic Jenkins
// and we don't care.
var $jquery = window.jenkinsCIGlobal.plugins["jquery-detached"].jquery2.exports;
doTweaks($jquery);
} catch(e) {
setTimeout(waitForJQuery, 50);
}
}
function doTweaks($jquery) {
$jquery(function() {
var contextUrlDiv = $jquery('#blueocean-context-url');
if (contextUrlDiv.length !== 0) {
var footer = $jquery('#footer');
var link = $jquery('<a>Open Blue Ocean</a>');
link.attr('id', 'open-blueocean-in-context');
link.attr('href', contextUrlDiv.attr('data-context-url'));
footer.append(link);
}
// Make the emission of the "complete" event (below) a bit
// more deterministic.
$jquery('body').addClass('open-blueocean-link-added');
});
}
waitForJQuery();
}());
});

setTimeout(function() {
self.api.waitForElementPresent('body.open-blueocean-link-added', function() {
self.emit('complete');
});
}, 10);

return this;
};

module.exports = Cmd;
@@ -0,0 +1,8 @@
exports.command = function (checkfor) {
this.waitForElementVisible('#open-blueocean-in-context');
this.click('#open-blueocean-in-context');
if (checkfor) {
this.waitForElementVisible(checkfor);
}
return this;
};
@@ -59,7 +59,8 @@ function doClassicPageIntercepts(browser) {
// page adjustments.
return navFunc.apply(this, arguments)
.removePageHead()
.moveClassicBottomStickyButtons();
.moveClassicBottomStickyButtons()
.addOpenBlueOceanLinkToFooter();
};

return thePage;
@@ -31,6 +31,9 @@ module.exports.commands = [{
* @returns {Object} self - nightwatch page object
*/
forJob: function(jobName, orgName) {
if (!orgName) {
orgName = 'jenkins';
}
const pageUrl = this.api.launchUrl + url.viewPipelineActivity(orgName, jobName);
this.jobName = jobName;
this.orgName = orgName;
@@ -40,12 +43,26 @@ module.exports.commands = [{
* Different test on general elements that should be visible on the page
* @returns {Object} self - nightwatch page object
*/
assertBasicLayoutOkay: function() {
const baseHref = url.viewPipeline(this.orgName, this.jobName);
assertBasicLayoutOkay: function(jobName) {
const baseHref = url.viewPipeline('jenkins', (jobName?jobName:this.jobName));
this.waitForElementVisible('@pipelinesNav');
this.waitForElementVisible('nav.page-tabs a[href="' + baseHref + '/activity"]');
this.waitForElementVisible('nav.page-tabs a[href="' + baseHref + '/branches"]');
this.waitForElementVisible('nav.page-tabs a[href="' + baseHref + '/pr"]');
this.waitForElementVisible('nav.page-tabs a');
this.waitForElementVisible('.Site-footer');
// Test the end of the active url and make sure it's on the
// expected activity page.
if (jobName) {
this.assert.urlEndsWith(jobName + '/activity');
} else {
this.assert.urlEndsWith('/activity');
}
},
/**
* Different test on general elements that should be visible on an empty activity page
* @returns {Object} self - nightwatch page object
*/
assertEmptyLayoutOkay: function(jobName) {
this.assertBasicLayoutOkay(jobName);
this.waitForElementVisible('@emptyStateShoes');
},
/**
* Wait for a specific run to appear in the activity table as a success
@@ -0,0 +1,28 @@
/**
* @module classicRun
* @memberof page_objects
* @description Represents a Job Run page on classic Jenkins
*/

exports.elements = {
consoleOutput: {
selector: '//a[text()="Console Output"]',
locateStrategy: 'xpath',
}
};
exports.commands = [
{
/**
* Navigate to a Job run page.
* @param jobName {String} name of the job.
* @param runId {String} The run Id of the run on the job.
* @returns {Object} self - nightwatch page object
*/
navigateToRun: function(jobName, runId) {
const runUrl = this.api.launchUrl + '/job/' + jobName + '/' + (runId?runId:'1');
this.navigate(runUrl);
this.waitForElementVisible('@consoleOutput');
return this;
}
}
];
@@ -4,7 +4,7 @@
],
"output_folder": "target/surefire-reports",
"custom_commands_path": "src/main/js/custom_commands",
"custom_assertions_path": "",
"custom_assertions_path": "src/main/js/custom_assertions",
"globals_path": "src/main/js/globals.js",
"page_objects_path": [
"src/main/js/page_objects/classic_jenkins",
@@ -73,6 +73,13 @@ module.exports = {
const freestyleCreate = browser.page.freestyleCreate();
freestyleCreate.createFreestyle(jobName, 'freestyle.sh');

// make sure the open blue ocean button works. In this case,
// it should bring the browser to an empty pipeline activity
// page.
var bluePipelineActivity = browser.page.bluePipelineActivity();
browser.openBlueOcean();
bluePipelineActivity.assertEmptyLayoutOkay(jobName);
browser.assert.urlEndsWith('/blue/organizations/jenkins/firstFolder%2F三百%2Fñba%2F七%2FSohn/activity');
},
/** Create folder - "anotherFolder"
*
@@ -125,6 +132,17 @@ module.exports = {
browser.assert.equal(response.value.indexOf('firstFolder') > -1, true);
})
},
'test open blueocean from classic - run details': function(browser) {
var classicRunPage = browser.page.classicRun();

classicRunPage.navigateToRun('anotherFolder/job/三百/job/ñba/job/七/job/Sohn/job/feature%252F1');

// make sure the open blue ocean button works. In this case,
// it should bring the browser to the run details page for the first run.
browser.openBlueOcean();
browser.assert.urlEndsWith('/blue/organizations/jenkins/anotherFolder%2F三百%2Fñba%2F七%2FSohn/detail/feature%2F1/1/pipeline');

},
//FIXME the test is disabled due to https://cloudbees.atlassian.net/browse/OSS-1438
/** Validate correct encoding, pipeline graph and steps */
'step 07': !function (browser) {
@@ -17,8 +17,17 @@ module.exports = {
* @param browser
*/
'Step 02': function (browser) {
var bluePipelinesPage = browser.page.bluePipelines().navigate();

var bluePipelineActivity = browser.page.bluePipelineActivity();
var bluePipelinesPage = browser.page.bluePipelines();

// make sure the open blue ocean button works. In this case,
// it should bring the browser to an empty pipeline activity
// page.
browser.openBlueOcean();
bluePipelineActivity.assertEmptyLayoutOkay('my-pipeline');
browser.assert.urlEndsWith('/blue/organizations/jenkins/my-pipeline/activity');

bluePipelinesPage.navigate();
bluePipelinesPage.assertBasicLayoutOkay();
bluePipelinesPage.assertJob('my-pipeline');
},

0 comments on commit cdf8d0d

Please sign in to comment.
You can’t perform that action at this time.