How do I get the contents of a Selenium Webelement in Javascript? - javascript

I've got a Selenium Webdriver on node.js and Firefox. I want to find and retrieve the outerHTML contents of a set of elements, but I'm failing at it.
Here's my code:
let Elements = await driver.findElements(By.xpath('//img'));
Elements.forEach(function(Element){
console.log(Element);
});
This produces a set of objects like:
WebElement {
driver_: Driver {
session_: Promise { [Session] },
executor_: Executor { customCommands_: [Map], log_: [Logger], w3c: true },
fileDetector_: null,
onQuit_: [Function: onQuit]
},
id_: Promise { 'b4519d34-d176-47e2-acf0-07d868e89a4e' }
}
I've also tried:
for(let Element of Elements) {
text = await Element.innerHTML;
console.log("Element: " + Element + " - Text:" + text);
}
This gives me the results:
Element: [object Object] - Text:undefined
And I've tried:
Elements = promise.fullyResolved(Elements);
console.log("Fullyresolved: " + Elements);
Elements.forEach(function(Element){
let text = Element.getAttribute("innerHTML");
console.log("Fullyresolved: " + text);
});
Which gives me:
Fullyresolved: [object Promise]
and a crash that Elements.forEach is not a function.
Any help appreciated!
TIA!

Related

JavascriptExecutor SyntaxError: Unexpected identifier. Why?

Java
JavascriptExecutor js = (JavascriptExecutor) driver;
Boolean ready = (Boolean)js.executeScript("the below JavaScript");
JavaScript
var ready = false;
window.onload = function() {
ready = true;
}
window.sleep = function() {
return new Promise(resolve => setTimeout(resolve, 2000));
}
for(var i = 0; i < 30; i++) {
if(ready) {
return true;
}
await sleep();
}
return false;
UPDATE: Sorry about the previous syntax error "funtion" in my post. That was a typo not in my actual code. All syntax errors should be gone, but I still get "SyntaxError: Unexpected identifier".
What this code is trying to do is wait for a maximum amount of time for the page to be loaded. I typically return document.readyState to check for that condition but in unique circumstances Chrome will suddenly stop loading the page and document.readyState hangs for 5+ minutes. This is killing my code so I am attempting to develop single-threaded code to kind of mimic a typically multi-threaded process.
Since JavaScript is single threaded (such a disappointing feature for a cool language like JavaScript), we have to be creative.
This code works in the browser console if you replace return true; with console.log('true'); and return false; with console.log('false'); so I don't see what the problem is.
Indeed there are some mistakes in your JavaScript code.
The first mistake is, in third line
window.sleep = funtion() { return new Promise(resolve => setTimeout(resolve, 2000)); }, the function spelling is wrong.
The second mistake is you should not use await when there is no async in your function definition. Here is the thing, async ensures that the function returns a promise, and wraps non-promises in it. The keyword await makes JavaScript wait until that promise settles and returns its result. await works only inside async functions. So you can avoid using these completely or you need to format it accordingly.
The third mistake is, you are trying to do return true; from the for loop of the if condition which is not allowed basically because it is not wrapped inside the function.
The fourth mistake is, you are not calling the window.onload function - as the result, it is always returns false even though the page is loaded.
The fifth thing is, I don't know what incomplete resolve is doing in window.sleep function.
The sixth thing is, you are returning return false; at the end without any reference which is completely meaningless.
I have modified the code and avoided the above mistakes, please look into it.
Below is the modified JavaScript code:
var status = false;
window.sleep = function() {
return setTimeout(() => {
console.log("=> Waited for 2 seconds...");
}, 2000);
}
var getStatus = function() {
for(var i = 0; i < 30; i++) {
if(window.onload = function() {
return true;
}) {
status = true;
console.log(i+"). Loaded ? "+status);
break;
} else {
console.log(i+"). Loaded ? "+status);
sleep();
}
}
return status;
}
getStatus();
Try the below Java code which prints true after the page loads :
System.setProperty("webdriver.chrome.driver", "C:\\NotBackedUp\\chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.get("http://www.google.com");
JavascriptExecutor js = (JavascriptExecutor) driver;
Boolean result = (Boolean) js.executeScript("var status = false;\r\n" +
"window.sleep = function() { \r\n" +
" return setTimeout(() => {\r\n" +
" console.log(\"=> Waited for 2 seconds...\");\r\n" +
" }, 2000);\r\n" +
"}\r\n" +
"var getStatus = function() {\r\n" +
" for(var i = 0; i < 30; i++) {\r\n" +
" if(window.onload = function() {\r\n" +
" return true;\r\n" +
" }) {\r\n" +
" status = true;\r\n" +
" console.log(i+\"). Loaded ? \"+status);\r\n" +
" break;\r\n" +
" } else {\r\n" +
" console.log(i+\"). Loaded ? \"+status);\r\n" +
" sleep();\r\n" +
" }\r\n" +
" }\r\n" +
" return status;\r\n" +
"}\r\n" +
"return getStatus();");
System.out.println(result);
I hope it helps...

Async code execution in protractor jasmine test

I have the below jasmine test case where I would like to store the values returned from the function getAllUsageCategoriesDropListElements into an array so that I can access the array inside the test case and evaluate it contents with another array.
it('Validate the Usage Category droplist values matches with the Usage Categories Table',() => {
let allUsageCategoryDropListElements: string[] = [];
additionalCostsPage.getAllUsageCategoriesDropListElements(
element => {
console.log("Text from the usage Cateory Droplist elements " + element);
allUsageCategoryDropListElements.push(element);
console.log(" from inside " + allUsageCategoryDropListElements.length);
}
);
console.log("Size of the array is " +allUsagCategoryDropListElements.length );
});
Method is below:
getAllUsageCategoriesDropListElements(evaluationFunc: (element: string) => void) : void {
E2EUtil.click(this.addAdditionalCostDialogue.usageCategoryField);
E2EUtil.waitFor(this.addAdditionalCostDialogue.usageCategoryDropListContainer);
browser.sleep(2000);
var usageCategoryFromPage: string[] = [];
element.all(by.xpath("//*[#id='usageCategory']/div/div[3]/div/ul/li[*]/span"))
.each(function (element, index) {
element.getText().then(function (text){
// console.log("text extracted is " + text);
usageCategoryFromPage.push(text);
})
})
.then(function(){
usageCategoryFromPage.forEach(evaluationFunc);
});
}
Size of the array printed inside the function increments properly but when printed outside it 0. I think it is because of async code execution. Can any one please help ? I am very new to this typescript world.
To run your code with an await you will need to add the async and await keywords correctly. Try:
it('Validate the Usage Category droplist values matches with the Usage Categories Table', async () => {
let allUsageCategoryDropListElements: string[] = [];
await additionalCostsPage.getAllUsageCategoriesDropListElements(
element => {
console.log("Text from the usage Cateory Droplist elements " + element);
allUsageCategoryDropListElements.push(element);
console.log(" from inside " + allUsageCategoryDropListElements.length);
}
);
console.log("Size of the array is " +allUsagCategoryDropListElements.length );
});
EDIT: Your getAllUsageCategoriesDropListElements is not asynchronous but you are using promises. You can update it to make it asynchronous and then your await/async in the calling function will work correctly.
Try:
async getAllUsageCategoriesDropListElements(evaluationFunc: (element: string) => void) : void {
E2EUtil.click(this.addAdditionalCostDialogue.usageCategoryField);
E2EUtil.waitFor(this.addAdditionalCostDialogue.usageCategoryDropListContainer);
browser.sleep(2000);
var usageCategoryFromPage: string[] = [];
let elements = element.all(by.xpath("//*[#id='usageCategory']/div/div[3]/div/ul/li[*]/span"));
for(var i = 0; i < elements.length; i++) {
let element = elements[i];
let text = await element.getText();
// console.log("text extracted is " + text);
usageCategoryFromPage.push(text);
}
usageCategoryFromPage.forEach(evaluationFunc);
}

Firebase Web - How to retrieve nested data from database? Keeping getting back [object Object] [duplicate]

This question already has answers here:
Converting an object to a string
(40 answers)
Closed 4 years ago.
I am new to Firebase and I am trying to learn how to retrieve nested data from Firebase and print out on a webpage. I can print out all the data in the console but not on the webpage and I will get back [object Object] on the webpage. Can someone please kindly explain to me why is my webpage is printing out [object Object]? Is it something to do with "ref.on" or "snap =>"?
This is my function to get the data from Firebase:
function GetData()
{
var ref = firebase.database().ref('Scores');
var PrintData = document.getElementById('PrintOutData');
ref.on("value", function(snapshot)
{
snapshot.forEach(function(childSnapshot)
{
var childData = childSnapshot.val();
var id=childData.id;
console.log(childData);
ref.on('value', snap => PrintData.innerText = childData);
});
});
}
This is what in my console log:console.png
And this is what on my webpage: web.png
And this is my data on my Firebase:
What I want to get on my webpage is like this:
name:"AAA", score:100, time:"30s"
name:"AAA", score:100, time:"30s"
...
Thanks alot!
You can use:
let keys = Object.keys(childData)
keys.forEach(key => {
let value = childData[key.toString()];
console.log(value) // Use Value
})
Yikes, that looks pretty bad. Not sure why you're nesting an on("value" listener, and I also don't know where .id comes from. You're probably looking for something along these lines:
ref.on("value", function(snapshot) {
PrintData.innerText = ""; // We got a new value, so clear the existing HTML
snapshot.forEach(function(childSnapshot) {
var childData = childSnapshot.val();
PrintData.innerText += "name: " + childData.name + " score: " + childData.score + " time: " + childData.time + " ";
});
});

Rx.js, using Subject to multicast from Observable

If there are any Rx.js experts out there? I'm trying to multicast an observable by using a subject, as per the instructions on any number of websites, including rx.js docs.
var mainDataSource = Rx.Observable.from(summaries[0].added)
//add a timestamp to each live entry as it passes through
.timestamp()
.map(function(scriptElement) {
var array = [scriptElement, scriptElement.timestamp]; return array;
})
//check contents of the array
.do(function(array) { console.log(array); });
var multicaster = new Rx.Subject();
var subSource = mainDataSource.subscribe(multicaster);
//attach the inline observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to inlineScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('inlineScriptObserver onCompleted'); }
);
//attach the external observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to externalScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('externalScriptObserver onCompleted'); }
);
And the output I'm getting is as follows:
[Object, 1493425651491]
inlineScriptObserver onCompleted
externalScriptObserver onCompleted
So the Subject and the Observable are clearly connected as the onCompleted event is being transmitted from one to the other. However I am getting no data travelling alongside. The data in the correct format is there at the end of the Observable but it is not printing in the console from the Subject's Observer.
What am I missing? It's eluding me.
OK, it may be bad form answering your own question but in case anyone else comes along with the same problem...
The docs I read must have been outdated, relating to rx.js 4 not 5, or something. As of today, and according to this page,
https://github.com/ReactiveX/rxjs/blob/master/doc/subject.md
The correct syntax for the above example is as follows:
var multicaster = new Rx.Subject();
var mainDataSource = Rx.Observable.from(summaries[0].added)
//add a timestamp to each live entry as it passes through
.timestamp()
//log the timestamp for testing purposes
.do(function(scriptElement) { console.log("mainDataSource Timestamp: " + scriptElement.timestamp); })
//include the timestamp in array and deliver that array to subscribers
.map(function(scriptElement) { var array = [scriptElement, scriptElement.timestamp]; return array; })
//check contents of the array
do(function(array) { console.log(array); });
var multicastedDataSource = mainDataSource.multicast(multicaster);
//attach the inline observer to the multicaster subject
multicastedDataSource.subscribe(val => console.log(val), null, () => console.log('inlineScriptObserver complete'));
//attach the external observer to the multicaster subject
multicastedDataSource.subscribe(val => console.log(val), null, () => console.log('externalScriptObserver complete'));
multicastedDataSource.connect();
The key differences being the use of multicast() rather than subscribe on the observable and then the requirement to connect() to the subject piping the multicasted observable as well as having the observers subscribing.
No wonder my older rx.js book was so cheap on Amazon...
Either subscribe before firing an events or use ReplaySubject. See the working fiddle:
var mainDataSource = Rx.Observable.from([1, 2, 3])
//add a timestamp to each live entry as it passes through
.timestamp()
.map(function(scriptElement) {
var array = [scriptElement, scriptElement.timestamp]; return array;
})
//check contents of the array
.do(function(array) { console.log(array); });
var multicaster = new Rx.ReplaySubject();
var subSource = mainDataSource.subscribe(multicaster);
//attach the inline observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to inlineScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('inlineScriptObserver onCompleted'); }
);
//attach the external observer to the multicaster subject
multicaster.subscribe(
function (x) { console.log('Value published to externalScriptObserver: ' + x); },
function (e) { console.log('onError: ' + e.message); },
function () { console.log('externalScriptObserver onCompleted'); }
);
The output is:
[Object, 1493467831996]
[Object, 1493467831999]
[Object, 1493467832000]
Value published to inlineScriptObserver: [object Object],1493467831996
Value published to inlineScriptObserver: [object Object],1493467831999
Value published to inlineScriptObserver: [object Object],1493467832000
inlineScriptObserver onCompleted
Value published to externalScriptObserver: [object Object],1493467831996
Value published to externalScriptObserver: [object Object],1493467831999
Value published to externalScriptObserver: [object Object],1493467832000
externalScriptObserver onCompleted

Get JavaScript errors coming from WKWebView

I implemented the WKScriptMessageHandler protocol and I have defined the userContentController(:didReceiveScriptMessage:) method.
When there is a error in the Javascript, I get (in the WKScriptMessage object) something not really useful like:
{
col = 0;
file = "";
line = 0;
message = "Script error.";
type = error;
}
On the other hand, if I open the Safari Web Inspector, I can see the real error which is (for instance):
TypeError: FW.Ui.Modal.sho is not a function. (In 'FW.Ui.Modal.sho', 'FW.Ui.Modal.sho' is undefined)
Is there a way to get that error back in my native code?
EDIT:
Just to clarify, the javascript code is written by a javascript developer (who doesn't have access to the native source code, so he can't debug the app via Xcode). The code he writes it's then pushed to the iOS app (downloaded from an enterprise app store).
You can wrap the expression in a try catch block before evaluating it. Then have the JavaScript return the error message if it fails. Here is an example taken from the Turbolinks-iOS adapter, available on GitHub.
extension WKWebView {
func callJavaScriptFunction(functionExpression: String, withArguments arguments: [AnyObject?] = [], completionHandler: ((AnyObject?) -> ())? = nil) {
guard let script = scriptForCallingJavaScriptFunction(functionExpression, withArguments: arguments) else {
NSLog("Error encoding arguments for JavaScript function `%#'", functionExpression)
return
}
evaluateJavaScript(script) { (result, error) in
if let result = result as? [String: AnyObject] {
if let error = result["error"] as? String, stack = result["stack"] as? String {
NSLog("Error evaluating JavaScript function `%#': %#\n%#", functionExpression, error, stack)
} else {
completionHandler?(result["value"])
}
} else if let error = error {
self.delegate?.webView(self, didFailJavaScriptEvaluationWithError: error)
}
}
}
private func scriptForCallingJavaScriptFunction(functionExpression: String, withArguments arguments: [AnyObject?]) -> String? {
guard let encodedArguments = encodeJavaScriptArguments(arguments) else { return nil }
return
"(function(result) {\n" +
" try {\n" +
" result.value = " + functionExpression + "(" + encodedArguments + ")\n" +
" } catch (error) {\n" +
" result.error = error.toString()\n" +
" result.stack = error.stack\n" +
" }\n" +
" return result\n" +
"})({})"
}
private func encodeJavaScriptArguments(arguments: [AnyObject?]) -> String? {
let arguments = arguments.map { $0 == nil ? NSNull() : $0! }
if let data = try? NSJSONSerialization.dataWithJSONObject(arguments, options: []),
string = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
return string[string.startIndex.successor() ..< string.endIndex.predecessor()]
}
return nil
}
}

Categories