Selenium's PhantomJS webdriver not loading page in reactjs - javascript

I'm trying to test a new feature of a website. This is the only page so far built in React. When I attempt to run the test in Selenium with PhantomJS the page index loads, but the full page load never triggers.
The page body is:
<body>
<main id="content"></main>
<script type="text/javascript">
function loadBundleJS( jsSource){
var bundleJSScriptTag=document.createElement('script')
bundleJSScriptTag.setAttribute("type","text/javascript")
bundleJSScriptTag.setAttribute("src", jsSource)
if (typeof bundleJSScriptTag != 'undefined'){
document.getElementsByTagName('head')[0].appendChild(bundleJSScriptTag);
}
}
var paramsArray = window.location.search.substring(1).split("&");
Object.keys(paramsArray).forEach(function(key){
var param = paramsArray[key];
if (param.indexOf("/")>-1){
param = param.substring(0, param.indexOf("/"))
}
})
loadBundleJS('js/bundle.0.0.2.js')
</script>
</body>
When the site runs in a browser the content is appended to the main tag. However, in PhantomJS this content never gets appended and PhantomJS loads a blank page.

The problem is not in your code, is in WebKit browser that PhantomJS runs. PhantomJS run an old version of WebKit engine which use an older version of ECMAScript.
ReactJS use Function.bind method from ECMAScript 5.
The solution is pretty simple, you need to define the Function.prototype.bind in your code if not exist.
** Make sure that the code is loaded before including react.js.
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
Code taken from: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind#Polyfill

Related

Running jquery scripts on react router redirect

I am looking to port my existing website that uses a theme to utilise react components.
The theme has a lot of functions that makes the UI render properly (including several animations).
The theme's js imports a lot of other js libraries.
This means that there is no way for me to write the react version of whatever UI elements that the theme provides.
However, the actually elements can be used as 'dumb components' and I dont need any data bound functionality from them.
I can import my js libraries' dependencies in my public/html folder so thats not a big issue.
The real issue is my theme's library (which I will call scripts.js) not getting loaded every time there is a redirect using react-router.
Since react-router doesnt actually reload the page, there is no way for my scripts.js to know when to do its UI functions.
This discussion here
talks about wrapping the scripts.js in a function and calling it every time an on update happens at the Router level.
Since react-router 4 doesnt have onUpdate , I decided to subscribe to the history.
I am doing that like so:
import {createBrowserHistory} from 'history';
const history = createBrowserHistory();
history.listen(() => {
console.log("url has changed");
});
export {history};
Now I am able to know every time a route change happens, all I need now is to actually get the scripts.js to 'load'.
My scripts.js is very long and some of it is proprietary , so I will only post a snippet of it here.
mr = (function (mr, $, window, document){
"use strict";
mr = mr || {};
var components = {documentReady: [],documentReadyDeferred: [], windowLoad: [], windowLoadDeferred: []};
mr.status = {documentReadyRan: false, windowLoadPending: false};
$(document).ready(documentReady);
$(window).on("load", windowLoad);
function documentReady(context){
context = typeof context === typeof undefined ? $ : context;
components.documentReady.concat(components.documentReadyDeferred).forEach(function(component){
component(context);
});
mr.status.documentReadyRan = true;
if(mr.status.windowLoadPending){
windowLoad(mr.setContext());
}
}
function windowLoad(context){
if(mr.status.documentReadyRan){
mr.status.windowLoadPending = false;
context = typeof context === "object" ? $ : context;
components.windowLoad.concat(components.windowLoadDeferred).forEach(function(component){
component(context);
});
}else{
mr.status.windowLoadPending = true;
}
}
mr.setContext = function (contextSelector){
var context = $;
if(typeof contextSelector !== typeof undefined){
return function(selector){
return $(contextSelector).find(selector);
};
}
return context;
};
mr.components = components;
mr.documentReady = documentReady;
mr.windowLoad = windowLoad;
return mr;
}(window.mr, jQuery, window, document));
Another bit :
//////////////// Scroll Functions
mr = (function (mr, $, window, document){
"use strict";
mr.scroll = {};
var raf = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
mr.scroll.listeners = [];
mr.scroll.busy = false;
mr.scroll.y = 0;
mr.scroll.x = 0;
var documentReady = function($){
//////////////// Capture Scroll Event and fire scroll function
jQuery(window).off('scroll.mr');
jQuery(window).on('scroll.mr', function(evt) {
if(mr.scroll.busy === false){
mr.scroll.busy = true;
raf(function(evt){
mr.scroll.update(evt);
});
}
if(evt.stopPropagation){
evt.stopPropagation();
}
});
};
mr.scroll.update = function(event){
// Loop through all mr scroll listeners
var parallax = typeof window.mr_parallax !== typeof undefined ? true : false;
mr.scroll.y = (parallax ? mr_parallax.mr_getScrollPosition() : window.pageYOffset);
mr.scroll.busy = false;
if(parallax){
mr_parallax.mr_parallaxBackground();
}
if(mr.scroll.listeners.length > 0){
for (var i = 0, l = mr.scroll.listeners.length; i < l; i++) {
mr.scroll.listeners[i](event);
}
}
};
mr.scroll.documentReady = documentReady;
mr.components.documentReady.push(documentReady);
return mr;
}(mr, jQuery, window, document));
I am not well versed with the way these libraries are compiled from web pack to the browser, so sorry if the code block was inadequate/unnecessary
My question is, how do I make sure my script is loaded every time a react-routerpage change happens.

Testing multiple browsers with protractor backed by page objects

I'm writing a test where two browsers need to interact. The problem with simply forking the browser is that my page objects still reference the old browser. I didn't want to rewrite all of my PO's to take the browser as a parameter so I tried the first solution found in the link below where they overwrite the global variables with the new browser's version :
Multiple browsers and the Page Object pattern
However, changing the global variables doesn't seem to work as all the subsequent page object functions that I call are performed against the original browser instance. I have tried logging the window handler before and after the switch and they are indeed different which only baffles me further. Here's some of the code.
spec:
var MultiBrowserFunctions = require('../common/multiBrowserFunctions.js');
var HomePage = require('../home/home.po.js');
describe('blah', function(){
it('blah', function(){
MultiBrowserFunctions.openNewBrowser(true);
HomePage.initializePage();
});
});
MultiBrowserFunctions:
(function() {
var browserRegistry = [];
module.exports = {
openNewBrowser: function(isSameUrl){
if(typeof browserRegistry[0] == 'undefined'){
browserRegistry[0] = {
browser: browser,
element: element,
$: $,
$$: $$,
}
}
var tmp = browser.forkNewDriverInstance(isSameUrl);
var id = browserRegistry.length;
browserRegistry[id] = {
browser: tmp,
element: tmp.element,
$: tmp.$,
$$: tmp.$$,
}
switchToBrowserContext(id);
return id;
},
resetBrowserInstance : function(){
browserRegistry.splice(1,browserRegistry.length);
switchToBrowserContext(0);
}
}
function switchToBrowserContext(id){
console.log('---------------------------switching to browser: ' + id);
browser=browserRegistry[id].browser;
element=browserRegistry[id].element;
$=browserRegistry[id].$;
$$=browserRegistry[id].$$;
}
}());
My questions are:
(1) why doesn't this work?
(2) Is there some other solution that doesn't involve rewriting all of my po's?
What you can do is, save the browsers in different variables and then switch between them by overriding the globals via a utility or something.
describe('Switching browsers back and forth', function () {
var browserA, browserB;
it('Browser Switch', function () {
var browsers = {
a : browser,
b : browser.forkNewDriverInstance(true)
};
browserA = browsers.a;
browserB = browsers.b;
var browserAndElement = switchBrowser(browserB);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
var browserAndElement = switchBrowser(browserA);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
});
});
The switchBrowser() can look like following:
this.switchBrowser = function (currentBrowser) {
browser = currentBrowser;
element = currentBrowser.element;
return {
browser : browser,
element : element
}
}
In this way you don't have to rewrite your POs to take in the new globals.
Hope it helps!
Cheers

Jasmine tests pass in Chrome and Firefox but fail with PhantomJS

I am building a basic blogging app with React. I am using Jasmine and Karma to run my front end tests. I got my first test up and running and it passes in Chrome (Chromium) and Firefox, but when it runs in PhantomJS I get the following error:
PhantomJS 1.9.8 (Linux 0.0.0) ERROR
TypeError: 'undefined' is not a function (evaluating 'ReactElementValidator.createElement.bind(
null,
type
)')
at /home/michael/repository/short-stories/test/karma_tests/story_test.js:1742
My test file looks like this:
var React = require('react/addons');
var Story = require('../../app/js/components/story.jsx');
var TestUtils = React.addons.TestUtils;
var testUtilsAdditions = require('react-testutils-additions');
describe('Story component', function () {
var component;
beforeEach(function () {
component = TestUtils.renderIntoDocument(React.createElement('story'));
component.props.storyTitle = 'front end test title';
component.props.author = 'front end author';
component.props.storyText = 'front end story text';
});
it('should display a story', function () {
expect(component.props).toBeDefined();
expect(component.props.storyTitle).toBeDefined();
expect(component.props.storyTitle).toBe('front end test title');
expect(component.props.author).toBe('front end author');
expect(component.props.storyText).toBe('front end story text')
});
});
I tried deleting my node_modules, and the npm cache clear and npm install, but it didn't fix it. I'm not sure how my tests could pass in Firefox and Chrome, but not in PhantomJS. You can see the full project here: https://github.com/mrbgit/short-stories . Let me know if there's any more info that could help. Any help is appreciated. Thanks!
PhantomJS uses a rather old version of Qt-Webkit that does not provide Function.prototype.bind. This is a problem for a lot of libraries, so a polyfill NPM module called 'phantomjs-polyfill' is available.
If you'd rather not use NPM modules (if you're testing a browser site that hasn't been bundled with browserify/webpack), the following polyfill for bind is provided on the MDN page and you can attach it yourself:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}

Javascript endess loop only in Internet explorer

When running my javascript program on IE, it loops forever.
It works on Firefox, Safari and Chrome.
There is a similar question in Stackoverflow, but here the suggestion was to remove the functionname of the embedded function. I don't have a name on it.
Here an excerpt of the function to set up an asynchronous httprequest. 'rq' is the request object, with open() already called, but not send().
I am trying to set a response function. I am using the 'self' var so that the function will have the same this pointer (also member of same object as calling function).
var self = this;
rq.onreadystatechange = function()
{
self._reqStateChange(rq); // *
} // *
this._req[sl]=rq;
rq.send(null);
return true;
IE endless loops the two lines marked with // *, also the first return in the embedded function:
this._reqStateChange = function(rq)
{
if( (!rq) || rq.readyState!=4 )
return; // exits here every time.
IE 9.0, using built-in debugger.
no jQuery or any other libs inplace.
I have no idea. thanks for helping. I would like to understand the logic here...
Note:
I just built a little script to see if the self-construct is wrong. That works. Seems to be related to the onreadystatechange function. Everytime it exits, it is immediately called again.
This code does NOT lock up in IE:
someObject = function()
{
this.callback = function(){}
this.funcCallsCallback = function()
{
// call the callback
this.callback();
};
}
muell = new function()
{
this.value = 42;
this.test = function()
{
var rq = new someObject();
var self = this;
rq.callback = function()
{
self.endFunc();
}
// now:
rq.funcCallsCallback();
};
this.endFunc = function()
{
alert("called! value="+this.value.toString());
};
}
muell.test();

How to change jQuery default context?

I loaded jQuery on a Firefox addon, which causes it to load using XUL's window.
I want to change the default context so it will use the correct window object.
The classic example I always see is:
var $ = function(selector,context){
return new jQuery.fn.init(selector,context|| correctWindow );
};
$.fn = $.prototype = jQuery.fn;
But doing it I kill a lot of functions, like .ajax, .browser, ... I need these methods.
AMO won't let me create an wrapper to jQuery to send the correct window object.
So my only option is to somehow change jQuery's default context without changing its source code.
What options do I have?
On the first content script I added
var loader = Components
.classes["#mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://extensions/js/jquery-1.7.2.min.js");
var initjQuery = jQuery.fn.init;
$.fn.init = function(s,c,r) {
c = c || window.document;
return new initjQuery(s,c,r);
};
This way it works.
You change the default context with this wrapper:
jQuery.fn.init2 = jQuery.fn.init;
jQuery.prototype.init = function (selection, context, root) {
return new jQuery.fn.init2(selection, context, jQuery.context || root);
}
$(document).ready(function () {
jQuery.context = $(newContextSelector);
$("*").css("color", "red");
});
Look at context that is a jquery object.

Categories

Resources