Skip to content

Instantly share code, notes, and snippets.

@tomyam1
Last active April 9, 2020 15:02
Show Gist options
  • Save tomyam1/145236601eb97384f516 to your computer and use it in GitHub Desktop.
Save tomyam1/145236601eb97384f516 to your computer and use it in GitHub Desktop.
Custom matchers for Protractor and Jasmine 2
'use strict';
const _ = require('lodash');
const Tinycolor2 = require('tinycolor2');
/**
* Custom matchers for protractor and jasmine 2
*
* expect(el).toBePresent();
* expect(el).toBeDisplayed();
* expect(el).toContainText('text to contain');
* expect(el).toHaveExactText('exact text to have');
* expect(el).toHaveValue('exact value to have');
* expect(el).toHaveAttribute('href', 'http://example.com');
* expect(el).toMatchAttribute('href', '^http://example.com/.*$');
* expect(el).toBeChecked();
* expect(el).toBeSelected();
* expect(el).toBeNearLocation({ x: x coordinate, y: y coordinate }, [max distance, defaults to 2])
* expect(el).toHaveClass('class1 class2') - expects to have class1 and class2
* expect(el).not.toHaveClass('class1 class2') - expects to have neither class1 nor class2
* expect(el).toHaveCssValue('background-color', '#ff0000') - expected to have
* value for CSS property
*
*/
// str => "str"
function formatStr(str) { return "\"" + str + "\""; }
// { x: 1, y: 2 } => "(1, 2)"
function formatCoordinates(obj) {
return "(" + obj.x + ", " + obj.y + ")";
}
var matchers = {
toBePresent: function() {
return {
compare: function(element) {
var ret = {
pass: element.isPresent().then(function(isPresent) {
var pass = !!isPresent;
ret.message = "Expected" + (pass ? " NOT " : "") + " to be present";
return pass;
})
};
return ret;
}
};
},
toBeDisplayed: function() {
return {
compare: function(element) {
var ret = {
pass: element.isDisplayed().then(function(isDisplayed) {
var pass = !!isDisplayed;
ret.message = "Expected" + (pass ? " NOT " : "") + " to be displayed";
return pass;
})
};
return ret;
}
};
},
toContainText: function() {
return {
compare: function(element, expectedText) {
var ret = {
pass: element.getText().then(function(actualText) {
var pass = actualText.indexOf(expectedText.trim()) >= 0;
if (pass) {
ret.message = "Expected NOT to contain text " + formatStr(expectedText);
} else {
ret.message = "Expected to contain text " + formatStr(expectedText) + " BUT text is " + formatStr(actualText);
}
return pass;
})
};
return ret;
}
};
},
toHaveExactText: function() {
return {
compare: function(element, expectedText) {
var ret = {
pass: element.getText().then(function(actualText) {
var pass = actualText.trim() === expectedText.trim();
if (pass) {
ret.message = "Expected NOT to have text " + formatStr(expectedText);
} else {
ret.message = "Expected to have text " + formatStr(expectedText) + " BUT has text " + formatStr(actualText);
}
return pass;
})
};
return ret;
}
};
},
toHaveTextMatchedBy: function() {
return {
compare: function(element, pattern) {
if (!_.isRegExp(pattern) && !_.isString(pattern)) {
throw new Error(`toHaveTextMatchedBy expects either a RegExp or a string, given: ${pattern}`)
}
if (_.isString(pattern)) pattern = new RegExp(pattern);
var ret = {
pass: element.getText().then(function(actualText) {
var pass = pattern.test(actualText);
if (pass) {
ret.message = `Expected NOT to have match ${pattern}`;
} else {
ret.message = `Expected to match ${pattern}`;
}
return pass;
})
};
return ret;
}
};
},
toHaveValue: function() {
return {
compare: function (element, expectedValue) {
var ret = {
pass: element.getAttribute('value').then(function (actualValue) {
var pass = actualValue === expectedValue;
if (pass) {
ret.message = "Expected NOT to have value " + formatStr(expectedValue);
} else {
ret.message = "Expected to have value " + formatStr(expectedValue) + " BUT has value " + formatStr(actualValue);
}
return pass;
})
};
return ret;
}
};
},
toHaveAttribute: function() {
return {
compare: function (element, attribute, expectedValue) {
var ret = {
pass: element.getAttribute(attribute).then(function (actualValue) {
var pass = actualValue === expectedValue;
if (pass) {
ret.message = "Expected NOT to have " + attribute + " " + formatStr(expectedValue);
} else {
ret.message = "Expected to have " + attribute + " " + formatStr(expectedValue) + " BUT has " + attribute + " " + formatStr(actualValue);
}
return pass;
})
};
return ret;
}
};
},
toMatchAttribute: function () {
return {
compare: function (element, attribute, expectedMatch) {
var ret = {
pass: element.getAttribute(attribute).then(function (actualValue) {
const regex = new RegExp(expectedMatch);
var pass = regex.test(actualValue);
if (pass) {
ret.message = "Expected " + attribute + " NOT to match " + formatStr(regex);
} else {
ret.message = "Expected " + attribute + " to match " + formatStr(regex) + " BUT it's value is " + formatStr(actualValue);
}
return pass;
})
};
return ret;
}
};
},
toBeChecked: function() {
return {
compare: function(element) {
var ret = {
pass: element.getAttribute('checked').then(function(checked) {
var pass = checked === 'true';
ret.message = "Expected" + (pass ? " NOT " : "") + " to be checked";
return pass;
})
};
return ret;
}
};
},
toBeSelected: function() {
return {
compare: function(element) {
var ret = {
pass: element.isSelected().then(function(isSelected) {
var pass = !!isSelected;
ret.message = "Expected" + (pass ? " NOT " : "") + " to be selected";
return pass;
})
};
return ret;
}
};
},
toHaveWidth: function() {
return {
compare: function(element, expectedWidth) {
var ret = {
pass: element.getSize().then(function(size) {
var pass = size.width == expectedWidth;
if (pass) {
ret.message = "Expected NOT to have width " + expectedWidth;
} else {
ret.message = "Expected to have width " + expectedWidth + " BUT has width " + size.width;
}
return pass;
})
};
return ret;
}
};
},
toHaveSize: function() {
const sizeToString = (size) => {
return `Width: ${size.width}, Height: ${size.height}`;
};
return {
compare: function(element, expectedSize) {
var ret = {
pass: element.getSize().then(function(actualSize) {
var pass = actualSize.width === expectedSize.width && actualSize.height === expectedSize.height;
if (pass) {
ret.message = `Expected NOT to have size ${sizeToString(expectedSize)}`;
} else {
ret.message = `Expected to have size ${sizeToString(expectedSize)}} BUT has size ${sizeToString(actualSize)}`;
}
return pass;
})
};
return ret;
}
};
},
toBeAtLocationX: function() {
return {
compare: function(element, expectedX) {
const ret = {
pass: element.getLocation().then(function(actualLocation) {
var pass = actualLocation.x === expectedX;
if (pass) {
ret.message = `Expected to be at location X ${expectedX} but is at location X ${actualLocation.x}`;
} else {
ret.message = `Expected NOT to be at location X ${expectedX}`;
}
return pass;
})
};
return ret;
}
};
},
toBeNearLocation: function() {
return {
compare: function(element, expectedLocation, maxDistance) {
maxDistance = _.isUndefined(maxDistance) ? 2 : maxDistance;
var ret = {
pass: element.getLocation().then(function(actualLocation) {
var distance = Math.sqrt(
Math.pow(actualLocation.x - expectedLocation.x, 2) +
Math.pow(actualLocation.y - expectedLocation.y, 2)
);
var pass = distance <= maxDistance;
ret.message = "Expected " + (pass ? " NOT" : "") + " to be near " + formatCoordinates(expectedLocation) + " but is at " + formatCoordinates((actualLocation)) + ", " + distance + " pixels from it.";
return pass;
})
};
return ret;
}
};
},
// Expect an element to have all classes, or none of them in the negative case
// expect($(#id)).toHaveClass('class1 class2')
// expect($(#id)).not.toHaveClass('class1 class2')
toHaveClass: function() {
return {
compare: function(element, expectedClasses) {
var ret = {
pass: element.getAttribute('class').then(function(actualClasses) {
var actualClassesArr = actualClasses.split(/\s/),
expectedClassesArr = expectedClasses.split(/\s/),
notSatisfiedClassesArr = _.difference(expectedClassesArr, actualClassesArr);
if (expectedClassesArr.length === 1) {
ret.message = "Expected to have class " + expectedClassesArr[0];
} else {
ret.message = "Expected to have classes " + expectedClassesArr.join(', ') + " but does not have classes " + notSatisfiedClassesArr.join(', ');
}
return notSatisfiedClassesArr.length === 0;
})
};
return ret;
},
negativeCompare: function(element, forbiddenClasses) {
var ret = {
pass: element.getAttribute('class').then(function(actualClasses) {
var actualClassesArr = actualClasses.split(/\s/),
forbiddenClassesArr = forbiddenClasses.split(/\s/),
satisfiedClassesArr = _.intersection(forbiddenClassesArr, actualClassesArr);
if (forbiddenClassesArr.length === 1) {
ret.message = "Expected to NOT have class " + forbiddenClassesArr[0];
} else {
ret.message = "Expected to NOT have classes " + forbiddenClassesArr.join(', ') + " but does have classes " + satisfiedClassesArr.join(', ');
}
return satisfiedClassesArr.length === 0;
})
};
return ret;
}
};
},
toHaveCssValue: function() {
return {
compare: function(element, cssProperty, expectedValue, options) {
if (options && options.normalizeColor) {
var parsedColor = Tinycolor2(expectedValue);
if (!parsedColor.isValid()) {
throw new Error('toHaveCssValue: expected value must be a valid color when using the normalizeColor option');
}
expectedValue = parsedColor.toHexString();
}
var ret = {
pass: element.getCssValue(cssProperty).then(function(actualValue) {
if (options && options.normalizeColor) {
var parsedColor = Tinycolor2(actualValue);
if (parsedColor.isValid()) {
actualValue = parsedColor.toHexString();
} else {
console.log('Warning: toHaveCssValue could not parse color ' + actualValue);
}
}
var pass = actualValue === expectedValue;
if (pass) {
ret.message = "Expected NOT to have value " + formatStr(expectedValue) + " for CSS property " + cssProperty;
} else {
ret.message = "Expected to have value " + formatStr(expectedValue) + " for CSS property " + cssProperty + " BUT has value " + formatStr(actualValue);
}
return pass;
})
};
return ret;
}
};
}
// Could not get this matcher to work, `els.count` is undefined
//toHaveCountOf: function() {
// return {
// compare: function(els, expectedCount) {
//
// console.log(_.keys(els));
// console.log(els.count);
//
// var ret = {
// pass: els.count().then(function(actualCount) {
// var pass = expectedCount === actualCount;
// if (pass) {
// ret.message = "Expected NOT to have count of " + expectedCount;
// } else {
// ret.message = "Expected to have count of " + expectedCount + " BUT has count of " + actualCount;
// }
// return pass;
// })
// };
// return ret;
// }
// };
//}
};
beforeEach(function() {
jasmine.addMatchers(matchers);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment