chai-smoothie: to.eventually.be.displayed on a function doesn't work? - serenity-js

First-off, thanks for the chai-smoothie!
I've been trying to polish some of the test code and use chai-smoothie more, but I have ran into some trouble:
This is what it looked like before:
return expect(Promise.all([
userMenuPage.userMenuItemIcon.isDisplayed(),
userMenuPage.userMenuItemText().isDisplayed()
])).to.eventually.be.eql([true, true])
and this is how I expected it to work in chai smoothie
expect(userMenuPage.userMenuItemIcon).to.eventually.be.displayed
expect(userMenuPage.userMenuItemText()).to.eventually.be.displayed
when running the new code, I receive the following error:
Step Definition: steps/userMenuSteps.js:23
Message:
TypeError: assertion._obj.locator is not a function
at <repo>/tests/node_modules/chai-smoothie/lib/src/chai-smoothie.ts:42:65
at <repo>/tests/node_modules/protractor/built/element.js:798:32
at ManagedPromise.invokeCallback_ (<repo>/tests/node_modules/selenium-webdriver/lib/promise.js:1379:14)
at TaskQueue.execute_ (<repo>/tests/node_modules/selenium-webdriver/lib/promise.js:2913:14)
at TaskQueue.executeNext_ (<repo>/tests/node_modules/selenium-webdriver/lib/promise.js:2896:21)
at asyncRun (<repo>/tests/node_modules/selenium-webdriver/lib/promise.js:2775:27)
at <repo>/tests/node_modules/selenium-webdriver/lib/promise.js:639:7
at process._tickCallback (internal/process/next_tick.js:103:7)
Page model definition looks like this:
userMenuItemText: () => $('#desktop-menu #desktop-menu-classic span').getText(),
userMenuItemIcon: $('#desktop-menu #desktop-menu-classic .fa-fjunk'),
The issue is with the second row "userMenuPage.userMenuItemText().isDisplayed()". If I just use "userMenuPage.userMenuItemIcon.isDisplayed()", then I get no problem, and if I just use "userMenuPage.userMenuItemText().isDisplayed()", then I get the failure.
Do you have any recommendationts on how to overcome this and still use chai smoothie?

The problem is caused by the assertion to.eventually.be.displayed applied to the text of the element, rather than the element itself.
Update: as of version 0.2.1, chai-smoothie can work with either.
Hope this helps!
Jan
Nevertheless, it might be cleaner to assert on the element to verify its visibility, and on its text to verify the contents:
So instead of:
const userMenuPage = {
userMenuItemText: () => $('#desktop-menu #desktop-menu-classic span').getText(),
userMenuItemIcon: $('#desktop-menu #desktop-menu-classic .fa-fjunk')
}
maybe try:
const userMenuPage = {
userMenuItem: $('#desktop-menu #desktop-menu-classic span'),
userMenuItemIcon: $('#desktop-menu #desktop-menu-classic .fa-fjunk')
}
and then:
expect(userMenuPage.userMenuItem).to.eventually.be.displayed
expect(userMenuPage.userMenuItem.getText()).to.eventually.equal('expected text')
With Serenity/JS you could also define the page object as follows:
class UserMenuPage {
static Item = Target.the('Menu item').located(by.css('#desktop-menu #desktop-menu-classic span');
static Item_Text = Text.of(UserMenuPage.Item);
static Item_Icon = Target.the('Menu icon').located(by.css('#desktop-menu #desktop-menu-classic .fa-fjunk');
}
which allows you to define "questions" such as UserMenuPage.Item_Text in the same place as targets and avoid having to call .getText() in the scenario.

Related

Unable to add event listener to webNavigation.onCompleted

Using the mock function below along with the dev console:
This call will work:
chrome.webNavigation.onCompleted.addListener(processWebNavChange, filtera);
but when I actually pass in my real var filter it throws this error:
Uncaught TypeError: Could not add listener
My actual data looks like this:
{
url: [ {hostContains: ".im88rmbOwZ"} ]
}
function registerWebNavListener() {
var matchers = getUrlMatchers();
var filter = {
url: matchers
};
// test with mock data filtera that actually works
const filtera = {
url:
[
{hostContains: "example.com"},
]
}
if (matchers.length > 0) {
chrome.webNavigation.onCompleted.addListener(processWebNavChange, filtera);
}
}
async function processWebNavChange(data) {
}
Is there something wrong with my data structure that I'm actually using? I don't believe that the filter object I returned is incorrect
}
EDIT:
I added a new
const filterb = {
url: [ {hostContains: ".im88rmbOwZ"} ]
};
and it still fails with that. The single entry {hostContains: ".im88rmbOwZ"}, was the first item returned from getURLMatchers() which I used as an example of real data being returned.
The above comment on the upper-case letters was the cause of the issue. Converting everything to lowercase resolved the problem.
Although, I am not clear as to why that was a problem to begin with. (If there are any hints in the chromium source code event filter handlers, I'd appreciate it if it could be pointed out).

How can I call a function of an exported function of a module in another script? NodeJS

I want to use a function, which is inside another function (function2) in another js file (2nd.js).
My current code looks something like this:
1st.js
module.exports = {
function1(){
function2(){
//...
}
}
}
2nd.js
const { function2 } = require("1st.js")
function2()
This sadly doesn't work and I have no idea how to solve this problem.
You can work with class approach or some older syntax. Any of these approaches results in the same thing, after all, the class syntax in JavaScript is just a thing for programmers, and does not affect the code at all. It is called syntax sugar.
See in the example below how to solve your problem without classes:
Export a object "container" with your functions:
Just as you referred in your example, 1st.js
export default {
functionOne: () => {
console.log("one");
},
functionTwo: (num) => {
console.log(num);
},
functionThree: ({number}) => {
console.log(number);
},
}
And import it with any name you want
...and so on, 2nd.js
import myFunctions from "../myFunctions";
const { functionOne, functionTwo, functionThree} = myFunctions;
functionOne();
functionTwo("two");
functionThree({number: "three"});

Execute eval on dynamic expression

I have a dictionary that is params["actions"]["browser.evaluateJs"]. It has different expressions that need to be executed. I am trying to execute like this:
if("browser.evaluateJs" in params["actions"]){
for (let key of Object.keys(params["actions"]["browser.evaluateJs"])) {
console.log(params["actions"]["browser.evaluateJs"][key]);
ctx[key] = eval(await params["actions"]["browser.evaluateJs"][key]);
console.log(ctx[key]);
}
}
expression contained in that dictionary is page.waitForXPath('//div[#class='price-text']'). I am trying to scrape a website using pupeteer. And need to get this xpath. But it is giving this error:
UnhandledPromiseRejectionWarning: SyntaxError: missing ) after argument list
How do I fix it?
Edit:
I have used page.evaluate like this:
if("browser.evaluateJs" in params["actions"]){
for (let key of Object.keys(params["actions"]["browser.evaluateJs"])) {
console.log(params["actions"]["browser.evaluateJs"][key]);
ctx[key] = page.evaluate(params["actions"]["browser.evaluateJs"][key]);
console.log(ctx[key]);
}
}
and params["actions"]["browser.evaluateJs"][key] contains element => {return element.textContent;}, (await page.$x('//div[#class='price-text']'))[0]
but this gives Promise { }. How do I fix it?
Edit 2:
I put await like this:
ctx[key] = await page.evaluate(params["actions"]["browser.evaluateJs"][key]);
and it is throwing this error:
Error: Evaluation failed: SyntaxError: Unexpected identifier
Edit3:
Since Im reading it from yml file so I have put it like this:
mykey : "element => {return element.textContent;}, (await page.$x('//div[#class=\"price-text\"]'))[0]"
The error still persists

How do I test a function that is in a class with Jest

I have a function that is in a class :
Simplified version :
export class Button {
getAttributes(el) {
//random code that was removed for simplicity
return dataAttrs;
}
}
I was wondering how do I test this in Jest.
Here is what worked for me :
test('get attributes on element', () => {
let button= new Button();
var element = document.createElement('a');
element.setAttribute('href', 'https://www.google.ca/');
element.innerHTML = 'Test';
expect(breadcrumb.getAttributes(element)).toBe('Hello');
});
if there is simple class like the one that u define u can do:
it('We can check the class constructor', () => {
const classObject = new classObject();
expect(classObject ).toHaveBeenCalledTimes(1);
});
and use whatever the methods that Jest have.
But if there are complex classes and methods that ones are dependent from others i suggest you to read this and you can automatize it or can do it manually, mock the dependences and test it out.

Protractor compare string numbers

Today I've faced interesting problem of create test for pretty simple behavior: 'Most recent' sorting. All what test need to know:
Every item have ID
Previous ID is less then next in this case of sorting
Approach: writing ID in to attribute of item, getting that id from first item with getAttribute() and either way for second.
Problem: getAttribute() promise resulting with string value and Jasmine is not able to compare (from the box) string numbers.
I would like to find elegant way to compare them with toBeLessThan() instead of using chains of few .then() that will be finished with comparing that things.
Root of no-type-definition evil
Thanks guys <3
You can create a helper function to convert string number to actual number, which will make use of Promises:
function toNumber(promiseOrValue) {
// if it is not a promise, then convert a value
if (!protractor.promise.isPromise(promiseOrValue)) {
return parseInt(promiseOrValue, 10);
}
// if promise - convert result to number
return promiseOrValue.then(function (stringNumber) {
return parseInt(stringNumber, 10);
});
}
And then use the result with .toBeLessThan, etc:
expect(toNumber(itemId)).toBeLessThan(toNumber(anotherItemId));
I forgot of native nature of promises but tnx to Michael Radionov I've remembered what I want to do.
expect(first.then( r => Number(r) )).toBe(next.then( r => Number(r) ));
I guess this stroke looks simple.
UPDATE
ES6:
it('should test numbers', async function () {
let first = Number(await $('#first').getText());
let second = Number(await $('#second').getText());
expect(first).toBeGreaterThan(second);
})
One option to approach it with a custom jasmine matcher:
toBeSorted: function() {
return {
compare: function(actual) {
var expected = actual.slice().sort(function (a, b) {
return +a.localeCompare(+b);
});
return {
pass: jasmine.matchersUtil.equals(actual, expected)
};
}
};
},
Here the matcher takes an actual input array, integer-sort it and compare with the input array.
Usage:
expect(element.all(by.css(".myclass")).getAttribute("id")).toBeSorted();
Note that here we are calling getAttribute("id") on an ElementArrayFinder which would resolve into an array of id attribute values. expect() itself is patched to implicitly resolve promises.

Resources