layout |
---|
default |
{% include links %}
- TOC {:toc}
- Design and code for clarity, simplicity and robustness (but within reason). Don't optimise minor parts or if optimisation adds much complexity.
- Use meaningful symbols and objects.
- Perform checks early. When they fail, generate errors. Let errors show up, don't silence them.
- Write documentation before implementation. You will benefit from it yourself:
- This extra time enables you to reflect on what you want to achieve at higher level.
- Reflecting on what you intend to implement can save you unnecessary work (immediately or later).
- Complexity makes coding often slow and frustrating. By documenting first, you will be free to relax with no paperwork duty once implemented.
- If you can't finish implementation or you need assistance, it's easier for someone else to pick it up if there's some documentation.
- Create Selenese [scripts][script] to be run from Selenium IDE - see PackagedScripts.
- Don't use fixed line length - see DocumentationStandard. However, split complex expressions on multiple lines.
- Use ESLint. Install it and run it as
eslint .
in bothSeLite
andSelBlocksGlobal
folders. - Apply Mozilla WebExtensions code style.
If you've used Javascript only for web pages, you’ll find out some new terms and patterns here. This applies to development of [extensions of Selenium IDE][Extension of Selenium IDE] (including SeLite frameworks as in GeneralFramework) or Firefox extensions in general. They run in privileged mode. That provides extra features and it also sets some restrictions.
Privileged Javascript controls (or extends or overrides) Firefox functionality. It can only come from {{chromeUrl}}s or file:// URLs. That is why Bootstrap and SettingsInterface (and SettingsAPI) can't load files over http (neither https). chrome:// URLs are governed by extension's chrome.manifest
(which maps a custom chrome://xyz/ URL prefix to a location within the extension).
Javascript for web applications has only two levels of scope: global and local (within functions). On the other hand, Firefox avoids conflicts between components (add-ons) by running them with separate global scopes. However, extensions can share Javascript files without mixing their scopes: they can specify global scope used in those files. (The code from such files is still shared, even though global scope for each of its usages may be different. See {{navLoadingJavascriptFiles}}.) See also [Core scope].
If you add custom functionality at [Core scope], do it only for new Selenese commands or for use in parameters of Selenese commands. The later should be grouped in an object with a name unlikely to cause conflicts. Such object serves as a namespace. You can have multi-level namespace objects, or group classes and functions in a Java package-like notation.
When adding functionality that doesn't need to be directly available to Selenese expressions, use {{navJavascriptCodeModules}}.
If adding Selenese commands specific to a web application, give their names a prefix unlikely to cause conflicts.
In Javascript files, have the following as the very first statement (at the top level): "use strict";
That adds extra checks that help to prevent errors and some bad practice code. Strict mode only applies to the code within that file; it doesn't apply to any functions called from there. See MDN > Strict mode .
See also > MDN > Functions.
This is a definition by function
statement. When employing {{navStrictJavascript}}, have such definitions at file level only and not within other functions or conditional/loop blocks (see MDN > Strict mode > Paving the way for future ECMAScript versions).
"use strict";
function myFunction( param, anotherParam... ) {
...
}
This applies function expression. It generates closures, functions that can access non-global variables from outside their scope (i.e. from the scope that contains that function
expression). See examples below, at Isolate the local scope and {{navFunctionIntercepts}}.
A function name "hides" any outer symbol with the same name. Hence, a function can't have the name same as any outer symbols (variables or functions) that it accesses.
var mySymbol= "hi";
// The following function can't have name mySymbol, otherwise it won't return the outer mySymbol variable, but it returns reference to the function itself instead.
var result= (function someNameOtherThan_mySymbol() {
return mySymbol;
})();
alert( result ); // => "hi"
// You most likely don't want this:
result= (function mySymbol() {
return mySymbol;
})();
alert( typeof result ); // => "function"
If you redefine a function within the same scope, the new definition will apply to wherever you've assigned the old definition! See:
"use strict";
function f() {return 1;}
var first=f;
function f() { return 2;}
first(); // This returns 2 rather than 1!
That's when you have two or more definitions of the function with the same name (within the same scope), even though you save it to a different variable or to a different object/prototype. This can happen if you extend the existing code and you forget to check what methods there were already, or if someone else extends your code. It creates highly confusing conflicts.
Often, functions need variables/symbols that are global-like (or static-like), but you don't want such symbols in the global scope. This applies mostly to [Core extensions][core extension] (which share the [Core scope]), including SeLite frameworks (as in GeneralFramework](GeneralFramework)). (Javascript code modules have separate scopes, so they don't need this.)
You could have all your code defined within one long function, and then call it at the end of the file. However, if you name such a function, that name itself becomes part of the global scope.
The solution is to have such a (long) function, but make it anonymous. It then looks like
"use strict";
var usefulExportedFunction;
( function() {
var localVariable;
usefulExportedFunction= function usefulExportedFunction( anyParameters... ) {
use localVariable here...
};
}
)();
If you want a function (anonymous or not) to define global symbols, you can pass the global scope (i.e. 'global object') to it. It's accessible via this
keyword at global scope (similar to $GLOBALS
in PHP).
Then the above example could be:
"use strict";
( function(global) {
var localVariable;
global.usefulExportedFunction= function usefulExportedFunction( anyParameters... ) {
use localVariable here...
};
}
)( this );
When you define a function the classic way (whether named or anonymous), this
keyword refers to the instance that the function will be invoked on (if any). But often you want that code to refer to this
as it were during execution of code that defined that function (i.e. this
from the scope that creates the new Function object by that function
statement).
You need to save that outer this
into a variable (usually called self
) local in the scope outside of the new function (one being created by function
statement). Then use that variable (e.g. self
) instead of this
where needed. See also MDN > Operator this and MDN > Functions > Closures.
"use strict";
function applyToArray( array, method ) {
for( var i=0; i<array.length; i++ ) {
method.call( null, array[i] );
}
}
function TestArrayWorker( array, multiplyBy ) {
this.array= array;
this.multiplyBy= multiplyBy;
}
TestArrayWorker.prototype.run= function() {
var self= this;
var result= 0;
applyToArray(
this.array,
function(item) {
result+= item*self.multiplyBy;
}
);
return result;
};
new TestArrayWorker( [1, 2], 5 ).run(); // This returns 15
As an easier alternative, use JavascriptComplex > ECMAScript 6 and 7 > MDN: Arrow fuctions.
Document the functions, parameters, variables etc. in JSDoc format. Apply JSDoc 3 when useful (even though NetBeans 7.3 only supports JSDoc 2). Some JSDoc tags are not obvious - e.g. use @extends for documenting class inheritance. For documenting Javascript modules see JavascriptComplex.
Document why you do things, not what you do.
When commenting the code, do not use sequences of = (e.g. ======= or longer) as visual separators. ======= serves to separate conflict parts when a GIT merge (or SVN merge/update) causes conflicts.
If you intercept or extend Selenium, or if you figure out any non-trivial, non-obvious or unclear relationships between objects and/or functions, describe them as per DocumentationStandard > Textual object diagrams.
There's no need and no benefit from Google Closure with Selenium. Do not use JSLint, since it refuses iteration variables defined in for
loop to be used after the loop.
Firefox allows easy iteration over arrays, if you don't need to keep track of the index:
var a=[1, 2];
for( var value of a) {
// value is a value from a[]
}
However, NetBeans 8.0.1 doesn't support it (it breaks Navigator and block expand/collapse). See NetBeans issue #237640 and vote for it (and for the rest of ThirdPartyIssues). Until NetBeans gets fixed, SeLite uses the classic way
var a=[1, 2];
for( var i=0; i<a.length; i++ ) {
var value= a[i];
}