Skip to content

Instantly share code, notes, and snippets.

@leyluj
Last active February 9, 2020 16:27
Show Gist options
  • Save leyluj/1e5ebca39840905e1e134c4add9523f2 to your computer and use it in GitHub Desktop.
Save leyluj/1e5ebca39840905e1e134c4add9523f2 to your computer and use it in GitHub Desktop.
Record Browser Input for Cypress
"use strict";
/*
## author: Andres Santos (andres.santos@neoris.com)
## Demo usage
- Add the next lines to your main html file so that the cypressRecorder start working
- <script src="http://code.jquery.com/jquery.min.js" type="text/javascript"></script>
- <script src="http://www.cemexgo.com/cdn/cypress/cypressRecorder.js" type="text/javascript"></script>
- once you added those two scripts open your browser developer console and within the console you can run any of these two methods
- startRecording(); /// this method will start recording the interaction with your web app
- playRecording(); /// this method will show you the cypress steps in the browser console recorded until you exec this method
*/
var $ = window.jQuery;
$(window).load(function () {
jQuery.fn.getPath = function () {
if (this.length != 1) throw 'Requires one element.';
var path, node = this;
if (this[0].id != "" && this[0].id != null && this[0].id != undefined) {
return "#" + this[0].id;
}
var classItems = new String(this[0].className.repeat(1));
var arrayItems = classItems.split(" ");
var classesToIgnore = ["ng-dirty", "ng-touched", "ng-touched", "ng-untouched", "ng-valid","ng-invalid", "ng-pristine"]
for (var q = 0; q <= classesToIgnore.length - 1; q++) {
for (var qi = arrayItems.length; qi > -1; qi--) {
if (arrayItems[qi] === classesToIgnore[q]) {
arrayItems.splice(qi, 1);
}
}
}
if (document.getElementsByClassName(arrayItems.join(" ")).length == 1) {
return arrayItems.join(".");
}
while (node.length) {
var realNode = node[0],
name = realNode.localName;
if (!name) break;
name = name.toLowerCase();
var parent = node.parent();
var siblings = parent.children(name);
if (siblings.length > 1) {
// cypress nthChild
name += window.getNthChild(realNode);
}
path = name + (path ? ' > ' + path : ' ');
node = parent;
}
return path.split('html > ')[1];
};
});
function getNthChild(element) {
var counter = 0;
var k = void 0;
var sibling = void 0;
var parentNode = element.parentNode;
if (Boolean(parentNode)) {
var childNodes = parentNode.childNodes;
var len = childNodes.length;
for (k = 0; k < len; k++) {
sibling = childNodes[k];
if (sibling.nodeType == 1) {
counter++;
if (sibling === element) {
return ':nth-child(' + (counter) + ')';
}
}
}
}
return null;
}
// Data type for storing a recording
var recording = {
events: [],
startTime: -1,
htmlCopy: ''
};
var plainRecording = "";
var stepCounter = 0;
// Record each type of event
var handlers = [{
eventName: 'click',
handler: function handleClick(e) {
recording.events.push({
type: 'click',
target: e.target,
elementType: $(e.target).get(0).tagName.toLowerCase(),
xpath: $(e.target).getPath(),
x: e.pageX,
y: e.pageY,
time: Date.now(),
value: e.target.value,
});
if (recording.events[recording.events.length - 1].elementType !== "select") {
plainRecording += "\nit('Interaction step " + stepCounter + "', function(){";
if (recording.events[recording.events.length - 1].elementType == "input" &&
recording.events[recording.events.length - 1].target.type != "checkbox") {
plainRecording += "\ncy.get('" + selectSelector(recording.events[recording.events.length - 1]) + "').click({force:true});";
}
//verify if it is a container
if (recording.events[recording.events.length - 1].target.children.length == 0) {
// is just an empty element, let's extract the text and use it as a test
if (recording.events[recording.events.length - 1].target.textContent.trim() != "" &&
recording.events[recording.events.length - 1].target.textContent.trim() != undefined &&
recording.events[recording.events.length - 1].target.textContent.trim() != null &&
recording.events[recording.events.length - 1].target.textContent.trim() != " ") {
plainRecording += "\ncy.get('" + selectSelector(recording.events[recording.events.length - 1]) + "').contains('" + recording.events[recording.events.length - 1].target.textContent.trim() + "');";
}
} else {
plainRecording += "\ncy.get('" + selectSelector(recording.events[recording.events.length - 1]) + "').children().should('have.length', " + recording.events[recording.events.length - 1].target.children.length + ")";
}
// also extract the classes to make sure we detect disables and enables
if (recording.events[recording.events.length - 1].target.className != "" &&
recording.events[recording.events.length - 1].target.className != " " &&
recording.events[recording.events.length - 1].target.className != null &&
recording.events[recording.events.length - 1].target.className != undefined
/*recording.events[recording.events.length-1].target.className.hasOwnProperty("split")*/
) {
var classList = recording.events[recording.events.length - 1].target.className.split(" ");
for (var k = 0; k <= classList.length - 1; k++) {
if (classList[k] != "ng-dirty" &&
classList[k] != "ng-touched" &&
classList[k] != "ng-untouched" &&
classList[k] != "" &&
classList[k] != " ") {
plainRecording += "\ncy.get('" + selectSelector(recording.events[recording.events.length - 1]) + "').should('have.class', '" + classList[k] + "');";
}
}
}
if (recording.events[recording.events.length - 1].elementType != "input" &&
recording.events[recording.events.length - 1].target.type != "checkbox") {
plainRecording += "\ncy.get('" + selectSelector(recording.events[recording.events.length - 1]) + "').click({force:true});";
}
plainRecording += "\n});";
plainRecording += minputTypes(recording.events[recording.events.length - 1], stepCounter);
stepCounter = stepCounter + 1;
saveToLocalStorage(plainRecording)
}
}
}, {
eventName: 'keypress',
handler: function handleKeyPress(e) {
recording.events.push({
type: 'keypress',
target: e.target,
elementType: $(e.target).get(0).tagName.toLowerCase(),
xpath: $(e.target).getPath(),
value: e.target.value,
keyCode: e.keyCode,
time: Date.now()
});
}
}, {
eventName: 'change',
handler: function handleChange(e) {
//console.log($(e.target).getPath());
recording.events.push({
type: 'change',
target: e.target,
elementType: $(e.target).get(0).tagName.toLowerCase(),
xpath: $(e.target).getPath(),
value: e.target.value,
time: Date.now()
});
plainRecording += minputTypes(recording.events[recording.events.length - 1], stepCounter);
stepCounter = stepCounter + 1;
saveToLocalStorage(plainRecording)
}
}, {
eventName: 'scroll',
handler: function handleChange(e) {
//console.log($(e.target).getPath());
recording.events.push({
type: 'scroll',
target: e.target,
elementType: $(e.target).get(0).tagName.toLowerCase(),
xpath: $(e.target).getPath(),
value: e.target.scrollTop,
time: Date.now()
});
plainRecording += "\nit('Interaction step " + stepCounter + "', function(){";
plainRecording += "\ncy.get('" + selectSelector(recording.events[recording.events.length - 1]) + "').scrollTo(0," + recording.events[recording.events.length - 1].value + ");";
plainRecording += "\n});";
stepCounter = stepCounter + 1;
saveToLocalStorage(plainRecording)
}
}];
function saveToLocalStorage(plainRecording) {
localStorage.setItem("recording", plainRecording);
}
// Attach recording button
function startRecording() {
// start recording
console.log("====CmxRecording started===");
if (localStorage.getItem("recording") !== null) {
console.log("Looks like the Cypress recorder has a previous recording, it will continue to use that recording unless you clean it with cleanRecording()")
}
plainRecording = localStorage.getItem("recording");
recording.startTime = Date.now();
recording.events = [];
recording.htmlCopy = $(document.documentElement).html();
recording.height = $(window).height();
recording.width = $(window).width();
handlers.map(function (x) {
return listen(x.eventName, x.handler);
});
};
//decide wheater to use xpath, id, data-tid or classes
function selectSelector(target) {
//first find it using the data-tid
if (target.target.getAttribute("data-tid") !== null) {
//verify if the attribute identifies one element only
if ($("[data-tid='" + target.target.getAttribute("data-tid") + "']").length === 1) {
return '[data-tid="' + target.target.getAttribute("data-tid") + '"]';
}
} else if (target.target.getAttribute("data-id") !== null) {
//verify if the attribute identifies one element only
if ($("[data-tid='" + target.target.getAttribute("data-id") + "']").length === 1) {
return '[data-id="' + target.target.getAttribute("data-id") + '"]';
}
} else if (target.target.className !== "" && target.target.className !== " " &&
target.target.className !== null && target.target.className !== undefined) {
//verify if the attribute identifies one element only
var classList = target.target.className.split(" ");
for (var k = classList.length; k > -1; k--) {
if (classList[k] == "ng-dirty" &&
classList[k] == "ng-touched" &&
classList[k] == "ng-untouched" &&
classList[k] == "" &&
classList[k] == " ") {
classList.splice(k, 1);
}
}
if (classList.length > 0 &&
$(classList.join(".")).length === 1) {
console.log(classList.join("."))
var avoidDoublePeriod = classList.join(".").replace(/../g, ".");
return avoidDoublePeriod;
}
} else if (target.target.id !== null && target.target.id !== undefined &&
target.target.id !== "" && target.target.id !== " ") {
return "#" + target.target.id;
} else {
target.xpath;
}
return target.xpath;
}
// Replay
function playRecording() {
// generate cypress xpath
console.log(recording);
var cypressCommands = "describe('" + document.title + " APP',function(){";
cypressCommands += "\nit('starts the application', function(){";
cypressCommands += "\ncy.clearCookies();";
cypressCommands += "\ncy.clearLocalStorage();";
cypressCommands += "\ncy.window().then((win) => {";
cypressCommands += "\nwin.sessionStorage.clear();"
cypressCommands += "\n});"
cypressCommands += "\ncy.visit('" + window.location.href + "/');";
cypressCommands += "\n});";
cypressCommands += localStorage.getItem("recording");
cypressCommands += "\n});";
console.log(cypressCommands);
};
function cleanRecording() {
localStorage.removeItem("recording")
plainRecording = '';
}
function minputTypes(recorderEvent, step) {
var cypressCommand = "";
if (recorderEvent.value == null || recorderEvent.value == undefined || recorderEvent.value == "") {
return "";
}
if (recorderEvent.elementType === "select") {
cypressCommand += "\nit('Interaction step " + step + "', function(){";
cypressCommand += "\ncy.get('" + recorderEvent.xpath + "').select('" + recorderEvent.value + "');";
cypressCommand += "\n});";
} else if (recorderEvent.elementType === "input" &&
recorderEvent.target.type != "checkbox" &&
recorderEvent.target.type != "radio") {
cypressCommand += "\nit('Interaction step " + step + "', function(){";
cypressCommand += "\ncy.get('" + recorderEvent.xpath + "').clear();";
cypressCommand += "\ncy.get('" + recorderEvent.xpath + "').type('" + recorderEvent.value + "');";
cypressCommand += "\n});";
} else if (recorderEvent.elementType === "input" &&
(recorderEvent.target.type == "checkbox" ||
recorderEvent.target.type == "radio")) {
cypressCommand += "\nit('Interaction step " + step + "', function(){";
if (recorderEvent.target.checked) {
cypressCommand += "\ncy.get('" + recorderEvent.xpath + "').check();";
} else {
cypressCommand += "\ncy.get('" + recorderEvent.xpath + "').uncheck();";
}
cypressCommand += "\n});";
}
return cypressCommand;
}
// Helpers
function listen(eventName, handler) {
// listens even if stopPropagation
return document.documentElement.addEventListener(eventName, handler, true);
}
function removeListener(eventName, handler) {
// removes listen even if stopPropagation
return document.documentElement.removeEventListener(eventName, handler, true);
}
function flashClass($el, className) {
$el.addClass(className).delay(200).queue(function () {
return $el.removeClass(className).dequeue();
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment