Dynamically import functions into an ES5 JS file? - javascript

This might seem a little convoluted, so I apologize in advance if it does.
I'm attempting to create a build tool in Node.js for Chrome Extensions, so the final JS file has to be ES5 compliant.
I have separate JS files for each setting within the extension that export an object, the function within this object needs to be imported into the contentScript.js file and placed within it's own settings object.
I've put together a basic screenshot that shows how this should flow, but I'm not sure of how to approach this issue. I've considered some type of string interpolation within the contentScript which Node would then replace, but that seems more of a workaround than a solution.
Say we have a folder called settings, within those settings are two JavaScript files, each with a different name, type, and function, they'd look like this.
// settingName1.js
module.exports = {
name: 'Setting Name 1',
type: 'switch',
function: () => {
console.log('Setting 1 initialized');
}
}
Ideally, both of these files would have their respective functions imported into the contentScript, under a settings object, for example.
// contentScript.js
// Settings Functions Go Here
const settings = {
settingName1: function() {
console.log('Setting 1 initialized')
},
settingName2: function() {
console.log('Setting 2 initialized')
}
}
});
Basically cutting/copying the function from the source setting file itself, and pasting it under a function (named using the file's name) within the contentScript's settings object.

Here's an idea for the generated file:
// header on the file
const capturedExports = [];
// insert this prologue before each inserted file
(function() {
// =============================
// insert settingName1.js here
module.exports = {
name: 'Setting Name 1',
type: 'switch',
function: () => {
console.log('Setting 1 initialized');
}
}
// =============================
// insert this epilogue after each inserted file
})();
capturedExports.push(module.exports);
// insert this prologue before each inserted file
(function() {
// =============================
// insert settingName2.js here
module.exports = {
name: 'Setting Name 2',
type: 'switch',
function: () => {
console.log('Setting 2 initialized');
}
}
// =============================
// insert this epilogue after each inserted file
})();
capturedExports.push(module.exports);
// insert code that builds the settings object
const settings = {};
for (let exportItem of capturedExports) {
let name = exportItem.name.replace(/\s/, "");
name = name.slice(0, 1).toLowerCase() + name.slice(1);
settings[name] = exportItem.function;
}
You do the following steps to output a new file that is collection of all the settingNamex.js files.
Create the new file.
Write a header to it with the const capturedExports = []; line and the start of the IIFE.
For each settingNameX.js file, write that file to it.
Then write the close of the IIFE and the capturedExports.push(module.exports); after it. This will grab whatever the previous code assigned to module.exports and add it to the caputuredExports array.
Repeat this process for each settingNameX.js file.
Then, insert the code that builds the settings object from the capturedExports array.
Enclosing each inserted module in its own IIFE, gives it its own scope so it doesn't create symbol conflicts with the other inserted modules.
This makes the following assumptions.
It assumes that the code from each of the settingNameX.js files assigns the appropriate object to module.exports like your examples show. In the real world, you probably want to add a bunch of tests to see if the right thing is assigned to module.exports (proper defensive coding).
It assumes that the inserted modules are not assigning conflicting things to the global object.
It assumes your own module doesn't need the module.exports or can overwrite it at the end after the inserted modules have used it. If this assumption isn't OK, then you'd have to manually change module.exports = to something else that you define in your master module.

Related

How to set environment variables like test environment for playwright-cucumber js

I've been using Playwright with Cucumber for e2e-automation of a react web application. This repo has been my starting point and it's been working out pretty good.
However, I'm looking for pointers on how to run these tests on different test environments - like development or QA, so that the target urls and other params vary as per the environment passed. For eg -
if (env == dev){
baseurl = dev_url
}
else{
baseurl = qa_url
}
The Cucumber documentation mentions the World parameter - an this issue looks like a similar issue, however I'm skeptical of passing a different JSON for this task.
Can this be achieved only at a Cucumber level or is there a Playwright or Node way of doing this?
As you are already using cucumber, define your world file like this:
First you can segregate your properties files into: properties-dev.json and properties-qa.json. Below code reads properties file based on env we are passing in defaultOptions object and stores entire properties file data into 'this'. Use 'this' in your hooks file and call this.keyNameForUrl to get url specific to environment.
Note: 'this' can be accessible only in world and hooks files (refer // https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/world.md). If you need this data in other files, create a separate class and declare all public static varibles in it. In Hooks BeforeAll function, reassign values from 'this' to the static variables created in the class.
import { setWorldConstructor } from 'cucumber';
const fs = require('fs');
const defaultOptions = {
env: 'qa'
};
function processOptions(options) {
let envFile = 'properties-' + options.env + '.json';
let environment = JSON.parse(fs.readFileSync(envFile));
return Object.assign(options, environment);
}
function World(input) {
this.World = input;
});
Object.assign(this, processOptions(Object.assign(defaultOptions, options)), options);
}
setWorldConstructor(World);
// https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/world.md

Best way to share an instance of a module that is inside a closure [duplicate]

If I want to span my JavaScript project across multiple source files, but have each file have access to the same private variable, how would one do that?
For example, if I have the following code:
APP = (function () {
var _secret = {},
app = {};
// Application part 01:
app.part01 = (function () { /* function that uses _secret */ }());
// Application part 02:
app.part02 = (function () { /* function that uses _secret */ }());
//
return app;
}());
How do I put app.part01 and app.part02 in seperate files, but still have access to _secret?
I don't want to pass it as an argument. That's just giving the secret away, as app.part01() could be replaced by any other function.
Maybe I am asking the impossible, but your suggestions might lead me in the right way.
I want to work with multiple files, but I don't know how. Copying and pasting everything inside a single function each time before testing is not something I want to do.
How do I put app.part01 and app.part02 in seperate files, but still have access to _secret?
That's impossible indeed. Script files are executed in the global scope, and don't have any special privileges. All variables that they will be able to access are just as accessible to all other scripts.
Copying and pasting everything inside a single function each time before testing is not something I want to do
What you are looking for is an automated build script. You will be able to configure it so that it bundles your files together, and wraps them in an IEFE in whose scope they will be able to share their private state. The most simple example:
#!/bin/sh
echo "APP = (function () {
var _secret = {},
app = {};" > app.js
cat app.part01.js >> app.js
cat app.part02.js >> app.js
echo " return app;
}());" >> app.js
The only way that you can share _secret is attaching it to the application object and then application object to the window object. Here is an example.
// FIRST JS FILE...
var application; // will be attached to window
(function(app) {
app.secret = "blah!"; // will be attached to application
})(application || (application = {}));
// ANOTHER JS FILE
var application;
(function(app) {
app.method1 = function(){ console.log(app.secret); }; // will be attached to application;
})(application || (application = {}));
console.log(application.method1()); // will display 'blah!' on the console
Working example on jsbin
One way I was able to accomplish this was to create a JS file that contained the global object.
// Define a global object to contain all environment and security variables
var envGlobalObj = {
appDatabase: process.env.YCAPPDATABASEURL,
sessionDatabase: process.env.YCSESSIONDATABASEURL,
secretPhrase: process.env.YCSECRETPHRASE,
appEmailAddress: process.env.YCAPPEMAILADDRESS,
appEmailPassword: process.env.YCAPPEMAILPASSWORD
}
module.exports = envGlobalObj
Then in the files I wish to reference this object, I added a require statement.
var envGlobalObj = require("./envGlobalObj.js");
This allowed me to centralize the environment and secrect variables.

How to make a globally accessible variable?

How can I make a globally accessible variable in nightwatch.js? I'm using a variable to store a customized url (dependent on which store is loaded in our online product), but I need it to be accessible across several javascript functions. It appears the value of it resets after each function ends, despite it being declared outside of the function at the head of the file.
It's been some time since you asked your question and support for what you requested might not have been (natively) available before. Now it is.
In the developer guide two methods are provided for creating global variables accessible from any given test, depending on your needs. See here for good reading.
Method 1:
For truly global globals, that is, for all tests and all environments. Define an object, or pass a file, at the "globals_path" section of your nightwatch.json file, i.e.
"globals_path": "./lib/globals.js",
You will need to export the variables, however, so brushing up on Node is a good idea. Here is a basic globals.js file example:
var userNames = {
basicAuth: 'chicken',
clientEmail: 'SaddenedSnail#domain.com',
adminEmail: 'admin#domain.com',
};
module.exports = {
userNames: userNames
}
This object/file will be used for all of your tests, no matter the environment, unless you specify a different file/object as seen in method 2 below.
To access the variables from your test suite, use the mandatory browser/client variable passed to every function (test), i.e:
'Account Log In': function accLogin(client) {
var user = client.globals.userNames.clientEmail;
client
.url(yourUrl)
.waitForElementVisible('yourUserNameField', 1000)
.setValue('yourUserNameField', user)
.end();
}
Method 2:
For environment based globals, which change depending on the environment you specify. Define an object, or pass a file, at the "globals" section of your nightwatch.json file, nested under your required environment. I.e.
"test_settings" : {
"default" : {
"launch_url" : "http://localhost",
"selenium_port" : 4444,
"selenium_host" : "localhost",
"globals": {
"myGlobal" : "some_required_global"
}
}
}
Please note that at the time of writing, there seems to be a bug in nightwatch and thus passing a file using Method 2 does not work (at least in my environment). More info about said bug can be found here.
To expand on Tricote's answer, Nightwatch has built-in support for this. See the documentation.
You can either specify it in the nightwatch.json file as "globals": {"myvar": "whatever"} or in a globals.js file that you reference within nightwatch.json with "globals": "path/to/globals.js". In the latter case, globals.js could have:
module.exports = {
myvar: 'whatever'
};
In either case, you can access the variable within your tests as Tricote mentioned:
module.exports = {
"test": function(browser) {
console.log(browser.globals.myvar); // "whatever"
}
};
I'll probably get down-voted for this, but another option that I have been using successfully to store and retrieve objects and data is to do a file write as key value pairs to an existing file.
This allows me to, at the end of a test run, see any data that was randomly created. I create this file in my first test script using all of the data I will use to create the various accounts for the test. In this way, if I see a whole lot of failures, I can take a look at the file and see what data was used, then say, log in as that user and go to that location manually.
In custom commands I have a file that exports the following function:
saveToFile : function(path, filename, data) {
this.yfs = fs;
buffer = new Buffer(data);
console.log("Note: About to update the configuration with test data" )
fs.open(path, 'w', function(err, fd) {
if (err) {
throw 'error opening file: ' + err;
}
fs.write(fd, buffer, 0, buffer.length, null, function(err) {
if (err) throw 'error writing file: ' + err;
return fs.close(fd, function() {
console.log('File write: ' + path + ' has been updated.' );
})
});
})
},
In this file, 'data' is key value pairs like "username" : "Randy8989#mailinator.com". As a result I can use that data in later scripts, if so desired.
This being true, I'll be exploring GrayedFox's answer immediately.
Not sure it's the best way, but here is how I do it : you can define a variable in the browser.globals and access it in your different tests
For instance :
module.exports = {
before: function(browser) {
console.log("Setting up...");
// initialize global variable state
browser.globals.state = {};
},
"first test": function(browser) {
var settings = browser.globals,
state = browser.globals.state;
state.my_shared_var = "something";
browser.
// ...
// use a shared variable
.setValue('input#id', state.my_shared_var)
// ...
// ...
// save something from the page in a variable
.getText("#result", function(result) {
state.my_shared_result = result.value;
})
// ...
},
"second test": function(browser) {
var settings = browser.globals,
state = browser.globals.state;
browser.
// ...
// use the variables
.url("http://example.com/" + state.my_shared_result + "/show")
.assert.containsText('body', state.my_shared_var)
// ...
}
}
An alternative of globals.json if you need read data with complex procedure, is just to create a function in the same test file.
In the following example, I needed simple values and data from csv.
So I created getData() function and I can invoke directly from inside:
let csvToJson = require('convert-csv-to-json');
function getData(){
let users = csvToJson.getJsonFromCsv("/../users.csv");
return {
"users:": users,
"wordToSearch":"JRichardsz"
}
}
module.exports = {
"login": function(browser) {
//data is loading here
var data = getData();
browser
.url('https://www.google.com')
.waitForElementVisible('input[name="q"]', 4000)
.setValue('input[name="q"]', data.wordToSearch)
.keys(browser.Keys.ENTER)
.waitForElementVisible('#result-stats', 4000)
.end();
}
};
generaly it is a bad practice, but you can assign it as field of window class.
window.someGlobalVar = 'http://example.org/'
and window object is accessible globally

Mangle nested classes and variables with UglifyJS

I use UglifyJS to minify a concatenated set of files, which works fine but not good enough. The built lib uses namespaces, so classes, functions and constants are stored in a root namespace variable:
(function() {
var root = { api:{}, core:{}, names:{} };
/* util.js file */
root.names.SOME_LONG_NAMED_CONST='Angel';
/* Person.js file */
root.core.Person = function(name) { this.name = name };
/* API.js with the functions we want to expose */
root.api.perform = function(param_for_api) { /* do something */ }
window.lib_name.perform = root.api.perform;
})();
which is minified to the not-so-minimal version
(function(){var a={api:{},core:{},names:{}};a.names.SOME_LONG_NAMED_CONST="Angel",a.core.Person=function(a){this.name=a},a.api.perform=function(){},window.lib_name.perform=a.api.perform})();
I understand uglify probably thinks that root var is a data structure that must be kept as-is and can't be changed. Is there a way to let UglifyJS mangle the nested names in the root namespace?
When you minimize Javascript you can only change names of variables, the api, core and names are not variables but properties of an object. If these were changed by the minimizer, you would potentially get unexpected results. What if in your code you would call
root["api"].perform = function()...
or even something like
function doIt(section, method, argument) {
root[section][method](argument);
}
doIt('api','perform', 101);
All perfectly legal JS, but a minimizer could never figure out what's going on.
I have been trying to use --mangle-props of UglifyJS2 and can tell you: 'it makes a mess'.
As someone pointed out: 'Developer should decide what properties to mangle, not uglifyjs'
I am approaching the problem using this options:
--mangle-props
--mangle-regexp="/_$/"
The regex matches any property with a underscore at the end.
You asked to mangle nested names in the root namespace. So, your code:
(function() {
var root = { api:{}, core:{}, names:{} };
root.names.SOME_LONG_NAMED_CONST_='Angel';
root.core.Person_ = function(name) { this.name = name };
root.api.perform_ = function(param_for_api) { }
window.lib_name.perform = root.api.perform;
})();
Would result in this:
(function() {
var n = {
api: {},
core: {},
names: {}
};
n.names.a = "Angel";
n.core.b = function(n) {
this.name = n;
};
n.api.c = function(n) {};
window.lib_name.perform = n.api.c;
})();
Command:
uglifyjs --beautify --mangle --mangle-props --mangle-regex="/_$/" -- file.js
If you want to mangle first level of root namespace (api, core, names) just put a underscore on them (api_, core_, names_), you are in control ;)
Just a side note: when you are mangling properties usable by other js files, you should mangle all files together with the same command, so the same identifier will be used over all files.
Aside from #JanMisker 's point (which is completely valid), rewriting properties is unsafe because they can be exposed to code outside the scope of the minification.
Although the self executing function has a scope, and if the code is only
(function() {
var root = { api:{}, core:{}, names:{} };
root.names.SOME_LONG_NAMED_CONST='Angel';
alert(root.names.SOME_LONG_NAMED_CONST); // some code that does something
})();
It is true that outside of the function, there is no way to access the root object, so rewriting the property names is safe, and the following code would result in the same:
(function() {
var a = { b:{}, c:{}, d:{} };
a.d.e='Angel';
alert(a.d.e);
})();
But even if you are inside your private scope you can access, and more importantly assign to variables from an outer scope! Imagine this:
(function() {
var root = { api:{}, core:{}, names:{} };
root.api.perform = function(param_for_api) { /* do something */ }
window.lib_name = root.api;
})();
You are not only exposing a function but an object with a function on it. And the function will be visible from any place where window is visible.
So, for example writing the following in the javascript console would yield different results with and without minification:
window.lib_name.perform(asdf);
With minification you would have to write:
window.lib_name.f(asdf);
Or something similar.
Remember that there can always be code outside your minification.
It is not that crucial to have the absolute minimal JS, but if IT IS that crucial for some reason (for example: aliens abducted your stepdaughter, and the only way to have her back is to minify this below 100 characters or so), you can manually replace an undesirably long property name to a shorter one, just be sure that it will not be exposed anywhere, and isn't be accessed through associative array notation (root['api']).
as #Jan-Misker explained in his answer, property name mangling is NOT an good idea because it could potentially break your code.
However, you can workaround it by define the property names as local variables, and modify all .properties to [keys], to make smaller file size:
(function() {
var API = 'api';
var CORE = 'core';
var NAMES = 'names';
var SLNC = 'SOME_LONG_NAMED_CONST';
var root = {};
root[API]={};
root[CORE]={};
root[NAMES]={};
/* util.js file */
root[NAMES][SLNC] ='Angel';
/* Person.js file */
root[CORE].Person = function(name) { this.name = name };
/* API.js with the functions we want to expose */
root[API].perform = function(param_for_api) { /* do something */ }
window.lib_name.perform = root[API].perform;
})();
Because now all the properties became a local variable, uglify js will mangle/shorten the variable names and as consequence you overall file size reduced:
!function(){var a="api",b="core",c="names",d="SOME_LONG_NAMED_CONST",e={};e[a]={},e[b]={},e[c]={},e[c][d]="Angel",e[b].Person=function(a){this.name=a},e[a].perform=function(){},window.lib_name.perform=e[a].perform}();
However, reduced file size doesn't mean you will get shorter downloading time on real server, because usually our http transport is gzipped, most of the repetitions will be compressed by your http server and it does a better job than human.
The latest release of uglify (today) has object property mangling, see v2.4.18. It also supports reserved files for excluding both object properties and variables that you don't want mangled. Check it out.
Use the --mangle-props option and --reserved-file filename1.json filename2.json etc....

Google Docs Scripts addToFolder

I'm attempting to use Google Docs' ability to run scripts to create the necessary folder structure for new clients (to upload content/images into). Here's what I have thusfar:
/**
* This script creates the necessary folder structure for a new client
*/
function newClientSetup() {
var initial = DocsList.getFolder("Clients");
var client = DocsList.addtoFolder.createFolder("Client Name");
var file = DocsList.addtoFolder(client);
};
Now this is not working (TypeError: Cannot call method "createFolder" of undefined. (line 7)), but I'm having trouble figuring out how to use the Folder class within DocList. I saw that DocsList has a createFolder method, that I could use like:
var folder = DocsList.createFolder("Folder Name");
but I'm trying to start off with a parent folder, called Clients (already in Google Docs) and then create the following structure:
Clients
Client Name
Content
Images
Ideally I could run this script, but pass in a variable for Client Name to actually create the client name, but I haven't found much help from the docs. Any suggestions? Thanks!
Here is an example of how it works, see comments :
function createSubFolder(subfolder) { // the argument is the name of the folder you want to create
var parentfolder = DocsList.getFolder('Clients'); //begin in the client folder (you could also open by Id, I prefer the ID as I find it more failsafe (ID are unique, names aren't necessarily
var newFolder = DocsList.createFolder(subfolder); // create the new subfolder from the argument of the function
newFolder.addToFolder(parentfolder);// add the newly created folder to 'Clients'
}
to test this function simply use something like this :
function test(){
createSubFolder('test');// this will create a new folder called test in your Clients folder
}
note : to get the ID of your folder, take the value right behind folders/ in the url of the folder. Example in bold : https://drive.google.com/?hl=fr&tab=wo#folders/0B3qSFxxxxxxxxxdsMTFZMDQ
The sequence might be much longer if you have more folder levels... but the structure is always the same and is unique for every folder.
Here's a function and some code I wrote which might help you. It uses a sub function to see if the folder already exists and if it doesn't makes it. If the folder does already exist it returns that object which helps with chaining (referenced here on how it is used):
function newClientSetup() {
var ROOT_FOLDER = "Clients";
var CLIENTNAME_FOLDER = "Client Names";
// get a the system route folder (if it deosn't existing make it
var rootFolder = folderMakeReturn(ROOT_FOLDER);
// create/get draft and release folders
var clientNamesFolder = folderMakeReturn(CLIENTNAME_FOLDER,rootFolder, ROOT_FOLDER+"/"+CLIENTNAME_FOLDER);
}
// function to see if folder exists in DocList and returns it
// (optional - if it doesn't exist then makes it)
function folderMakeReturn(folderName,optFolder,optFolderPath){
try {
if (optFolderPath != undefined){
var folder = DocsList.getFolder(optFolderPath);
} else {
var folder = DocsList.getFolder(folderName);
}
return folder;
} catch(e) {
if (optFolder == undefined) {
var folder = DocsList.createFolder(folderName);
} else {
var folder = optFolder.createFolder(folderName);
}
return folder;
}
}
I know the question/answer is 3 years old and it was correct earlier, but this solution is no more working correctly with Google Apps script as on 2015, current easiest way to create a subfolder on a specific folder is,
function createSubFolder(parentId, folderName)
{
var id="";
try
{
var parent=DriveApp.getFolderById(parentId); // get parent folder
var folder =parent.createFolder(folderName); // create sub folder
id=folder.getId(); // get the subfolder id to return
}
catch(e)
{
}
return id;
}
pass the parent folder id with the sub folder name you want to create, the Folder class has the method createFolder to create a folder under it which returns the created subfolder's object.

Categories

Resources