Created
December 1, 2010 07:31
-
-
Save joelklabo/723114 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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