Selecting Frames in node.js - node.js

For some reason I can't switch to nested frames in selenium-webdriver using node.js. I have tried setting timeouts to let the page load or timeouts to give the driver time to switch to another frame, nothing worked. This question is most likely a continuation of this. I am getting a NoSuchFrameError.
HTML - full url here
<!DOCTYPE html>
<html>
<head>
<title>HTML Target Frames</title>
</head>
<frameset rows="16%,84%">
<frame src="./framesHtml/top.htm" name="top_page" >
<frameset cols="50%,50%">
<frame src="./framesHtml/menu.htm" name="menu_page" >
<frame src="./framesHtml/main.html" name="main_page" >
</frameset>
</frameset>
</html>
Function switching to main_page
var webdriver = require('selenium-webdriver');
var chrome = require('selenium-webdriver/chrome');
var chromePath = require('selenium-chromedriver').path;
var FrameHandler = require('../../JS-Selenium-Toolkit/src/FrameHandler');
var expect = require('chai').expect;
describe('FrameHandler', function () {
it('Should pass if the set frame is main_page', function (done) {
this.timeout(15000);
var service = new chrome.ServiceBuilder(chromePath).build();
chrome.setDefaultService(service);
var chromeDriver = new webdriver.Builder()
.withCapabilities(webdriver.Capabilities.chrome())
.build();
var frameHandler = new FrameHandler(chromeDriver);
//check current frame name
frameHandler.getCurrentFrameName(function (name) {
console.log(name + ' current frame inside second function');
});
chromeDriver.get('http://orasi.github.io/Selenium-Java-Core/sites/unitTests/orasi/utils/frameHandler.html').then(function () {
frameHandler.switchToFrame('top_page').then(function () {
frameHandler.switchToFrame('main_page').then(function () {
frameHandler.getCurrentFrameName(function (name) {
console.log(name + ' this frame was switched to ');
expect(name).to.equal('main_page');
done();
});
});
});
});
});
});
FrameHandler Object
var webdriver = require('selenium-webdriver');
var FrameHandler = function (driver) {
this.switchToFrame = function (name)
{
if (typeof name !== 'string' || name === '' || !name)
{
console.log('error');
}
else
{
console.log(this.getCurrentFrameName(function (name) {
console.log(name + ' this is the current frame before switch');
}));
console.log(name + ' switch to this frame');
return driver.switchTo().frame(name);
}
};
this.getCurrentFrameName = function (callback)
{
driver.executeScript('return self.name').then(function (name)
{
return callback(name);
});
};
};
module.exports = FrameHandler;

I haven't verified myself with your example, but the consensus is that you cannot switch directly to a nested <frame> from the top-level, you need to switch into a parent <frame> first.
In other words, into 'top_page', then into 'main_page'.
See the top answers to:
Unable to click with Selenium in nested frames
How to navigate a subframe inside a frameset using Selenium WebDriver?

After further testing and switching drivers I found that the chrome driver doesn't support nested framesets. I tried going from top_page to menu_page and also tried just going directly to the menu_page, neither worked. I tried all types of solutions and was unable to get anything to work until I switched from the chrome driver to ie or firefox. I also tried removing the nested frameset in the html file and then running the chrome driver, the test case passed. So this is in fact an issue with nested framesets and the chrome driver. I really need to get a fix for this as this can affect automation using the chrome driver.

Related

Getting error while running selenium test in node.js

The code I am using to run the automated test for google search is below.
const webdriver = require('selenium-webdriver'),
By = webdriver.By,
until = webdriver.until;
const driver = new webdriver.Builder()
.forBrowser('chrome')
.build();
driver.get('http://www.google.com');
driver.findElement(By.name('q')).sendKeys('webdriver');
driver.sleep(10000).then(function() {
driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB);
});
driver.findElement(By.name('btnK')).click();
driver.sleep(20000).then(function() {
driver.getTitle().then(function(title) {
if(title === 'webdriver - Google Search') {
console.log('Test passed');
} else {
console.log('Test failed');
}
driver.quit();
});
});
and it is throwing an error which says that element is not interactable. I have added extra time delays for loading page successfully.
(node:32241) UnhandledPromiseRejectionWarning: ElementNotInteractableError: element not interactable
element not interactable is telling you that the element you are trying to click on - is just not clickable.
You have 2 ways to overcome this:
Find the child element of the element you are trying to click on or its parent element.
Force the element to be clicked by injecting JavaScript into it:
JavascriptExecutor jse = (JavascriptExecutor)driver;
jse.executeScript("document.getElementsByName
('btnK')[0].click();");
You need to make a couple of adjustments as follows:
Remove the first occurance of findElement(By.name('q')) as you are already using the line of code later.
Modify the second occurance of findElement(By.name('q')) to send the text and Key.RETURN
Remove the line findElement(By.name('btnK')) as you are already using the line of code later.
Your effective line of code will be:
const driver = new webdriver.Builder()
.forBrowser('chrome')
.build();
driver.get('http://www.google.com');
driver.sleep(10000).then(function() {
driver.findElement(By.name('q')).sendKeys('webdriver' + Key.RETURN);
});
driver.sleep(20000).then(function() {
driver.getTitle().then(function(title) {
if(title === 'webdriver - Google Search') {
console.log('Test passed');
} else {
console.log('Test failed');
}
driver.quit();
});
});
Basically driver.findElement() return promise,
So it can be anywhere in your code, so basically you have sync your code
may be here:
:driver.findElement(By.name('q')).sendKeys('webdriver');
or:
driver.findElement(By.name('btnK')).click();
so you need to make your code sync. just put ==>
await driver.findElement(By.name('btnK')).click();
-> this method won't work. Because you do not know browser behaviour some time it is fast and sometimes slow so this won't help you, driver.sleep().
Better to use:
await driver.wait(until.elementedlocated(By.xpath("xpath"),5000));

Make parallel requests to a Selenium Webdriver grid

I'm trying to use a selenium server grid to run multiple commands in parallel.
Here is my first test code:
var webdriver = require('selenium-webdriver');
for(var u = 0; u < 3; u++) {
makeScreenshot('foo/test' + u + '.png');
}
function makeScreenshot(path) {
var driver = new webdriver.Builder().forBrowser('firefox').usingServer('http://someurl:44111/wd/hub/').build();
console.log('Get');
driver.get('http://www.somepage.com').then(function() {
console.log('Screenshot');
driver.takeScreenshot().then(function(data){
console.log(path);
//var decodedImage = new Buffer(data, 'base64')
driver.quit();
});
});
}
That is the result:
Get
Get
Get
Screenshot
foo/test0.png
Screenshot
foo/test1.png
Screenshot
foo/test2.png
screenshot of requests
The "Get" appears immediately in sequence, "driver.get" creates a promise. My idea here is that the three requests are made asynchronously and thus appear almost simultaneously. But as you can see in the screenshot they will be made one after the other.
The grid definitely has enough selenium instances so why isn't the driver working in parallel?
It seems to me that "new webdriver.Builder()" creates some kind of singleton that doesn't work async but waits for the previous request to finish!?
Thanks for any help!
The answer may be multiple control flows:
WebDriverJS supports defining "parallel" flows using
webdriver.promise.createFlow(). This function accepts a callback which
will be passed the newly created flow. Tasks scheduled within this
flow will be synchronized with each other, but will remain independent
of any other control flows. Each call to createFlow() returns a
promise that will resolve when the flow has completed.
The example at the end of the chapter (which I'll quite verbatim) shows multiple Google search terms being tested concurrently:
var terms = [
'javascript',
'selenium',
'webdriver'
];
var flows = terms.map(function(term) {
return webdriver.promise.createFlow(function() {
var driver = new webdriver.Builder().build();
driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys(term);
driver.findElement(webdriver.By.name('btnG')).click();
driver.getTitle().then(function(title) {
if (title !== (term + ' - Google Search')) {
throw Error('Unexpected title: ' + title);
}
});
});
});
webdriver.promise.fullyResolved(flows).then(function() {
console.log('All tests passed!');
});
It should be easy enough to add your custom driver build and lookups into that example. Perhaps the following:
var flows = [0,1,2,3].map(function(index) {
return webdriver.promise.createFlow(function() {
var driver = new webdriver.Builder().forBrowser('firefox').usingServer('http://someurl:44111/wd/hub/').build();
console.log('Get');
driver.get('http://www.somepage.com').then(function() {
console.log('Screenshot');
driver.takeScreenshot().then(function(data){
console.log('foo/test' + index + '.png');
//var decodedImage = new Buffer(data, 'base64')
driver.quit();
});
});
});
});

Selenium WebDriver browser abstraction

Is there a way to use the same test code for Firefox and chrome? Kind of abstraction where I can execute piece of code for all browser driver, without repeating blocks of test code for each browser driver
for example code
var searchBox = ffDriver.findElement(webdriver.By.name('q'));
repeats for each browser driver.
var assert = require('assert'),
test = require('selenium-webdriver/testing'),
webdriver = require('selenium-webdriver'),
SeleniumServer = require('selenium-webdriver/remote').SeleniumServer;
var server = new SeleniumServer('selenium-server-standalone-2.39.0.jar', {
port: 4444
});
server.start();
var ffDriver = new webdriver.Builder()
.usingServer(server.address())
.withCapabilities(webdriver.Capabilities.firefox())
.build();
var chromeDriver = new webdriver.Builder()
.withCapabilities(webdriver.Capabilities.chrome())
.build();
test.describe('Google Search', function () {
test.it('should have a query box that you can type in ff', function () {
ffDriver.get('http://www.google.com');
var searchBox = ffDriver.findElement(webdriver.By.name('q'));
searchBox.sendKeys('webdriver');
searchBox.getAttribute('value').then(function (value) {
assert.equal(value, 'webdriver');
});
ffDriver.quit();
});
test.it('should have a query box that you can type in chrome', function () {
chromeDriver.get('http://www.google.com');
var searchBox = chromeDriver.findElement(webdriver.By.name('q'));
searchBox.sendKeys('webdriver');
searchBox.getAttribute('value').then(function (value) {
assert.equal(value, 'webdriver');
});
chromeDriver.quit();
});
});
Yes.
var chromeDriver = //Create your chromeDriver here
var ffDriver = //Create your ffDriver here
var drivers = [chromeDriver, ffDriver];//This creates an array of all of the drivers
for (var i = 0; i< drivers.length; i++){//Run the following block of code for each driver
var driver = drivers[i];//Assign the driver variable
driver.get('http://www.google.com');//Use the driver variable.
//The rest of the automation code
}
Note, that in the future, you are probably going to want more structure in your code, but the code above should work fine.

Custom chrome extension not launching device list

I'm trying to launch the cast api from a chrome extension that i'm writing. When I run this same code in a regular old HTML file on my webserver, it works perfectly. It doesn't work when I use this code in the popup of my extension. The requestSession call doesn't work at all in that case and nothing seems to happen after it's called.
I believe the expected behaviour is that the google cast extension will show the device list popup. Here is my javascript and html. Most of it is copied right out of CastHelloVideo-chrome.
HTML:
<html>
<head>
<title>Popup</title>
<style>
body {
min-width:5px;
overflow-x:hidden;
}
</style>
<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
</head>
<body>
<button type="button" id="castButton">Cast!</button>
</body>
<script type="text/javascript" src="popup.js"></script>
</html>
Javascript:
//Constants
var applicationID = "<App ID>";
var session;
var currentMediaSession;
///////////////////////////////////////////////////////////////////
//Chromecast API
///////////////////////////////////////////////////////////////////
if (!chrome.cast || !chrome.cast.isAvailable) {
setTimeout(initializeCastApi, 1000);
}
function loadMedia() {
if (!session) {
console.log("no session");
return;
}
var mediaInfo = new chrome.cast.media.MediaInfo('http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4');
mediaInfo.contentType = 'video/mp4';
var request = new chrome.cast.media.LoadRequest(mediaInfo);
request.autoplay = false;
request.currentTime = 0;
session.loadMedia(request,
onMediaDiscovered.bind(this, 'loadMedia'),
onMediaError);
}
/**
* callback on success for loading media
* #param {Object} e A non-null media object
*/
function onMediaDiscovered(how, mediaSession) {
console.log("new media session ID:" + mediaSession.mediaSessionId);
currentMediaSession = mediaSession;
}
/**
* callback on media loading error
* #param {Object} e A non-null media object
*/
function onMediaError(e) {
console.log("media error");
}
//////////////////////////////////////////////////////////////////
//UI
//////////////////////////////////////////////////////////////////
var castbutton = document.getElementById("castButton");
castButton.onclick=function(){
window.close();
chrome.cast.requestSession(onRequestSessionSuccess, onLaunchError);
// loadMedia();
};
//////////////////////////////////////////////////////////////////
//Helper Functions
//////////////////////////////////////////////////////////////////
function initializeCastApi() {
var sessionRequest = new chrome.cast.SessionRequest(applicationID);
var apiConfig = new chrome.cast.ApiConfig(sessionRequest,
sessionListener,
receiverListener);
chrome.cast.initialize(apiConfig, onInitSuccess, onError);
}
function sessionListener(e) {
console.log('New session ID: ' + e.sessionId);
session = e;
}
function receiverListener(e) {
console.log(e);
}
function onInitSuccess() {
console.log("init success");
}
function onError() {
console.log("error");
}
function onSuccess(message) {
console.log(message);
}
function onRequestSessionSuccess(e) {
session = e;
console.log("session created");
}
function onLaunchError(e) {
console.log(e.description);
}
I think this may be caused because I'm trying to run this out of the popup of my extension. Maybe chrome doesn't allow multiple plugins to show their (the device list is google cast's popup) popup window's at the same time? I've tried closing the popup before executing the requestSession call, but it still doesn't work. Anyone have any ideas?
Invoking requestionSession API call within a Chrome extension is not supported. That's why you cannot get a list of devices.
In any case this method call only works from a regular web page and it triggers the extension to show a pop-up of Cast devices. That's the only current way it's supposed to work.

chrome extension connection issues

I have written an extension for google chrome and I have a bug I need a help solving.
what I do is using either a text selection or an input of text search for photos on flickr and then create a results tab.
The extension works most of the times. but sometimes it creates a blank tab with no results and when I repeat the same search it then shows results. I figured that it's something to do with the html files messaging maybe something to do with them communicating. I have to say that I always receive the results from flickr so that the request/responce with flickr works ok. Sometimes the error happens when I play with other tabs or do something on other tabs while waiting for results. can you please help me figure out where's the fault?
the background file:
function searchSelection(info,tab){
var updated;
if(info.selectionText==null){
var value = prompt("Search Flickr", "Type in the value to search");
updated=makeNewString(value);
}
else{
updated=makeNewString(info.selectionText);
}
var resultHtml;
var xhReq = new XMLHttpRequest();
xhReq.open(
"GET",
"http://api.flickr.com/services/rest/?method=flickr.photos.search&text="+updated+
"&api_key=a0a60c4e0ed00af8d70800b0987cae70&content_type=7&sort=relevance&per_page=500",
true);
xhReq.onreadystatechange = function () {
if (xhReq.readyState == 4) {
if (xhReq.status == 200) {
chrome.tabs.executeScript(tab.id, {code:"document.body.style.cursor='auto';"});
var photos = xhReq.responseXML.getElementsByTagName("photo");
if(photos.length==0){
alert("No results found for this selection");
chrome.tabs.executeScript(tab.id, {code:"document.body.style.cursor='auto';"});
return;
}
var myJSPhotos=[];
for(var i=0; i<photos.length; i++){
var data={"id":photos[i].getAttribute("id"),"owner":photos[i].getAttribute("owner"),
"secret":photos[i].getAttribute("secret"),"server":photos[i].getAttribute("server"),
"farm":photos[i].getAttribute("farm"),"title":photos[i].getAttribute("title")};
myJSPhotos[i]=data;
}
chrome.tabs.create({"url":"results.html"},function(thistab){
var port= chrome.tabs.connect(thistab.id);
port.postMessage({photos:myJSPhotos});
});
}
};
};
xhReq.send(null);
chrome.tabs.executeScript(tab.id, {code:"document.body.style.cursor='wait';"});
}
var context="selection";
var id = chrome.contextMenus.create({"title": "search Flickr", "contexts":[context,'page'],"onclick":searchSelection});
results html: has only a reference to the js file res.js
res.js :
chrome.extension.onConnect.addListener(function(port) {
port.onMessage.addListener(function(msg) {
//*****//
var photos=msg.photos;
createPage(photos);
});
});
I have to mention that when the tab is empty if I put alert on the //*****// part it won't
fire.
but when I print out the photos.length at the tab create call back function part it prints out the correct result.
Try to set "run_at":"document_start" option for your res.js in the manifest.
I think callback from chrome.tabs.create is fired right away without waiting for page scripts to be loaded, so you might try something like this instead:
//global vars
var createdTabId = null;
var myJSPhotos = null;
xhReq.onreadystatechange = function () {
//assign myJSPhotos to a global var
chrome.tabs.create({"url":"results.html"},function(thistab){
createdTabId = thistab.id;
});
}
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if(changeInfo.status == "complete" && tab.id == createdTabId) {
createdTabId = null;
//now page is loaded and content scripts injected
var port = chrome.tabs.connect(tab.id);
port.postMessage({photos:myJSPhotos});
}
});

Resources