xPath in selenium with multiple premises in multiple node layers - javascript

I am having a little problem with xpath in seleniumdriver.
I would like an xpath locator to narrow down its selection via two variables using exact matching at different points of the node hiearchy. This part is done.
You may imagine my case as addressing a two dimensional array in the xml with xPath, with each dimension being given as the two variables I have in it(they are standard text searches via js variables, not xpath variables).
What I'm struggling with is the resulting construction does not tell the difference between the elements of the first dimension, so as long as the given variable value is one of the dimensions, it will address every element in the second dimension fine. I can not assume they are unique or they are in any order. I am using it for testing so this is not acceptable.
How can I form an expression that will not doesn't do the same mistake?
I have tried the 'and' expression but both selenium and xpath tools say the value is '1' for 'found' but it doesn't give me a node locator to work with.
Example, my structure looks similar, so addressing it properly by x1/y1 for example looks fine.
//x1//y1
//x1//y2
//x2//y3
//x2//y4
//x3//y5
//x3//y6
Should work, works ok.
//x1x//y1
//x1x//y2
//x2x//y3
//x2x//y4
//x3x//y5
//x3x//y6
(Giving nonexistent input as 1st dimension.) My input is not fault tolerant, I look for exact value so the tests fail here as they should.
//x2//y1
//x2//y2
//x3//y3
//x3//y4
//x1//y5
//x1//y6
DING, the locator finds y values here when it should not(the y vales are on different leaves of the node tree). I need help with this.
Here is the locator in question:
return element(by.xpath(".//div[#name='typeList']//div[.//text()='" + moduleName + "']//div[./text()='" + typeName + "']")).getText();
TypeList is the name of the owner element, it does not make any differnece if I remove it, but please keep it in mind when giving me examples.

In the end, it was indeed a syntactical problem, before the text keywords.
I was trying this
//div[./text()='Zero']//div[./text()='Number']
Instead, I needed something like this.
//div[.//text()='Zero']/div[.//text()='Number']
Apparently the first one does looks for 'Number' regardless the value of the first constraint as long as every is defined in my file(does not have to be in its upward xnode path.)
As a final note, I advise against using the chrome xpath helper as its behavior is near random, it gives different results after deleting and replacing the same expression. Ugh. The only other one for chrome is adware... I figured my result out by trial and error with the firefox xpath checker tool.
1, I ended up needing to additionally add an node upwards for the element for angular select ui tool(we use selectize.js, a searchable select box), else it was confused what to return, but this is unrelated to the original question as I tried that before with the original expression.
2, I also had to add a node between the first and second text search, else it would look for the second expression in the first one too, eg. looking for Number in Zero, and treat it like a valid value if found. The problem still occurs the other way around, this can be fixed too by applying additional type/name constraints in the first one(not in final example to save space).
So this is what I ended up with:
.//div[#name='typeList']//div/div[.//text()='Zero']/div/div/div[.//text()='Number']

Related

Is there a way to distinguish selecting from a datalist vs typing the same string manually?

As far as I can tell there isn't, but I figured I'd ask.
I have a text input. Autocomplete suggestions are fetched dynamically as you type and fill a datalist attached to the input. Normally, typing something and pressing the "search" button brings up a table of search results to select from.
Since the datalist is basically the exact same thing, but simplified, and selecting an option from it is unambiguous, I'd like it to just carry on with my selection handlers without having to bring up the list for selection a second time. When the person manually types something though, I still want them to explicitly pick from the list, especially since some options may be substrings of the others, so I don't want it to auto-select a result for you if it matches halfway through.
I ended up not reimplementing it like ControlAltDel suggested in his comment and instead went with the following slightly hacky but functional solution:
Since I am refetching the search results as you type, if only 1 search result is returned (ie. it's unambiguous) and the current string is a case-insensitive exact match to that result, then select it. It works well for what I need it for, but I could imagine this not working for everyone.
The JS is roughly as follows:
if (searchResults.length === 1
&& searchString.toLowerCase() === searchResults[0].toLowerCase()
) {
selectResult(searchResults[0]);
}
I'm calling this in my handler for when the search results list changes, not the input's handler, since the results are only re-fetched after the input has already been changed.

Navigate to a tab of a website based on title name in VBA

I am trying to navigate to another tab shown in the html code below from my current tab in VBA.
<div title="0274 AP INVOICES SAP" data-bind="text: Name, attr: { title: Name }">0274 AP INVOICES SAP</div>
I tried the following:
ie.document.querySelector("[title*=AP INVOICES SAP].menu-row active-route").Click
but this gave me the "Method 'querySelector' of object 'JScriptTypeInfo' failed" error, and I also tried the following:
ie.document.querySelector("div[data-bind*='0274 AP INVOICES SAP']").Click
but this gave me the "Object required" error.
What should I do to navigate to this tab properly? Please let me know if you need more information
You have half the answer (syntax wise) in each of your two lines.
You need
ie.document.querySelector("[title='0274 AP INVOICES SAP']").click
Assuming a proper page load wait has happened before and that this title = value combination is either unique and/or you want the first; that it is not dynamic and that it is the correct target for the click event.
Due to the spaces in the value string you need to use single quotes around the it. Also, after the attribute selector you should not be using class selectors which aren't part of the target element.
If you continue to get a JScriptTypeInfo error, which I have seen before with pages using knockoutjs, you need to either resort to MS ScriptControl and a lot more code (on a 32 bit system) - which I don't recommend, or switch to using a getElement(s) by method where this problem shouldn't occur. You are ending up with an array like object that VBA is calling JSCriptTypeInfo and if you inspect it you will mostly likely see a string denoting an array of objects (nodes) which come, I suspect, from a default member call.

TypeError: Cannot call method "indexOf" of null

I'm triying to find the records that includes "SO -" or "NS - SO" or "SO –" or "SWAT" on THE "RESUMEN" field from a CSV file to asigne a new category (in this cases would be "Call Center"). So, I used "indexOf" funtion witch worked so well.
The problem comes when I change the data source (It is a CSV too), this gave me next error on that step:
"Caused by: org.mozilla.javascript.EcmaError: TypeError: Cannot call method "indexOf" of null (script#2)"
The objective is to assign a category by identifying the words on the source file
My code
if (RESUMEN.indexOf("SO -")!=-1 || RESUMEN.indexOf("NS - SO")!=-1 || RESUMEN.indexOf("SO –" )!=-1 || RESUMEN.indexOf("SWAT")!=-1)
{
var RESULTADO = "Call Center"
}
else RESULTADO = ""
I expect to assigne call center category like I got with the first file (I did not change nothing)
regards!
You're overcomplicating the issue.
Before the answer, remember something, there are several steps, and combinations of steps, that achieve an incredible number of transformations to make usable patterns, the last resort IS User defined Java Expression.
Seems like what you want to achieve is a Value Mapping, thou the difference from a direct value map in your case, is that the row you're testing must contain "SO -", and the other cases, somewhere in the text.
With this simple filter, you can transform your data that contains those informations as you desire, and on the "FALSE" side, treat it for errors.
This will expand your transformation a bit, but when you need to change something it will be easier than with a single step with a lot of code.
As another answer pointed out, you can achieve the same result with different steps, you don't need the javascript step.
But, if you want to go that route, you should first convert null values into, e.g., empty strings.
Simply add this to the beginning of your javascript code:
if (!RESUMEN){ RESUMEN = ''}
That'll convert nulls to empty strings and then indexOf returns correctly.

Javascript Object Not Empty (But it should be)

I am encountering very strange behavior in Javascript, which I have never seen before. Below is a general example of the problem I am seeing:
var myObject = {};
$.each(myDict,function(k,v){
myObject[k]={nodes:[],links:[]};
console.log(myObject[v]);
//then perform some calculations to create a var link and a var node
myObject[k].links.push(link);
myObject[k].nodes.push(node);
})
When I do the console.log(myObject[v]), it will show "Object {nodes: Array[0], links: Array[0]}", which is what is expected. However, when I expand that (in the console), it shows "links: Array[35]" and "nodes: Array[40]". I have also checked to make sure the links and nodes I am pushing have the correct values, and they do, however they do not even appear in myObject[k].link even immediately after they were pushed. Instead, the links and nodes mysteriously already in myObject[v] have random null fields all over the place, which is impossible, given that none of the links and nodes I generate have such field values.
I can't figure out why in the world myObject isn't empty. I have also tried deleting the object using "delete", but to no avail--I suspected this could be some memory management issue. This behavior is extremely odd. It is also worth nothing that if I change the dictionary field names, it works fine a single time, and then after that I encounter the same problem.
Has anyone encountered this problem before?
Push without mentioning [k] index.
myObject.links.push(link);

jQuery "Autocomplete" plugin is messing up the order of my data

I'm using Jorn Zaefferer's Autocomplete plugin on a couple of different pages. In both instances, the order of displayed strings is a little bit messed up.
Example 1: array of strings: basically they are in alphabetical order except for General Knowledge which has been pushed to the top:
General Knowledge,Art and Design,Business Studies,Citizenship,Design and Technology,English,Geography,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Displayed strings:
General Knowledge,Geography,Art and Design,Business Studies,Citizenship,Design and Technology,English,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Note that Geography has been pushed to be the second item, after General Knowledge. The rest are all fine.
Example 2: array of strings: as above but with Cross-curricular instead of General Knowledge.
Cross-curricular,Art and Design,Business Studies,Citizenship,Design and Technology,English,Geography,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Displayed strings:
Cross-curricular,Citizenship,Art and Design,Business Studies,Design and Technology,English,Geography,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Here, Citizenship has been pushed to the number 2 position.
I've experimented a little, and it seems like there's a bug saying "put things that start with the same letter as the first item after the first item and leave the rest alone". Kind of mystifying. I've tried a bit of debugging by triggering alerts inside the autocomplete plugin code but everywhere i can see, it's using the correct order. it seems to be just when its rendered out that it goes wrong.
Any ideas anyone?
max
EDIT - reply to Clint
Thanks for pointing me at the relevant bit of code btw. To make diagnosis simpler i changed the array of values to ["carrot", "apple", "cherry"], which autocomplete re-orders to ["carrot", "cherry", "apple"].
Here's the array that it generates for stMatchSets:
stMatchSets = ({'':[#1={value:"carrot", data:["carrot"], result:"carrot"}, #3={value:"apple", data:["apple"], result:"apple"}, #2={value:"cherry", data:["cherry"], result:"cherry"}], c:[#1#, #2#], a:[#3#]})
So, it's collecting the first letters together into a map, which makes sense as a first-pass matching strategy. What i'd like it to do though, is to use the given array of values, rather than the map, when it comes to populating the displayed list. I can't quite get my head around what's going on with the cache inside the guts of the code (i'm not very experienced with javascript).
SOLVED - i fixed this by hacking the javascript in the plugin.
On line 549 (or 565) we return a variable csub which is an object holding the matching data. Before it's returned, I reorder this so that the order matches the original array of value we were given, ie that we used to build the index in the first place, which i had put into another variable:
csub = csub.sort(function(a,b){ return originalData.indexOf(a.value) > originalData.indexOf(b.value); })
hacky but it works. Personally i think that this behaviour (possibly coded more cleanly) should be the default behaviour of the plugin: ie, the order of results should match the original passed array of possible values. That way the user can sort their array alphabetically if they want (which is trivial) to get the results in alphabetical order, or they can preserve their own 'custom' order.
What I did instead of your solution was to add
if (!q && data[q]){return data[q];}
just above
var csub = [];
found in line ~535.
What this does, if I understood correctly, is to fetch the cached data for when the input is empty, specified in line ~472: stMatchSets[""] = []. Assuming that the cached data for when the input is empty are the first data you provided to begin with, then its all good.
I'm not sure about this autocomplete plugin in particular, but are you sure it's not just trying to give you the best match possible? My autocomplete plugin does some heuristics and does reordering of that nature.
Which brings me to my other answer: there are a million jQuery autocomplete plugins out there. If this one doesn't satisfy you, I'm sure there is another that will.
edit:
In fact, I'm completely certain that's what it's doing. Take a look around line 474:
// loop through the array and create a lookup structure
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
/* some code */
var firstChar = value.charAt(0).toLowerCase();
// if no lookup array for this character exists, look it up now
if( !stMatchSets[firstChar] )
and so on. So, it's a feature.

Categories