Selenium relative locator searches from a WebElement extremely slow - performance

I am using Selenium 2.0, Firefox 11.0, and Java to process a table. I have a table element composed of td cells, some which contain text included in a span element, others which contain input elements which have text in their value attributes. My goal is to get the text of every cell so I can output the table contents and compare them against expected values. I thought I would just do something like this:
Locate the table WebElement by id
List<WebElement> cells = tableElem.findElements(By.xpath(".//td"));
Then I would loop through all the cells and run findElements with the xpath ".//input" and if the list was empty I would run getText on the webElement, and if the list wasn't empty I would run getAttribute on the input element.
But to my surprise, this took several minutes to run on firefox (I'm afraid to try it on IE, which is where its supposed to be tested). When I debug it is obvious that the bottleneck is the .//input search from the td which is killing me. It is upwards of ten seconds, and so even with just a few cells my tests are taking forever. I've tried all sorts of minor variations to the xpath, tried going to css selectors, and continue to get the same results.
I want some advice about how to either tackle this problem differently or how to optimize my current method. I was hoping this would only take a couple of seconds.
I've included some sample code that should illustrate the slowdown I'm experiencing. This is not the website I'm screen scraping, but the slowness is the same:
webDriver.navigate().to("https://accounts.google.com/NewAccount");
List<WebElement> TDxpath = webDriver.findElements(By.xpath("//td"));
List<WebElement> TDcss = webDriver.findElements(By.cssSelector("td"));
for (WebElement td : TDcss) {
List<WebElement> q = td.findElements(By.cssSelector("input"));
}
for (WebElement td : TDxpath) {
List<WebElement> r = td.findElements(By.xpath(".//input"));
}

Do you really need a browser? You could try HtmlUnitDriver, that will be blazingly fast!
Or you could do it as a JS, that also only takes a fraction of time and you can get Lists from the script:
(JavascriptExecutor)driver.executeScript(
"var tds = document.getElementsByTagName('td');"
"for (var i = 0; i < tds.length; i++) {" +
" var inputs = tds[i].getElementsByTagName('input');" +
"}"
);

Related

kendo ui editor how to modify user selection with range object

Kendo UI 2015.2.805 Kendo UI Editor for Jacascript
I want to extend the kendo ui editor by adding a custom tool that will convert a user selected block that spans two or more paragraphs into block of single spaced text. This can be done by locating all interior p tags and converting them into br tags, taking care not to change the first or last tag.
My problem is working with the range object.
Getting the range is easy:
var range = editor.getRange();
The range object has a start and end container, and a start and end offset (within that container). I can access the text (without markup)
console.log(range.toString());
Oddly, other examples I have seen, including working examples, show that
console.log(range);
will dump the text, however that does not work in my project, I just get the word 'Range', which is the type of the object. This concerns me.
However, all I really need however is a start and end offset in the editor's markup (editor.value()) then I can locate and change the p's to br's.
I've read the telerik documentation and the referenced quirksmode site's explanation of html ranges, and while informative nothing shows how to locate the range withing the text (which seems pretty basic to me).
I suspect I'm overlooking something simple.
Given a range object how can I locate the start and end offset within the editor's content?
EDIT: After additional research it appears much more complex than I anticipated. It seems I must deal with the range and/or selection objects rather than directly with the editor content. Smarter minds than I came up with the range object for reasons I cannot fathom.
Here is what I have so far:
var range = letterEditor.editor.getRange();
var divSelection;
divSelection = range.cloneRange();
//cloning may be needless extra work...
//here manipulate the divSelection to how I want it.
//divSeletion is a range, not sure how to manipulate it
var sel = letterEditor.editor.getSelection()
sel.removeAllRanges();
sel.addRange(divSelection);
EDIT 2:
Based on Tim Down's Solution I came up with this simple test:
var html;
var sel = letterEditor.editor.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
html = html.replace("</p><p>", "<br/>")
var range = letterEditor.editor.getRange();
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = html;
var frag = document.createDocumentFragment(), child;
while ((child = div.firstChild)) {
frag.appendChild(child);
}
range.insertNode(frag);
The first part, getting the html selection works fine, the second part also works however the editor inserts tags around all lines so the result is incorrect; extra lines including fragments of the selection.
The editor supports a view html popup which shows the editor content as html and it allows for editing the html. If I change the targeted p tags to br's I get the desired result. (The editor does support br as a default line feed vs p, but I want p's most of the time). That I can edit the html with the html viewer tool lets me know this is possible, I just need identify the selection start and end in the editor content, then a simple textual replacement via regex on the editor value would do the trick.
Edit 3:
Poking around kendo.all.max.js I discovered that pressing shift+enter creates a br instead of a p tag for the line feed. I was going to extend it to do just that as a workaround for the single-space tool. I would still like a solution to this if anyone knows, but for now I will instruct users to shift-enter for single spaced blocks of text.
This will accomplish it. Uses Tim Down's code to get html. RegEx could probably be made more efficient. 'Trick' is using split = false in insertHtml.
var sel = letterEditor.editor.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
var block = container.innerHTML;
var rgx = new RegExp(/<br class="k-br">/gi);
block = block.replace(rgx, "");
rgx = new RegExp(/<\/p><p>/gi);
block = block.replace(rgx, "<br/>");
rgx = new RegExp(/<\/p>|<p>/gi);
block = block.replace(rgx, "");
letterEditor.editor.exec("insertHtml", { html: block, split: false });
}

Applying change event on j-Query Data-Table Search Box?

I have j-Query data-table with many records and I have builtin search-box. What I am trying is to sum all values in all the tds which have class="amount". It's happening succesfully. Now, the problem is search box. I want to sum the values of tds with class name amount which are only visible. I tried many ways but nothing worked Following is my code:
var salaryTable = $('#tblSalary').DataTable();
salaryTable.on('search', function () {
var sum = 0;
$(".amount").each(function() {
var value = $(this).text();
if(!isNaN(value) && value.length != 0) {
sum += parseFloat(value);
}
});
alert(sum);
});
This logic is not working as expected. How can I solve this or What am I doing wrong? Is there any better approach?
Update: The problem is when I search something it gives me total of visible and invisible records. When I clear the search box with backspace, it gives me total of all records where were visible before.
If you only want visible elements with class amount you could use the jQuery :visible selector
$(".amount:visible").each(...)
jQuery docs https://api.jquery.com/visible-selector/

the .click event not working ONLY for the last button (events defined in a for-loop)

I made a function that sets .click for the number of buttons passed to it.
The function is called when another Jquery detects the number of buttons on the page
...
var n = $(".button").length + 1;
...
set_navig(n);
...
function set_navig(n){
for(i=1 ; i<n ; i++){
var btn = "#pb" + i;
$(document).ready(function(){
$(btn).click(function(){
alert('Working!');
});
});
}
}
I have tried removing or adding buttons on the page - correct (n) is passed to the function, but ALWAYS only the last one doesn't work at all.
ANY IDEAS?
Thnx to EVERYBODY for so much good stuff available here. You where the major source of Jquery knowledge when I started learning it.
Well, I originally thought you should use i<=n, but you pass length + 1 (that's unusual by the way). You could just pass the button array instead of the length. Then you can iterate through each one.
I don't see a specific error with the code other than style, so perhaps you are not getting as many .button objects as you think or perhaps you have a typo in the last ID. You should paste a sample of the html code or just debug it yourself using alert() to see what values are being passed.
For future reference, common loops in many languages are either: for(i=0;i<n;i++){} or for(i=1;i<=n;i++){} I would say passing length + 1 to your function is very poor practice.

SeleniumWebdriver - size of collection selected elements

I would like to select all childs and grandchilds and grand-grand etc. in root div,
But problem is: when collection of these selected elements has size greater than 55, The collection cut first half of elements,
I have 98 divs in my parent div overall, but selenium doesn`t know to sum them.
Try running this code and see how many elements are displayed, for me it showed 1000+ elements:
public static void main(String[] args) {
WebDriver driver = new FirefoxDriver();
driver.get("http://yahoo.com");
List<WebElement> allElements = driver.findElements(By.xpath("//*"));
System.out.println("number of elements in the page: " + allElements.size());
driver.quit();
}
It doesn't really matter which elements are selected, you can change the xpath to By.xpath("//div") and it'll still show you 300+ results.
Could it be that your IDE only displays partial info during debug?

How do I use an add on to create a rule to find and replace text in a Site?

I have both stylish and grease monkey installed in Firefox 5. I want to know if either of them or another add on has the capability of finding text and replacing it with something else, or better yet locating a div by its id and replacing the span within with another string of text.
From OP comment:
I have a website with a div (id=siteLinkList), with a ul and multiple lis inside the div.
Each li has an a with text that needs to be replaced. I want the script to search for the div and then find and replace text inside that div.
Here is what I have so far:
var els = document.getElementsByTagName("*");
for(var i = 0, l = els.length; i < l; i++)
{
var el = els[i];
el.innerHTML = el.innerHTML.replace(/EGN1935: 5091, Summer B 2011/gi, 'Success');
el.innerHTML = el.innerHTML.replace(/EGN1935: 5088, Summer B 2011/gi, 'Chemistry');
}
The script works but I fear that it delays the loading time.
Yes, Greasemonkey can do this. (Even Stylish can do this in a limited way with CSS content.)
There must be zillions of scripts that do this at userscripts.org.
See also, related SO questions like:
Greasemonkey script in Firefox 4, want to change one line of code on webpage
Use Greasemonkey to remove table
Find and replace in a webpage using javascript.
You need to post details of what the page is/should-be, before and after.
More specific answer based on update(s) from OP:
Speed up your code by focusing on the kinds of elements you want, AMAP, instead of a fetching every element.
Code like so, should work. :
var TargLinks = document.querySelectorAll ('div#siteLinkList ul li a');
for (var J = TargLinks.length - 1; J >= 0; --J)
{
/*--- Does "EGN1935: 5088, Summer B 2011" only appear in the text of
the link or in the href?
The first block will be more efficient if it works, otherwise use
the 2nd block.
*/
var el = TargLinks[J];
el.textContent = el.textContent.replace (/EGN1935: 5091, Summer B 2011/gi, 'Success');
el.textContent = el.textContent.replace (/EGN1935: 5088, Summer B 2011/gi, 'Chemistry');
/* Only use this block if the first block did not work.
el.innerHTML = el.innerHTML.replace(/EGN1935: 5091, Summer B 2011/gi, 'Success');
el.innerHTML = el.innerHTML.replace(/EGN1935: 5088, Summer B 2011/gi, 'Chemistry');
*/
}
You can do this with Firebug - http://getfirebug.com/. Once you install it, activate it by clicking the bug looking icon on the page you want to edit. A view of the HTML document tree will appear, and you can click arrows to drill further down. Alternatively, you can use the pointer icon inside Firebug to select any HTML element on the page (such as a div with a specific ID).
Once you have the element selected, you can select the text that it contains and edit it as you like.
You can edit a ton of other things with this plugin, but it's important to know that once you reload the page your edits will go away.

Resources