Skip to content

Latest commit

 

History

History
93 lines (70 loc) · 9.4 KB

ExtensionSequencer.md

File metadata and controls

93 lines (70 loc) · 9.4 KB
layout
default

{% include links %}

  • TOC {:toc}

Summary

SeLite Extension Sequencer (Components > Extension Sequencer) allows [extensions of Selenium IDE][Extension of Selenium IDE] to declare dependencies. Then it ensures that they are loaded in a correct order.

It only works for extensions packaged as Firefox add-ons (as .xpi files, or through proxy files). It doesn't cover extensions loaded from single Javascript files via Selenium IDE menu Options > Options > General, neither through Bootstrap.

Use cases

Say you have two or more extensions of the same type (Core or IDE). Then Firefox and Selenium IDE don't guarantee a specific order of loading them up.

Some extensions may depend on others and require them to be initiated first. For example

  • the dependant tail-intercepts functionality originating in the dependee
  • both extensions tail-intercept same functionality and their order matters
  • initiation of the dependant relies on the dependee

Loading via Extension Sequencer

Your add-on needs to have {{chromeUrl}} chrome://my-plugin/content/SeLiteExtensionSequencerManifest.js (in UTF-8) containing something like:

"use strict";
SeLiteExtensionSequencer.registerPlugin( {
    id: 'my-unique@plugin-id',
    infoURL: 'https://addons.mozilla.org/en-US/firefox/addon/dummy-test-journey/',
    downloadURL: 'https://addons.mozilla.org/en-US/firefox/addon/dummy-test-journey/vesions/' // optional; if not set and infoURL is at addons.mozilla.org, then downloadURL is auto-generated by appending 'versions/'
    coreURL: 'chrome://my-plugin/content/extensions/core-extension.js', // optional; it may be an array
    ideURL: 'chrome://my-plugin/content/extensons/ide-extension.js', // optional; it may be an array
    requisitePlugins: { // optional. These are *direct* dependencies only. E.g.:
        '[email protected]': {
            name: 'SeLite Miscellaneous',
            infoURL: 'https://addons.mozilla.org/en-US/firefox/addon/dummy-test-train/',
            downloadURL: 'https://addons.mozilla.org/en-US/firefox/addon/dummy-test-train/versions/', // optional, see above
            compatibleVersion: "0.10", // optional, the minimal value of oldestCompatibleVersion that this requisite add-on must have
            minVersion: "0.10" // optional; the minimal version that this requisite add-on must have
        },
        ...
    },
    nonSequencedRequisitePlugins: { // optional. This is for *direct* dependencies that don't use Extension Sequencer.
        'someOtherPlugin@domain': {
            // structure like above
        }
        ...
    },
    oldestCompatibleVersion: "0.22", // the oldest version of this add-on that this version (the one being registered) is compatible with. Optional. If present, then it's compared to 'compatibleVersion' in manifests of any add-ons that depend on this add-on.
    preActivate: function(api) { // optional
        ....
    }
} );

Sequencer will find and process this file. Then it will initiate the plugin after all its sequenced dependencies. If it depends on any add-ons that don't use ExtensionSequencer, then this doesn't guarantee their respective activation order. You can use window.setTimeout() in your extension to delay the parts of its activation that depend on any non-sequenced add-ons. Alternatively, encourage the third party to use ExtensionSequencer, too.

Examples of SeLiteExtensionSequencerManifest.js

See SeLiteExtensionSequencerManifest.js files in source of various SeLite Components. For full API, see function SeLiteExtensionSequencer.registerPlugin(prototype) in SeLiteExtensionSequencer.js.

You may have multiple add-ons that override same parts of Selenium core and that don't need each other, but if used together then they need to override those parts in a specific order. Then you may want to declare optional dependency between them, so that they are loaded in appropriate order. For examples of dependencies, see SeLiteExtensionSequencerManifest.js and source of Shell tests.

Validate that SeLite Extension Sequencer is present (optional)

If your Firefox add-on has SeLiteExtensionSequencerManifest.js but Extension Sequencer is missing, your add-on won't get activated. Users may then be confused by the lack of functionality. It's worth to make it check whether Extension Sequencer is present. If not, then it should alert the users that they need to install Extension Sequencer.

See browser.js of SeLite DB Objects as an example.

Shell tests

ExtensionSequencer is tested against a list of variations of SeLiteExtensionSequencerManifest.js in several plain extensions. You can browse their source code at extension-sequencer/shell-tests. See a list of those tests with test descriptions and expected outputs.

Installing shell tests

The tests use a separate Firefox profile called SeLiteExtensionSequencerTest, with some extra extensions. To set up that profile and its add-ons, download source of whole SeLite as per InstallFromSource. Then run extension-sequencer\setup_proxies.bat or extension-sequencer/setup_proxies.sh. It will start Firefox and create and set up that profile.

On Windows (and probably on Mac OS, too) you'll need to install apply Windows/Mac OS-specific steps from InstallFromSource.

(You don't need any other SeLite Components for these tests.)

Running and modifying shell tests

Invoke run_tests.ps1 (on Windows), run_tests_mac.sh (on Mac OS) or run_tests.sh.

When modifying or re-using those tests, follow the existing special format of their SeLiteExtensionSequencerManifest.js files. For oldestCompatibleVersion, minVersion and compatibleVersion, enclose the version numbers within quotes "..". Otherwise they do not get compared well when the versions end with 0's. Have any commas separating the fields at the beginning of lines rather than at the end, and have preActivate entry (including the whole function) on one line only. That enables run_tests.ps1, run_tests_mac.sh and run_tests.sh to comment or uncomment those lines.

To debug Extension Sequencer itself with Firefox Browser Toolbox, visit {{chromeUrl}} chrome://selite-extension-sequencer/content/extensions/invoke.xul.

Core extensions loaded twice

Because of ThirdPartyIssues > Core extensions are loaded 2x), [Core extensions][core extension] get loaded 2x (whether loaded via Selenium IE menu > Options > Options... > Core extension, from an .xpi file or through a proxy file - regardless of ExtensionSequencer, but not when loaded via Bootstrap). That's OK if the extension just adds new Selenese commands. But it can be a problem if it tail/head intercepts Selenese or Selenium Core. You don't want to intercept Selenese or Selenium Core twice.

In extensions loaded via ExtensionSequencer you can use SeLiteExtensionSequencer.coreExtensionsLoadedTimes to keep track of whether the extension is loaded for the first time or the second one. See {{chromeUrl}} chrome://selite-extension-sequencer/content/SeLiteExtensionSequencer.js or online.

Global symbols and strict mode

That Selenium issue also causes problems when adding new global symbols to [Core scope] and using {{navStrictJavascript}}. When Selenium IDE loads a [Core extension] for the first and second time, it uses different [Core scope] and a different Selenium class! If the extension in strict mode defines any global symbols, they are thrown away after the first load: only the second load stays in Selenium scope.

That may tempt you not to use strict mode and set the global symbols as undeclared (without var statement). This would create them in Selenium global scope (bypassing the local scope, which will be thrown away - see MDN mozIJSSubScriptLoader.loadSubScript). However, there's a way to do it in strict mode. When setting the new global symbols, also set them as fields on an existing object accessible from global scope (e.g. a class constructor). At the beginning of your extension, check whether those fields are set on that (existing) object; if not, then set them there and also in the global scope, otherwise retrieve them from that global object and set them in the global scope. See se-testcase-debug-context.js.