Skip to content

Instantly share code, notes, and snippets.

@joelklabo
Created December 1, 2010 07:31
Show Gist options
  • Save joelklabo/723114 to your computer and use it in GitHub Desktop.
Save joelklabo/723114 to your computer and use it in GitHub Desktop.
// init() (FlexPilot.as)
public class FlexPilot extends Sprite {
public static var config:Object = {
context: null, // Ref to the Stage or Application
timeout: 20000, // Default timeout for waits
domains: [],
strictLocators: false
};
public static var controllerMethods:Array = [];
public static var assertMethods:Array = [];
public static var packages:Object = {
controller: {
// Ref to the namespace, since you can't
// do it via string lookup
packageRef: org.flex_pilot.FPController,
// Gets filled with the list of public methods --
// used to generate the wrapped methods exposed
// via ExternalInterface
methodNames: []
},
assert: {
packageRef: org.flex_pilot.FPAssert,
methodNames: []
}
};
// Initializes the FlexPilot Flash code
// 1. Saves a reference to the stage in config.context
// this is the equivalent of the window obj in
// FlexPilot's JS impl. See FPLocator to see how
// it's used
// 2. Does some introspection/metaprogramming to
// expose all the public methods in FPController
// and FPAsserts through the ExternalInterface
// as wrapped functions that return either the
// Boolean true, or the Error object if an error
// happens (as in the case of all failed tests)
// 3. Exposes the start/stop method of FPExplorer
// to turn on and off the explorer
public static function init(params:Object):void {
var methodName:String;
var item:*;
var descr:XML;
var sparkApplication:*;
trace("Youre in the init function");
// A reference to the Stage
// ----------------
if (!(params.context is Stage || params.context is Application)) {
// See if we have a flex 4 app
try {
sparkApplication = ApplicationDomain.currentDomain.getDefinition('spark.components.Application');
if (!params.context is sparkApplication) {
throw new Error('spark match failed');
}
}
catch (e:Error) {
throw new Error('FlexPilot.config.context must be a reference to the Application or Stage.');
}
}
config.context = params.context;
// Allow script access to talk to the FlexPilot API
// via ExternalInterface from the following domains
if ('domains' in params) {
var domainsArr:Array = params.domain is Array ?
params.domains : [params.domains];
config.domains = domainsArr;
for each (var d:String in config.domains) {
FlexPilot.addDomain(d);
}
}
// Set up the locator map
// ========
FPLocator.init();
// Create dynamic asserts
// ========
FPAssert.init();
// Returns a wrapped version of the method that returns
// the Error obj to JS-land instead of actually throwing
var genExtFunc:Function = function (func:Function):Function {
return function (...args):* {
try {
for (var arg:* in args){
// Terrible hack working around half baked
// ExternalInterface support provided by
// Safari on Windows
// takes json strings and turns them into objects
if (args[arg] is String){
var o : Object = JSON.decode(args[arg]);
args[arg] = o;
}
}
return func.apply(null, args);
}
catch (e:Error) {
if (e.errorID == 1009){
e.message = "There was a problem with your parameters, " + JSON.encode(args);
}
return e;
}
}
}
// Expose controller and non-dynamic assert methods
// ----------------
for (var key:String in packages) {
// Introspect all the public packages
// to expose via ExternalInterface
descr = flash.utils.describeType(
packages[key].packageRef);
for each (item in descr..method) {
packages[key].methodNames.push([email protected]());
}
// Expose public packages via ExternalInterface
// 'dragDropOnCoords' becomes 'fp_dragDropOnCoords'
// The exposed method is wrapped in a try/catch
// that returns the Error obj to JS instead of throwing
for each (methodName in packages[key].methodNames) {
ExternalInterface.addCallback('fp_' + methodName,
genExtFunc(packages[key].packageRef[methodName]));
}
}
// Expose dynamic asserts
// ----------------
// These *will not*
// show up via introspection with describeType, but
// they *are there* -- add them manually by iterating
// through the same list that used to build them
var asserts:* = FPAssert;
for (methodName in asserts.assertTemplates) {
ExternalInterface.addCallback('fp_' + methodName,
genExtFunc(asserts[methodName]));
}
// Other misc ExternalInterface methods
// ----------------
var miscMethods:Object = {
explorerStart: FPExplorer.start,
explorerStop: FPExplorer.stop,
recorderStart: FPRecorder.start,
recorderStop: FPRecorder.stop,
runASTests: ASTest.run,
lookupFlash: FPLocator.lookupDisplayObjectBool
}
for (methodName in miscMethods) {
ExternalInterface.addCallback('fp_' + methodName,
genExtFunc(miscMethods[methodName]));
}
// Wrap controller methods for AS tests to do auto-wait
// ========
ASTest.init();
}
// addDomain() (FlexPilot.as)
public static function addDomain(domain:String):void {
trace("Youre in the addDomain function");
flash.system.Security.allowDomain(domain);
}
// getStage (FlexPilot.as)
public static function getStage():Stage {
trace("Youre in the getStage function");
var context:* = config.context;
var stage:Stage;
if (contextIsApplication()) {
stage = context.stage;
}
else if (contextIsStage()) {
stage = context;
}
else {
throw new Error('FlexPilot.config.context must be a reference to an Application or Stage.' +
' Perhaps FlexPilot.init has not run yet.');
}
return stage;
}
// contextIsApplication (FlexPilot.as)
public static function contextIsApplication():Boolean {
trace("Youre in the contextIsApplication function");
//In flex 4 you want to try the sparks.components.Application
var retVal:Boolean;
try {
var sparkApplication:*= ApplicationDomain.currentDomain.getDefinition('spark.components.Application')
retVal = ( config.context is Application || config.context is sparkApplication );
}
catch(e:Error) {
retVal = (config.context is Application);
}
return retVal;
}
// contextIsStage (FlexPilot.as)
public static function contextIsStage():Boolean {
trace("Youre in the contextIsStage function");
return (config.context is Stage);
}
// generateLocator (FPLocator.as)
// Generates a chained-locator expression for the clicked-on item
public static function generateLocator(item:*, ...args):String {
trace("Youre in the generateLocator function");
var strictLocators:Boolean = FlexPilot.config.strictLocators;
if (args.length) {
strictLocators = args[0];
}
var expr:String = '';
var exprArr:Array = [];
var attr:String;
var attrVal:String;
// Verifies the property exists, and that the child can
// be found from the parent (in some cases there is a parent
// which does not have the item in its list of children)
var weHaveAWinner:Function = function (item:*, attr:String):Boolean {
var winner:Boolean = false;
// Get an attribute that actually has a value
if (usableAttr(item, attr)) {
//If its a raw child container
if (par is IRawChildrenContainer) {
if (par.rawChildren.contains(item)){
winner = true;
}
}
else {
// Make sure that the parent can actually see
// this item in its list of children
var par:* = item.parent;
var count:int = 0;
if (par is DisplayObjectContainer) {
count = par.numChildren;
}
if (count > 0) {
var index:int = 0;
while (index < count) {
var kid:DisplayObject;
kid = par.getChildAt(index);
if (kid == item) {
winner = true;
break;
}
index++;
}
}
}
}
return winner;
};
var usableAttr:Function = function (item:*, attr:String):Boolean {
// Item has to have an attribute of that name
if (!(attr in item)) {
return false;
}
// Attribute's value cannot be null
if (!item[attr]) {
return false;
}
// If strict locators are on, don't accept an auto-generated
// 'name' attribute ending in a number -- e.g., TextField05
// These are often unreliable as locators
if (strictLocators &&
attr == 'name' && /\d+$/.test(item[attr])) {
return false;
}
return true;
};
var isValidLookup:Function = function (exprArr:Array):Boolean {
expr = exprArr.join('/');
// Make sure that the expression actually looks up a
// valid object
var validLookup:DisplayObject = lookupDisplayObject({
chain: expr
});
return !!validLookup;
};
// Attrs to look for, ordered by priority
var locatorPriority:Array = FPLocator.locatorLookupPriority;
do {
// Try looking up a value for each attribute in order
// of preference
for each (attr in locatorPriority) {
// If we find one of the lookup keys, we may have a winner
if (weHaveAWinner(item, attr)) {
// Prepend onto the locator expression, then check to
// see if the chain still results in a valid lookup
attrVal = attr == 'htmlText' ?
FPLocator.cleanHTML(item[attr]) : item[attr];
exprArr.unshift(attr + ':' + attrVal);
// If this chain looks up an object correct, keeps going
if (isValidLookup(exprArr)) {
break;
}
// Otherwise throw out this attr/value pair and keep
// trying
else {
exprArr.shift();
}
}
}
item = item.parent;
} while (item.parent && !(item.parent == FlexPilot.getContext() ||
item.parent == FlexPilot.getStage()))
if (exprArr.length) {
expr = exprArr.join('/');
return expr;
}
else {
return null;
}
}
// lookupDisplayObject (FPLocator.as)
public static function lookupDisplayObject(
params:Object):DisplayObject {
trace("Youre in the lookupDisplayObject function");
var res:DisplayObject;
res = lookupDisplayObjectForContext(params, FlexPilot.getContext());
if (!res && FlexPilot.contextIsApplication()) {
res = lookupDisplayObjectForContext(params, FlexPilot.getStage());
}
return res;
}
// getContext (FlexPilot.as)
public static function getContext():* {
trace("Youre in the getContext function");
return config.context;
}
// lookupDisplayObjectForContext (FPLocator.as)
public static function lookupDisplayObjectForContext(
params:Object, obj:*):DisplayObject {
trace("Youre in the lookupDisplayObjectForContext function");
var locators:Array = [];
var queue:Array = [];
var checkFPLocatorChain:Function = function (
item:*, pos:int):DisplayObject {
var map:Object = FPLocator.locatorMapObj;
var loc:Object = locators[pos];
// If nothing specific exists for that attr, use the basic one
var finder:Function = map[loc.attr] || FPLocator.findBySimpleAttr;
var next:int = pos + 1;
if (!!finder(item, loc.attr, loc.val)) {
// Move to the next locator in the chain
// If it's the end of the chain, we have a winner
if (next == locators.length) {
return item;
}
// Otherwise recursively check the next link in
// the locator chain
var count:int = 0;
if (item is DisplayObjectContainer) {
if (item is IRawChildrenContainer) {
count = item.rawChildren.numChildren;
}
else {
count = item.numChildren;
}
}
if (count > 0) {
var index:int = 0;
while (index < count) {
var kid:DisplayObject;
if (item is IRawChildrenContainer) {
kid = item.rawChildren.getChildAt(index);
}
else {
kid = item.getChildAt(index);
}
var res:DisplayObject = checkFPLocatorChain(kid, next);
if (res) {
return res;
}
index++;
}
}
}
return null;
};
var str:String = normalizeFPLocator(params);
locators = parseFPLocatorChainExpresson(str);
queue.push(obj);
while (queue.length) {
// Otherwise grab the next item in the queue
var item:* = queue.shift();
// Append any kids to the end of the queue
if (item is DisplayObjectContainer) {
var count:int = 0;
if (item is IRawChildrenContainer) {
count = item.rawChildren.numChildren;
}
else {
count = item.numChildren;
}
var index:int = 0;
while (index < count) {
var kid:DisplayObject
if (item is IRawChildrenContainer) {
kid = item.rawChildren.getChildAt(index);
}
else {
kid = item.getChildAt(index);
}
queue.push(kid);
index++;
}
}
var res:DisplayObject = checkFPLocatorChain(item, 0);
// If this is a full match, we're done
if (res) {
return res;
}
}
return null;
}
// normalizeFPLocator (FPLocator.as)
private static function normalizeFPLocator(params:Object):String {
trace("Youre in the normalizeFPLocator function");
if ('chain' in params) {
return params.chain;
}
else {
var map:Object = FPLocator.locatorMap;
var attr:String;
var val:*;
// FPLocators have an order of precedence -- ComboBox will
// have a name/id, and its sub-options will have label
// Make sure to do name-/id-based lookups first, label last
for each (var item:Array in map) {
if (item[0] in params) {
attr = item[0];
val = params[attr];
break;
}
}
return attr + ':' + val;
}
}
// findBySimpleAttr (FPLocator.as)
// Default locator for all basic key/val attr matches
private static function findBySimpleAttr(
obj:*, attr:String, val:*):Boolean {
trace("Youre in the findBySimpleAttr function");
//check to see if a childIndex is specified
var childIndexReg:RegExp = new RegExp("\\[.*\\]", "g");
var childIndex:String = childIndexReg.exec(val);
//do we have an child index constraint
if (childIndex != null) {
//remove it so it doesn't mess up further matching
val = val.replace(childIndexReg, "");
childIndex = childIndex.replace("[","");
childIndex = childIndex.replace("]","");
var childIndexInt:int = Number(childIndex);
//If this node matches the provided child index
var par:* = obj.parent;
if (par is DisplayObjectContainer) {
var realObjIndex:int;
if (par is IRawChildrenContainer) {
realObjIndex = par.rawChildren.getChildIndex(obj);
}
else {
realObjIndex = par.getChildIndex(obj);
}
//if we were given a childIndex, make sure this passes that req
if (childIndexInt != realObjIndex) {
return false;
}
else if (val == ""){
return true;
}
}
}
//if we receive a simple attr with an asterix
//we create a regex allowing for wildcard strings
if (val.indexOf("*") != -1) {
if (attr in obj) {
//replace wildcards with any character match
var valRegExp:String = val.replace(new RegExp("\\*", "g"), "(.*)");
//force a beginning and end
valRegExp = "^"+valRegExp +"$";
var wildcard:RegExp = new RegExp(valRegExp);
var result:Object = wildcard.exec(obj[attr]);
return !!(result != null);
}
}
return !!(attr in obj && obj[attr] == val);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment