Get scrollheight cross-browser through Selenium - javascript

I'm working on a project which is using Selenium and I'd like to try to get the full web page height cross-browser and cross-platform. IE8 is being stubborn as always, is there anybody who has an idea of how to solve this?
The problem: When you scroll down a page for e.g. 500px and you keep doing this until the bottom of the page, the last scroll will be less than 500px. I need to know how much this last scroll was.
Two ways of solving:
1) Find the offset that has been scrolled each time (works everywhere except IE8)
2) Find the total height of the webpage
I know JQuery's height() function does this but I can't use this function from Selenium RC.
If you know a way of calling JQuery functions through Selenium or any other solution, please tell!
Cheers,
Henry

I've found a solution to my own problem.
When you run tests with Selenium, it starts two windows:
1) The Selenium window executing all the commands
2) The Browser window in which the website is tested.
When you try to get info about window 2 via JavaScript functions, you need to do the following:
selenium.browserbot.getCurrentWindow()
To get the full height of a browser window cross browser via selenium, you'll need following script:
function getPageHeight(){
$scrOfY = 0;
$test = $this->getEval("typeof(selenium.browserbot.getCurrentWindow().pageYOffset)");
if(strcmp($test,"number") == 0) {
//Netscape compliant
$scrOfY = (int)$this->getEval("selenium.browserbot.getCurrentWindow().pageYOffset;");
//scrOfX = window.pageXOffset;
} else if( (bool)$this->getEval("selenium.browserbot.getCurrentWindow().document.body != null") && (bool)$this->getEval("selenium.browserbot.getCurrentWindow().document.body.scrollTop != null")) {
//DOM compliant
$scrOfY = (int)$this->getEval("selenium.browserbot.getCurrentWindow().document.body.scrollTop;");
//scrOfX = document.body.scrollLeft;
} else if( (bool)$this->getEval("selenium.browserbot.getCurrentWindow().document.documentElement != null") && (bool)$this->getEval("selenium.browserbot.getCurrentWindow().document.documentElement.scrollTop != null")) {
//IE6 standards compliant mode
$scrOfY = (int)$this->getEval("selenium.browserbot.getCurrentWindow().document.documentElement.scrollTop;");
//scrOfX = document.documentElement.scrollLeft;
}
if(!$scrOfY || $scrOfY <= 0)
$scrOfY = $this->getEval("selenium.browserbot.getCurrentWindow().document.body.offsetHeight");
return $scrOfY;
}

Related

Make trackpad wheel event run once [duplicate]

Is there any way to detect if the client is using a touchpad vs. a mouse with Javascript?
Or at least to get some reasonable estimate of the number of users that use touchpads as opposed to mice?
Compare e.wheelDeltaY and e.deltaY (or e.deltaMode in Firefox) to detect touchpad mouse device
function handler(e) {
var isTouchPad = e.wheelDeltaY ? e.wheelDeltaY === -3 * e.deltaY : e.deltaMode === 0
// your code
document.body.textContent = isTouchPad ? "isTouchPad" : "isMouse"
}
document.addEventListener("mousewheel", handler, false);
document.addEventListener("DOMMouseScroll", handler, false);
The answer above by Lauri seems to work, but it took me a while to understand why it works. So here I'll provide a slightly more human readable version, along with a conceptual explanation. First, that same code written out in a human readable manner:
function detectTrackPad(e) {
var isTrackpad = false;
if (e.wheelDeltaY) {
if (e.wheelDeltaY === (e.deltaY * -3)) {
isTrackpad = true;
}
}
else if (e.deltaMode === 0) {
isTrackpad = true;
}
console.log(isTrackpad ? "Trackpad detected" : "Mousewheel detected");
}
document.addEventListener("mousewheel", detectTrackPad, false);
document.addEventListener("DOMMouseScroll", detectTrackPad, false);
This works because wheelDeltaY measures the physical distance that the actual hardware mouse wheel has travelled, while deltaY measures the amount of scrolling produced on screen. A conventional mouse typically has a much lower "scroll resolution" than a trackpad. That is to say, with a trackpad you can make a tiny motion and a get a tiny scroll on screen. A conventional mouse scrolls in chunkier, low resolution clicks. To complete a full rotation of the mouse wheel, it might make 10 clicks. There is no such thing as a half click or quarter click.
For a conventional mouse, a single wheel click is reported as 120 wheelDeltaY "units" and results in about ~100px worth of scrolling. The physical wheelDeltaY unit is a completely arbitrary number, it is not measuring inches or degrees or anything like that. The number 120 was selected simply because it has a lot of useful factors. The amount of scrolling on screen is represented by deltaY, and it varies significantly by browser. (Sidenote, deltaY is generally measured in "lines" not pixels, though it's complicated, see previous link).
Interacting with a trackpad is different in two ways. First of all, you can get wheelDeltaY values much smaller than 120, because very subtle finger gestures are detectable. Second, the wheelDeltaY is exactly 3x the deltaY value (at least in every browser I've managed to test). So, for instance, if you make a physical finger gesture equal to 12 click units, it will generally result in 4 pixels worth of scrolling. Lauri's code uses this second property (Y1 = Y2 * 3) to detect the existence of a trackpad, but you could probably also be successful simply by checking if abs(wheelDeltaY) equals 120
I haven't tested this, but I think it would also work:
function detectTrackPad(e) {
var isTrackpad = false;
if (e.wheelDeltaY) {
if (Math.abs(e.wheelDeltaY) !== 120) {
isTrackpad = true;
}
}
else if (e.deltaMode === 0) {
isTrackpad = true;
}
console.log(isTrackpad ? "Trackpad detected" : "Mousewheel detected");
}
document.addEventListener("mousewheel", detectTrackPad, false);
document.addEventListener("DOMMouseScroll", detectTrackPad, false);
This topic may be already solved, but the answer was there is no way to detect it. Well I needed to get a solution, it was very important. So I found a acceptable solution for this problem:
var scrolling = false;
var oldTime = 0;
var newTime = 0;
var isTouchPad;
var eventCount = 0;
var eventCountStart;
var mouseHandle = function (evt) {
var isTouchPadDefined = isTouchPad || typeof isTouchPad !== "undefined";
console.log(isTouchPadDefined);
if (!isTouchPadDefined) {
if (eventCount === 0) {
eventCountStart = new Date().getTime();
}
eventCount++;
if (new Date().getTime() - eventCountStart > 100) {
if (eventCount > 10) {
isTouchPad = true;
} else {
isTouchPad = false;
}
isTouchPadDefined = true;
}
}
if (isTouchPadDefined) {
// here you can do what you want
// i just wanted the direction, for swiping, so i have to prevent
// the multiple event calls to trigger multiple unwanted actions (trackpad)
if (!evt) evt = event;
var direction = (evt.detail<0 || evt.wheelDelta>0) ? 1 : -1;
if (isTouchPad) {
newTime = new Date().getTime();
if (!scrolling && newTime-oldTime > 550 ) {
scrolling = true;
if (direction < 0) {
// swipe down
} else {
// swipe up
}
setTimeout(function() {oldTime = new Date().getTime();scrolling = false}, 500);
}
} else {
if (direction < 0) {
// swipe down
} else {
// swipe up
}
}
}
}
And registering the events:
document.addEventListener("mousewheel", mouseHandle, false);
document.addEventListener("DOMMouseScroll", mouseHandle, false);
It may need some optimization and is maybe less than perfect, but it works! At least it can detect a macbook trackpad. But due to the design i'd say it should work anywhere where the pad introduces a lot of event calls.
Here is how it works:
When the user first scrolls, it will detect and check that in 50ms not more than 5 events got triggered, which is pretty unusual for a normal mouse, but not for a trackpad.
Then there is the else part, which is not for importance for the detection, but rather a trick to call a function once like when a user swipes. Please come at me if I wasn't clear enough, it was very tricky to get this working, and is of course a less than ideal workaround.
Edit: I optimized the code now as much as I can. It detects the mouseroll on the second time and swipe on trackpad instantly. Removed also a lot of repeating and unnecessary code.
Edit 2 I changed the numbers for the time check and numbers of events called from 50 to 100 and 5 to 10 respectively. This should yield in a more accurate detection.
You could detect JS events.
A touch device will fire touch events such as touchstart in addition to mouse events.
A non-touch device will only fire the mouse events.
In the general case, there is no way to do what you want. ActiveX might allow you to see and examine USB devices, but in the best case, even if that is somehow possible, that limits you to IE users. Beyond that, there is no way to know.
You might be able to discern patterns in how (or how often) a touchpad user moves the cursor versus how a mouse user might move the cursor. Differentiating between physical input devices in this way is an absurdly difficult prospect, and may be wholly impossible, so I include here it for completeness only.
Wheel event triggered by touchpad will give much smaller event.deltaY,1 or 2,but trigger by mouse wheel will give like 100,200.
Trust me. This is the easiest and only solution that also works for Safari (as far as I know).
isTrackPad(e) {
const { deltaY } = e;
if (deltaY && !Number.isInteger(deltaY)) {
return false;
}
return true;
}
From testing plugging in a mouse to a mac which does have a touchpad and also a windows machine with one, I can summarize how I got this working.
Detect if the navigator user agent contains "Mobile" or "Mac OS" If either
of these are true, it is likely a touch based system, but work to
eliminate that. Set boolean hasTouchPad to true
If the above is true, detect "mouse" events and run a test like high
numbers, or a frequency of non integers or Kalman filtering.
Keep these in a queue and if that queue's sum passes a threshold,
disable the hasTouchpad variable and disconnect the event.
let isMouseCounts: Array<number> = []
if (Client.hasTouchpad) {
document.addEventListener('wheel', detectMouseType);
}
function detectMouseType(e:WheelEvent) {
if (!Client.hasTouchpad) return
let isMouse = e.deltaX === 0 && !Number.isInteger(e.deltaY)
isMouseCounts.push(isMouse ? 1 : 0)
if (isMouseCounts.length > 5) isMouseCounts.shift()
let sum = isMouseCounts.reduce(function(a, b) { return a + b; });
if (sum > 3 && e.type === "wheel") {
console.log("Touchpad disabled")
document.removeEventListener('wheel', detectMouseType);
Client.hasTouchpad = false;
}
}
You could just check for the device driver softwares installed into the local package as functioning. Like in windows synaptics, elan hardware, as for UNIX(Linux) you could just check for the package installed during the basic installed onto. A lot of packages come in different formats in different versions of Linux and Linux like systems(Not linux entirely) but they use the same package name for all. Just got to know the code to pull it. Still working on it.

Javascript function does not work in firefox but does in chrome, safari, IE and edge

Could anyone tell me why this function won't execute in Firefox. The rest of the Javascript functions throughout the file work successfully except for this one (it flat out won't execute, no errors in the console). I suspected it was the pageX property, however that doesn't seem to be the case.
var prevX = 0;
var i = 0;
var drgleft = 0;
var drgright = 0;
function sequence_1(event){
if(prevX == 0){
prevX = event.pageX;
return false;
}
//drag left
if(prevX > event.pageX){
console.log('dragged left');
drgleft++;
if(drgleft == 2){
drgleft = 0;
i--;
if(i < 0){
i = 30; //for optimization reasons, input the cache.length value manually (this avoids unnecessary errors in the console and laggy framerate as a result).
}
document.getElementById("TheBigOne").src = cache[i].src; //use console.log(i); as a method of verifying that the code is executing correctly
}
}
else if(prevX < event.pageX){
console.log('dragged right');
drgright++;
if(drgright == 2){
drgright = 0;
i++;
if(i > 30){ //for optimization reasons, input the cache.length value manually (this avoids unnecessary errors in the console and laggy framerate as a result).
i=0;
}
document.getElementById("TheBigOne").src = cache[i].src;
}
}
else{
}
prevX = event.pageX
}
EDIT:
In my first post I didn't include the relevent HTML code that calls the function, for that I apologise and will implement it below.
<!DOCTYPE html>
<html>
<body>
<div class="The_main_event" draggable = "true" ondrag="sequence_1(event)" id = "GD">
<img src="file:///C:/Users/Harry/Desktop/Website/Web_aeroplane/Web%20Test.0031.png" id="TheBigOne">
</div>
</body>
</html>
Open Firefox's debugger (Ctrl + Shift + S on Windows) set a breakpoint at the first line of the function body. The contents of event will be inspectable. You can step through the function (F10 in Windows) to see what's happening. If the event is not directly from mouse/touch, it may not have a pageX property, therefore have no effects.
Is it a UIEvent, or a MouseEvent?
https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/pageX
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX
UIEvents aren't standard and the documentation linked above has Firefox as unknown compatibility.
I'm guessing when you say it won't run, you mean, it's not working how you expect it to. It looks like in your case it is either returning false, or is doing nothing except trying to update prevX. If event.pageX is undefined and therefore prevX is undefined, it would cause your function to have no effect and appear to not run.
EDIT:
After seeing your update, it looks like it's because the event you're listening to doesn't have pageX. See here for documentation:
https://developer.mozilla.org/en-US/docs/Web/Events/drag
This may also help FireFox onDrag Pagex PageY always zero ;(
Unfortunately this is not supported by firefox Browser and W3C. Other browsers ignore this specification and use Mousemove instead, to address this specific issue. However you can use ondragstart and ondragend to get the co-ordinates of the specific event.

Multiple javascript functions on an index page

I am trying to update some existing javascript code in place that navigates the user to a desktop depending on whether their device has Flash or not. However, I now want the option to navigate the user to a Mobile site if the screen width is smaller that 800px.
I have included the code below which is not working and I think I have the characters wrong between the two function requirements. Any guidance would be appreciated.
<script type="text/javascript">
if (screen.width <= 800) {
window.location = "mobile.htm";
}
if ((navigator.appName == "Microsoft Internet Explorer" &&
navigator.appVersion.indexOf("Mac") == -1 &&
navigator.appVersion.indexOf("3.1") == -1) ||
(navigator.plugins && navigator.plugins["Shockwave Flash"])
|| navigator.plugins["Shockwave Flash 2.0"]){
window.location='home_f.htm';
}
else {
window.location='home_n.htm';
}
</script>
I think what is happening is that the code above (with the screen.width and all) is executing properly, but then, while mobile.htm is still loading, the code continues. Because you have Flash, the code below it still executes and before mobile.htm can fully load the browser instead decides to obey the later if statement and go to home_f.htm .
Try creating a function that returns after the first bit, like so:
function redirect() {
if (screen.width <= 800) {
window.location = "mobile.htm";
return;
}
if ((navigator.appName == "Microsoft Internet Explorer" &&
navigator.appVersion.indexOf("Mac") == -1 &&
navigator.appVersion.indexOf("3.1") == -1) ||
(navigator.plugins && navigator.plugins["Shockwave Flash"])
|| navigator.plugins["Shockwave Flash 2.0"]){
window.location='home_f.htm';
}
else {
window.location='home_n.htm';
}
}
redirect();
I'd also like to note that screen.width measures the screen resolution, but if the browser is resized, it will not take into account the browser's new size. It'll take the size of the entire monitor regardless of the size of the browser. If that isn't what you want, read up on measuring the browser width and height here: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
Finally, for what you're doing, that is, detecting mobile devices, you may prefer browser detection to basing it off the screen size. There is a simple way to detect if your user is using Android via a user agent. In your case scenario feature detection is probably not applicable. But do remember to provide a link to the mobile version of the site from your non-mobile version just in case of false positives! You never know.
if(navigator.userAgent.toLowerCase().indexOf("android")!=-1||navigator.userAgent.toLowerCase().indexOf("googletv")!=-1||navigator.userAgent.toLowerCase().indexOf("htc_flyer")!=-1) {
// this person is using Android. Redirect the the mobile site!
window.location = "mobile.htm";
return;
}

Some part of Javascript code stopped working in latest Chrome update

I bought some files through codecanyon.net and they've been working fine on all browsers. Just recently I noticed they weren't working in Chrome.
The code is really big and I've tried to change some things through trial and error on the js file but was unsuccessful. You can see the slider at http://miguelsart.com/scroller-test.
As you can see, the captions are supposed to be hidden and once you hover they have slide up. But with Chrome, the captions appear automatically and nothing happens when you hover.
I think something is wrong in this part of the code:
//init captions
Scroller.prototype.initCaptions = function() {
var $captions = this._$slides.find(">p:first");
if (this._displayCaption) {
var padding = $captions.outerWidth() - $captions.width();
$captions.css({width:this._slideWidth - padding, display:"inline-block"}).click(preventDefault);
if (this._captionPos == OUTSIDE) {
var heights = $captions.map(function() { return $(this).height(); }).get();
var maxHeight = Math.max.apply(Math, heights);
$captions.css({top:this._captionAlign == TOP ? 0 : this._slideHeight, height:maxHeight});
this._extHeight = $captions.outerHeight();
if (this._captionAlign == TOP) {
this._extOffset = this._extHeight;
}
$captions.addClass("outside");
}
else {
if (jQuery.browser.msie && parseInt(jQuery.browser.version) > 6 && parseInt(jQuery.browser.version) < 9) {
$captions.addClass("ie-inside");
}
else {
$captions.addClass("inside");
}
}
}
else {
$captions.hide();
}
}
I've tried messing around replacing display for opacity or for visibility but nothing worked. Does anyone have any clue what might be wrong?
Thanks in advance!
I believe I've figured out what's wrong with the author's implementation, and you are correct, it has to do with the latest version of Chrome.
On line 43 of jquery.wt.scroller.js
this._mouseoverCaption = window.Touch ? false : opts.mouseover_caption;
The author of the plugin is testing for native touch capabilities (by determining if window.Touch is defined). Chrome must've recently added native touch API capabilities in a recent version.
So what the author was going for, was saying that 'you can't hover on a touch device, so we can't show the captions on hover on a touch device so we'll just always show them' - which is logically.
However, just because touch capabilities exist, however, doesn't mean that touch input is the default (as in this case). Modernizr solves this issue (for now) by performing the following conditional:
if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
Something tells me this will also soon be broken. (https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js#L42)
So, long story short (too late, I know) add this to the plugin code:
Add this to line 7 (push all lines down one):
var TOUCH = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
Find and replace all occurrences of window.Touch with TOUCH in the plugin code.
Tell the author of the plugin I'll send him a bill. :)

Registering iframe events in Internet Explorer

I have a web application that allows users to enter search criteria and the results get appended to a portlet container right below the input fields for the search criteria (mentioned above).
Environment - JSP, JAVA, and TAG libraries
The search page essentially consists of a few JSP pages which reference a myriad tag libraries.
After the search results are returned (AJAX) - the portlet iframe should re-size based on the new height. A JavaScript method (resident to one of the tag libraries) called setContainerHeight is what does this re-sizing.
The problem -
Internet Explorer does not call my JS method which does this re-size!
I suspect that the following post could help with this problem.
Javascript IE Event
Chrome, Firefox, Opera....all do!
Since I cannot explicitly tell the page to refresh (I will loose my search criteria parameters), nor can I explicitly check what type of browser is making the request (my JavaScript is not getting called); how can I explicitly tell the page to call my resize method after the callback?
In case anyone was curious - this is my re-size method:
function setContainerHeight() {
//reset the height to shrink the scroll height for the usecase where the portlet's contents got smaller.
getPortlet().style.height = '${frameHeight}px';
getPortlet().height = '${frameHeight}px';
try {
height = getFrameHeight(getPortlet());
} catch (e) {
//fallback for crossdomain permission problems.
height = '${frameHeight}';
}
//set the portlet & portlet container to be the same height - not using 100% for the portlet to avoid the inner scrollbar
try {
getPortletContainer().style.height = height + 'px';
} catch ( ex ) {
// do nothing, we can't get to the container
}
getPortlet().style.height = (height + getHorScrollBarHeight()) + 'px';
getPortlet().height = (height + getHorScrollBarHeight()) + 'px';
}
And this is the section of the code that calls this method -
/* resizes the portlet container to fit the size of the porlet. */
function resizePortletContainer() {
if (hasContainerHeightChanged()) {
saveScrollPosition();
setContainerHeight();
restoreScrollPosition();
}
//width handling needs some work
//if (hasContainerWidthChanged()) {
//setContainerWidth();
//}
}
//registering event handlers...
var frame = getPortlet();
var prevPortletLoadEvent = frame.onload ? frame.onload : function () {};
frame.onload = function () {prevPortletLoadEvent(); resizePortletContainer(); };
var prevPortletResizeEvent = frame.onresize ? frame.onresize : function () {};
var onresize = function () {prevPortletResizeEvent(); resizePortletContainer(); };
A bit more information -
After placing alert statements after the register event handler code above; I noticed that both IE and Firefox called this portion of code only ONCE (my alert box was triggered only once in either case when the initial search screen was displayed to the browser.) Thus, that leads me to believe that for some reason, only Firefox likes the above code that I am using to register my events; IE perhaps is looking for a different method of registering this event?
I suspect that the following post could be useful in my search for the answer to this problem --
Javascript IE Event
I found my answer in another post, but essentially the problem was that Internet Explorer registers its iframe events differently than all other browsers.
Also a good post for you to look at - iframe behaviour of onload vs addEventListener('load')
The code that I used to fix the problem is listed below. I hope this helps someone else that gets into a similar problem I had.
I had to do a simple check on what browser was making the request and the 2 conditional blocks that you see below do the same thing, except the syntax is different for IE. Good 'ol IE. LoL.
//Microsoft Internet Explorer Browser - iFrame event register
if (navigator.appName.indexOf("Microsoft") != -1) {
var frame = getPortlet();
var prevPortletLoadEvent = frame.onload ? frame.onload : function () {};
var refresh = function() { prevPortletLoadEvent(); resizePortletContainer(); };
if(frame.attachEvent) frame.attachEvent('onload', refresh);
else frame.addEventListener('load', refresh, false);
}
//All other major browsers - iFrame event register
else {
var frame = getPortlet();
var prevPortletLoadEvent = frame.onload ? frame.onload : function () {};
frame.onload = function () {prevPortletLoadEvent(); resizePortletContainer(); };
var prevPortletResizeEvent = frame.onresize ? frame.onresize : function () {};
var onresize = function () {prevPortletResizeEvent(); resizePortletContainer(); };
}

Categories

Resources