So I'm working with an enterprise tool where we have javascript scripts embedded throughout. These scripts have access to certain built-in objects.
Unfortunately, the tool doesn't give any good way to unit test these scripts. So my thinking was to maintain the scripts in a repo, mock the built-in objects, and then set up unit tests that run on my system.
I'm pretty ignorant to how JavaScript works in terms of building, class loading, etc. but I've been just trying things and seeing what works. I started by trying out Mocha by making it a node project (even though it's just a directory full of scripts, not a real node project). The default test works, but when I try and test functions from my code, I get compiler errors.
Here's what a sample script from my project looks like. I'm hoping to test the functions, not the entire script:
var thing = builtInObject.foo();
doStuff(thing);
doMoreStuff(thing);
function doStuff(thing) {
// Code
}
function doMoreStuff(thing) {
// More Code
}
Here's what a test file looks like:
var assert = require('assert');
var sampleScript = require('../scripts/sampleScript.js');
describe('SampleScript', function() {
describe('#doStuff()', function() {
it('should do stuff', function() {
assert.equal(-1, sampleScript.doStuff("input"));
});
});
});
Problem happens when I import ("require") the script. I get compilation errors, because it doesn't builtInObject. Is there any way I can "inject" those built in objects with mocks? So I define variables and functions that those objects contain, and the compiler knows what they are?
I'm open to alternative frameworks or ideas. Sorry for my ignorance, I'm not really a javascript guy. And I know this is a bit hacky, but it seems like the best option since I'm not getting out of the enterprise tool.
So if I get it right you want to do the unit tests for the frontened file in the Node.js environment.
There are some complications.
First, in terms of Node.js each file has it's own scope so the variables defined inside of the file won't be accessible even if you required the file. So you need to export the vars to use them.
module.exports.doStuff = doStuff; //in the end of sample script
Second, you you start using things like require/module.exports on the frontend they'll be undefined so you'll get an error.
The easiest way to run your code would be. Inside the sample script:
var isNode = typeof module !== 'undefined' && module.exports;
if (isNode) {
//So we are exporting only when we are running in Node env.
//After this doStuff and doMoreStuff will be avail. in the test
module.exports.doStuff = doStuff;
module.exports.doMoreStuff = doMoreStuff;
}
What for the builtInObject. The easies way to mock it would be inside the test before the require do the following:
global.builtInObject = {
foo: function () { return 'thing'; }
};
The test just passed for me. See the sources.
Global variables are not good anyway. But in this case seems you cannot avoid using them.
Or you can avoid using Node.js by configuring something like Karma. It physically launches browser and runs the tests in it. :)
Related
I'm using Webdriver.io to run tests on a large number of pages. Because all the specs for the pages are in a JSON file, I have a special class that sets up the test. It looks like this:
module.exports = class PageTester {
suiteName = '';
browser = {};
constructor (suiteName, browser) {
this.suiteName = suiteName;
this.browser = browser;
}
testModel(currentModel) {
describe(this.suiteName + ' endpoint ' + currentModel.url, () => {
this.browser.url(currentModel.url);
/* it() statements for the test */
});
}
}
Then in my specs folder I have a file that loads the JSON and plugs it into the PageTester class, like this:
const PageTester = require('../modules/PageTester');
const models = require('/path/to/some/file.json');
const pageTester = new PageTester('Some Name', browser);
for (const modelName in models) {
pageTester.testModel(models[modelName]);
}
When I run this code, WebdriverIO gives me the following warning:
WARN #wdio/mocha-framework: Unable to load spec files quite likely because they rely on `browser` object that is not fully initialised.
`browser` object has only `capabilities` and some flags like `isMobile`.
Helper files that use other `browser` commands have to be moved to `before` hook.
Spec file(s): /suite/test/specs/test.js
All the tests seem to run fine, so I don't actually understand what this warning is complaining about and what negative consequences ignoring it may have. So I would like to a) understand why this is happening and b) how it would be possible to get rid of this warning given the way my code is set up.
In my case, I resolve it by fixing the path for the require files. I noticed that my path was wrong. But the error that wdio throws is not really helpful. :/
you can only interact with browser object inside it blocks because it is not fully accessible before the browser session is started.
See https://webdriver.io/blog/2019/11/01/spec-filtering.html for details.
You simply should ensure your spec file and respective page file are kept on a similar folder structure.
I have an app that runs in different modes (think of it as running for different platforms as well as using different protocols), one of which has a long loading period every time a page is opened. There are some other minor changes, but all of those could just be taken care of using wdio's setting variables.
Currently I have one test file (with a describe) for each section of the app. Which would be fine if one of the configurations being tested didn't have such a long wait time. Anyway, I've decided to deal with this test case, to handle it all in one file, which will all be on the same page.
Anyway, instead of copying and pasting all the tests I had previously to this one large file I was wondering if I could somehow reuse them, as if they were functions.
As it is right now I did just wrap things in functions, so for example:
// test1.js
module.exports = function test1 () {
describe('Test1', function () {
var settings = {}
before(function () {
// do something
})
it('do something', function () {
assert.ok(true)
})
it('do something else', function () {
assert.ok(true)
})
})
}
In another file we run every single function we created:
test1 = require('./test1')
test2 = require('./test2')
...
test10 = require('./test10')
describe('Main Test', function () {
test1()
test2()
...
test10()
}
This would have solved my DRY problem, if I could somehow select which test functions to run upon my command using
wdio wdio/wdio.conf.js --specs
wdio/test/spects/browser/test1.js
Which obviously will not work.
Basically I want a solution to be able to reuse my tests (the describe blocks). Is what I was doing the right path? If not, how should it be done?
So I have figured out the best way to go about this after I found some documentation about it here.
I will just do as I previously described, however instead of shoving all those functions in the same file, I'll keep them in their own files. There still may be a better solution out there, but it is still an improvement from copying and pasting all test cases for the different modes of running my app.
Just programmatically create different describe blocks. Wrap the describe block in a function with all the parameters that change (including the name of the block) and simply invoke the function to create the variations.
I made a small repo to show this in practice: https://github.com/fatso83/forum-support-code/commit/cb2bc10b1d8bdae31e8f0a8c4e724c70583a5e11
I have one js files . I load it using other javascrupt file using eval() function. I have seen eval is slow and with some other limtation. Since i need to store my JS file object in cache and use it anytime i need after apllication starts. I dont want to do eval() everytime.
Is there anyway to do it in simple way.
var evalObj;
if(evalObj) {
console.log('eval object already evaluated');
_myfunctionInJSFile_(layouts.FormatDate(startTime), threadName, level, categoryName, message);
}
else {
evalObj = eval(fs.readFileSync('./myJSFile', 'utf8'));
console.log('re evaluating object ..' );
_myfunctionInJSFile_(layouts.FormatDate(startTime), threadName, level,message);
}
myJSFile
var _sigmaAlarmHandler_ =function(args)
{
var args = Array.prototype.slice.call(arguments);
args.unshift();
console.log('Alarm : ', args);
}
Either the conditional eval is not working.
In node.js you can simple require your js-file:
var obj = require('./myJSFile');
obj.foo();
./myJSFile.js:
exports.foo = function() {
console.log('foo');
}
This file becomes a module with exported functions, that you need.
It loads once, then every require reuse already loaded module.
If it is not commonjs-compliant (i.e. using module.exports will not work), then you can run it in its own vm:
var vm = require('vm');
vm.runInNewContext(jscode,{/*globalvars*/});
where the second parameter is an object with global vars made available in the context in which the jscode is run. So if the second param is, say, {a:1,b:"foo"} then your jscode will run with the global variable a set to 1 and the global variable b set to "foo".
The jscode itself is a string that you load from a file or elsewhere.
Think of vm.runInNewContext() as "practice safe eval". Well, relatively safe, you can still do some dangerous stuff if you pass in particular vars, like process or file etc.
I used this for the declarative part of cansecurity http://github.com/deitch/cansecurity for nodejs
You can view the sample in the file lib/declarative.js
Here is the API for vm http://nodejs.org/api/vm.html
There are options to run in the same context, etc. But that is very risky.
When you actually run the code, using your example above:
_myfunctionInJSFile_(layouts.FormatDate(startTime), threadName, level,message);
you are looking to pass in 4 params: startTime, threadName, level, message and execute the function. The issue is that you cannot run the function on the current context. You need the function to be defined and run in the file. So you should have something like:
vm.runInNewContext(jscode,{startTime:layouts.FormatDate(startTime),threadName:threadName,level:level,message:message});
And then the jscode should look like
function _myfunctionInJSFile(startTime,threadName,level,message) {
// do whatever you need to do
}
// EXECUTE IT - the above vars are set by the global context provide in vm.runInNewContext
_myfunctionInJSFile(startTime,threadName,level,message);
If you prefer to define the function and have it loaded and run in this context, then just use the commonjs format.
I think i have found the answer for this.
Since my application is running in node js which uses v8 engine platform. When the application starts v8 engine caches all the code/configuration and can be used anytime.
Similarly in my code i will pre-load the JS code using eval and i will do it only once. So on next call i will return only the loaded JS code. Here i need to modify the code to load once.
But main point we have look is that in future if any body has similar requirement they can cache their JS codes using eval (thanks to v8 engine) and use it till your application is running.
I would like to include a module/library in a protractor test case, however, as soon as I add the line
var lib = require('./loginactions.js');
All the references to protractor and related objects are lost. In other words, if I don't have the require line, the 'protractor' and 'browser' variables are found and test runs fine (using the functions within the file), but after adding that line the variables are not found any more.
Here is a minimal test case:
var lib = require('./loginactions.js'); //problematic line
describe('Login / Logout to Application',function(){
var ptor;
beforeEach(function(){
ptor = protractor.getInstance(); //protractor reference lost
browser.get('http://localhost:80'); //browser reference lost
});
it('should login and then logout successfully', function(){
//Do things here
lib.login(user, pass, ptor);
});
});
I export the functions in this way:
module.exports.Login = Login;
module.exports.Logout = Logout;
//Params: ptor - protractor instance
function Login(user, pass, ptor)
{
//stuff
}
function Logout(ptor)
{
//stuff
}
I also wonder, is this even the correct way of including the own libraries into the project. So my question is, how to properly include libraries into a protractor test case?
To answer my own question, using protractor as a library method worked, this way the references to protractor were restored. So adding these two requires solved my issue:
var protractor = require('/path/to/protractor');
require('/path/to/protractor/jasminewd');
So my test looked similar to the updated code in
'no method expect' when using Protractor as a library question.
However, I am not entirely sure about the global browser object. It is a wrapper around the WebDriver object according to http://www.ng-newsletter.com/posts/practical-protractor.html, but so is protractor instance. So I decided to replace all 'browser' variables with 'ptor' and so far no complaints. This may backfire, but as I said, I'm not entirely sure whether the global browser object that is created along with the global protractor object, when running protractor normally and not as library.
What is the right way to handle the Page Objects model for writing Protractor E2E testing for AngularJS? I feel like I should write them in separate files (like homepage.js, page2.js, etc) then include somehow them into the specs like modules as need. However, I don't know how to inject them. Would appreciate any suggestions. Thanks!
Keep them in separate files, and use Node's require to pull in any helpers or page objects you need. Check out this super simple example: https://github.com/juliemr/ng-page-e2e/blob/master/test/angularsite_test.js#L2
Have you tried with astrolabe? It's a PageObject implementation on top of Protractor that could guide your structure: https://github.com/stuplum/astrolabe
There you can see that the recommended structure is one page object per file.
E.g.: singInPage.js
var Page = require('astrolabe').Page;
module.exports = Page.create({
url: { value: 'http://<somesite>.com/signin' },
username: { get: function() { return this.findElement(this.by.input('username')); } }, // finds an input element with the name 'username'
submit: { get: function() { return this.findElement(this.by.id('submit')); } } // finds an element with the id 'submit'
});
it makes the writing of test cases and even page object very compact and readable.
You should keep them in separate files, yes.
And in your protractor referenceConf.js (config to launch protractor with) you should write:
specs: ['<your_path>/test/pages/*Test.js']
In this case< protractor will launch all files from dir "/test/pages" with mask *Test.js (loginPageTest.js, homePageTest.js)
I'm afraid that there's no common standards when it comes to testing with page objects.
You could find several proposals among protractor's issues: https://github.com/angular/protractor/issues/401 and https://github.com/angular/protractor/issues/78
For my needs I created very simple page objects in my open source project, see: https://github.com/9ci/angle-grinder/pull/124
Also quite interesting implementation you could find in https://github.com/juliemr/ng-page-e2e/tree/master/test