Selenium & Node.js: Switching to Created Tab dynamically - javascript

In the case of 1 tab, the newest tab will always be the last tab.
Same applies if it is the last tab that is used to open a new tab.
The following code will switch to last tab.
EDIT : Thanks to #JimEvans
In most answers on SO, you will find hardcoded indexes for windows. Like below:
return await driver.wait(async function () {
return await driver.getAllWindowHandles().then(async function (handles) {
// var isHandleCount2 = (handles.length == 2);
if (handles.length > 1) {
return driver.switchTo().window(handles[handles.length - 1]);
}
return false;
});
}).then(function () {
// Now do some stuff in new tab
});
What about the case where we have 5 tabs, and active tab 3 opens a new tab and the new tab is launched between tab 3 and tab 4.
How do we get the index of the newly created tab dynamically, without hardcoding its index, as in most examples on SO?

Yeah people do things all the time that they don't completely understand and don't realize that the code doesn't always work as they expect.
As Jim pointed out, the order of the window handles (tabs) in the returned array from .getAllWindowHandles() is not guaranteed. You can call .getAllWindowHandles(), store that list as before, create your new tab, call .getAllWindowHandles() again and store that new list as after, and then compare the two and find the handle in the new list that doesn't exist in the old list... that's the handle of your newly created tab.
I don't do JS but you can use the .filter() function to do this comparison.
function array_diff(a, b) {
return a.filter(function(value) {
return (b.indexOf(value) === -1);
});
}
console.log(array_diff(["a", "b", "c"], ["a", "b"]));
This returns ["c"].
I can imagine you doing something like
before = driver.getAllWindowHandles();
// do something that creates a new window/tab
// might need to wait for the new window to be created
after = driver.getAllWindowHandles();
handle = array_diff(after, before);
driver.switchTo().window(handle);

Related

Array with localStorage doesn't save or gets overwritten?

In order to summarize the problem I'll explain what the task is first.
So for the eastern event, we are going to add 3 bunny images across a website (different pages, same domain, same website). Once you've found and clicked on all 3 images it should open a new window with a specific URL.
Right now I managed to write the code which saves the clicks of the 3 pictures in an array and then opens the new window with an URL. But sadly it doesn't work once I change the page. The Array either didn't save in the browser storage or gets overwritten once I open a new page.
I'm not exactly sure what the issue is right now. I hope any of you could help me out.
I've tried to work with localStorage and sessionStorage but I don't think I used them properly. I'll provide you with my current code below.
Javascript
$(function(){
var imageStore = [];
$('.osterhasen').click(function(e){
localStorage.id = $(this).attr('id');
// returns index of the element in the array, if the element was not found returns false
var imageExists = $.inArray(localStorage.id, imageStore);
if (imageExists >= 0){
// If element exists, do nothing
e.preventDefault;
} else {
// If element doesn't exist, add element
imageStore.push(localStorage.id);
}
localStorage.setItem('imageStore', JSON.stringify(imageStore));
localStorageimageStorage = JSON.parse(localStorage.getItem('imageStore'));
console.log(localStorageimageStorage);
if (localStorageimageStorage.length == 3) {
window.open('https://www.google.ch');
}
});
});
HTML
<body>
<div class="container">
<div id="1" class="osterhasen"><img src="img/choco.png"></img></div>
<div id="2" class="osterhasen"><img src="img/geschichte.png"></img></div>
<div id="3" class="osterhasen"><img src="img/mitarbeiter.jpg"></img></div>
</div>
</body>
In the end the clicks on the images should be saved in the browser storage across the whole website and once you've found all 3 images it should open a new window with a specfic URL.
Thank you very much for your time.
Best regards
You can't assign properties to localStorage like this (it doesn't exist, and you should be using it's setItem method anyway):
localstorage.id = $(this).attr('id');
var imageExists = $.inArray(localstorage.id, imageStore);
So assign id to a variable instead:
const id = $(this).attr('id');
const imageExists = $.inArray(id, imageStore);
Working version
Yes, you're overriding the key every time. To store an array as you want, you can try the following:
$(function(){
var imageStore = [];
$('.osterhasen').click(function(e){
if(localStorage.getItem('imageStore') === null){ // check if such key exists
localStorage.setItem('imageStore', JSON.stringify([$(this).attr('id')])); // if it doesn't create an array with first item imageStore and set it to key imagestore
} else {
var currentStorage = JSON.parse((localStorage.getItem('imageStore')));
if(!currentStorage.includes($(this).attr('id')){ // if id doesn't exist add it.
currentStorage.push($(this).attr('id')); // push to new image inside of it
localStorage.setItem('imageStore', JSON.stringify(currentStorage)); // set the key to the new value
}
}
localStorageimageStorage = JSON.parse(localStorage.getItem('imageStore')); // you should have all the 3 pictures here in an array
console.log(localStorageimageStorage);
if (localStorageimageStorage.length == 3) {
window.open('https://www.google.ch');
}
});
});

Iterate through multiple web pages in JS with sleep in between

I was checking some simple solutions for showing multiple web pages for some dashboard and currently fighting with simple HTML page with javascript inside to achieve what I want to see there.
var urls = new Array();
urls[0] = "https://stackoverflow.com/"
urls[1] = "https://www.google.com"
var arrayLength = urls.length;
for (var i = 0; i < arrayLength; i++) {
window.location.assign(urls[i]);
sleep(3000);
}
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds) {
break;
}
}
}
Currently this page opens only first page (after some time) and looks like it doesn't do iteration trough other pages. Maybe you could help me to make it work? I want to rotate those pages forever on screen (will add some infinite while loop after making this part working).
Currently this page opens only first page (after some time) and looks
like it doesn't do iteration trough other pages.
Once you change your window.location, and go to the first url from the array, you are losing all of your JS code (as it is not present in just opened url any more).
You can do this by installing a chrome plugin (which will not lose your JS after window.location change).
The plugin will run the added JS at DOMContentLoaded (no need to attach any event listener).
I needed also to do this, check things on the page, store some information and move on to the next page. I know, this can be done with Python and other stuff but by doing this it can be done on the FE side also.
I used the localStorage to store my information.
I pasted this into the browser console to prepare all the stuff and clean the localStorage:
// clear the localStorage
localStorage.clear();
// set an array that will keep all our pages to iterate into the localStorage
localStorage.setItem(
"pages",
JSON.stringify([
"https://my-page-1.html",
"https://my-page-2.html",
"https://my-page-3.html",
"https://my-page-4.html",
])
);
// set an array that will keep our findings
localStorage.setItem("resultArray", JSON.stringify([]));
// move to the first page of the iteration
window.location.href = "https://my-page-1.html";
After doing this, I opened the plugin interface and added the following code:
(function check() {
// array saved into the localStorage that contains all the pages to iterate
const pagesArray = JSON.parse(localStorage.getItem("pages"));
// array to store your stuff
const resultArray = JSON.parse(localStorage.getItem("resultArray"));
// whatever you want to check on that page
const myFancyCondition = true;
if (myFancyCondition) {
// push any data to the array so that you can check it later
resultArray.push({
page: pagesArray[0],
message: "I found what I was looking for!",
});
}
//remove the current page from the array
pagesArray.shift();
//reset the array value after the first page was already checked
localStorage.setItem("pages", JSON.stringify(pagesArray));
//store the array data
localStorage.setItem("resultArray", JSON.stringify(resultArray));
// quit if the iteration is over and there are no more pages to check
if(!pagesArray.length) return;
//go to the next page
window.location.href = pagesArray[0];
})();
Then, to check the results you just need to read the data from the localStorage like:
JSON.parse(localStorage.getItem('resultArray'))
I hope this helps :)!

JavaScript Tab Amount

is there a way to check if the user has more than one tab open (not globally, only on my website) or how many tabs the user has open?
In the best case i need the amount but a boolean value would be enough.
I need a solution in vanilla JavaScript (ES6) without jQuery or something else.
Thank you in advance.
You could create an ID for every opened tab and save it in an array in the localStorage (document.cookie would also work, but requires a bit more effort with getting and setting):
var tabId = Math.random();
var tabs = JSON.parse(localStorage.getItem('tabs')) || [];
tabs.push(tabId);
localStorage.setItem('tabs', JSON.stringify(tabs));
Now, to check how many tabs we have open on the current domain, we check the length of the tabs array in the localStorage:
function getTabCount()
{
return JSON.parse(localStorage.getItem('tabs')).length;
}
Now, you can use getTabCount() to get the amount of tabs that are currently open.
Finally, we need to make sure our tabId is removed from the array when we close it:
window.addEventListener('beforeunload', function(e)
{
tabs = JSON.parse(localStorage.getItem('tabs'));
var index = tabs.indexOf(tabId);
if (index !== -1)
tabs.splice(index, 1);
localStorage.setItem('tabs', JSON.stringify(tabs));
});
Do keep in mind that you need to keep using getTabCount() to check if any tabs have been opened or closed since you last used it.

Rxjs - Consume API output and re-query when cache is empty

I'm trying to implement a version of this intro to RxJS (fiddle here) that instead of picking a random object from a returned API array, it consumes a backthrottled stream of objects from the returned API array.
Here's a portion of the code that produces a controlled Observable from the API response (full fiddle here):
var responseStream = requestStream.flatMap(function (requestUrl) {
return Rx.Observable.fromPromise(fetch(requestUrl));
}).flatMap(function(response) {
return Rx.Observable.fromPromise(response.json());
}).flatMap(function(json) {
return Rx.Observable.from(json);
}).controlled();
I just dump each emitted user in console.log, and use a click event stream to trigger the request() call in the controlled Observable:
responseStream.subscribe(function(user) {
console.log(user);
});
refreshClickStream.subscribe(function (res) {
responseStream.request(1);
});
There's about 50 user objects returned from the GitHub API, and I'd like to backthrottle-consume them one per click (as seen above). However, after I'm fresh out of user objects I'd like to send in another call to requestStream to fetch another API call, replenish the responseStream and continue providing user objects to console.log upon each click. What would be the RxJS-friendly way to do so?
I'd do it similarly to the article example with combineLatest() although I wonder if there's an easier way than mine.
I'm making request for only 3 items. Working with 3 items is hardcoded so you'll want to modify this. I was thinking about making it universal but that would require using Subject and made it much more complicated so I stayed with this simple example.
Also, I'm using concatMap() to trigger fetching more data. However, just clicking the link triggers just the combineLatest() which emits another item from the array.
See live demo: https://jsfiddle.net/h3bwwjaz/12/
var refreshButton = document.querySelector('#main');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click')
.startWith(0)
.scan(function(acc, val, index) {
return index;
});
var usersStream = refreshClickStream
.filter(function(index) {
return index % 3 === 0;
})
.concatMap(function() {
var randomOffset = Math.floor(Math.random() * 500);
var url = 'https://api.github.com/users?since=' + randomOffset + '&per_page=3';
return Rx.Observable.fromPromise(fetch(url))
.flatMap(function(response) {
return Rx.Observable.fromPromise(response.json());
});
})
.combineLatest(refreshClickStream, function(responseArray, index) {
return responseArray[index % 3];
})
.distinct();
usersStream.subscribe(function(user) {
console.log(user);
});
I use refreshClickStream twice:
to emit next item in the array in combineLatest()
to check whether this is the end of the array and we need to make another request (that's the filter() operator).
At the end distinct() is required because when you click index % 3 === 0 time triggers in fact two emission. First is the one from downloading the data and the second one is directly in combineLatest() that we want to ignore because we don't want to iterate the same data again. Thanks to distinct() it's ignored and only the new values is passed.
I was trying to figure out a method without using distinct() but I couldn't find any.

How to open different popups with the same title in CasperJS?

I'm trying to automate some tasks using casperJS, and I need to open multiple popups. However, all popups have the exact same url (http://.../printit.aspx/...), so that whenever I use
this.withPopup(/printit/, function() {...});
it always opens the first popup. I can't access the other ones.
I suppose there are two possibilities :
close each popup after visiting it, but I can't find how to do this
accessing popups using another way than the URL regex /printit/. Maybe using casper.popups, but the documentation is very vague about this.
There is no easy and documented way of disambiguating two popups. The documentation says that casper.popups is an array-like property. So you could iterate over it. Judging by the code, the popups property itself is a pagestack. One can easily modify the pagestack.findByRegExp() function to do this kind of thing.
It seems that the casper.popups property contains duplicate entries, so one can filter them out.
casper.findAllPopupsByRegExp = function(regexp){
var popups = this.popups.filter(function(popupPage) {
return regexp.test(popupPage.url);
});
if (!popups) {
throw new CasperError(f("Couldn't find popup with url matching pattern %s", regexp));
}
// remove duplicates
var uniquePopups = [];
popups.forEach(function(p){
if (uniquePopups.indexOf(p) === -1) {
uniquePopups.push(p);
}
});
return uniquePopups;
}
casper.withPopup() accepts three types of inputs to identify a popup page. The third one is a the page object itself. So you can retrieve the matching popup page objects with findAllPopupsByRegExp(), select the one that you want and pass that to withPopup() to change into its context:
casper.then(function(){
var popups = this.findAllPopupsByRegExp(/printit/);
this.withPopup(popups[1], function(){
...
});
});
In my case i have a list of links. Every link calls some javascript that opens a new tab(=popup in casperjs), always with the same url (...\View.aspx).
Inside the tab i have to click a button that changes the url in the tab (...\List.aspx).
on("popup.loaded"...) is called twice, pushing every new page in the casper.popups array. They usually alternate, but for some reason (i guess asyncrony) not always: sometimes casper.popups[/*LAST*/].url matches /View\.aspx/, sometimes it matches /List\.aspx/.
I always had to use casper.withPopup( /*LAST VIEW.ASPX LOADED*/, ...); that was not always the last popup loaded and neither the one matching /View.aspx/ (it could be one of the oldes), so i had to find the latest loaded matching /View\.aspx/.
Here's my solution:
var is_view_loaded=false;
casper.on('popup.loaded', function(page) {
if(page.url.match(/View\.aspx/)) {
is_view_loaded=true;
}
}
// return last popup which url matches the required regexp
casper.getLastRegExPopup=function(regex) {
var l=casper.popups.length;
var i=l-1;
while(!regex.test(casper.popups[i].url)) {
i--;
if(i<0) return null;
}
return casper.popups[i];
}
Then in my core steps:
.
.
// clicked the link, must wait for popup (but which?!)
casper.waitFor(
function test() {
return is_view_loaded;
},
function then() {
var popup=casper.getLastRegExPopup(/View\.aspx/);
casper.withPopup(popup, function() {
// do things with "this"
});
is_view_loaded=false;
}
//, timeout
);
.
.

Categories