Using a json file to store configurations for a dojo application - dojo

I am writing a fontend web app using dojo that does a lot of calls to rest endpoints using xhr. I would like to have a place to store configurations for things like endpoint locations and html tag references. I thought I would use an xhr call to a json file to do this, but I am having trouble getting my functions to trigger in the right order/at all. Below is my main js file which has an init() function that I am passing as the callback to my conf initializer ("ebs/conf") module, also below. I have used the Chrome debugger to set breakpoints within my conf.get() method, and it looks as though it never gets called.
Can someone give me some advice please?
Main JS File:
// module requirements
require([ "dojo/dom", "dojo/on", "ebs/prices", "ebs/cart", "ebs/conf",
"dojo/ready" ], function(dom, on, prices, cart, conf, ready) {
ready(function() {
conf.get("/js/config.json", init());
function init(config) {
on(dom.byId("height"), "keyup", function(event) {
prices.calculate(config);
});
on(dom.byId("width"), "keyup", function(event) {
prices.calculate(config);
});
on(dom.byId("qty"), "keyup", function(event) {
prices.calculate(config);
});
on(dom.byId("grills"), "change", function(event) {
prices.calculate(config);
});
cart.putSampleCart();
cart.load(config);
}
});
});
And here is my 'conf' module ("ebs/conf"):
define(["dojo/json", "dojo/request/xhr"], function(json, xhr) {
return {
get : function(file, callback) {
// Create config object from json config file
var config = null;
xhr(file, {
handleAs : "json"
}).then(function(config) {
callback(config);
}, function(error) {
console.error(error);
return error;
});
}
}
});

Your are not passing the function as the callback. You are executing it and passing the result as the second argument.
conf.get("/js/config.json", init());
should be
conf.get("/js/config.json", init);

Related

VueJS getting "undefined" data from ipcRenderer (ElectronJS)

When trying to get a message from ipcMain to ipcRenderer (without node integration and with contextIsolation), it's received but as undefined. Not only that, but if I were to reload the VueComponent (regardless of what change I make to it), the number of responses gets doubled.
For example, the first time I start my application, I get 1x undefined at a time every time I click the button. If I reload the component, I start getting 2x undefined every time I click the button. I reload again and get 4x undefined every time I click the button... and it keeps doubling. If I restart the application, it goes back to 1x.
SETUP
ElectronJS + VueJS + VuetifyJS has been set up as described here.
preload.js as per the official documentation.
import { contextBridge, ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
// whitelist channels
let validChannels = ['toMain']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
receive: (channel, func) => {
let validChannels = ['fromMain']
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
background.js (main process) as per the official documentation for the preload.js file. The omitted code via ... is the default project code generated upon creation.
...
const path = require('path')
const { ipcMain } = require('electron')
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
},
icon: 'src/assets/icon.png',
})
ipcMain.on('toMain', (event, data) => {
console.log(data)
event.sender.send('fromMain', 'Hello IPC Renderer')
// The two lines below return 'undefined' as well in the 'ipcRenderer'
//win.webContents.send('fromMain', "Hello IPC Renderer")
//event.reply('fromMain', 'Hello IPC Renderer')
})
...
}
...
vue.config.js file:
module.exports = {
...
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
}
}
}
main.js (renderer process) contains only the default project code generated upon creation.
VueComponent.vue
<template>
<div id="vue-component">
<v-btn #click="sendMessageToIPCMain()">
</div>
</template>
<script>
export default {
name: "VueComponent",
components: {
//
},
data: () => ({
myData: null,
}),
methods: {
// This works. I get 'Hello IPC Main' in the CMD console.
sendMessageToIPCMain() {
var message = "Hello IPC Main"
window.ipcRenderer.send("toMain", message);
}
},
mounted() {
window.ipcRenderer.receive('fromMain', (event, data) => {
// this.myData = data // 'myData' is not defined error
this.$refs.myData = data;
console.log('myData variable: ' + this.$refs.myData) // undefined
console.log(data) // undefined
})
},
}
</script>
The VueComponent.vue's mounted() has been set up as described here, though If I try to send the data to a variable using this.myData = data, I get an error saying that myData has not been defined - using this.$refs.myData works, though it's still undefined.
P.S. myData has not been defined error =/= undefined. The former is a proper error in red letters while the latter is as seen in the image above.
For solving the first problem (doubling of function calls) you have to remove window.ipcRenderer = ipcRenderer. In contextIsolation mode the approach is to use contextBridge.exposeInMainWorld() only. Using both implementation definitely causes issues.
For the second problem, the callback to receive in ipcRenderer is called with only ...args from main (no event passed to func). see:
ipcRenderer.on(channel, (event, ...args) => func(...args)) <-- func() is called with only args
The only thing you should change is your function in mounted, to accept only data:
window.ipcRenderer.receive('fromMain', (data) => {
console.log(data) // should log you data
})

Casperjs doesn't execute javascript code

I have a page, and I'm referencing a bunch of scripts with regular script tag in the page's header: jQuery, etc, and my compiled script called index.js. Well the latter is big one, around 1.2M (don't ask why).
My index.html seems like this:
<html>
<head>
<title>Cool</title>
<script src='jQuery.js'></script>
<!-- more scripts -->
<script src='index.js'></script>
<!-- more stuff -->
The problem is if I use PhantomJS engine, my script doesn't get executed on any platforms. If I use SlimerJS/XUL then it gets executed on Windows, but doesn't get on OSX. I'm verifying it with a regular waitFor, evaluating a global variable's value, nothing extraordinary:
casper.test.begin('index.js executes', 1, function (test) {
casper.start(host);
casper.waitFor(
function () {
return casper.evaluate(function () {
return !!window.ENV;
});
},
function () {
test.pass('index.js run');
},
function () {
test.fail('index.js did not run');
});
casper.run(function () {
test.done();
});
});
On the other hand jQuery gets initialized in either environments, I can run jQuery code in evaluate context well, so this will pass:
casper.test.begin('jQuery works', 1, function suite(test) {
casper.start(host);
casper.waitFor(
function () {
return casper.evaluate(function () {
try {
return !!$('html').length;
}
catch (e) {
return false;
}
});
},
function () {
test.pass('jQuery works');
},
function () {
test.fail('jQuery did not init');
});
casper.run(function () {
test.done();
});
});
If I log out page events I can see that index.js loads with HTTP result of 200, it just doesn't get executed.
Did someone experienced this before? Does PhantomJS or SlimeJS have some platform specific limitations of script size/complexity?

How do I take a screenshot when a test in internjs fails?

I am having issues figuring out how to take a screenshot ONLY when a test fails in InternJs. I have this simple test in my registerSuite;
'verify google homepage': function () {
var url = 'https://www.google.com/';
return this.remote
.get(url)
.getCurrentUrl()
.then(function (data) {
assert.strictEqual(data, url, 'Incorrect URL');
})
.findByName('q')
.click()
}
I can simply create a screenshot using the following code;
.takeScreenshot
.then(function (data) {
fs.writeFileSync('/path/to/some/file', data, 'base64');
)}
I want to only take a screenshot, if the above test fails the assertion or is unable to find the locator.
I looked into the afterEach method, but I can't figure out how to get the status of the last test to apply a conditional.
So my question is, has anyone setup their internjs test to only take screenshots on failures and how was it accomplished?
It is not currently possible to interact with the currently executing test from beforeEach or afterEach methods; this capability is coming in the next version of Intern.
Selenium server, by default, provides a screenshot on every Selenium command failure, which is a Buffer object on the error.detail.screen property. If a Selenium command fails, just use this property which already has the screenshot waiting for you.
For assertion failures, you can create a simple promise helper to take a screenshot for you:
function screenshotOnError(callback) {
return function () {
try {
return callback.apply(this, arguments);
}
catch (error) {
return this.remote.takeScreenshot().then(function (buffer) {
fs.writeFileSync('/path/to/some/file', buffer);
throw error;
});
}
};
}
// ...
'verify google homepage': function () {
return this.remote.get(url).getCurrentUrl().then(screenshotOnError(function (actualUrl) {
assert.strictEqual(actualUrl, url);
}));
}
If it’s too inconvenient to wrap all your callbacks manually like this, you can also create and use a custom interface for registering your tests that wraps the test functions automatically for you in a similar manner. I’ll leave that as an exercise for the reader.
You can use catch method at the end of your chain and use error.detail.screen as suggested by C Snover.
'verify google homepage': function () {
return this.remote
.get(require.toUrl('./fixture.html'))
.findById('operation')
.click()
.type('hello, world')
.end()
.findById('submit')
.click()
.end()
.catch(function(error){
fs.writeFileSync('/tmp/screenshot.png', error.detail.screen);
})
}
I've been playing with this today and have managed to get it for an entire suite rather than needing to add the code to every single test which seems quite needless.
var counter = -1,
suite = {
beforeEach: function () {
counter++;
},
afterEach: function () {
var currentTest = this.tests[counter];
if (!currentTest.error) {
return;
}
this.remote
.takeScreenshot().then(function (buffer) {
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
}
fs.writeFileSync('/tmp/' + currentTest.name + '.png', buffer);
});
}
};
The annoying thing you will need to do is do this for every test suite rather than "globally" but is much better than doing it for every test.
Building on the answer by Hugo Oshiro,
// tests/support/CleanScreenshots.js
define([
'intern/dojo/node!path',
'intern/dojo/node!del',
], function(path, del) {
return new Promise((resolve, reject) => {
let directory = 'tests/screenshots';
del(path.join(directory, '**/*'))
.then(resolve)
.catch(reject);
});
});
Then in your intern config:
/* global define */
define([
'tests/support/CleanScreenshots'
], function (CleanScreenshots) {
return {
...
setup: function () {
return CleanScreenshots();
},
...
};
});
According to this issue, starting with the Intern 3.0 you can do a custom reporter that take an Screenshots when test fail. So you can centralize it in a simple way, just referencing the custom reporter in your config.js. In my case, what can I just add a reporter array in the config.js with the path to my custom array:
reporters: [
{ id: 'tests/support/ScreenShot' }
],
than I made an custom reporter overriding testFail:
'use strict';
define([
'intern/dojo/node!fs',
], function(fs) {
function ScreenShot(config) {
config = config || {};
}
ScreenShot.prototype.testFail = function(test) {
test.remote.takeScreenshot().then(function(buffer) {
try {
fs.writeFileSync('./screenshots/' + test.parent.name.replace(/ /g, '') + '-' +
test.name.replace(/ /g, '') + '.png', buffer);
} catch (err) {
console.log('Failed to take a screenshot: ' + err);
}
});
};
return ScreenShot;
});
Pay attention to the relative paths both to reference the custom reporter and the place for screenshots. They all seems to be taken considering where you run intern-runner, not the place the source files are located.
For more info about custom reporters go to this page.

PhantomJS passing variable to page.open

I need to capture an URL on one page and then open the target. What's the most elegant solution? The code below doesn't work, because the variable url is basically local.
function() {
page.open("https://www.google.com/blah");
},
function() {
page.evaluate(function() {
var url=document.getElementById('link42')[0]; //URL captured
});
},
//opening the target
function() {
page.open(url);
},
function() {
page.evaluate(function() {
console.log(document.querySelectorAll('html')[0].outerHTML);
});
}
You can probably make it global. You just have to mind how you get the url out of the page context. It is not possible to return DOM elements, but you can return everything that is JSON serializable.
Quote from page.evaluate:
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
var url;
// function chainer
function() {
page.open("https://www.google.com/blah");
},
function() {
url = page.evaluate(function() {
return document.getElementById('link42').href; // href captured
});
},
//opening the target
function() {
page.open(url);
}
getElementById only returns a single element, so you can't use [0].

Accessing ioArgs from callback functions

I'm upgrading a bunch of old dojo to 1.8. For our ajax request handling we've got a decorator (well, function wrapper) that will perform redirects in certain cases based on the response content, for example:
// Decorator func:
var redirectDecorator = function(func) {
var f = function(data, ioArgs) {
if(data.redirect) {
// A manual location redirect:
window.location.href = data.redirect;
if(data.redirect_xhr) {
// clone ioArgs, spawn new request to follow redirect etc
// <snip>
} else {
func(response);
}
}
return f;
}
// Used like so:
dojo.xhrPost({
url: url
handleAs: "json",
form: form,
load: redirectDecorator(function(data, ioArgs) {
// do stuff
})
});
Now, in dojo 1.8 (the dojo/request/xhr module) xhr() returns a Deferred for chaining and the callbacks are only supplied the data argument (no ioArgs - apparently these are attached to the promise - see http://bugs.dojotoolkit.org/ticket/12126).
In other words, the above ajax call becomes:
xhr.post(url, {
handleAs: "json",
form: form
}).then(function(data) {
// do stuff
});
Problem is, I can no longer wrap the anonymous function because ioArgs are not supplied. Inspecting the deferred (by breaking the chaining) doesn't appear to work either and would require more re-engineering than I'd like.
Any ideas?
Thanks Ken (for your help at #dojo too). To elaborate, the solution is to use dojo/request and use the .response deferred promise instead, which provides the necessary info:
// Decorator func:
var redirectDecorator = function(func) {
var f = function(response) {
var data = response.data;
if(data.redirect) {
// A manual location redirect:
window.location.href = data.redirect;
if(data.redirect_xhr) {
request(data.redirect_xhr, response.options).then(func);
} // more conditions follow.
}
return f;
}
request.post(url, {
handleAs: "json",
form: form
}).response.then(redirectDecorator(function(response) { // <-- note .response.then(
// do stuff where data is response.data
}));
Promises returned from dojo/request are actually objects with an additional response promise that provides more information. See the following places for information:
http://www.sitepen.com/blog/2012/08/21/introducing-dojorequest/
http://dojotoolkit.org/reference-guide/1.8/dojo/request.html
http://dojotoolkit.org/documentation/tutorials/1.8/ajax/