Selenium Cucumber Fails to Check for Image - selenium-chromedriver

I was trying to test one of my web application using selenium-cucumber-js.
I have a Feature file written using the Gherkin syntax.
Feature: UPS XXX Troubleshooting
#
##Chatbot must start automatically
Scenario: Chatbot automatically says hello
Given a chat window "localhost:3000/"
Then the text displayed in position "1" shall contain following statements
| statement |
| Hey there! I’m a bot that can help you troubleshoot issues with UPS. |
| We’ll first need to determine exactly which UPS you have by collecting the model and serial number from its bottom or rear panel. It will be found on a white, rectangular sticker with two barcodes. |
| Please enter your Model Number from the barcode sticker |
And there shall be at least an embedded image in position "2" containing "UPSBarCode.jpg" file
I have written the Step definitions below
const timeout=60000
module.exports = function () {
this.Given(/^a chat window "([^"]*)"$/, function (url) {
return helpers.loadPage(url);
})
this.When(/^there shall be at least an embedded image in position "([^"]*)" containing "([^"]*)" file$/, function (position, jpgOrPng) {
return page.apcupsTroubleshooting.expectImage(jpgOrPng, position, timeout);
})
this.Then(/the text displayed in position "([^"]*)" shall contain following statements$/, function (position, table) {
table.rows().forEach(function (option, index) {
console.log("checking " + option);
return page.apcupsTroubleshooting.expectChatbotText(option[0], position, timeout);
});
})
};
Here is my Page objects
const expect = require('chai').expect;
module.exports = {
url: 'localhost:3000/',
elements: {
textInput: by.name("inputText"),
textOutputs: by.className("message")
},
/**
* types something in chatbot
* #param {any} userInput
*/
typeMessage: function (userInput) {
console.log("Going to type '" + userInput + "'");
var selector = page.apcupsTroubleshooting.elements.textInput;
return driver.findElement(selector).sendKeys(userInput, selenium.Key.ENTER);
},
expectChatbotText: function (text, position, timeout) {
console.log("checking '" + text + "'");
theXpath = "//div[#class='message']//p[contains(text(),\"" + text + "\")]";
console.log("xpath:" + theXpath)
return driver.wait(until.elementsLocated(by.xpath(theXpath)), timeout)
.then(() => {
driver.findElement(by.xpath(theXpath)).getText()
.then(t => {
try {
expect(t).to.contain(text)
}
catch (e)
{
return Promise.reject(false)
}
})
})
},
expectImage: function (imageFile, position, timeout) {
console.log("checking image " + imageFile + " existance in position " + position);
theXpath = " //img[contains(#src,'" + imageFile + "')]";
return driver.wait(
until.elementsLocated(by.css('.cardImage')), timeout)
.then(() => {
console.log('xxxxxxxxxxxxxxxxxxxx')
driver.FindElements(by.css('.cardImage'))
.then(t => {
console.log('yyyyyyyyyyyyyy')
try {
expect(t[0].GetAttribute("src").ToString()).to.contain(imageFile)
}
catch (e)
{
return Promise.reject(false)
}
})
})
},
checkOptionElement: function (option, position, timeout) {
console.log("checking list entry " + option + " existance in frame " + position);
theXpath = " //div[#class='card']/div/ul/li[contains(text(),\"" + option +"\")]";
return driver.wait(until.elementsLocated(by.xpath(theXpath)), timeout)
.then(() => {
return driver.findElement(by.xpath(theXpath));
})
}
};
Here is my html page
<section class="messages-wrapper">
<div class="messages">
<div class="group group-bot" id="message-group-bot-1">
<div>
<div>
<div class="messageParentContainer">
<div class="message">
<p>
<div>Hey there! I’m a bot that can help you troubleshoot issues with UPS.</div>
</p>
</div>
</div>
</div>
</div>
<div>
<div>
<div class="">
<div class="message">
<p>
<div>We’ll first need to determine exactly which UPS you have by collecting the model and serial number from its bottom or rear panel. It will be found on a white, rectangular sticker with two barcodes.</div>
</p>
<div class="cardImageContainer"> <img class="cardImage" src="https://somedomain/UPSBarCode.jpg"></div>
<div class="hide-container"> <object class="videoFrame" data=""></object></div>
</div>
</div>
</div>
</div>
<div>
<div>
<div class="messageParentContainer">
<div class="message">
<p>
<div>Please enter your Model Number from the barcode sticker</div>
</p>
</div>
</div>
</div>
</div>
</div>
<div style="float: left; clear: both;"></div>
</div>
</section>
I have also tried
expectImage: function (imageFile, position, timeout) {
console.log("checking image " + imageFile + " existance in position " + position);
theXpath = " //img[contains(#src,'" + imageFile + "')]";
return driver.wait(until.elementsLocated(by.xpath(theXpath)), timeout)
.then(() => {
return driver.findElement(by.xpath(theXpath));
})
},
So my problem is, I am not able to detect the Image so that the tests are failing. How to solve this issue?

I don't see the issue just by inspection, but a few things you could try:
Use FindElement instead of FindElements. FindElement will (helpfully) fail immediately if the element is not found, instead of happily finding nothing and passing an empty array downstream to fail in a more confusing way later.
I've had issues where the page simply wasn't in the expected state when the assertion ran, which is really frustrating when you look at the page a few milliseconds later and it's right there. You might try dumping the entire DOM to console just before the assertion (I think it's driver.getPageSource(), but don't quote me).
Some gratuitous, cheeky code review you didn't ask for (sorry):
When I'm debugging, I find it tremendously helpful for the step definition to follow the same flow as the feature file, and to make sure each step has the same Given/When/Then prefix in both places (it's kind of a nit, but you have a Then as a When currently).
Sometimes a whole set of problems will go away if you keep your regex as narrow as possible. I doubt this is related to your immediate problem, but those position number capture groups could be defined to only accept digits (%d+), and then you could even drop the double quotes if you prefer.
I've had great luck defining locators (e.g. by.css('.cardImage')) in a single location of the page object, so they aren't duplicated

Related

ChromiumWebBrowser - how to set height to HTML doc height

I have a chromiumWebBrowser hosted in my application.
user can navigate between html pages (with binging to address).
I need xaml scroller (not css), so I have a scrollViewer and inside the Chromium browser.
Every time the Address changes, chromium height needs to be full html doc height, to get the scrolling right.
I have tried setting it similar to answer in this Q:
cefSharp ChromiumWebBrowser size to page content
which works well first time, but when navigating, it only grows - if first page is 600, and next is 200 - it returns 600 second time too.
XAML:
<ScrollViewer x:Name="scroller" >
<wpf:ChromiumWebBrowser x:Name="chrome"
Address="{Binding CurrentUrl}" />
</ScrollViewer>
C#:
chrome.LoadingStateChanged += async (s, e) =>
{
if (!e.IsLoading) // browser.CanExecuteJavascriptInMainFrame == TRUE !
{
JavascriptResponse response =
await chrome.EvaluateScriptAsync(
// GET HEIGHT OF CONTENT
$"(function() " +
"{ var _docHeight = " +
" document.documentElement.scrollHeight; " +
" " +
" return _docHeight; " +
"} " +
")();");
int docHeight = (int)response.Result;
chrome.Dispatcher.Invoke(() => { chrome.Height = docHeight; });
}
};

VueJs - Updating class with a setInterval function not working [duplicate]

I'm new to Vuejs. Made something, but I don't know it's the simple / right way.
what I want
I want some dates in an array and update them on a event. First I tried Vue.set, but it dind't work out. Now after changing my array item:
this.items[index] = val;
this.items.push();
I push() nothing to the array and it will update.. But sometimes the last item will be hidden, somehow... I think this solution is a bit hacky, how can I make it stable?
Simple code is here:
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items[index] = val;
this.items.push();
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button>
{{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
EDIT 2
For all object changes that need reactivity use Vue.set(object, prop, value)
For array mutations, you can look at the currently supported list here
EDIT 1
For vuex you will want to do Vue.set(state.object, key, value)
Original
So just for others who come to this question. It appears at some point in Vue 2.* they removed this.items.$set(index, val) in favor of this.$set(this.items, index, val).
Splice is still available and here is a link to array mutation methods available in vue link.
VueJS can't pickup your changes to the state if you manipulate arrays like this.
As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items.$set(index, val)
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button> {{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>
As stated before - VueJS simply can't track those operations(array elements assignment).
All operations that are tracked by VueJS with array are here.
But I'll copy them once again:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
During development, you face a problem - how to live with that :).
push(), pop(), shift(), unshift(), sort() and reverse() are pretty plain and help you in some cases but the main focus lies within the splice(), which allows you effectively modify the array that would be tracked by VueJs.
So I can share some of the approaches, that are used the most working with arrays.
You need to replace Item in Array:
// note - findIndex might be replaced with some(), filter(), forEach()
// or any other function/approach if you need
// additional browser support, or you might use a polyfill
const index = this.values.findIndex(item => {
return (replacementItem.id === item.id)
})
this.values.splice(index, 1, replacementItem)
Note: if you just need to modify an item field - you can do it just by:
this.values[index].itemField = newItemFieldValue
And this would be tracked by VueJS as the item(Object) fields would be tracked.
You need to empty the array:
this.values.splice(0, this.values.length)
Actually you can do much more with this function splice() - w3schools link
You can add multiple records, delete multiple records, etc.
Vue.set() and Vue.delete()
Vue.set() and Vue.delete() might be used for adding field to your UI version of data. For example, you need some additional calculated data or flags within your objects. You can do this for your objects, or list of objects(in the loop):
Vue.set(plan, 'editEnabled', true) //(or this.$set)
And send edited data back to the back-end in the same format doing this before the Axios call:
Vue.delete(plan, 'editEnabled') //(or this.$delete)
One alternative - and more lightweight approach to your problem - might be, just editing the array temporarily and then assigning the whole array back to your variable. Because as Vue does not watch individual items it will watch the whole variable being updated.
So you this should work as well:
var tempArray[];
tempArray = this.items;
tempArray[targetPosition] = value;
this.items = tempArray;
This then should also update your DOM.
Observe object and array reactivity here:
https://v2.vuejs.org/v2/guide/reactivity.html

Why chrome.sync.set updates a value only once

I want the extension to count how many times the browser was opened.
Below is the piece of code which should do the job, but does not work as
expected. Why?
chrome.runtime.onStartup.addListener(function() {
chrome.storage.sync.get({'number' : 1}, function(result) {
// prints 1 the first time, then keeps printing 2 all the time, why?
console.log("Old number is " + result.number);
// Trying to increment 'number' value by 1
chrome.storage.sync.set({'number' : (result.number + 1)},
function() {})
});
});
I'm not sure you should be using "1" in get...
chrome.runtime.onStartup.addListener(function() {
chrome.storage.sync.get(['number'], function(result) {
let number;
if ('number' in result)
number = result.number;
else
number = 1;
// prints 1 the first time, then keeps printing 2 all the time, why?
console.log("Old number is " + number);
number += 1;
// Trying to increment 'number' value by 1
chrome.storage.sync.set({number: number},
function() {console.log("value as set to " + number);});
});
});
This code should probably be on a "background script", and the only console that will show anything is the console you open from "tools, more tools, extensions" and click the view link in your listed extension.
If you're having problems with syncing as mentioned in comments, you can try using chrome.storage.local.get / set.
I think that for what you're doing, this is better.

Display array from json data to cards

So, im a little bit lost here and i need some help.
I have a json that come from the server with data that i dont know.
Based on that i found a solution to display the data on html here on SO:
https://stackoverflow.com/a/50352965/9721446
But the problem is that each "item" is an entry from array, so if i ngfor array, it outputs each line as an item, and i want the item to be all entries of each result.
heres the html:
<ng-container *ngFor="let item of singleArray | paginate: { itemsPerPage:411, currentPage: p} ">
<!-- All the entries -->
<div class="w3-container">
<!-- Table view-->
<table class="center">
<tr *ngIf="!item.tag.includes('URL') && !item.tag.includes('linkChal')">
<td><div class="col-xs-auto thick">{{item.tag.toLowerCase() | translate}}</div></td>
<td class="tab">{{item.value}}</td>
</tr>
<tr *ngIf="item.tag.includes('URL')">
<td>Link da entrada: </td>
<td> - Ver mais -</td>
</tr>
<tr *ngIf="item.tag.includes('linkChal')">
<td>Link do Challenge: </td>
<td> - Challenge -</td>
</tr>
</table>
<div style="background-color: #ff7d2a">
<ul *ngIf=" item.tag.includes('---------')"><p>New Entry</p></ul>
</div>
</div>
</ng-container>
Ts:
for(let i in res)
{
//array with entities from json
this.entity.push(i);
for(let j in res[i])
{
let val = Number(j)+1;
this.cont.push(i +" - nº: " + val );
this.singleArray.push({
tag: i,
value: val
});
for(let t in res[i][j])
{
this.test.push(t);
this.cont.push(t +" - "+ this.responseList[i][j][t]) ;
if(t.split(".",2)[1] === "CompositeId")
{
this.test.push("URL:");
//Get the id
this.cont.push(this.moduleName + "/" + t.split(".",2)[0] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]);
//debugger;
this.singleArray.push({
tag: "URL:",
value: this.moduleName + "/" + t.split(".",2)[0] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]
});
}
else if(t.split(".",2)[1] === "Challenge")
{
this.singleArray.push({
tag: "linkChal",
value: this.moduleName + "/" +t.split(".",2)[1] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]
});
}
else {
this.singleArray.push({
tag: t,
value: this.responseList[i][j][t]
});
}
}
this.test.push("\n");
this.cont.push("\n");
this.singleArray.push({
tag: "---------\n",
value: "--------\n"
});
//it ends an item here
}
}
Heres the output i have with that:
Each one line is an entry from the array, the big question is, how to transform all lines/entries until "New Entry" and made an single item to ngfor and display data into a card that i already have..)
I've tried to create an array and push the singleArray into it (hoping each entry of that new array was an item that i want), at the end of for(let j in res[i]) on .ts but it just repeated all the entries creating a bunch of entries..
here, at the end of that for, i've tried to push an array with something, then ngfor it (it gives me the number items that i want, but then i dont have the results to access them..)
Has anyone had this problem before?
thanks in advance
Edit: here's what singleArray looks like:
Your best bet here is to follow the single responsibility principal and separate the concerns of each class.
Stop trying to do this all in the view and separate out the responsibility of formatting the data and the problem will seem much simpler.
Make a new class to define the model you want your view to use
Have your view implement this new ideal model that you control
Generate some test data to make get this looking like what you want
Create a new class who's entire responsibility is to turn the external model from the api response into this new internal model
json2ts may help generate a better external model from the response, but it may not be of much use in this case
Once you have done the above, based on your sample output, it should be fairly simple to convert from the external model into the internal model. It's hard to convey this, but assuming the hyphens are the item separator you could simply do something like the following:
const externalItems = // data from api
const internalItems = [];
let currentInternalItem = {};
externalItems.forEach(item => {
if (item.tag.startsWith('---------')) {
internalItems.push(currentInternalItem);
currentInternalItem = {};
} else {
currentInternalItem[item.tag] = item.value;
}
});
This would group the array back into an object that you can use in your view.
I think I'm complicating too much.. The objective here is to display what comes from JSON into specific locations, like a card, with header and content, to better display the results.
I have a service that gives me a JSON, that i never knows what inside, that depends on the search term and can bring much information. For example:
If the term is "Idea":
If the term is Challenge:
My .ts file is only console.log what comes from the api.
ngOnInit() {
var setting = {setting to connect the server..}
enter code here
$.ajax(settings).done((rest) => this.response(rest));
}
response(res){
console.log(res);
}
How can i display that data the way i want?
Sorry for the long post, and for not beeing objective on the main question.

How to filter or custom filter array of objects based on matching values from another object

I implemented an advance search with 15 input fields in AngularJS.
In the page load itself the result set is return from database in JSON format and i need to do the filter in client side only.
The input criteria's equivalent column is available in the result set and i need to check in its respective column only.
I am converting each column by JSON.stringify() and check with the search params like the below :
$scope.filteredData = $scope.actualData.filter(function(item) {
return JSON.stringify(item.FirstName).toLowerCase().indexOf(lowerFirstName) != -1 &&
JSON.stringify(item.LastName).toLowerCase().indexOf(lowerLastName) != -1 &&
JSON.stringify(item.EmailAddress).toLowerCase().indexOf(lowerEmailAddress) != -1 &&
JSON.stringify(item.Address1).toLowerCase().indexOf(lowerAddress1) != -1 &&
JSON.stringify(item.Address2).toLowerCase().indexOf(lowerAddress2) != -1;
...... etc // upto 15 fields
});
Since i have the 15 input fields and the actual result set contains a minimum of 50,000 records.
So converting each record's each column by JSON.stringify() and check with search params will surely cause the performance issue.
Is there any other way to achieve the filtering in client side with other approach.
I posted a sample code in Plunker with 5 input fields only : http://plnkr.co/edit/nUWZEbGvz7HG6gb91YZP
sylwester's answer is the normal way you'd filter things. Your code looks like you want to filter down to only the object that matches every input field. You code attempts to find an object where every property matches the searchParams object. At that point, I don't see what benefit there is to finding that object, because the user already created the object again! Nonetheless, here's a proper version of your code:
Live demo here.
<div ng-repeat="data in actualData | filter:searchData()">
$scope.searchData = function() {
return function(item) {
return Object.keys(item).every(function(key) {
// skip the $$hashKey property Angular adds to objects
if (key === '$$hashKey') { return true; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
return item[key].toLowerCase() === $scope.searchParams[searchKey].toLowerCase();
});
};
};
You really need to limit the data coming from the server for the browser's sake and for the server's sake. It's easy to implement a LIMIT, OFFSET system. It sounds like, overall, you just need to be able to query the server for a certain record.
From your comments, it seems you definitely want Angular's built in filter filter:searchParams, and just capitalize your searchParams models to match your data. For fun, I'll include more options for finer tuning.
This one almost mimics filter:searchParams. You can change > 1 to adjust when the partial matching kicks in, or have it return true only when both items are strictly equal === to disable partial matching. The difference here is that all items are hidden until matched, whereas filter:searchParams will show all items and then remove what doesn't match.
Live demo here.
$scope.searchData = function() {
return function(item) {
return Object.keys(item).some(function(key) {
if (key === '$$hashKey') { return false; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
var currentVal = $scope.searchParams[searchKey].toLowerCase();
var match = item[key].toLowerCase().match(currentVal);
return currentVal.length > 1 && match;
});
};
};
Lastly, to perfectly mimic filter:searchParams, you'd just put in a check to NOT filter the items until there is user input and the input is long enough to start the partial match.
Live demo here.
$scope.searchData = function() {
var partialMatchLength = 2;
return function(item) {
var shouldFilter = Object.keys($scope.searchParams).some(function(key) {
return $scope.searchParams[key] && $scope.searchParams[key].length >= partialMatchLength;
});
if (!shouldFilter) { return true; }
return Object.keys(item).some(function(key) {
if (key === '$$hashKey') { return false; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
var currentVal = $scope.searchParams[searchKey].toLowerCase();
var match = item[key].toLowerCase().match(currentVal);
return currentVal.length >= partialMatchLength && match;
});
};
};
First of all you ng-repeter with 50.000 records more likely is going to kill your browser, so you should thing about pagination.
Secondly you can easy filter your data using angular filter please see that demo
http://plnkr.co/edit/R8b8G4xCMSQmX1144UJG?p=preview
<div ng-controller="ListCtrl">
<br />
First Name:
<input type="text" id="txtFirstname" ng-model="searchParams.FirstName">
<br/>Last Name:
<input type="text" id="txtLastname" ng-model="searchParams.LastName">
<br/>Email Address:
<input type="text" id="txtEmailAddress" ng-model="searchParams.EmailAddress">
<br/>Address 1:
<input type="text" id="txtAddress1" ng-model="searchParams.Address1">
<br/>Address 2:
<input type="text" id="txtAddress2" ng-model="searchParams.Address2">
<br/>
<button class="btn btn-primary" ng-click="searchData()">Search</button>
<br />
<hr />
<b>Filtered Data(s):</b>
<div ng-repeat="data in actualData | filter:searchParams ">
<span ng-bind="data.FirstName"></span>
<span ng-bind="data.LastName"></span> |
Address : {{data.Address1}}
</div>
<hr />
</div>