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;
};
}
Related
I am writing library using es6, transpiling it with babel via webpack and npm.
The problem is, that my lib is dependent on some code, that I can not change but need to use. I don't know how to load var stuff (from the following example) in my tests so that it is visible for the module.
See the example:
external-stuff.js - this one can not be changed and is loaded before my lib is loaded on prod env.
var stuff = {
get some() { return "some"; }
get stuff() { return "stuff"; }
}
some-module.js - this is one of the modules in the library
export class foo {
static get whatever() { return stuff.some; }
static get whichever() { return stuff.stuff; }
}
test
import {foo} from "../src/foo.js";
let assert = require('assert');
describe('foo', function() {
describe('#whatever()', function() {
it("should do some", function () {
assert.equals(foo.whatever(), "some");
});
});
});
If I run this I get "ReferenceError: stuff is not defined"
I already tried to define "stuff" in before() hook, but no success.
In the end I found a solution that's 'good enough'. I am not sure it would be sufficient for some advanced library though.
I have created file called globals.js
var g = typeof(window) === 'undefined' ? global : window;
// Dependencies - add as many global stuff as needed
g.stuff= typeof(stuff) === 'undefined' ? {} : stuff;
And I import this 'es6module' at the beginning of test
import * as globals from "../lib/global/globals"
import {foo} from "../src/foo.js";
And then I use node-import npm module with which I load the global to the tests in beforeEach hook.
beforeEach(function () {
global.stuff = imports.module("lib/global/stuff.js").stuff;
});
This is perfect for unit testing because I can also mock stuff. And its even more awesome because this way I have a place where I 'define' global dependencies. It would be nice to improve on it and make it per es6modul dependencies... and build on it something fancy... ya know.. dependency injection.
complete test
require('node-import'); // +
import * as globals from "../lib/global/globals"; // +
import {foo} from "../src/foo.js";
let assert = require('assert');
describe('foo', function() {
beforeEach(function () { // +
global.stuff = imports.module("lib/global/stuff.js").stuff; // +
}); // +
describe('#whatever()', function() {
it("should do some", function () {
assert.equals(foo.whatever(), "some");
});
});
});
The problem:
In one of our tests we have a "long click"/"click and hold" functionality that we solve by using:
browser.actions().mouseDown(element).perform();
browser.sleep(5000);
browser.actions().mouseUp(element).perform();
Which we would like to ideally solve in one line by having sleep() a part of the action chain:
browser.actions().mouseDown(element).sleep(5000).mouseUp(element).perform();
Clearly, this would not work since there is no "sleep" action.
Another practical example could be the "human-like typing". For instance:
browser.actions().mouseMove(element).click()
.sendKeys("t").sleep(50) // we should randomize the delays, strictly speaking
.sendKeys("e").sleep(10)
.sendKeys("s").sleep(20)
.sendKeys("t")
.perform();
Note that these are just examples, the question is meant to be generic.
The Question:
Is it possible to extend browser.actions() action sequences and introduce custom actions?
Yes, you can extend the actions framework. But, strictly speaking, getting something like:
browser.actions().mouseDown(element).sleep(5000).mouseUp(element).perform();
means messing with Selenium's guts. So, YMMV.
Note that the Protractor documentation refers to webdriver.WebDriver.prototype.actions when explaining actions, which I take to mean that it does not modify or add to what Selenium provides.
The class of object returned by webdriver.WebDriver.prototype.actions is webdriver.ActionSequence. The method that actually causes the sequence to do anything is webdriver.ActionSequence.prototype.perform. In the default implementation, this function takes the commands that were recorded when you called .sendKeys() or .mouseDown() and has the driver to which the ActionSequence is associated schedule them in order. So adding a .sleep method CANNOT be done this way:
webdriver.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
driver.sleep(delay);
return this;
};
Otherwise, the sleep would happen out of order. What you have to do is record the effect you want so that it is executed later.
Now, the other thing to consider is that the default .perform() only expects to execute webdriver.Command, which are commands to be sent to the browser. Sleeping is not one such command. So .perform() has to be modified to handle what we are going to record with .sleep(). In the code below I've opted to have .sleep() record a function and modified .perform() to handle functions in addition to webdriver.Command.
Here is what the whole thing looks like, once put together. I've first given an example using stock Selenium and then added the patches and an example using the modified code.
var webdriver = require('selenium-webdriver');
var By = webdriver.By;
var until = webdriver.until;
var chrome = require('selenium-webdriver/chrome');
// Do it using what Selenium inherently provides.
var browser = new chrome.Driver();
browser.get("http://www.google.com");
browser.findElement(By.name("q")).click();
browser.actions().sendKeys("foo").perform();
browser.sleep(2000);
browser.actions().sendKeys("bar").perform();
browser.sleep(2000);
// Do it with an extended ActionSequence.
webdriver.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
// This just records the action in an array. this.schedule_ is part of
// the "stock" code.
this.schedule_("sleep", function () { driver.sleep(delay); });
return this;
};
webdriver.ActionSequence.prototype.perform = function () {
var actions = this.actions_.slice();
var driver = this.driver_;
return driver.controlFlow().execute(function() {
actions.forEach(function(action) {
var command = action.command;
// This is a new test to distinguish functions, which
// require handling one way and the usual commands which
// require a different handling.
if (typeof command === "function")
// This puts the command in its proper place within
// the control flow that was created above
// (driver.controlFlow()).
driver.flow_.execute(command);
else
driver.schedule(command, action.description);
});
}, 'ActionSequence.perform');
};
browser.get("http://www.google.com");
browser.findElement(By.name("q")).click();
browser.actions().sendKeys("foo")
.sleep(2000)
.sendKeys("bar")
.sleep(2000)
.perform();
browser.quit();
In my implementation of .perform() I've replaced the goog... functions that Selenium's code uses with stock JavaScript.
Here is what I did (based on the perfect #Louis's answer).
Put the following into onPrepare() in the protractor config:
// extending action sequences
protractor.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
this.schedule_("sleep", function () { driver.sleep(delay); });
return this;
};
protractor.ActionSequence.prototype.perform = function () {
var actions = this.actions_.slice();
var driver = this.driver_;
return driver.controlFlow().execute(function() {
actions.forEach(function(action) {
var command = action.command;
if (typeof command === "function")
driver.flow_.execute(command);
else
driver.schedule(command, action.description);
});
}, 'ActionSequence.perform');
};
protractor.ActionSequence.prototype.clickAndHold = function (elm) {
return this.mouseDown(elm).sleep(3000).mouseUp(elm);
};
Now you'll have sleep() and clickAndHold() browser actions available. Example usage:
browser.actions().clickAndHold(element).perform();
I think it is possible to extend the browser.actions() function but that is currently above my skill level so I'll lay out the route that I would take to solve this issue. I would recommend setting up a "HelperFunctions.js" Page Object that will contain all of these Global Helper Functions. In that file you can list your browser functions and reference it in multiple tests with all of the code in one location.
This is the code for the "HelperFunctions.js" file that I would recommend setting up:
var HelperFunctions = function() {
this.longClick = function(targetElement) {
browser.actions().mouseDown(targetElement).perform();
browser.sleep(5000);
browser.actions().mouseUp(targetElement).perform();
};
};
module.exports = new HelperFunctions();
Then in your Test you can reference the Helper file like this:
var HelperFunctions = require('../File_Path_To/HelperFunctions.js');
describe('Example Test', function() {
beforeEach(function() {
this.helperFunctions = HelperFunctions;
browser.get('http://www.example.com/');
});
it('Should test something.', function() {
var Element = element(by.className('targetedClassName'));
this.helperFunctions.longClick(Element);
});
});
In my Test Suite I have a few Helper files setup and they are referenced through out all of my Tests.
I have very little knowledge of selenium or protractor, but I'll give it a shot.
This assumes that
browser.actions().mouseDown(element).mouseUp(element).perform();
is valid syntax for your issue, if so then this would likely do the trick
browser.action().sleep = function(){
browser.sleep.apply(this, arguments);
return browser.action()
}
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
I'm just working on writing some random puzzles on codewars.com and am curious if anyone can think of a way to eval code after the following code has been run:
eval = function(){};
delete Function.prototype.constructor;
Function = undefined;
// the following are to block require('vm') -- if anyone wants to run this
// in production it may be better to block that one module (others?)
require = undefined;
module.__proto__.require = undefined; // added this due to alexpod's answer, modified due to FabrÃcio Matté's :)
module.constructor = undefined; // added this due to alexpod's answer
This is in node.js, so setTimeout( "string" ) doesn't work.
Well, also you have module variable in node. So you can require vm package and run code using its require method:
var vm = module.require('vm');
vm.runInThisContext(' console.log("hello") ');
UPD
Well, you updated the question, but we can hack it again:
var vm = module.constructor.prototype.require('vm');
vm.runInThisContext(' console.log("hello") ');
UPD2
Another variant:
var vm = module.constructor._load('vm');
vm.runInThisContext(' console.log("hello") ');
UPD3
Again conditions are changed so the next variant:
module.constructor.prototype._compile(' console.log("again hacked") ');
// or
module.__proto__._compile(' console.log("again hacked") ');
// or
Object.getPrototypeOf(module)._compile(' console.log("again hacked") ');
I think better to set module = undefined to make question more complex:)
UPD4
There are another variant without module:)
process.stdin.push(' console.log("here we are") \n ');
But it works only in CLI ("repl")
UPD5
Also in iojs and in node with version >= 0.11.x you can use contextify binding:
var contextify = process.binding('contextify');
var script = new contextify.ContextifyScript(' console.log("im here, buddy") ');
script.runInThisContext();
In node with version < 0.11.x you can use evals binding:
var evals = process.binding('evals');
var script = new evals.NodeScript(' console.log("here I am") ')
script.runInThisContext();
module.require = undefined; is not enough as require is inherited from the Module prototype:
module.require = undefined;
var vm = module.__proto__.require('vm');
vm.runInThisContext('console.log(1)');
Instead, you should:
module.__proto__.require = undefined;
// now this fails and you can't use the __proto__ trick:
var vm = module.require('vm');
Using the GeneratorFunction constructor:
(function*(){}).constructor('console.log(1);')().next().value;
Trying to call this custom matcher in jasmine testing tool but I got this error:
TypeError: matcherCompare is undefined
var result = matcherCompare.apply(null, args);
jasmine.js (line 1192)
My matcher:
/*
* Extends jasmine expectations by defining new matchers
*/
beforeEach(function () {
jasmine.addMatchers({
toEqualArray: function(){
var s = typeof this.actual,
result = false;
if (s === 'object'){
if (this.actual){
if (Object.prototype.toString.call(this.actual) === Object.prototype.toString.call([])) { //'[object Array]'
result = true;
}
}
}
this.message = function(){
if (result){
return "Is Array";
}
return "Is not an Array";
};
return result;
}
});
});
The core of the code inside toEqualArray is already tested as a simple js function and is ok. My matcher doesn't have an argument as you can see. I use jasmine 2.0 standalone for my tests and my matcher resides in an external js file like in the example at the standalone version of jasmine. I even moved my matcher inside my specs replacing jasmine with this but with no result!
What am I doing wrong?
Jasmine hangs when I put in my spec this specific command:
expect(o.get('any')).toEqualArray();
where o is my object that returns (I tested and it's ok) an array!
I have to debug jasmine now :(
For jasmine 2.0, the syntax for custom matchers was changed. Updated documentation is here: http://jasmine.github.io/2.0/custom_matcher.html