From f588037d82554aa6e85e8f509208fe6a4559c39f Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 18 Dec 2014 17:48:03 -0500 Subject: [PATCH 01/47] broke up the try test into individual cases --- sel-blocksTests/_SelBlocks-TestSuite.html | 23 ++- ...- bubble out of nested function calls.html | 181 ++++++++++++++++++ ...t of noop try to catch specific error.html | 131 +++++++++++++ ... - bubble up to catch ALL via finally.html | 136 +++++++++++++ .../try - bubble up to catch ALL.html | 126 ++++++++++++ ...p to catch specific error via finally.html | 141 ++++++++++++++ ...y - bubble up to catch specific error.html | 131 +++++++++++++ sel-blocksTests/try - catch but no error.html | 79 ++++++++ ...y - catch specific error then finally.html | 136 +++++++++++++ .../try - catch specific error.html | 84 ++++++++ .../try - catch throw finally throw.html | 156 +++++++++++++++ ...ommand bubbling continue with finally.html | 119 ++++++++++++ ...d bubbling error replaced by return 2.html | 129 +++++++++++++ ...and bubbling error replaced by return.html | 129 +++++++++++++ ...ry - command bubbling intra try break.html | 119 ++++++++++++ ...- command bubbling intra try continue.html | 119 ++++++++++++ ...and bubbling return replaced by error.html | 156 +++++++++++++++ ... command bubbling, break with finally.html | 119 ++++++++++++ ...ry - exitTest with finally processing.html | 89 +++++++++ .../try - finally with no error.html | 79 ++++++++ sel-blocksTests/try - noop.html | 69 +++++++ .../try - throw catch rethrow.html | 141 ++++++++++++++ .../try - try without matching catch.html | 89 +++++++++ 23 files changed, 2680 insertions(+), 1 deletion(-) create mode 100644 sel-blocksTests/try - bubble out of nested function calls.html create mode 100644 sel-blocksTests/try - bubble up out of noop try to catch specific error.html create mode 100644 sel-blocksTests/try - bubble up to catch ALL via finally.html create mode 100644 sel-blocksTests/try - bubble up to catch ALL.html create mode 100644 sel-blocksTests/try - bubble up to catch specific error via finally.html create mode 100644 sel-blocksTests/try - bubble up to catch specific error.html create mode 100644 sel-blocksTests/try - catch but no error.html create mode 100644 sel-blocksTests/try - catch specific error then finally.html create mode 100644 sel-blocksTests/try - catch specific error.html create mode 100644 sel-blocksTests/try - catch throw finally throw.html create mode 100644 sel-blocksTests/try - command bubbling continue with finally.html create mode 100644 sel-blocksTests/try - command bubbling error replaced by return 2.html create mode 100644 sel-blocksTests/try - command bubbling error replaced by return.html create mode 100644 sel-blocksTests/try - command bubbling intra try break.html create mode 100644 sel-blocksTests/try - command bubbling intra try continue.html create mode 100644 sel-blocksTests/try - command bubbling return replaced by error.html create mode 100644 sel-blocksTests/try - command bubbling, break with finally.html create mode 100644 sel-blocksTests/try - exitTest with finally processing.html create mode 100644 sel-blocksTests/try - finally with no error.html create mode 100644 sel-blocksTests/try - noop.html create mode 100644 sel-blocksTests/try - throw catch rethrow.html create mode 100644 sel-blocksTests/try - try without matching catch.html diff --git a/sel-blocksTests/_SelBlocks-TestSuite.html b/sel-blocksTests/_SelBlocks-TestSuite.html index 27faa8c..c860da9 100644 --- a/sel-blocksTests/_SelBlocks-TestSuite.html +++ b/sel-blocksTests/_SelBlocks-TestSuite.html @@ -12,7 +12,28 @@ eval branching if -try +try - noop +try - finally with no error +try - catch but no error +try - catch specific error +try - catch specific error then finally +try - catch throw finally throw +try - bubble up to catch ALL +try - bubble up to catch ALL via finally +try - bubble up out of noop try to catch specific error +try - bubble up to catch specific error +try - bubble up to catch specific error via finally +try - throw catch rethrow +try - bubble out of nested function calls +try - command bubbling, break with finally +try - command bubbling intra try break +try - command bubbling continue with finally +try - command bubbling intra try continue +try - command bubbling error replaced by return +try - command bubbling error replaced by return 2 +try - command bubbling return replaced by error +try - exitTest with finally processing +try - try without matching catch while for foreach diff --git a/sel-blocksTests/try - bubble out of nested function calls.html b/sel-blocksTests/try - bubble out of nested function calls.html new file mode 100644 index 0000000..f11aa4e --- /dev/null +++ b/sel-blocksTests/try - bubble out of nested function calls.html @@ -0,0 +1,181 @@ + + + + + + +try - bubble out of nested function calls + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - bubble out of nested function calls
log"-- bubble out of nested function calls --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybub-invoke
emit"trying"
emit"calling"
callsubBubn = 0
assertEval"this command should not be reached, due error thrown in function"
catch/blamo/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTrybub-invoke
assertEvalselblocks.tcf.nestingLevel-1
functionsubBub
trytcf-inner
emit"${n}) trying-inner"
ifn < 2
emit"${n}) calling"
callsubBubn = n+1
else
emit"${n}) throwing"
throw"blamo"
endIf
finally
emit"${n}) finally"
endTrytcf-inner
assertEvalselblocks.tcf.nestingLevel0
endFunction
emit"/])"
assertEmitted"([/~trying~calling~0) trying-inner~0) calling~1) trying-inner~1) calling~2) trying-inner~2) throwing~2) finally~1) finally~0) finally~caught 'blamo'~finally~/])"
+ + diff --git a/sel-blocksTests/try - bubble up out of noop try to catch specific error.html b/sel-blocksTests/try - bubble up out of noop try to catch specific error.html new file mode 100644 index 0000000..aee57d7 --- /dev/null +++ b/sel-blocksTests/try - bubble up out of noop try to catch specific error.html @@ -0,0 +1,131 @@ + + + + + + +try - bubble up out of noop try to catch specific error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - bubble up out of noop try to catch specific error
log"-- bubble out of no-op try to catch specific error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trynoop-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
tryop-inner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing inner"
throw"blamo"
assertEval"this command should not be reached, due to throw"
endTryop-inner
catch/blamo/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTrynoop-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing inner~caught 'blamo'~finally~/])"
+ + diff --git a/sel-blocksTests/try - bubble up to catch ALL via finally.html b/sel-blocksTests/try - bubble up to catch ALL via finally.html new file mode 100644 index 0000000..bb14827 --- /dev/null +++ b/sel-blocksTests/try - bubble up to catch ALL via finally.html @@ -0,0 +1,136 @@ + + + + + + +try - bubble up to catch ALL via finally + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - bubble up to catch ALL via finally
log"-- bubble up to catch ALL via finally --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-allfin-outer
emit"trying-outer"
assertEvalselblocks.tcf.nestingLevel0
tryt-allfin-inner
emit"trying"
throw"blamo try-catch-finally"
assertEval"this command should not be reached, due to throw"
catch/will NOT catch it/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTryt-allfin-inner
assertEvalselblocks.tcf.nestingLevel0
catch
emit"caught-outer '" + _error.message + "'"
endTryt-allfin-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying-outer~trying~finally~caught-outer 'blamo try-catch-finally'~/])"
+ + diff --git a/sel-blocksTests/try - bubble up to catch ALL.html b/sel-blocksTests/try - bubble up to catch ALL.html new file mode 100644 index 0000000..4a92a44 --- /dev/null +++ b/sel-blocksTests/try - bubble up to catch ALL.html @@ -0,0 +1,126 @@ + + + + + + +try - bubble up to catch ALL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - bubble up to catch ALL
log"-- bubble up to catch ALL --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-all-outer
emit"trying-outer"
assertEvalselblocks.tcf.nestingLevel0
tryt-all-inner
emit"trying"
throw"blamo try-catch-finally"
assertEval"this command should not be reached, due to throw"
catch/will NOT catch it/
emit"caught '" + _error.message + "'"
endTryt-all-inner
assertEvalselblocks.tcf.nestingLevel0
catch
emit"caught-outer '" + _error.message + "'"
endTryt-all-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying-outer~trying~caught-outer 'blamo try-catch-finally'~/])"
+ + diff --git a/sel-blocksTests/try - bubble up to catch specific error via finally.html b/sel-blocksTests/try - bubble up to catch specific error via finally.html new file mode 100644 index 0000000..ce43952 --- /dev/null +++ b/sel-blocksTests/try - bubble up to catch specific error via finally.html @@ -0,0 +1,141 @@ + + + + + + +try - bubble up to catch specific error via finally + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - bubble up to catch specific error via finally
log"-- bubble up to catch specific error via finally --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybub-spec-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
trybub-spec-inner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing inner"
throw"blamo"
assertEval"this command should not be reached, due to throw"
finally
emit"finally"
endTrybub-spec-inner
catch/blamo/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTrybub-spec-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing inner~finally~caught 'blamo'~finally~/])"
+ + diff --git a/sel-blocksTests/try - bubble up to catch specific error.html b/sel-blocksTests/try - bubble up to catch specific error.html new file mode 100644 index 0000000..a7af661 --- /dev/null +++ b/sel-blocksTests/try - bubble up to catch specific error.html @@ -0,0 +1,131 @@ + + + + + + +try - bubble up to catch specific error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - bubble up to catch specific error
log"-- bubble up to catch specific error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybub-spec-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
trybub-spec-inner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing inner"
throw"blamo"
assertEval"this command should not be reached, due to throw"
finally
emit"finally"
endTrybub-spec-inner
catch/blamo/
emit"caught '" + _error.message + "'"
endTrybub-spec-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing inner~finally~caught 'blamo'~/])"
+ + diff --git a/sel-blocksTests/try - catch but no error.html b/sel-blocksTests/try - catch but no error.html new file mode 100644 index 0000000..86c37a3 --- /dev/null +++ b/sel-blocksTests/try - catch but no error.html @@ -0,0 +1,79 @@ + + + + + + +try - catch but no error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - catch but no error
log"-- try/catch, but no error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trytc0
emit"trying"
catch
assertEval"should NEVER enter this catch block due to no error"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~/])"
+ + diff --git a/sel-blocksTests/try - catch specific error then finally.html b/sel-blocksTests/try - catch specific error then finally.html new file mode 100644 index 0000000..bc7c505 --- /dev/null +++ b/sel-blocksTests/try - catch specific error then finally.html @@ -0,0 +1,136 @@ + + + + + + +try - catch specific error then finally + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - catch specific error then finally
log"-- catch specific error, then finally --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-spec-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel-1
emit"([/"
tryt-spec-inner
emit"trying inner"
throw"blamo inner"
assertEval"this command should not be reached, due to throw"
catch/blamo inner/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTryt-spec-inner
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
endTryt-spec-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~([/~trying inner~caught 'blamo inner'~finally~/])~/])"
+ + diff --git a/sel-blocksTests/try - catch specific error.html b/sel-blocksTests/try - catch specific error.html new file mode 100644 index 0000000..ce15200 --- /dev/null +++ b/sel-blocksTests/try - catch specific error.html @@ -0,0 +1,84 @@ + + + + + + +try - catch specific error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - catch specific error
log"-- catch specific error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trytc1
throw"blamo catch ALL"
assertEval"this command should not be reached, due to thrown"
catch
emit"caught '" + _error.message + "'"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~caught 'blamo catch ALL'~/])"
+ + diff --git a/sel-blocksTests/try - catch throw finally throw.html b/sel-blocksTests/try - catch throw finally throw.html new file mode 100644 index 0000000..cb152ec --- /dev/null +++ b/sel-blocksTests/try - catch throw finally throw.html @@ -0,0 +1,156 @@ + + + + + + +try - catch throw finally throw + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - catch throw finally throw
log"-- catch/throw, finally/throw --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trytcf-trap
assertEvalselblocks.tcf.nestingLevel0
trytcf
emit"trying"
emit"throwing A"
throw"blamoA"
assertEval"this command should not be reached, due to thrown"
catch/blamoA/
emit"caught '" + _error.message + "'"
emit"throwing B"
throw"blamoB"
finally
emit"finally"
emit"throwing C"
throw"blamoC"
endTry
assertEvalselblocks.tcf.nestingLevel0
catch
emit"caught '" + _error.message + "'"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~throwing A~caught 'blamoA'~throwing B~finally~throwing C~caught 'blamoC'~/])"
+ + diff --git a/sel-blocksTests/try - command bubbling continue with finally.html b/sel-blocksTests/try - command bubbling continue with finally.html new file mode 100644 index 0000000..1841938 --- /dev/null +++ b/sel-blocksTests/try - command bubbling continue with finally.html @@ -0,0 +1,119 @@ + + + + + + +try - command bubbling continue with finally + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - command bubbling continue with finally
log"-- command bubbling, continue w/finally --"
resetEmitted
emit"([/"
emit"while'g"
forw=3; w > 0; w--
assertEvalselblocks.tcf.nestingLevel-1
trycontinuer
emit"${w}) trying"
ifw==2
emit"${w}) CONTINUE"
continue
endIf
finally
emit"${w}) finallying"
endTrycontinuer
assertEvalselblocks.tcf.nestingLevel-1
emit"${w}) iterating"
endFor
emit"/])"
assertEmitted"([/~while'g~3) trying~3) finallying~3) iterating~2) trying~2) CONTINUE~2) finallying~1) trying~1) finallying~1) iterating~/])"
+ + diff --git a/sel-blocksTests/try - command bubbling error replaced by return 2.html b/sel-blocksTests/try - command bubbling error replaced by return 2.html new file mode 100644 index 0000000..cf8f5f3 --- /dev/null +++ b/sel-blocksTests/try - command bubbling error replaced by return 2.html @@ -0,0 +1,129 @@ + + + + + + +try - command bubbling error replaced by return 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - command bubbling error replaced by return 2
log"-- command bubbling, error replaced by return --"
resetEmitted
emit"([/"
emit"calling"
callcmdBubB
emit"returned ${_result}"
functioncmdBubB
emit"in cmdBubB"
assertEvalselblocks.tcf.nestingLevel-1
trycmdBub-tcf
emit"trying"
emit"throwing"
throw"short-lived error"
finally
emit"finallying"
emit"returning (2)"
return2
endTry
assertEval"this command should not be reached, due to throw/return above"
endFunction
emit"/])"
assertEmitted"([/~calling~in cmdBubB~trying~throwing~finallying~returning (2)~returned 2~/])"
+ + diff --git a/sel-blocksTests/try - command bubbling error replaced by return.html b/sel-blocksTests/try - command bubbling error replaced by return.html new file mode 100644 index 0000000..6d1428a --- /dev/null +++ b/sel-blocksTests/try - command bubbling error replaced by return.html @@ -0,0 +1,129 @@ + + + + + + +try - command bubbling error replaced by return + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - command bubbling error replaced by return
log"-- command bubbling, error replaced by return --"
resetEmitted
emit"([/"
emit"calling"
callcmdBubA
emit"returned ${_result}"
functioncmdBubA
emit"in cmdBubA"
assertEvalselblocks.tcf.nestingLevel-1
trycmdBub-tcf
emit"trying"
emit"returning (1)"
return1
finally
emit"finallying"
emit"returning (2)"
return2
endTry
assertEval"this command should not be reached, due to return/return above"
endFunction
emit"/])"
assertEmitted"([/~calling~in cmdBubA~trying~returning (1)~finallying~returning (2)~returned 2~/])"
+ + diff --git a/sel-blocksTests/try - command bubbling intra try break.html b/sel-blocksTests/try - command bubbling intra try break.html new file mode 100644 index 0000000..fa9d2fa --- /dev/null +++ b/sel-blocksTests/try - command bubbling intra try break.html @@ -0,0 +1,119 @@ + + + + + + +try - command bubbling intra try break + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - command bubbling intra try break
log"-- command bubbling, intra-try break --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybreaker
emit"trying"
emit"while'g"
forw=3; w > 0; w--
ifw==2
emit"${w}) BREAK"
break
endIf
emit"${w}) iterating"
endFor
finally
emit"finallying"
endTrybreaker
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~while'g~3) iterating~2) BREAK~finallying~/])"
+ + diff --git a/sel-blocksTests/try - command bubbling intra try continue.html b/sel-blocksTests/try - command bubbling intra try continue.html new file mode 100644 index 0000000..b135e67 --- /dev/null +++ b/sel-blocksTests/try - command bubbling intra try continue.html @@ -0,0 +1,119 @@ + + + + + + +try - command bubbling intra try continue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - command bubbling intra try continue
log"-- command bubbling, intra-try continue --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trycontinuer
emit"trying"
emit"while'g"
forw=3; w > 0; w--
ifw==2
emit"${w}) CONTINUE"
continue
endIf
emit"${w}) iterating"
endFor
finally
emit"finallying"
endTrycontinuer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~while'g~3) iterating~2) CONTINUE~1) iterating~finallying~/])"
+ + diff --git a/sel-blocksTests/try - command bubbling return replaced by error.html b/sel-blocksTests/try - command bubbling return replaced by error.html new file mode 100644 index 0000000..165cd6a --- /dev/null +++ b/sel-blocksTests/try - command bubbling return replaced by error.html @@ -0,0 +1,156 @@ + + + + + + +try - command bubbling return replaced by error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - command bubbling return replaced by error
log"-- command bubbling, return replaced by error --"
resetEmitted
emit"([/"
emit"calling"
callcmdBubC
emit"returned ${_result}"
functioncmdBubC
emit"in cmdBubC"
assertEvalselblocks.tcf.nestingLevel-1
trycmdBub-tcf-guardrail
assertEvalselblocks.tcf.nestingLevel0
trycmdBub-tcf
emit"trying"
emit"returning (1)"
return1
finally
emit"finallying"
emit"throwing"
throw"this-error-replaces-return-1"
endTry
catch/this-error-replaces-return-1/
emit"caught '" + _error.message + "'"
assertEvalselblocks.tcf.nestingLevel0
endTry
endFunction
emit"/])"
assertEmitted"([/~calling~in cmdBubC~trying~returning (1)~finallying~throwing~caught 'this-error-replaces-return-1'~returned 2~/])"
+ + diff --git a/sel-blocksTests/try - command bubbling, break with finally.html b/sel-blocksTests/try - command bubbling, break with finally.html new file mode 100644 index 0000000..4bd08df --- /dev/null +++ b/sel-blocksTests/try - command bubbling, break with finally.html @@ -0,0 +1,119 @@ + + + + + + +try - command bubbling, break with finally + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - command bubbling, break with finally
log"-- command bubbling, break w/finally --"
resetEmitted
emit"([/"
emit"while'g"
forw=3; w > 0; w--
assertEvalselblocks.tcf.nestingLevel-1
trybreaker
emit"${w}) trying"
ifw==2
emit"${w}) BREAK"
break
endIf
finally
emit"${w}) finallying"
endTrybreaker
assertEvalselblocks.tcf.nestingLevel-1
emit"${w}) iterating"
endFor
emit"/])"
assertEmitted"([/~while'g~3) trying~3) finallying~3) iterating~2) trying~2) BREAK~2) finallying~/])"
+ + diff --git a/sel-blocksTests/try - exitTest with finally processing.html b/sel-blocksTests/try - exitTest with finally processing.html new file mode 100644 index 0000000..a7f0082 --- /dev/null +++ b/sel-blocksTests/try - exitTest with finally processing.html @@ -0,0 +1,89 @@ + + + + + + +try - exitTest with finally processing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - exitTest with finally processing
log"-- exitTest w/finally processing --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryabort
emit"trying"
emit"exitTest ..."
exitTest
finally
emit"finallying"
assertEmitted"([/~trying~exitTest ...~finallying"
resetEmitted
endTryabort
assertEval"this command should not be reached, due to exitTest above"
+ + diff --git a/sel-blocksTests/try - finally with no error.html b/sel-blocksTests/try - finally with no error.html new file mode 100644 index 0000000..ec93f97 --- /dev/null +++ b/sel-blocksTests/try - finally with no error.html @@ -0,0 +1,79 @@ + + + + + + +try - finally with no error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - finally with no error
log"-- try/finally, w/no error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-fin
emit"trying"
finally
emit"finally"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~finally~/])"
+ + diff --git a/sel-blocksTests/try - noop.html b/sel-blocksTests/try - noop.html new file mode 100644 index 0000000..a6cb6e9 --- /dev/null +++ b/sel-blocksTests/try - noop.html @@ -0,0 +1,69 @@ + + + + + + +try - noop + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - noop
log"-- no-op try --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-noop
emit"trying"
endTryt-noop
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~/])"
+ + diff --git a/sel-blocksTests/try - throw catch rethrow.html b/sel-blocksTests/try - throw catch rethrow.html new file mode 100644 index 0000000..3674a70 --- /dev/null +++ b/sel-blocksTests/try - throw catch rethrow.html @@ -0,0 +1,141 @@ + + + + + + +try - throw catch rethrow + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - throw catch rethrow
log"-- throw, catch, rethrow --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryouter
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
tryinner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing blamo1"
throw"blamo1"
assertEval"this command should not be reached, due to throw"
catch/blamo1/
emit"caught '" + _error.message + "'"
emit"throwing blamo2"
throw"blamo2"
endTryinner
catch/blamo2/
emit"caught '" + _error.message + "'"
endTryouter
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing blamo1~caught 'blamo1'~throwing blamo2~caught 'blamo2'~/])"
+ + diff --git a/sel-blocksTests/try - try without matching catch.html b/sel-blocksTests/try - try without matching catch.html new file mode 100644 index 0000000..d4edc36 --- /dev/null +++ b/sel-blocksTests/try - try without matching catch.html @@ -0,0 +1,89 @@ + + + + + + +try - try without matching catch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
try - try without matching catch
log"-- try without matching catch --"
resetEmitted
assertEvalselblocks.tcf.nestingLevel-1
trymiss
assertEvaltruetrue
throw"blamo will NOT be caught at all"
assertEval"this command should not be reached"
catch/will NOT catch it/
log"caught miss '" + _error.message + "'"
finally
log"finally"
endTrymiss
assertEvalselblocks.tcf.nestingLevel-1
emit"trying"
+ + From 479e3444206e1e4ee4bb4d1ce563246e6f36f44a Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 18 Dec 2014 17:58:40 -0500 Subject: [PATCH 02/47] fixed typo in file name, deleted original try test --- sel-blocksTests/_SelBlocks-TestSuite.html | 2 +- ... command bubbling break with finally.html} | 0 sel-blocksTests/try.html | 2323 ----------------- 3 files changed, 1 insertion(+), 2324 deletions(-) rename sel-blocksTests/{try - command bubbling, break with finally.html => try - command bubbling break with finally.html} (100%) delete mode 100644 sel-blocksTests/try.html diff --git a/sel-blocksTests/_SelBlocks-TestSuite.html b/sel-blocksTests/_SelBlocks-TestSuite.html index c860da9..5cdf498 100644 --- a/sel-blocksTests/_SelBlocks-TestSuite.html +++ b/sel-blocksTests/_SelBlocks-TestSuite.html @@ -25,7 +25,7 @@ try - bubble up to catch specific error via finally try - throw catch rethrow try - bubble out of nested function calls -try - command bubbling, break with finally +try - command bubbling break with finally try - command bubbling intra try break try - command bubbling continue with finally try - command bubbling intra try continue diff --git a/sel-blocksTests/try - command bubbling, break with finally.html b/sel-blocksTests/try - command bubbling break with finally.html similarity index 100% rename from sel-blocksTests/try - command bubbling, break with finally.html rename to sel-blocksTests/try - command bubbling break with finally.html diff --git a/sel-blocksTests/try.html b/sel-blocksTests/try.html deleted file mode 100644 index a4e16ee..0000000 --- a/sel-blocksTests/try.html +++ /dev/null @@ -1,2323 +0,0 @@ - - - - - - - -try - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
try
log"-- no-op try --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-noop
emit"trying"
endTryt-noop
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~/])"
log"-- try/finally, w/no error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-fin
emit"trying"
finally
emit"finally"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~finally~/])"
log"-- try/catch, but no error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trytc0
emit"trying"
catch
assertEval"should NEVER enter this catch block due to no error"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~/])"
log"-- catch specific error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trytc1
throw"blamo catch ALL"
assertEval"this command should not be reached, due to thrown"
catch
emit"caught '" + _error.message + "'"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~caught 'blamo catch ALL'~/])"
log"-- catch specific error, then finally --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-spec-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel-1
emit"([/"
tryt-spec-inner
emit"trying inner"
throw"blamo inner"
assertEval"this command should not be reached, due to throw"
catch/blamo inner/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTryt-spec-inner
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
endTryt-spec-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~([/~trying inner~caught 'blamo inner'~finally~/])~/])"
log"-- catch/throw, finally/throw --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trytcf-trap
assertEvalselblocks.tcf.nestingLevel0
trytcf
emit"trying"
emit"throwing A"
throw"blamoA"
assertEval"this command should not be reached, due to thrown"
catch/blamoA/
emit"caught '" + _error.message + "'"
emit"throwing B"
throw"blamoB"
finally
emit"finally"
emit"throwing C"
throw"blamoC"
endTry
assertEvalselblocks.tcf.nestingLevel0
catch
emit"caught '" + _error.message + "'"
endTry
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~throwing A~caught 'blamoA'~throwing B~finally~throwing C~caught 'blamoC'~/])"
log"-- bubble up to catch ALL --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-all-outer
emit"trying-outer"
assertEvalselblocks.tcf.nestingLevel0
tryt-all-inner
emit"trying"
throw"blamo try-catch-finally"
assertEval"this command should not be reached, due to throw"
catch/will NOT catch it/
emit"caught '" + _error.message + "'"
endTryt-all-inner
assertEvalselblocks.tcf.nestingLevel0
catch
emit"caught-outer '" + _error.message + "'"
endTryt-all-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying-outer~trying~caught-outer 'blamo try-catch-finally'~/])"
log"-- bubble up to catch ALL via finally --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryt-allfin-outer
emit"trying-outer"
assertEvalselblocks.tcf.nestingLevel0
tryt-allfin-inner
emit"trying"
throw"blamo try-catch-finally"
assertEval"this command should not be reached, due to throw"
catch/will NOT catch it/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTryt-allfin-inner
assertEvalselblocks.tcf.nestingLevel0
catch
emit"caught-outer '" + _error.message + "'"
endTryt-allfin-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying-outer~trying~finally~caught-outer 'blamo try-catch-finally'~/])"
log"-- bubble out of no-op try to catch specific error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trynoop-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
tryop-inner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing inner"
throw"blamo"
assertEval"this command should not be reached, due to throw"
endTryop-inner
catch/blamo/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTrynoop-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing inner~caught 'blamo'~finally~/])"
log"-- bubble up to catch specific error --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybub-spec-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
trybub-spec-inner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing inner"
throw"blamo"
assertEval"this command should not be reached, due to throw"
finally
emit"finally"
endTrybub-spec-inner
catch/blamo/
emit"caught '" + _error.message + "'"
endTrybub-spec-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing inner~finally~caught 'blamo'~/])"
log"-- bubble up to catch specific error via finally --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybub-spec-outer
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
trybub-spec-inner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing inner"
throw"blamo"
assertEval"this command should not be reached, due to throw"
finally
emit"finally"
endTrybub-spec-inner
catch/blamo/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTrybub-spec-outer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing inner~finally~caught 'blamo'~finally~/])"
log"-- throw, catch, rethrow --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryouter
emit"trying outer"
assertEvalselblocks.tcf.nestingLevel0
tryinner
emit"trying inner"
assertEvalselblocks.tcf.nestingLevel1
emit"throwing blamo1"
throw"blamo1"
assertEval"this command should not be reached, due to throw"
catch/blamo1/
emit"caught '" + _error.message + "'"
emit"throwing blamo2"
throw"blamo2"
endTryinner
catch/blamo2/
emit"caught '" + _error.message + "'"
endTryouter
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying outer~trying inner~throwing blamo1~caught 'blamo1'~throwing blamo2~caught 'blamo2'~/])"
log"-- bubble out of nested function calls --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybub-invoke
emit"trying"
emit"calling"
callsubBubn = 0
assertEval"this command should not be reached, due error thrown in function"
catch/blamo/
emit"caught '" + _error.message + "'"
finally
emit"finally"
endTrybub-invoke
assertEvalselblocks.tcf.nestingLevel-1
functionsubBub
trytcf-inner
emit"${n}) trying-inner"
ifn < 2
emit"${n}) calling"
callsubBubn = n+1
else
emit"${n}) throwing"
throw"blamo"
endIf
finally
emit"${n}) finally"
endTrytcf-inner
assertEvalselblocks.tcf.nestingLevel0
endFunction
emit"/])"
assertEmitted"([/~trying~calling~0) trying-inner~0) calling~1) trying-inner~1) calling~2) trying-inner~2) throwing~2) finally~1) finally~0) finally~caught 'blamo'~finally~/])"
log"-- command bubbling, break w/finally --"
resetEmitted
emit"([/"
emit"while'g"
forw=3; w > 0; w--
assertEvalselblocks.tcf.nestingLevel-1
trybreaker
emit"${w}) trying"
ifw==2
emit"${w}) BREAK"
break
endIf
finally
emit"${w}) finallying"
endTrybreaker
assertEvalselblocks.tcf.nestingLevel-1
emit"${w}) iterating"
endFor
emit"/])"
assertEmitted"([/~while'g~3) trying~3) finallying~3) iterating~2) trying~2) BREAK~2) finallying~/])"
log"-- command bubbling, intra-try break --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trybreaker
emit"trying"
emit"while'g"
forw=3; w > 0; w--
ifw==2
emit"${w}) BREAK"
break
endIf
emit"${w}) iterating"
endFor
finally
emit"finallying"
endTrybreaker
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~while'g~3) iterating~2) BREAK~finallying~/])"
log"-- command bubbling, continue w/finally --"
resetEmitted
emit"([/"
emit"while'g"
forw=3; w > 0; w--
assertEvalselblocks.tcf.nestingLevel-1
trycontinuer
emit"${w}) trying"
ifw==2
emit"${w}) CONTINUE"
continue
endIf
finally
emit"${w}) finallying"
endTrycontinuer
assertEvalselblocks.tcf.nestingLevel-1
emit"${w}) iterating"
endFor
emit"/])"
assertEmitted"([/~while'g~3) trying~3) finallying~3) iterating~2) trying~2) CONTINUE~2) finallying~1) trying~1) finallying~1) iterating~/])"
log"-- command bubbling, intra-try continue --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
trycontinuer
emit"trying"
emit"while'g"
forw=3; w > 0; w--
ifw==2
emit"${w}) CONTINUE"
continue
endIf
emit"${w}) iterating"
endFor
finally
emit"finallying"
endTrycontinuer
assertEvalselblocks.tcf.nestingLevel-1
emit"/])"
assertEmitted"([/~trying~while'g~3) iterating~2) CONTINUE~1) iterating~finallying~/])"
log"-- command bubbling, error replaced by return --"
resetEmitted
emit"([/"
emit"calling"
callcmdBubA
emit"returned ${_result}"
functioncmdBubA
emit"in cmdBubA"
assertEvalselblocks.tcf.nestingLevel-1
trycmdBub-tcf
emit"trying"
emit"returning (1)"
return1
finally
emit"finallying"
emit"returning (2)"
return2
endTry
assertEval"this command should not be reached, due to return/return above"
endFunction
emit"/])"
assertEmitted"([/~calling~in cmdBubA~trying~returning (1)~finallying~returning (2)~returned 2~/])"
log"-- command bubbling, error replaced by return --"
resetEmitted
emit"([/"
emit"calling"
callcmdBubB
emit"returned ${_result}"
functioncmdBubB
emit"in cmdBubB"
assertEvalselblocks.tcf.nestingLevel-1
trycmdBub-tcf
emit"trying"
emit"throwing"
throw"short-lived error"
finally
emit"finallying"
emit"returning (2)"
return2
endTry
assertEval"this command should not be reached, due to throw/return above"
endFunction
emit"/])"
assertEmitted"([/~calling~in cmdBubB~trying~throwing~finallying~returning (2)~returned 2~/])"
log"-- command bubbling, return replaced by error --"
resetEmitted
emit"([/"
emit"calling"
callcmdBubC
emit"returned ${_result}"
functioncmdBubC
emit"in cmdBubC"
assertEvalselblocks.tcf.nestingLevel-1
trycmdBub-tcf-guardrail
assertEvalselblocks.tcf.nestingLevel0
trycmdBub-tcf
emit"trying"
emit"returning (1)"
return1
finally
emit"finallying"
emit"throwing"
throw"this-error-replaces-return-1"
endTry
catch/this-error-replaces-return-1/
emit"caught '" + _error.message + "'"
assertEvalselblocks.tcf.nestingLevel0
endTry
endFunction
emit"/])"
assertEmitted"([/~calling~in cmdBubC~trying~returning (1)~finallying~throwing~caught 'this-error-replaces-return-1'~returned 2~/])"
log"-- exitTest w/finally processing --"
resetEmitted
emit"([/"
assertEvalselblocks.tcf.nestingLevel-1
tryabort
emit"trying"
emit"exitTest ..."
exitTest
finally
emit"finallying"
assertEmitted"([/~trying~exitTest ...~finallying"
resetEmitted
endTryabort
assertEval"this command should not be reached, due to exitTest above"
log"-- try without matching catch --"
resetEmitted
assertEvalselblocks.tcf.nestingLevel-1
trymiss
assertEvaltruetrue
throw"blamo will NOT be caught at all"
assertEval"this command should not be reached"
catch/will NOT catch it/
log"caught miss '" + _error.message + "'"
finally
log"finally"
endTrymiss
assertEvalselblocks.tcf.nestingLevel-1
emit"trying"
- - From 4779fa1bd7625edb77c004597ac099cbe19989e3 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Tue, 30 Dec 2014 23:21:58 -0500 Subject: [PATCH 03/47] Error: infinite loop calling func from another case An error is thrown when calling a function that hasn't been defined in the suite. If the function was defined in the same test case then it will run. If the function was defined in another test case, it looks like the index range is remembered but the commands are taken from the current test case. When those commands execute successfully, infinite recursion occurs. It looks like functions are almost global. They should be, it would be much more useful to define all the functions in one "case" that could be used like a library, then call those functions in cases that come after the library case(s) was/were loaded. --- sel-blocksTests/CallGlobalFunction.html | 41 ++++++++++++++ sel-blocksTests/CreateGlobalVars.html | 22 ++++++++ sel-blocksTests/GlobalFunctions.html | 56 +++++++++++++++++++ sel-blocksTests/GlobalFunctionsTestSuite.html | 16 ++++++ 4 files changed, 135 insertions(+) create mode 100644 sel-blocksTests/CallGlobalFunction.html create mode 100644 sel-blocksTests/CreateGlobalVars.html create mode 100644 sel-blocksTests/GlobalFunctions.html create mode 100644 sel-blocksTests/GlobalFunctionsTestSuite.html diff --git a/sel-blocksTests/CallGlobalFunction.html b/sel-blocksTests/CallGlobalFunction.html new file mode 100644 index 0000000..0410cb4 --- /dev/null +++ b/sel-blocksTests/CallGlobalFunction.html @@ -0,0 +1,41 @@ + + + + + + +CallGlobalFunction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CallGlobalFunction
getEvalglobalStoredVars.aGlobalVar = "reset";
assertNotEvalglobalStoredVars.aGlobalVar;set
assertEvalglobalStoredVars.aGlobalVar;reset
callsetGlobalVar
assertEvalglobalStoredVars.aGlobalVar;set
+ + diff --git a/sel-blocksTests/CreateGlobalVars.html b/sel-blocksTests/CreateGlobalVars.html new file mode 100644 index 0000000..942bab6 --- /dev/null +++ b/sel-blocksTests/CreateGlobalVars.html @@ -0,0 +1,22 @@ + + + + + + +CreateGlobalVars + + + + + + + + + + + + +
CreateGlobalVars
getEvalglobalStoredVars = {};
+ + diff --git a/sel-blocksTests/GlobalFunctions.html b/sel-blocksTests/GlobalFunctions.html new file mode 100644 index 0000000..d17e662 --- /dev/null +++ b/sel-blocksTests/GlobalFunctions.html @@ -0,0 +1,56 @@ + + + + + + +functions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
functions
functionsetGlobalVar
getEvalglobalStoredVars.aGlobalVar = "set";
endFunction
getEvalglobalStoredVars.aGlobalVar = "reset";
assertNotEvalglobalStoredVars.aGlobalVar;set
assertEvalglobalStoredVars.aGlobalVar;reset
callsetGlobalVar
assertEvalglobalStoredVars.aGlobalVar;set
+ + diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html new file mode 100644 index 0000000..e088f8e --- /dev/null +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -0,0 +1,16 @@ + + + + + + Test Suite + + + + + + + +
Test Suite
CreateGlobalVars
GlobalFunctions
CallGlobalFunction
+ + From 5c0815a3525918ecca65317ec548473964115b72 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Tue, 30 Dec 2014 23:33:03 -0500 Subject: [PATCH 04/47] removes unnecessary base url from test cases I forgot to turn off the option to remember base urls. --- sel-blocksTests/CallGlobalFunction.html | 1 - sel-blocksTests/CreateGlobalVars.html | 1 - sel-blocksTests/GlobalFunctions.html | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/sel-blocksTests/CallGlobalFunction.html b/sel-blocksTests/CallGlobalFunction.html index 0410cb4..a511df7 100644 --- a/sel-blocksTests/CallGlobalFunction.html +++ b/sel-blocksTests/CallGlobalFunction.html @@ -3,7 +3,6 @@ - CallGlobalFunction diff --git a/sel-blocksTests/CreateGlobalVars.html b/sel-blocksTests/CreateGlobalVars.html index 942bab6..969fed7 100644 --- a/sel-blocksTests/CreateGlobalVars.html +++ b/sel-blocksTests/CreateGlobalVars.html @@ -3,7 +3,6 @@ - CreateGlobalVars diff --git a/sel-blocksTests/GlobalFunctions.html b/sel-blocksTests/GlobalFunctions.html index d17e662..da38020 100644 --- a/sel-blocksTests/GlobalFunctions.html +++ b/sel-blocksTests/GlobalFunctions.html @@ -3,8 +3,7 @@ - -functions +GlobalFunctions From 1b2acc6631b2747f446c4a1ae566c3470e6855d9 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Wed, 31 Dec 2014 21:43:09 -0500 Subject: [PATCH 05/47] draft to add global functions This doesn't really work yet. It looks like it works sometimes but it's really not. --- .../chrome/content/extensions/selblocks.js | 730 +++++++++++++----- 1 file changed, 521 insertions(+), 209 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index f6ba739..71793e4 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -123,19 +123,18 @@ function $X(xpath, contextNode, resultType) { // Return a translated version of a string // given string args, translate each occurrence of characters in t1 with the corresponding character from t2 // given array args, if the string occurs in t1, return the corresponding string from t2, else null - String.prototype.translate = function(t1, t2) - { + String.prototype.translate = function (t1, t2) { assert(t1.constructor === t2.constructor, "translate() function requires arrays of the same type"); assert(t1.length === t2.length, "translate() function requires arrays of equal size"); var i; if (t1.constructor === String) { var buf = ""; for (i = 0; i < this.length; i++) { - var c = this.substr(i,1); + var c = this.substr(i, 1); var t; for (t = 0; t < t1.length; t++) { - if (c === t1.substr(t,1)) { - c = t2.substr(t,1); + if (c === t1.substr(t, 1)) { + c = t2.substr(t, 1); break; } } @@ -150,39 +149,225 @@ function $X(xpath, contextNode, resultType) { return t2[i]; } } - } - else { + } else { assert(false, "translate() function requires arguments of type String or Array"); } return null; }; + // ----- SelBlocksGlobal: + /** @param TestCase optional + * @return int 0-based index of given test case within the list of test cases + * of the test suite + **/ + function testCaseIdx(givenTestCase) { + var msg, + caseIndex; + givenTestCase = givenTestCase || testCase; + // Must not use assert() here, because that calls notifyFatalHere() which calls idxHere() + // which calls globIdx() which calls testCaseIdx() + if (typeof givenTestCase !== 'object') { + msg = "SelBlocks error: in testCaseIdx(), param givenTestCase is not an object, neither global testCase is."; + LOG.error(msg); + throw new Error(msg); + } + if (editor.app.testSuite.tests.length === 0) { + msg = "SelBlocks error: in testCaseIdx(), bad editor.app.testSuite.tests.length===0."; + LOG.error(msg); + throw new Error(msg); + } + for (caseIndex = editor.app.testSuite.tests.length - 1; caseIndex >= 0; caseIndex--) { + if (editor.app.testSuite.tests[caseIndex].content === givenTestCase) { + break; + } + } + if (caseIndex < 0) { + msg = "SelBlocks error: in testCaseIdx(), givenTestCase was not matched."; + LOG.error(msg); + throw new Error(msg); + } + return caseIndex; + } + + function logAndThrow(msg) { + var error = new Error(msg); + LOG.error(msg + "\n" + error.stack); + throw error; + } + /** This serves to generate unique global identifiers for test script commands. + * Results of this functions are usually values of symbols[] and other structures. + * @param {number} commandIndex 0-based index within givenTestCase (or within testCase). + * @param {TestCase} [givenTestCase] optional; using (current) testCase by default + // I'd rather use objects, but Javascript doesn't compare objects field by field + // - try javascript:a={first: 1}; b={first: 1}; a==b + @returns {string} global index of the command, in form testCaseIndex/commandIndex + */ + function globIdx(commandIndex, givenTestCase) { + givenTestCase = givenTestCase || testCase; + // Must not use assert() here, because that calls notifyFatalHere() which calls idxHere() which calls globIdx() + if (typeof commandIndex !== 'number' || commandIndex < 0) { + logAndThrow("SelBlocks error: in globIdx(), bad type/value of the first parameter commandIndex: " + commandIndex); + } + if (typeof givenTestCase !== 'object') { + logAndThrow("SelBlocks error: in globIdx(), bad type of the optional second parameter givenTestCase (or global testCase)."); + } + var caseIndex = testCaseIdx(givenTestCase); + return '' + caseIndex + '/' + commandIndex; + } + /** @return {number} (not a Number object) 0-based index of the respective command within its test case + * @param {string} globIdxValue Global index of a test command (test step). + */ + function localIdx(globIdxValue) { + var msg, + lastSlashIndex; + // Can't use assert() here, since assert indirectly calls fmtCmdRef() which calls localIdx() - recursion + if (typeof globIdxValue !== 'string') { + msg = 'globIdxValue must be a string, but got ' + (typeof globIdxValue) + ': ' + globIdxValue; + LOG.error(msg); + throw new Error(msg); + } + lastSlashIndex = globIdxValue.lastIndexOf('/'); + if (lastSlashIndex <= 0) { + msg = 'globIdxValue must contain "/" and not as the first character.'; + LOG.error(msg); + throw new Error(msg); + } + if (lastSlashIndex >= globIdxValue.length) { + msg = 'globIdxValue must contain "/" and not as the last character.'; + LOG.error(msg); + throw new Error(msg); + } + var afterSlash = globIdxValue.substr(lastSlashIndex + 1); + var afterSlashNumber = Number(afterSlash); + if (afterSlash !== '' + afterSlashNumber) { + msg = 'The part after "/" must be numeric.'; + LOG.error(msg); + throw new Error(msg); + } + var result = afterSlashNumber.valueOf(); + //"TODO:" + if (result < 0 || result >= editor.app.testSuite.tests[localCaseIdxPart(globIdxValue)].content.commands.length) { + msg = 'In localIdx("' + globIdxValue + '"), result ' + result + ' is not a valid command index'; + LOG.error(msg); + throw new Error(msg); + } + return result; + } + /**@param string result of globIdx() or of labelIdx() + * @return {number} (not a Number object) 0-based index of the test case (for the given global index) + * within the list of test cases (i.e. editor.app.testSuite.tests) + */ + function localCaseIdxPart(globIdxValue) { + assert(typeof globIdxValue === 'string', 'globIdxValue must be a string.'); + var lastSlashIndex = globIdxValue.lastIndexOf('/'); + assert(lastSlashIndex > 0, 'globIdxValue must contain "/" and not as the first character.'); + assert(lastSlashIndex < globIdxValue.length - 1, 'globIdxValue must contain "/" and not as the last character.'); + var beforeSlash = globIdxValue.substring(0, globIdxValue.lastIndexOf('/')); + var beforeSlashNumber = Number(beforeSlash); + assert('' + beforeSlash === '' + beforeSlashNumber, 'The part after "/" must be numeric.'); + var result = beforeSlashNumber.valueOf(); + assert(result >= 0 && result < editor.app.testSuite.tests.length, 'result not a valid index into editor.app.testSuite.tests.'); + return result; + } + /** global array of _usable_ test cases, set in compileSelBlocks(). + * It contains test cases in the same order as in editor.app.testSuite.tests[], + * but here they are as they come from editor.getTestCase() + **/ + var testCases = []; + + // @return TestCase test case for the given global index + function localCase(globIdxValue) { + var index = localCaseIdxPart(globIdxValue); + assert(index < testCases.length, 'case index: ' + index + ' but testCases[] has length ' + testCases.length); + return testCases[index]; + /* Following didn't work: + return editor.app.testSuite.tests[ localCaseIdxPart(globIdxValue) ].content; + */ + } + /** @return {Object} Command structure for given global index + * */ + function localCommand(globIdxValue) { + return localCase(globIdxValue).commands[localIdx(globIdxValue)]; + } + + /** This serves to generate and compare keys in symbols[] for label commands + * @param string label name + * @param TestCase test case where the label is; optional - using testCase by default + * @return string global label identifier in form 'test-case-index/label' + **/ + function labelIdx(label, givenTestCase) { + assert(typeof label === 'string', 'label must be a string.'); + givenTestCase = givenTestCase || testCase; + return '' + testCaseIdx(givenTestCase) + '/' + label; + } + // @TODO on insert, validate that function names are unique, i.e. no function overriding //=============== Call/Scope Stack handling =============== - var symbols = {}; // command indexes stored by name: function names - var blockDefs = null; // static command definitions stored by command index - var callStack = null; // command execution stack + /** @var object symbols */ + var symbols = {}; // command indexes stored by name: function names + /** @var {BlockDefs} Static command definitions stored by command index. Global, used for all test cases. */ + var blockDefs = null; // static command definitions stored by command index + /** @var {Stack} callStack Command execution stack */ + var callStack = null; // command execution stack // the idx of the currently executing command - function idxHere() { - return testCase.debugContext.debugIndex; + // SelBlocksGlobal added param relativeShift and made it return a global, cross-test case index, rather than local (test-case specific) index + /** @param {number} [relativeShift=0] Relative shift to the current command's position + * @return {string} global command index + * */ + function idxHere(relativeShift) { + // Must not use assert() here, because that calls notifyFatalHere() which calls idxHere() + return globIdx(localIdxHere(relativeShift)); + } + /** @param {number} [relativeShift=0] Relative shift to the current command's position + * @return {number} Current command's position (within current test case), adjusted by relativeShift. Depending on relativeShift the result may not be a valid position. + * */ + function localIdxHere(relativeShift) { + relativeShift = relativeShift || 0; + return testCase.debugContext.debugIndex + relativeShift; } // Command structure definitions, stored by command index + // SelBlocksGlobal: stored by command global index - i.e. value of idxHere() function BlockDefs() { - var blkDefs = []; - // initialize blockDef at the given command index - blkDefs.init = function(i, attrs) { + //@TODO use this.xxx=yyy, and define init() on BlockDefs.prototype. Then NetBeans navigation is easier. + /** @var {object} Serving as an associative array {globIdx => object of {any attributes, idx, cmdName}}. SelBlocksGlobal changed this from an array to an object. */ + var blkDefs = {}; + // initialize an entry in BlockDefs instance at the given command global index + /** @param {string} i Global index, a result of globIdx() function + * @param {Object} [attrs] Extra details to add, depending on the command: + * nature: 'if', 'try', 'loop', 'function' + * elseIfIdxs - array, used for 'if' + * ifIdx - used for 'else', 'elseIf' and 'endIf'; it's a global index of the matching 'if' step + * name - used for 'try', it's 'target' of the step (the 2nd column in Selenium IDE) + * tryIdx - used by 'catch', 'finally' and 'endTry', index of the matching 'try' + * finallyIdx + * beginIdx - used by 'continue', 'break', endWhile, endFor, endForeach, endForJson, endForXml; + * it's an index of the start of the current loop + * endIdx + * funcIdx - used by 'return', 'endfunction', 'endScript' + * @TODO check beginIdx and other fields - set after calls to blkDefFor(), blkDefAt() + * @return {object} A new entry just added to this collection. + * @see variable blkDefs + **/ + blkDefs.init = function BlockDefsInit(i, attrs) { + assert(typeof testCase.commands === 'object', 'BlockDefs::init() - testCase.commands is of bad type.'); + // @TODO assert regex numeric/numeric + assert(typeof i === 'string', 'BlockDefs::init() - param i must be a globIdx() result.'); + // @TODO change to use 'this' instead of 'blkDefs' - it will be clearer. blkDefs[i] = attrs || {}; blkDefs[i].idx = i; - blkDefs[i].cmdName = testCase.commands[i].command; + // Following line is from original SelBlocks, here just for documentation + //blkDefs[i].cmdName = testCase.commands[i].command; + blkDefs[i].cmdName = localCase(i).commands[localIdx(i)].command; return blkDefs[i]; }; return blkDefs; } // retrieve the blockDef at the given command idx + /** @param {string} idx Global index of a test step. */ function blkDefAt(idx) { return blockDefs[idx]; } @@ -201,18 +386,24 @@ function $X(xpath, contextNode, resultType) { // An Array object with stack functionality function Stack() { var stack = []; - stack.isEmpty = function() { return stack.length === 0; }; - stack.top = function() { return stack[stack.length-1]; }; - stack.findEnclosing = function(_hasCriteria) { return stack[stack.indexWhere(_hasCriteria)]; }; - stack.indexWhere = function(_hasCriteria) { // undefined if not found + stack.isEmpty = function isEmpty() { + return stack.length === 0; + }; + stack.top = function top() { + return stack[stack.length - 1]; + }; + stack.findEnclosing = function findEnclosing(_hasCriteria) { + return stack[stack.indexWhere(_hasCriteria)]; + }; + stack.indexWhere = function indexWhere(_hasCriteria) { // undefined if not found var i; - for (i = stack.length-1; i >= 0; i--) { + for (i = stack.length - 1; i >= 0; i--) { if (_hasCriteria(stack[i])) { return i; } } }; - stack.unwindTo = function(_hasCriteria) { + stack.unwindTo = function unwindTo(_hasCriteria) { if (stack.length === 0) { return null; } @@ -221,23 +412,35 @@ function $X(xpath, contextNode, resultType) { } return stack.top(); }; - stack.isHere = function() { + stack.isHere = function isHere() { return (stack.length > 0 && stack.top().idx === idxHere()); }; return stack; } // Determine if the given stack frame is one of the given block kinds - Stack.isTryBlock = function(stackFrame) { return (blkDefFor(stackFrame).nature === "try"); }; - Stack.isLoopBlock = function(stackFrame) { return (blkDefFor(stackFrame).nature === "loop"); }; - Stack.isFunctionBlock = function(stackFrame) { return (blkDefFor(stackFrame).nature === "function"); }; - + Stack.isTryBlock = function (stackFrame) { + return (blkDefFor(stackFrame).nature === "try"); + }; + Stack.isLoopBlock = function (stackFrame) { + return (blkDefFor(stackFrame).nature === "loop"); + }; + Stack.isFunctionBlock = function (stackFrame) { + return (blkDefFor(stackFrame).nature === "function"); + }; // Flow control - we don't just alter debugIndex on the fly, because the command // preceding the destination would falsely get marked as successfully executed + // SelBLocksGlobal: This is a global index of the next command - set to a result of globIdx() var branchIdx = null; // if testCase.nextCommand() ever changes, this will need to be revisited // (current as of: selenium-ide-2.4.0) + // See Selenium's {a6fd85ed-e919-4a43-a5af-8da18bda539f}/chrome/content/testCase.js + // This is for a head-intercept of TestCaseDebugContext.prototype.nextCommand(), and it adds support for SelBlocksGlobal branches (across test cases). + // We can't redefine/tail-intercept testCase.debugContext.nextCommand() at the time + // this SelBlocksGlobal source file is loaded, because testCase is not defined yet. Therefore we do it here + // on the first run of the enclosing tail intercept of Selenium.prototype.reset() below. + // And we intercept do it on the prototype, so that it applies to any test cases. function nextCommand() { if (!this.started) { this.started = true; @@ -246,11 +449,14 @@ function $X(xpath, contextNode, resultType) { else { if (branchIdx !== null) { $$.LOG.info("branch => " + fmtCmdRef(branchIdx)); - this.debugIndex = branchIdx; + this.debugIndex = localIdx(branchIdx); + + testCase= this.testCase= localCase(branchIdx); + testCase.debugContext= this; branchIdx = null; } else { - this.debugIndex++; + this.debugIndex++; // global removed this } } // skip over comments @@ -263,12 +469,17 @@ function $X(xpath, contextNode, resultType) { } return null; } + /** @param {string} globIdx value */ function setNextCommand(cmdIdx) { - assert(cmdIdx >= 0 && cmdIdx < testCase.commands.length, - " Cannot branch to non-existent command @" + (cmdIdx+1)); + var idx= localIdx(cmdIdx); + var localTestCase= localCase(cmdIdx); + assert( idx>=0 && idx< localTestCase.commands.length, + " Cannot branch to non-existent command @" +cmdIdx ); branchIdx = cmdIdx; } +(function () { // wrapper makes testCaseDebugContextWasIntercepted private + var testCaseDebugContextWasIntercepted; // undefined or true // Selenium calls reset(): // * before each single (double-click) command execution // * before a testcase is run @@ -287,37 +498,98 @@ function $X(xpath, contextNode, resultType) { callStack.push({ blockStack: new Stack() }); // top-level execution state $$.tcf = { nestingLevel: -1 }; // try/catch/finally nesting - // customize flow control logic - // TBD: this should be a tail intercept rather than brute force replace $$.LOG.debug("Configuring tail intercept: testCase.debugContext.nextCommand()"); $$.fn.interceptReplace(testCase.debugContext, "nextCommand", nextCommand); + + //if( testCaseDebugContextWasIntercepted===undefined ) { + // // SelBlocksGlobal: This is a head-intercept, rather than interceptReplace as in SelBlocks 2.0.1, + // // and I'm intercepting TestCaseDebugContext.prototype.nextCommand() rather than testCase.debugContext.nextCommand() + // $$.LOG.debug("Configuring head intercept: TestCaseDebugContext.prototype.nextCommand()"); + // $$.fn.interceptBefore(TestCaseDebugContext.prototype, "nextCommand", nextCommand); + // + // testCaseDebugContextWasIntercepted= true; + //} }); +}()); // get the blockStack for the currently active callStack function activeBlockStack() { return callStack.top().blockStack; } - // ================================================================================ // Assemble block relationships and symbol locations function compileSelBlocks() { + symbols= {}; // Let's clear symbols + // Currently, this is called multiple times when Se IDE runs the whole test suite + // - once per each test case. No harm in that, only a bit of wasted CPU. + + //alert( 'testCase===editor.suiteTreeView.getCurrentTestCase(): ' +(testCase===editor.suiteTreeView.getCurrentTestCase()) ); // --> false! + //alert( 'testCase===editor.getTestCase(): ' +(testCase===editor.getTestCase()) ); //--> true! + var testCaseOriginal= testCase; + testCaseOriginal= editor.getTestCase(); + var testCaseOriginalIndex= -1; + testCases= []; + //alert( 'editor.app.getTestSuite()===editor.app.testSuite: ' +editor.app.getTestSuite()===editor.app.testSuite ); // => false + //alert( 'editor.app.testSuite.tests.indexOf( testCase): ' +editor.app.testSuite.tests.indexOf( testCase) ); // => -1 + // SelBlocksGlobal: I set blockDefs before looping through test cases, because it's global - for all test cases blockDefs = new BlockDefs(); + for( var testCaseIndex=0; testCaseIndex=0, "testCaseOriginalIndex mut be non-negative!"); + // In the following, do not pass testCases[testCaseOriginalIndex]. + // is not the same as editor.app.getTestSuite().tests[testCaseOriginalIndex], because + // Application.prototype.showTestCaseFromSuite(givenTestCase) calls this.setTestCase(givenTestCase.content) + editor.app.showTestCaseFromSuite( editor.app.getTestSuite().tests[testCaseOriginalIndex] ); + testCase.debugContext.testCase= testCase; + } + // end of compileSelBlocks() + // SelBlocksGlobal: Following three functions were inside compileSelBlocksTestCase(), but that doesn't follow JS strict mode. Two of them didn't have to be closures. assertBlockIsPending() used to be a closure, now it received parameter lexStack and is not a closure anymore. + //- command validation + function assertNotAndWaitSuffix(cmdIdx) { + assertCmd(cmdIdx, localCase(cmdIdx).commands[ localIdx(cmdIdx) ].command.indexOf("AndWait") === -1, + ", AndWait suffix is not valid for SelBlocks commands"); + } + //- active block validation + function assertBlockIsPending(lexStack, expectedCmd, cmdIdx, desc) { + assertCmd(cmdIdx, !lexStack.isEmpty(), desc || ", without an beginning [" + expectedCmd + "]"); + } + //- command-pairing validation + function assertMatching(curCmd, expectedCmd, cmdIdx, pendIdx) { + assertCmd(cmdIdx, curCmd === expectedCmd, ", does not match command " + fmtCmdRef(pendIdx)); + } + // SelBlocksGlobal factored the following out of SelBlocks' compileSelBlocks(), to make 'testCase' not refer + // to global testCase + function compileSelBlocksTestCase( testCase ) { + // SelBlocksGlobal: I set lexStack here, since it's local stack per testCase (and only used during compilation) var lexStack = new Stack(); - var i; - for (i = 0; i < testCase.commands.length; i++) + // SelBlocksGlobal: Following loop variable commandIndex was renamed from 'i' in SelBlocks. + // I set variable 'i' to globIdx() value of commandIndex (i.e. of the original intended value of 'i'). This way + // the original SelBlocks code still uses variable 'i', so there are less merge conflicts. + var commandIndex, i; + for (commandIndex = 0; commandIndex < testCase.commands.length; commandIndex++) { - if (testCase.commands[i].type === "command") + if (testCase.commands[commandIndex].type === "command") { - var curCmd = testCase.commands[i].command; + var curCmd = testCase.commands[commandIndex].command; var aw = curCmd.indexOf("AndWait"); if (aw !== -1) { // just ignore the suffix for now, this may or may not be a SelBlocks commands curCmd = curCmd.substring(0, aw); } - var cmdTarget = testCase.commands[i].target; - + var cmdTarget = testCase.commands[commandIndex].target; + i= globIdx(commandIndex, testCase); var ifDef; var tryDef; var expectedCmd; @@ -325,7 +597,7 @@ function $X(xpath, contextNode, resultType) { { case "label": assertNotAndWaitSuffix(i); - symbols[cmdTarget] = i; + symbols[ labelIdx(cmdTarget, testCase) ] = i; break; case "goto": case "gotoIf": case "skipNext": assertNotAndWaitSuffix(i); @@ -337,7 +609,7 @@ function $X(xpath, contextNode, resultType) { break; case "elseIf": assertNotAndWaitSuffix(i); - assertBlockIsPending("elseIf", i, ", is not valid outside of an if/endIf block"); + assertBlockIsPending(lexStack, "elseIf", i, ", is not valid outside of an if/endIf block"); ifDef = lexStack.top(); assertMatching(ifDef.cmdName, "if", i, ifDef.idx); var eIdx = blkDefFor(ifDef).elseIdx; @@ -349,7 +621,7 @@ function $X(xpath, contextNode, resultType) { break; case "else": assertNotAndWaitSuffix(i); - assertBlockIsPending("if", i, ", is not valid outside of an if/endIf block"); + assertBlockIsPending(lexStack, "if", i, ", is not valid outside of an if/endIf block"); ifDef = lexStack.top(); assertMatching(ifDef.cmdName, "if", i, ifDef.idx); if (blkDefFor(ifDef).elseIdx) { @@ -360,7 +632,7 @@ function $X(xpath, contextNode, resultType) { break; case "endIf": assertNotAndWaitSuffix(i); - assertBlockIsPending("if", i); + assertBlockIsPending(lexStack, "if", i); ifDef = lexStack.pop(); assertMatching(ifDef.cmdName, "if", i, ifDef.idx); blockDefs.init(i, { ifIdx: ifDef.idx }); // endIf -> if @@ -376,7 +648,7 @@ function $X(xpath, contextNode, resultType) { break; case "catch": assertNotAndWaitSuffix(i); - assertBlockIsPending("try", i, ", is not valid without a try block"); + assertBlockIsPending(lexStack, "try", i, ", is not valid without a try block"); tryDef = lexStack.top(); assertMatching(tryDef.cmdName, "try", i, tryDef.idx); if (blkDefFor(tryDef).catchIdx) { @@ -391,7 +663,7 @@ function $X(xpath, contextNode, resultType) { break; case "finally": assertNotAndWaitSuffix(i); - assertBlockIsPending("try", i); + assertBlockIsPending(lexStack, "try", i); tryDef = lexStack.top(); assertMatching(tryDef.cmdName, "try", i, tryDef.idx); if (blkDefFor(tryDef).finallyIdx) { @@ -405,7 +677,7 @@ function $X(xpath, contextNode, resultType) { break; case "endTry": assertNotAndWaitSuffix(i); - assertBlockIsPending("try", i); + assertBlockIsPending(lexStack, "try", i); tryDef = lexStack.pop(); assertMatching(tryDef.cmdName, "try", i, tryDef.idx); if (cmdTarget) { @@ -430,7 +702,7 @@ function $X(xpath, contextNode, resultType) { case "endWhile": case "endFor": case "endForeach": case "endForJson": case "endForXml": assertNotAndWaitSuffix(i); expectedCmd = curCmd.substr(3).toLowerCase(); - assertBlockIsPending(expectedCmd, i); + assertBlockIsPending(lexStack, expectedCmd, i); var beginDef = lexStack.pop(); assertMatching(beginDef.cmdName.toLowerCase(), expectedCmd, i, beginDef.idx); blkDefFor(beginDef).endIdx = i; // begin -> end @@ -452,14 +724,14 @@ function $X(xpath, contextNode, resultType) { break; case "return": assertNotAndWaitSuffix(i); - assertBlockIsPending("function", i, ", is not valid outside of a function/endFunction block"); + assertBlockIsPending(lexStack, "function", i, ", is not valid outside of a function/endFunction block"); var funcCmd = lexStack.findEnclosing(Stack.isFunctionBlock); blockDefs.init(i, { funcIdx: funcCmd.idx }); // return -> function break; case "endFunction": case "endScript": assertNotAndWaitSuffix(i); expectedCmd = curCmd.substr(3).toLowerCase(); - assertBlockIsPending(expectedCmd, i); + assertBlockIsPending(lexStack, expectedCmd, i); var funcDef = lexStack.pop(); assertMatching(funcDef.cmdName.toLowerCase(), expectedCmd, i, funcDef.idx); if (cmdTarget) { @@ -487,20 +759,7 @@ function $X(xpath, contextNode, resultType) { } throw new SyntaxError(cmdErrors.join("; ")); } - //- command validation - function assertNotAndWaitSuffix(cmdIdx) { - assertCmd(cmdIdx, (testCase.commands[cmdIdx].command.indexOf("AndWait") === -1), - ", AndWait suffix is not valid for SelBlocks commands"); - } - //- active block validation - function assertBlockIsPending(expectedCmd, cmdIdx, desc) { - assertCmd(cmdIdx, !lexStack.isEmpty(), desc || ", without an beginning [" + expectedCmd + "]"); - } - //- command-pairing validation - function assertMatching(curCmd, expectedCmd, cmdIdx, pendIdx) { - assertCmd(cmdIdx, curCmd === expectedCmd, ", does not match command " + fmtCmdRef(pendIdx)); - } - } + } // end of compileSelBlocksTestCase() // -------------------------------------------------------------------------------- @@ -608,8 +867,8 @@ function $X(xpath, contextNode, resultType) { } if (n !== 0) { // if n=0, execute the next command as usual - destIdx = idxHere() + n + 1; - assertIntraBlockJumpRestriction(idxHere(), destIdx); + destIdx = globIdx(localIdxHere()+n+1); + assertIntraBlockJumpRestriction(localIdxHere(), localIdxHere()+n+1); setNextCommand(destIdx); } }; @@ -617,9 +876,10 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doGoto = function(label) { assertRunning(); - assert(symbols[label]!==undefined, " Target label '" + label + "' is not found."); - assertIntraBlockJumpRestriction(idxHere(), symbols[label]); - setNextCommand(symbols[label]); + var symbolIndex= labelIdx(label); + assert(symbols[symbolIndex], " Target label '" + label + "' is not found."); + assertIntraBlockJumpRestriction(localIdxHere(), localIdx(symbols[symbolIndex])); + setNextCommand(symbols[symbolIndex]); }; Selenium.prototype.doGotoIf = function(condExpr, label) @@ -637,7 +897,7 @@ function $X(xpath, contextNode, resultType) { var ifDef = blkDefHere(); var ifState = { idx: idxHere(), elseIfItr: arrayIterator(ifDef.elseIfIdxs) }; activeBlockStack().push(ifState); - cascadeElseIf(ifState, condExpr); + this.cascadeElseIf(ifState, condExpr); }; Selenium.prototype.doElseIf = function(condExpr) { @@ -648,7 +908,7 @@ function $X(xpath, contextNode, resultType) { setNextCommand(blkDefAt(blkDefHere().ifIdx).endIdx); } else { - cascadeElseIf(ifState, condExpr); + this.cascadeElseIf(ifState, condExpr); } }; Selenium.prototype.doElse = function() @@ -668,9 +928,9 @@ function $X(xpath, contextNode, resultType) { // fall out of if-endIf }; - function cascadeElseIf(ifState, condExpr) { - assertCompilable("", condExpr, ";", "Invalid condition"); - if (!evalWithVars(condExpr)) { + Selenium.prototype.cascadeElseIf = function cascadeElseIf(ifState, condExpr) { + this.assertCompilable("", condExpr, ";", "Invalid condition"); + if (!this.evalWithVars(condExpr)) { // jump to next elseIf or else or endif var ifDef = blkDefFor(ifState); if (ifState.elseIfItr.hasNext()) { setNextCommand(ifState.elseIfItr.next()); } @@ -681,13 +941,13 @@ function $X(xpath, contextNode, resultType) { ifState.skipElseBlocks = true; // continue into if/elseIf block } - } + }; // ================================================================================ // throw the given Error Selenium.prototype.doThrow = function(err) { - err = evalWithVars(err); + err = this.evalWithVars(err); if (!(err instanceof Error)) { err = new SelblocksError(idxHere(), err); } @@ -711,7 +971,7 @@ function $X(xpath, contextNode, resultType) { // log an advisory about the active catch block if (tryDef.catchIdx) { - var errDcl = testCase.commands[tryDef.catchIdx].target; + var errDcl = localCommand( tryDef.catchIdx ).target; $$.LOG.debug(tryName + " catchable: " + (errDcl || "ANY")); } @@ -767,7 +1027,7 @@ function $X(xpath, contextNode, resultType) { // $$.tcf.bubbling = null; } if ($$.tcf.bubbling) { - reBubble(); + this.reBubble(); } else { $$.LOG.debug("no bubbling in process"); @@ -782,14 +1042,14 @@ function $X(xpath, contextNode, resultType) { // alter the behavior of Selenium error handling // returns true if catch/finally bubbling is active - function handleCommandError(err) + Selenium.prototype.handleCommandError = function handleCommandError(err) { var tryState = bubbleToTryBlock(Stack.isTryBlock); var tryDef = blkDefFor(tryState); if (tryState) { $$.LOG.debug("error encountered while: " + tryState.execPhase); if (hasUnspentCatch(tryState)) { - if (isMatchingCatch(err, tryDef.catchIdx)) { + if (this.isMatchingCatch(err, tryDef.catchIdx)) { // an expected kind of error has been caught $$.LOG.info("@" + (idxHere()+1) + ", error has been caught" + fmtCatching(tryState)); tryState.hasCaught = true; @@ -818,11 +1078,26 @@ function $X(xpath, contextNode, resultType) { } $$.LOG.info("No handling provided in this try section for this error: '" + err.message + "'"); return false; // stop test - } + }; // execute any enclosing finally block(s) until reaching the given type of enclosing block - function bubbleCommand(cmdIdx, _isContextBlockType) + Selenium.prototype.bubbleCommand = function bubbleCommand(cmdIdx, _isContextBlockType) { + var self= this; + //- determine if catch matches an error, or there is a finally, or the ceiling block has been reached + function isTryWithMatchingOrFinally(stackFrame) { + if (_isContextBlockType && _isContextBlockType(stackFrame)) { + return true; + } + if ($$.tcf.bubbling && $$.tcf.bubbling.mode === "error" && hasUnspentCatch(stackFrame)) { + var tryDef = blkDefFor(stackFrame); + if (self.isMatchingCatch($$.tcf.bubbling.error, tryDef.catchIdx)) { + return true; + } + } + return hasUnspentFinally(stackFrame); + } + var tryState = bubbleToTryBlock(isTryWithMatchingOrFinally); var tryDef = blkDefFor(tryState); $$.tcf.bubbling = { mode: "command", srcIdx: cmdIdx, _isStopCriteria: _isContextBlockType }; @@ -838,35 +1113,21 @@ function $X(xpath, contextNode, resultType) { setNextCommand(tryDef.endIdx); // jump out of try section } - - //- determine if catch matches an error, or there is a finally, or the ceiling block has been reached - function isTryWithMatchingOrFinally(stackFrame) { - if (_isContextBlockType && _isContextBlockType(stackFrame)) { - return true; - } - if ($$.tcf.bubbling && $$.tcf.bubbling.mode === "error" && hasUnspentCatch(stackFrame)) { - var tryDef = blkDefFor(stackFrame); - if (isMatchingCatch($$.tcf.bubbling.error, tryDef.catchIdx)) { - return true; - } - } - return hasUnspentFinally(stackFrame); - } - } + }; //- error message matcher - function isMatchingCatch(e, catchIdx) { - var errDcl = testCase.commands[catchIdx].target; + Selenium.prototype.isMatchingCatch = function isMatchingCatch(e, catchIdx) { + var errDcl = localCommand( catchIdx ).target; if (!errDcl) { return true; // no error specified means catch all errors } - var errExpr = evalWithVars(errDcl); + var errExpr = this.evalWithVars(errDcl); var errMsg = e.message; if (errExpr instanceof RegExp) { return (errMsg.match(errExpr)); } return (errMsg.indexOf(errExpr) !== -1); - } + }; // unwind the blockStack, and callStack (ie, aborting functions), until reaching the given criteria function bubbleToTryBlock(_hasCriteria) { @@ -876,6 +1137,12 @@ function $X(xpath, contextNode, resultType) { var tryState = unwindToBlock(_hasCriteria); while (!tryState && $$.tcf.nestingLevel > -1 && callStack.length > 1) { var callFrame = callStack.pop(); + var _result= storedVars._result; + storedVars= callFrame.savedVars; + storedVars._result= _result; + testCase= callFrame.testCase; + testCase.debugContext.testCase= testCase; + testCase.debugContext.debugIndex = localIdx( callFrame.returnIdx ); $$.LOG.info("function '" + callFrame.name + "' aborting due to error"); restoreVarState(callFrame.savedVars); tryState = unwindToBlock(_hasCriteria); @@ -893,11 +1160,11 @@ function $X(xpath, contextNode, resultType) { } // resume or conclude command/error bubbling - function reBubble() { + Selenium.prototype.reBubble = function reBubble() { if ($$.tcf.bubbling.mode === "error") { if ($$.tcf.nestingLevel > -1) { $$.LOG.debug("error-bubbling continuing..."); - handleCommandError($$.tcf.bubbling.error); + this.handleCommandError($$.tcf.bubbling.error); } else { $$.LOG.error("Error was not caught: '" + $$.tcf.bubbling.error.message + "'"); @@ -908,7 +1175,7 @@ function $X(xpath, contextNode, resultType) { else { // mode == "command" if (isBubblable()) { $$.LOG.debug("command-bubbling continuing..."); - bubbleCommand($$.tcf.bubbling.srcIdx, $$.tcf.bubbling._isStopCriteria); + this.bubbleCommand($$.tcf.bubbling.srcIdx, $$.tcf.bubbling._isStopCriteria); } else { $$.LOG.info("command-bubbling complete - suspended command executing now " + fmtCmdRef($$.tcf.bubbling.srcIdx)); @@ -916,10 +1183,10 @@ function $X(xpath, contextNode, resultType) { $$.tcf.bubbling = null; } } - } + }; // instigate or transform bubbling, as appropriate - function transitionBubbling(_isContextBlockType) + Selenium.prototype.transitionBubbling = function transitionBubbling(_isContextBlockType) { if ($$.tcf.bubbling) { // transform bubbling if ($$.tcf.bubbling.mode === "error") { @@ -935,29 +1202,29 @@ function $X(xpath, contextNode, resultType) { return true; } if (isBubblable(_isContextBlockType)) { // instigate bubbling - bubbleCommand(idxHere(), _isContextBlockType); + this.bubbleCommand(idxHere(), _isContextBlockType); return true; } // no change to bubbling return false; - } + }; // determine if bubbling is possible from this point outward function isBubblable(_isContextBlockType) { var canBubble = ($$.tcf.nestingLevel > -1); if (canBubble) { - var blkState = activeBlockStack().findEnclosing(isTryOrContextBlockType); + var blkState = activeBlockStack().findEnclosing( + //- determine if stackFrame is a try-block or the given type of block + function isTryOrContextBlockType(stackFrame) { + if (_isContextBlockType && _isContextBlockType(stackFrame)) { + return true; + } + return Stack.isTryBlock(stackFrame); + } + ); return (blkDefFor(blkState).nature === "try"); } return canBubble; - - //- determine if stackFrame is a try-block or the given type of block - function isTryOrContextBlockType(stackFrame) { - if (_isContextBlockType && _isContextBlockType(stackFrame)) { - return true; - } - return Stack.isTryBlock(stackFrame); - } } function hasUnspentCatch(tryState) { @@ -988,21 +1255,21 @@ function $X(xpath, contextNode, resultType) { bbl = "@" + ($$.tcf.bubbling.srcIdx+1) + " "; } var tryDef = blkDefFor(tryState); - var catchDcl = testCase.commands[tryDef.catchIdx].target; + var catchDcl = localCommand( tryDef.catchIdx ).target; return " :: " + bbl + catchDcl; } - // ================================================================================ Selenium.prototype.doWhile = function(condExpr) { + var self= this; enterLoop( function() { // validate assert(condExpr, " 'while' requires a condition expression."); - assertCompilable("", condExpr, ";", "Invalid condition"); + self.assertCompilable("", condExpr, ";", "Invalid condition"); return null; } ,function() { } // initialize - ,function() { return (evalWithVars(condExpr)); } // continue? + ,function() { return (self.evalWithVars(condExpr)); } // continue? ,function() { } // iterate ); }; @@ -1013,10 +1280,11 @@ function $X(xpath, contextNode, resultType) { // ================================================================================ Selenium.prototype.doFor = function(forSpec) { + var self= this; enterLoop( function(loop) { // validate assert(forSpec, " 'for' requires: ; ; ."); - assertCompilable("for ( ", forSpec, " );", "Invalid loop parameters"); + self.assertCompilable("for ( ", forSpec, " );", "Invalid loop parameters"); var specs = iexpr.splitList(forSpec, ";"); assert(specs.length === 3, " 'for' requires ; ; ."); loop.initStmt = specs[0]; @@ -1027,9 +1295,9 @@ function $X(xpath, contextNode, resultType) { validateNames(localVarNames, "variable"); return localVarNames; } - ,function(loop) { evalWithVars(loop.initStmt); } // initialize - ,function(loop) { return (evalWithVars(loop.condExpr)); } // continue? - ,function(loop) { evalWithVars(loop.iterStmt); } // iterate + ,function(loop) { self.evalWithVars(loop.initStmt); } // initialize + ,function(loop) { return (self.evalWithVars(loop.condExpr)); } // continue? + ,function(loop) { self.evalWithVars(loop.iterStmt); } // iterate ); }; Selenium.prototype.doEndFor = function() { @@ -1048,16 +1316,16 @@ function $X(xpath, contextNode, resultType) { } return varNames; } - // ================================================================================ Selenium.prototype.doForeach = function(varName, valueExpr) { + var self= this; enterLoop( function(loop) { // validate assert(varName, " 'foreach' requires a variable name."); assert(valueExpr, " 'foreach' requires comma-separated values."); - assertCompilable("[ ", valueExpr, " ];", "Invalid value list"); - loop.values = evalWithVars("[" + valueExpr + "]"); + self.assertCompilable("[ ", valueExpr, " ];", "Invalid value list"); + loop.values = self.evalWithVars("[" + valueExpr + "]"); if (loop.values.length === 1 && loop.values[0] instanceof Array) { loop.values = loop.values[0]; // if sole element is an array, than use it } @@ -1075,19 +1343,18 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doEndForeach = function() { iterateLoop(); }; - // ================================================================================ Selenium.prototype.doLoadJsonVars = function(filepath, selector) { assert(filepath, " Requires a JSON file path or URL."); var jsonReader = new JSONReader(filepath); - loadVars(jsonReader, "JSON object", filepath, selector); + this.loadVars(jsonReader, "JSON object", filepath, selector); }; Selenium.prototype.doLoadXmlVars = function(filepath, selector) { assert(filepath, " Requires an XML file path or URL."); var xmlReader = new XmlReader(filepath); - loadVars(xmlReader, "XML element", filepath, selector); + this.loadVars(xmlReader, "XML element", filepath, selector); }; Selenium.prototype.doLoadVars = function(filepath, selector) { @@ -1096,10 +1363,10 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doLoadXmlVars(filepath, selector); }; - function loadVars(reader, desc, filepath, selector) + Selenium.prototype.loadVars = function loadVars(reader, desc, filepath, selector) { if (selector) { - assertCompilable("", selector, ";", "Invalid selector condition"); + this.assertCompilable("", selector, ";", "Invalid selector condition"); } reader.load(filepath); reader.next(); // read first varset and set values on storedVars @@ -1108,23 +1375,23 @@ function $X(xpath, contextNode, resultType) { + ' (A specific ' + desc + ' can be selected by specifying: name="value".)'); } - var result = evalWithVars(selector); + var result = this.evalWithVars(selector); if (typeof result !== "boolean") { notifyFatalHere(", " + selector + " is not a boolean expression"); } // read until specified set found var isEof = reader.EOF(); - while (!isEof && evalWithVars(selector) !== true) { + while (!isEof && this.evalWithVars(selector) !== true) { reader.next(); // read next varset and set values on storedVars isEof = reader.EOF(); } - if (!evalWithVars(selector)) { + if (!this.evalWithVars(selector)) { notifyFatalHere(desc + " not found for selector expression: " + selector + "; in input file " + filepath); } - } + }; // ================================================================================ @@ -1223,7 +1490,7 @@ function $X(xpath, contextNode, resultType) { // ================================================================================ Selenium.prototype.doContinue = function(condExpr) { - var loopState = dropToLoop(condExpr); + var loopState = this.dropToLoop(condExpr); if (loopState) { // jump back to top of loop for next iteration, if any var endCmd = blkDefFor(loopState); @@ -1231,7 +1498,7 @@ function $X(xpath, contextNode, resultType) { } }; Selenium.prototype.doBreak = function(condExpr) { - var loopState = dropToLoop(condExpr); + var loopState = this.dropToLoop(condExpr); if (loopState) { loopState.isComplete = true; // jump to bottom of loop for exit @@ -1241,46 +1508,69 @@ function $X(xpath, contextNode, resultType) { // Unwind the command stack to the inner-most active loop block // (unless the optional condition evaluates to false) - function dropToLoop(condExpr) + Selenium.prototype.dropToLoop = function dropToLoop(condExpr) { assertRunning(); if (condExpr) { - assertCompilable("", condExpr, ";", "Invalid condition"); + this.assertCompilable("", condExpr, ";", "Invalid condition"); } - if (transitionBubbling(Stack.isLoopBlock)) { + if (this.transitionBubbling(Stack.isLoopBlock)) { return; } - if (condExpr && !evalWithVars(condExpr)) { + if (condExpr && !this.evalWithVars(condExpr)) { return; } var loopState = activeBlockStack().unwindTo(Stack.isLoopBlock); return loopState; - } + }; // ================================================================================ Selenium.prototype.doCall = function(funcName, argSpec) { + var loop = currentTest || htmlTestRunner.currentTest; // See Selenium.prototype.doRollup() assertRunning(); // TBD: can we do single execution, ie, run from this point then break on return? if (argSpec) { - assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)"); + this.assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)"); } var funcIdx = symbols[funcName]; - assert(funcIdx!==undefined, " Function does not exist: " + funcName + "."); + assert(funcIdx, " Function does not exist: " + funcName + "."); var activeCallFrame = callStack.top(); if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) { // returning from completed function - restoreVarState(callStack.pop().savedVars); + var popped= callStack.pop(); + loop.commandError= popped.originalCommandError; + var _result= storedVars._result; + storedVars= popped.savedVars; //restoreVarState( popped.savedVars ); + storedVars._result= _result; + assert( testCase===popped.testCase, "The popped testCase is different." ); // Not sure why, but this seems to be true. } else { // save existing variable state and set args as local variables - var args = parseArgs(argSpec); - var savedVars = getVarStateFor(args); - setVars(args); - - callStack.push({ funcIdx: funcIdx, name: funcName, args: args, returnIdx: idxHere(), - savedVars: savedVars, blockStack: new Stack() }); + var args = this.parseArgs(argSpec); + var savedVars = storedVars; + storedVars = args; + + var originalCommandError= loop.commandError; + // There can be several cascading layers of these calls - one per function call level. + loop.commandError= function doCallCommandError( result ) { + this.commandError= originalCommandError; + // See also bubbleToTryBlock(..) + editor.selDebugger.pause(); + originalCommandError.call( this, result ); // I've restored this.commandError above *before* calling originalCommandError(), because: if this was a deeper function call then originalCommandError() will restore any previous version of this.commandError, and I don't want to step on its feet here + }; + + callStack.push( { + funcIdx: funcIdx, + name: funcName, + args: args, + returnIdx: idxHere(), + savedVars: savedVars, + blockStack: new Stack(), + testCase: testCase, + originalCommandError: originalCommandError + }); // jump to function body setNextCommand(funcIdx); } @@ -1292,8 +1582,7 @@ function $X(xpath, contextNode, resultType) { var funcDef = blkDefHere(); var activeCallFrame = callStack.top(); if (activeCallFrame.funcIdx === idxHere()) { - // get parameter values - setVars(activeCallFrame.args); + //SelBlocks used to call setVars(activeCallFrame.args); here. But this was already handled in doCall(). } else { // no active call, skip around function body @@ -1307,19 +1596,19 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doFunction(scrName); }; Selenium.prototype.doReturn = function(value) { - returnFromFunction(null, value); + this.returnFromFunction(null, value); }; Selenium.prototype.doEndFunction = function(funcName) { - returnFromFunction(funcName); + this.returnFromFunction(funcName); }; Selenium.prototype.doEndScript = function(scrName) { - returnFromFunction(scrName); + this.returnFromFunction(scrName); }; - function returnFromFunction(funcName, returnVal) + Selenium.prototype.returnFromFunction = function(funcName, returnVal) { assertRunning(); - if (transitionBubbling(Stack.isFunctionBlock)) { + if (this.transitionBubbling(Stack.isFunctionBlock)) { return; } var endDef = blkDefHere(); @@ -1328,17 +1617,17 @@ function $X(xpath, contextNode, resultType) { // no active call, we're just skipping around a function block } else { - if (returnVal) { storedVars._result = evalWithVars(returnVal); } + if (returnVal) { storedVars._result = this.evalWithVars(returnVal); } activeCallFrame.isReturning = true; // jump back to call command setNextCommand(activeCallFrame.returnIdx); } - } + }; // ================================================================================ Selenium.prototype.doExitTest = function() { - if (transitionBubbling()) { + if (this.transitionBubbling()) { return; } // intercept command processing and simply stop test execution instead of executing the next command @@ -1347,8 +1636,7 @@ function $X(xpath, contextNode, resultType) { // ========= storedVars management ========= - - function evalWithVars(expr) { + Selenium.prototype.evalWithVars = function evalWithVars(expr) { var result = null; try { // EXTENSION REVIEWERS: Use of eval is consistent with the Selenium extension itself. @@ -1359,19 +1647,18 @@ function $X(xpath, contextNode, resultType) { notifyFatalErr(" While evaluating Javascript expression: " + expr, e); } return result; - } - - function parseArgs(argSpec) { // comma-sep -> new prop-set + }; + Selenium.prototype.parseArgs = function parseArgs(argSpec) { // comma-sep -> new prop-set var args = {}; var parms = iexpr.splitList(argSpec, ","); var i; for (i = 0; i < parms.length; i++) { var keyValue = iexpr.splitList(parms[i], "="); validateName(keyValue[0], "parameter"); - args[keyValue[0]] = evalWithVars(keyValue[1]); + args[keyValue[0].trim()] = this.evalWithVars(keyValue[1]); } return args; - } + }; function initVarState(names) { // new -> storedVars(names) if (names) { var i; @@ -1440,7 +1727,21 @@ function $X(xpath, contextNode, resultType) { throw err; } function notifyFatalCmdRef(idx, msg) { notifyFatal(fmtCmdRef(idx) + msg); } - function notifyFatalHere(msg) { notifyFatal(fmtCurCmd() + msg); } + function notifyFatalHere(msg) { + // This may be called before testCase is set + var commandRef; + if( testCase===undefined ) { + commandRef= 'unknown step: '; + } + else { + // SelBlocks used fmtCurCmd() here. However, this + // may be called right after TestCaseDebugContext's nextCommand(), which (as intercepted by SelBlocksGlobal) sets testCase.debugContext.debugIndex to -1. Then + // fmtCurCmd() would fail (as it invokes idxHere() -> globIdx(-1). + var stepLocalIdx= localIdxHere(); + commandRef= fmtCmdRef( globIdx( Math.max(stepLocalIdx, 0) ) )+ ': '; + } + notifyFatal( commandRef+msg ); + } function assertCmd(idx, cond, msg) { if (!cond) { notifyFatalCmdRef(idx, msg); } } function assert(cond, msg) { if (!cond) { notifyFatalHere(msg); } } @@ -1454,20 +1755,29 @@ function $X(xpath, contextNode, resultType) { assert(activeIdx === expectedIdx, " unexpected command, active command was " + fmtCmdRef(activeIdx)); } - function assertCompilable(left, stmt, right, explanation) { + Selenium.prototype.assertCompilable = function assertCompilable(left, stmt, right, explanation) { try { - evalWithVars("function selblocksTemp() { " + left + stmt + right + " }"); + this.evalWithVars("function selblocksTemp() { " + left + stmt + right + " }"); } catch (e) { throw new SyntaxError(fmtCmdRef(idxHere()) + " " + explanation + " '" + stmt + "': " + e.message); } - } + }; function fmtCurCmd() { return fmtCmdRef(idxHere()); } function fmtCmdRef(idx) { - return ("@" + (idx+1) + ": [" + $$.fmtCmd(testCase.commands[idx]) + "]"); + var test= localCase(idx); + var commandIdx= localIdx(idx); + + return "@" +test.filename+ ': ' +(commandIdx+1) + ": " + fmtCommand( test.commands[commandIdx] ); + } + function fmtCommand(cmd) { + var c = cmd.command; + if (cmd.target) { c += "|" + cmd.target; } + if (cmd.value) { c += "|" + cmd.value; } + return '[' + c + ']'; } //================= Utils =============== @@ -1482,12 +1792,13 @@ function $X(xpath, contextNode, resultType) { // produce an iterator object for the given array function arrayIterator(arrayObject) { - return new function(ary) { + function ArrayIteratorClosure(ary) { var cur = 0; this.hasNext = function() { return (cur < ary.length); }; this.next = function() { if (this.hasNext()) { return ary[cur++]; } }; - }(arrayObject); - }; + } + return new ArrayIteratorClosure(arrayObject); + } // ==================== Data Files ==================== // Adapted from the datadriven plugin @@ -1515,7 +1826,7 @@ function $X(xpath, contextNode, resultType) { } curVars = 0; - varNames = attrNamesFor(varsets[0]); + varNames = XmlReader.attrNamesFor(varsets[0]); return varNames; }; @@ -1530,22 +1841,23 @@ function $X(xpath, contextNode, resultType) { return; } varsetIdx++; - $$.LOG.debug(varsetIdx + ") " + serializeXml(varsets[curVars])); // log each name & value + $$.LOG.debug(varsetIdx + ") " + XmlReader.serialize(varsets[curVars])); // log each name & value - var expected = countAttrs(varsets[0]); - var found = countAttrs(varsets[curVars]); + var expected = XmlReader.countAttrs(varsets[0]); + var found = XmlReader.countAttrs(varsets[curVars]); if (found !== expected) { throw new Error("Inconsistent at element #" + varsetIdx + "; expected " + expected + " attributes, but found " + found + "." + " Each element must have the same set of attributes." ); } - setupStoredVars(varsets[curVars]); + XmlReader.setupStoredVars(varsets, varsetIdx, varsets[curVars]); curVars++; }; + } // end of XmlReader //- retrieve the names of each attribute on the given XML node - function attrNamesFor(node) { + XmlReader.attrNamesFor = function attrNamesFor(node) { var attrNames = []; var varAttrs = node.attributes; // NamedNodeMap var v; @@ -1553,15 +1865,16 @@ function $X(xpath, contextNode, resultType) { attrNames.push(varAttrs[v].nodeName); } return attrNames; - } + }; + //- determine how many attributes are present on the given node - function countAttrs(node) { + XmlReader.countAttrs = function countAttrs(node) { return node.attributes.length; - } - + }; + //- set selenium variables from given XML attributes - function setupStoredVars(node) { + XmlReader.setupStoredVars= function setupStoredVars(varsets, varsetIdx, node) { var varAttrs = node.attributes; // NamedNodeMap var v; for (v = 0; v < varAttrs.length; v++) { @@ -1574,17 +1887,16 @@ function $X(xpath, contextNode, resultType) { } storedVars[attr.nodeName] = attr.nodeValue; } - } + }; //- format the given XML node for display - function serializeXml(node) { + XmlReader.serializeXml = function serializeXml(node) { if (XMLSerializer !== "undefined") { return (new XMLSerializer()).serializeToString(node) ; } if (node.xml) { return node.xml; } throw "XMLSerializer is not supported or can't serialize " + node; - } - } + }; function JSONReader() @@ -1609,7 +1921,7 @@ function $X(xpath, contextNode, resultType) { } curVars = 0; - varNames = attrNamesFor(varsets[0]); + varNames = JSONReader.attrNamesFor(varsets[0]); return varNames; }; @@ -1624,42 +1936,43 @@ function $X(xpath, contextNode, resultType) { return; } varsetIdx++; - $$.LOG.debug(varsetIdx + ") " + serializeJson(varsets[curVars])); // log each name & value + $$.LOG.debug(varsetIdx + ") " + JSONReader.serializeJson(varsets[curVars])); // log each name & value - var expected = countAttrs(varsets[0]); - var found = countAttrs(varsets[curVars]); + var expected = JSONReader.countAttrs(varsets[0]); + var found = JSONReader.countAttrs(varsets[curVars]); if (found !== expected) { throw new Error("Inconsistent JSON object #" + varsetIdx + "; expected " + expected + " attributes, but found " + found + "." + " Each JSON object must have the same set of attributes." ); } - setupStoredVars(varsets[curVars]); + JSONReader.setupStoredVars(varsets, varsetIdx, varsets[curVars]); curVars++; }; + } // end of JSONReader - //- retrieve the names of each attribute on the given object - function attrNamesFor(obj) { + //- retrieve the names of each attribute on the given object + JSONReader.attrNamesFor = function attrNamesFor(obj) { var attrNames = []; var attrName; for (attrName in obj) { attrNames.push(attrName); } return attrNames; - } + }; //- determine how many attributes are present on the given obj - function countAttrs(obj) { + JSONReader.countAttrs = function countAttrs(obj) { var n = 0; var attrName; for (attrName in obj) { n++; } return n; - } + }; //- set selenium variables from given JSON attributes - function setupStoredVars(obj) { + JSONReader.setupStoredVars = function setupStoredVars(varsets, varsetIdx, obj) { var attrName; for (attrName in obj) { if (null === varsets[0][attrName]) { @@ -1670,20 +1983,19 @@ function $X(xpath, contextNode, resultType) { } storedVars[attrName] = obj[attrName]; } - } + }; //- format the given JSON object for display - function serializeJson(obj) { + JSONReader.serializeJson = function serializeJson(obj) { var json = uneval(obj); return json.substring(1, json.length-1); - } - } + }; function urlFor(filepath) { var URL_PFX = "file://"; var url = filepath; if (filepath.substring(0, URL_PFX.length).toLowerCase() !== URL_PFX) { - testCasePath = testCase.file.path.replace("\\", "/", "g"); + var testCasePath = testCase.file.path.replace("\\", "/", "g"); var i = testCasePath.lastIndexOf("/"); url = URL_PFX + testCasePath.substr(0, i) + "/" + filepath; } From ac760023e31cd5a80a7f519aefb27367ce4c05c5 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Wed, 31 Dec 2014 23:16:58 -0500 Subject: [PATCH 06/47] draft 2 I can call functions defined in one test case, from another test case. I can't run tests that have functions in them more than once or they'll always fail. When I run the script where the function is defined, it works the first time. If I run it again then things blow up. I can run the test that calls the function as many times as I want and it works. I wonder if I define another function in another test, if things would blow up when I ran the suite... --- .../chrome/content/extensions/selblocks.js | 24 +++++++++---------- sel-blocksTests/GlobalFunctions.html | 10 ++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 71793e4..f05db3c 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -473,7 +473,7 @@ function $X(xpath, contextNode, resultType) { function setNextCommand(cmdIdx) { var idx= localIdx(cmdIdx); var localTestCase= localCase(cmdIdx); - assert( idx>=0 && idx< localTestCase.commands.length, + assert( idx >= 0 && idx < localTestCase.commands.length, " Cannot branch to non-existent command @" +cmdIdx ); branchIdx = cmdIdx; } @@ -857,7 +857,7 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doSkipNext = function(spec) { assertRunning(); - var n = parseInt(evalWithVars(spec), 10); + var n = parseInt(this.evalWithVars(spec), 10); if (isNaN(n)) { if (spec.trim() === "") { n = 1; } else { notifyFatalHere(" Requires a numeric value"); } @@ -885,7 +885,7 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doGotoIf = function(condExpr, label) { assertRunning(); - if (evalWithVars(condExpr)) { + if (this.evalWithVars(condExpr)) { this.doGoto(label); } }; @@ -981,7 +981,7 @@ function $X(xpath, contextNode, resultType) { if ($$.tcf.nestingLevel === 0) { // enable special command handling $$.fn.interceptPush(editor.selDebugger.runner.currentTest, "resume", - $$.handleAsTryBlock, { manageError: handleCommandError }); + $$.handleAsTryBlock, { manageError: this.handleCommandError }); } $$.LOG.debug("++ try nesting: " + $$.tcf.nestingLevel); // continue into try-block @@ -1048,7 +1048,7 @@ function $X(xpath, contextNode, resultType) { var tryDef = blkDefFor(tryState); if (tryState) { $$.LOG.debug("error encountered while: " + tryState.execPhase); - if (hasUnspentCatch(tryState)) { + if (this.hasUnspentCatch(tryState)) { if (this.isMatchingCatch(err, tryDef.catchIdx)) { // an expected kind of error has been caught $$.LOG.info("@" + (idxHere()+1) + ", error has been caught" + fmtCatching(tryState)); @@ -1064,7 +1064,7 @@ function $X(xpath, contextNode, resultType) { // error not caught .. instigate bubbling $$.LOG.debug("error not caught, bubbling error: '" + err.message + "'"); $$.tcf.bubbling = { mode: "error", error: err, srcIdx: idxHere() }; - if (hasUnspentFinally(tryState)) { + if (self.hasUnspentFinally(tryState)) { $$.LOG.info("Bubbling suspended while finally block runs"); tryState.execPhase = "finallying"; tryState.hasFinaled = true; @@ -1089,19 +1089,19 @@ function $X(xpath, contextNode, resultType) { if (_isContextBlockType && _isContextBlockType(stackFrame)) { return true; } - if ($$.tcf.bubbling && $$.tcf.bubbling.mode === "error" && hasUnspentCatch(stackFrame)) { + if ($$.tcf.bubbling && $$.tcf.bubbling.mode === "error" && self.hasUnspentCatch(stackFrame)) { var tryDef = blkDefFor(stackFrame); if (self.isMatchingCatch($$.tcf.bubbling.error, tryDef.catchIdx)) { return true; } } - return hasUnspentFinally(stackFrame); + return self.hasUnspentFinally(stackFrame); } var tryState = bubbleToTryBlock(isTryWithMatchingOrFinally); var tryDef = blkDefFor(tryState); $$.tcf.bubbling = { mode: "command", srcIdx: cmdIdx, _isStopCriteria: _isContextBlockType }; - if (hasUnspentFinally(tryState)) { + if (self.hasUnspentFinally(tryState)) { $$.LOG.info("Command " + fmtCmdRef(cmdIdx) + ", suspended while finally block runs"); tryState.execPhase = "finallying"; tryState.hasFinaled = true; @@ -1227,10 +1227,10 @@ function $X(xpath, contextNode, resultType) { return canBubble; } - function hasUnspentCatch(tryState) { + Selenium.prototype.hasUnspentCatch = function hasUnspentCatch(tryState) { return (tryState && blkDefFor(tryState).catchIdx && !tryState.hasCaught); } - function hasUnspentFinally(tryState) { + Selenium.prototype.hasUnspentFinally = function hasUnspentFinally(tryState) { return (tryState && blkDefFor(tryState).finallyIdx && !tryState.hasFinaled); } @@ -1841,7 +1841,7 @@ function $X(xpath, contextNode, resultType) { return; } varsetIdx++; - $$.LOG.debug(varsetIdx + ") " + XmlReader.serialize(varsets[curVars])); // log each name & value + $$.LOG.debug(varsetIdx + ") " + XmlReader.serializeXml(varsets[curVars])); // log each name & value var expected = XmlReader.countAttrs(varsets[0]); var found = XmlReader.countAttrs(varsets[curVars]); diff --git a/sel-blocksTests/GlobalFunctions.html b/sel-blocksTests/GlobalFunctions.html index da38020..adcc056 100644 --- a/sel-blocksTests/GlobalFunctions.html +++ b/sel-blocksTests/GlobalFunctions.html @@ -15,11 +15,21 @@ + + + + + + + + + + From 29e73e89effa2cdd19a61d7a61ae042a71ceccee Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Wed, 31 Dec 2014 23:29:35 -0500 Subject: [PATCH 07/47] adding tests functions are defined in two different test cases. After they've run, I can call the functions defined in them from a third test case. I can call the functions as many times as I want to from there. Things get weird when I'm trying to call the functions from the test case they're defined in. --- sel-blocksTests/CallGlobalFunction.html | 5 ++ sel-blocksTests/GlobalFunctions 2.html | 46 +++++++++++++++++++ sel-blocksTests/GlobalFunctionsTestSuite.html | 1 + 3 files changed, 52 insertions(+) create mode 100644 sel-blocksTests/GlobalFunctions 2.html diff --git a/sel-blocksTests/CallGlobalFunction.html b/sel-blocksTests/CallGlobalFunction.html index a511df7..108bf33 100644 --- a/sel-blocksTests/CallGlobalFunction.html +++ b/sel-blocksTests/CallGlobalFunction.html @@ -35,6 +35,11 @@ + + + + +
setGlobalVar
openhttp://www.google.com
getEval globalStoredVars.aGlobalVar = "set";
openhttp://www.yahoo.com
endFunction globalStoredVars.aGlobalVar; set
calldoStuff
diff --git a/sel-blocksTests/GlobalFunctions 2.html b/sel-blocksTests/GlobalFunctions 2.html new file mode 100644 index 0000000..80832c9 --- /dev/null +++ b/sel-blocksTests/GlobalFunctions 2.html @@ -0,0 +1,46 @@ + + + + + + +GlobalFunctions 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GlobalFunctions 2
functiondoStuff
openhttps://github.com/
openhttp://en.wikipedia.org/wiki/Chili
endFunction
calldoStuff
assertLocationhttp://en.wikipedia.org/wiki/Chili
+ + diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index e088f8e..2a581a0 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -10,6 +10,7 @@ Test Suite CreateGlobalVars GlobalFunctions +GlobalFunctions 2 CallGlobalFunction From e5a8f4e2b628c1ff76ac5a813ee4b19142b54532 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 1 Jan 2015 17:44:56 -0500 Subject: [PATCH 08/47] revert selblocks.js to master I've got better ideas about how to handle global functions. Throwing out the selblocks global stuff I merged in and reverting to the current master version. --- .../chrome/content/extensions/selblocks.js | 746 +++++------------- 1 file changed, 217 insertions(+), 529 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index f05db3c..f6ba739 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -123,18 +123,19 @@ function $X(xpath, contextNode, resultType) { // Return a translated version of a string // given string args, translate each occurrence of characters in t1 with the corresponding character from t2 // given array args, if the string occurs in t1, return the corresponding string from t2, else null - String.prototype.translate = function (t1, t2) { + String.prototype.translate = function(t1, t2) + { assert(t1.constructor === t2.constructor, "translate() function requires arrays of the same type"); assert(t1.length === t2.length, "translate() function requires arrays of equal size"); var i; if (t1.constructor === String) { var buf = ""; for (i = 0; i < this.length; i++) { - var c = this.substr(i, 1); + var c = this.substr(i,1); var t; for (t = 0; t < t1.length; t++) { - if (c === t1.substr(t, 1)) { - c = t2.substr(t, 1); + if (c === t1.substr(t,1)) { + c = t2.substr(t,1); break; } } @@ -149,225 +150,39 @@ function $X(xpath, contextNode, resultType) { return t2[i]; } } - } else { + } + else { assert(false, "translate() function requires arguments of type String or Array"); } return null; }; - // ----- SelBlocksGlobal: - /** @param TestCase optional - * @return int 0-based index of given test case within the list of test cases - * of the test suite - **/ - function testCaseIdx(givenTestCase) { - var msg, - caseIndex; - givenTestCase = givenTestCase || testCase; - // Must not use assert() here, because that calls notifyFatalHere() which calls idxHere() - // which calls globIdx() which calls testCaseIdx() - if (typeof givenTestCase !== 'object') { - msg = "SelBlocks error: in testCaseIdx(), param givenTestCase is not an object, neither global testCase is."; - LOG.error(msg); - throw new Error(msg); - } - if (editor.app.testSuite.tests.length === 0) { - msg = "SelBlocks error: in testCaseIdx(), bad editor.app.testSuite.tests.length===0."; - LOG.error(msg); - throw new Error(msg); - } - for (caseIndex = editor.app.testSuite.tests.length - 1; caseIndex >= 0; caseIndex--) { - if (editor.app.testSuite.tests[caseIndex].content === givenTestCase) { - break; - } - } - if (caseIndex < 0) { - msg = "SelBlocks error: in testCaseIdx(), givenTestCase was not matched."; - LOG.error(msg); - throw new Error(msg); - } - return caseIndex; - } - - function logAndThrow(msg) { - var error = new Error(msg); - LOG.error(msg + "\n" + error.stack); - throw error; - } - /** This serves to generate unique global identifiers for test script commands. - * Results of this functions are usually values of symbols[] and other structures. - * @param {number} commandIndex 0-based index within givenTestCase (or within testCase). - * @param {TestCase} [givenTestCase] optional; using (current) testCase by default - // I'd rather use objects, but Javascript doesn't compare objects field by field - // - try javascript:a={first: 1}; b={first: 1}; a==b - @returns {string} global index of the command, in form testCaseIndex/commandIndex - */ - function globIdx(commandIndex, givenTestCase) { - givenTestCase = givenTestCase || testCase; - // Must not use assert() here, because that calls notifyFatalHere() which calls idxHere() which calls globIdx() - if (typeof commandIndex !== 'number' || commandIndex < 0) { - logAndThrow("SelBlocks error: in globIdx(), bad type/value of the first parameter commandIndex: " + commandIndex); - } - if (typeof givenTestCase !== 'object') { - logAndThrow("SelBlocks error: in globIdx(), bad type of the optional second parameter givenTestCase (or global testCase)."); - } - var caseIndex = testCaseIdx(givenTestCase); - return '' + caseIndex + '/' + commandIndex; - } - /** @return {number} (not a Number object) 0-based index of the respective command within its test case - * @param {string} globIdxValue Global index of a test command (test step). - */ - function localIdx(globIdxValue) { - var msg, - lastSlashIndex; - // Can't use assert() here, since assert indirectly calls fmtCmdRef() which calls localIdx() - recursion - if (typeof globIdxValue !== 'string') { - msg = 'globIdxValue must be a string, but got ' + (typeof globIdxValue) + ': ' + globIdxValue; - LOG.error(msg); - throw new Error(msg); - } - lastSlashIndex = globIdxValue.lastIndexOf('/'); - if (lastSlashIndex <= 0) { - msg = 'globIdxValue must contain "/" and not as the first character.'; - LOG.error(msg); - throw new Error(msg); - } - if (lastSlashIndex >= globIdxValue.length) { - msg = 'globIdxValue must contain "/" and not as the last character.'; - LOG.error(msg); - throw new Error(msg); - } - var afterSlash = globIdxValue.substr(lastSlashIndex + 1); - var afterSlashNumber = Number(afterSlash); - if (afterSlash !== '' + afterSlashNumber) { - msg = 'The part after "/" must be numeric.'; - LOG.error(msg); - throw new Error(msg); - } - var result = afterSlashNumber.valueOf(); - //"TODO:" - if (result < 0 || result >= editor.app.testSuite.tests[localCaseIdxPart(globIdxValue)].content.commands.length) { - msg = 'In localIdx("' + globIdxValue + '"), result ' + result + ' is not a valid command index'; - LOG.error(msg); - throw new Error(msg); - } - return result; - } - /**@param string result of globIdx() or of labelIdx() - * @return {number} (not a Number object) 0-based index of the test case (for the given global index) - * within the list of test cases (i.e. editor.app.testSuite.tests) - */ - function localCaseIdxPart(globIdxValue) { - assert(typeof globIdxValue === 'string', 'globIdxValue must be a string.'); - var lastSlashIndex = globIdxValue.lastIndexOf('/'); - assert(lastSlashIndex > 0, 'globIdxValue must contain "/" and not as the first character.'); - assert(lastSlashIndex < globIdxValue.length - 1, 'globIdxValue must contain "/" and not as the last character.'); - var beforeSlash = globIdxValue.substring(0, globIdxValue.lastIndexOf('/')); - var beforeSlashNumber = Number(beforeSlash); - assert('' + beforeSlash === '' + beforeSlashNumber, 'The part after "/" must be numeric.'); - var result = beforeSlashNumber.valueOf(); - assert(result >= 0 && result < editor.app.testSuite.tests.length, 'result not a valid index into editor.app.testSuite.tests.'); - return result; - } - /** global array of _usable_ test cases, set in compileSelBlocks(). - * It contains test cases in the same order as in editor.app.testSuite.tests[], - * but here they are as they come from editor.getTestCase() - **/ - var testCases = []; - - // @return TestCase test case for the given global index - function localCase(globIdxValue) { - var index = localCaseIdxPart(globIdxValue); - assert(index < testCases.length, 'case index: ' + index + ' but testCases[] has length ' + testCases.length); - return testCases[index]; - /* Following didn't work: - return editor.app.testSuite.tests[ localCaseIdxPart(globIdxValue) ].content; - */ - } - /** @return {Object} Command structure for given global index - * */ - function localCommand(globIdxValue) { - return localCase(globIdxValue).commands[localIdx(globIdxValue)]; - } - - /** This serves to generate and compare keys in symbols[] for label commands - * @param string label name - * @param TestCase test case where the label is; optional - using testCase by default - * @return string global label identifier in form 'test-case-index/label' - **/ - function labelIdx(label, givenTestCase) { - assert(typeof label === 'string', 'label must be a string.'); - givenTestCase = givenTestCase || testCase; - return '' + testCaseIdx(givenTestCase) + '/' + label; - } - // @TODO on insert, validate that function names are unique, i.e. no function overriding //=============== Call/Scope Stack handling =============== - /** @var object symbols */ - var symbols = {}; // command indexes stored by name: function names - /** @var {BlockDefs} Static command definitions stored by command index. Global, used for all test cases. */ - var blockDefs = null; // static command definitions stored by command index - /** @var {Stack} callStack Command execution stack */ - var callStack = null; // command execution stack + var symbols = {}; // command indexes stored by name: function names + var blockDefs = null; // static command definitions stored by command index + var callStack = null; // command execution stack // the idx of the currently executing command - // SelBlocksGlobal added param relativeShift and made it return a global, cross-test case index, rather than local (test-case specific) index - /** @param {number} [relativeShift=0] Relative shift to the current command's position - * @return {string} global command index - * */ - function idxHere(relativeShift) { - // Must not use assert() here, because that calls notifyFatalHere() which calls idxHere() - return globIdx(localIdxHere(relativeShift)); - } - /** @param {number} [relativeShift=0] Relative shift to the current command's position - * @return {number} Current command's position (within current test case), adjusted by relativeShift. Depending on relativeShift the result may not be a valid position. - * */ - function localIdxHere(relativeShift) { - relativeShift = relativeShift || 0; - return testCase.debugContext.debugIndex + relativeShift; + function idxHere() { + return testCase.debugContext.debugIndex; } // Command structure definitions, stored by command index - // SelBlocksGlobal: stored by command global index - i.e. value of idxHere() function BlockDefs() { - //@TODO use this.xxx=yyy, and define init() on BlockDefs.prototype. Then NetBeans navigation is easier. - /** @var {object} Serving as an associative array {globIdx => object of {any attributes, idx, cmdName}}. SelBlocksGlobal changed this from an array to an object. */ - var blkDefs = {}; - // initialize an entry in BlockDefs instance at the given command global index - /** @param {string} i Global index, a result of globIdx() function - * @param {Object} [attrs] Extra details to add, depending on the command: - * nature: 'if', 'try', 'loop', 'function' - * elseIfIdxs - array, used for 'if' - * ifIdx - used for 'else', 'elseIf' and 'endIf'; it's a global index of the matching 'if' step - * name - used for 'try', it's 'target' of the step (the 2nd column in Selenium IDE) - * tryIdx - used by 'catch', 'finally' and 'endTry', index of the matching 'try' - * finallyIdx - * beginIdx - used by 'continue', 'break', endWhile, endFor, endForeach, endForJson, endForXml; - * it's an index of the start of the current loop - * endIdx - * funcIdx - used by 'return', 'endfunction', 'endScript' - * @TODO check beginIdx and other fields - set after calls to blkDefFor(), blkDefAt() - * @return {object} A new entry just added to this collection. - * @see variable blkDefs - **/ - blkDefs.init = function BlockDefsInit(i, attrs) { - assert(typeof testCase.commands === 'object', 'BlockDefs::init() - testCase.commands is of bad type.'); - // @TODO assert regex numeric/numeric - assert(typeof i === 'string', 'BlockDefs::init() - param i must be a globIdx() result.'); - // @TODO change to use 'this' instead of 'blkDefs' - it will be clearer. + var blkDefs = []; + // initialize blockDef at the given command index + blkDefs.init = function(i, attrs) { blkDefs[i] = attrs || {}; blkDefs[i].idx = i; - // Following line is from original SelBlocks, here just for documentation - //blkDefs[i].cmdName = testCase.commands[i].command; - blkDefs[i].cmdName = localCase(i).commands[localIdx(i)].command; + blkDefs[i].cmdName = testCase.commands[i].command; return blkDefs[i]; }; return blkDefs; } // retrieve the blockDef at the given command idx - /** @param {string} idx Global index of a test step. */ function blkDefAt(idx) { return blockDefs[idx]; } @@ -386,24 +201,18 @@ function $X(xpath, contextNode, resultType) { // An Array object with stack functionality function Stack() { var stack = []; - stack.isEmpty = function isEmpty() { - return stack.length === 0; - }; - stack.top = function top() { - return stack[stack.length - 1]; - }; - stack.findEnclosing = function findEnclosing(_hasCriteria) { - return stack[stack.indexWhere(_hasCriteria)]; - }; - stack.indexWhere = function indexWhere(_hasCriteria) { // undefined if not found + stack.isEmpty = function() { return stack.length === 0; }; + stack.top = function() { return stack[stack.length-1]; }; + stack.findEnclosing = function(_hasCriteria) { return stack[stack.indexWhere(_hasCriteria)]; }; + stack.indexWhere = function(_hasCriteria) { // undefined if not found var i; - for (i = stack.length - 1; i >= 0; i--) { + for (i = stack.length-1; i >= 0; i--) { if (_hasCriteria(stack[i])) { return i; } } }; - stack.unwindTo = function unwindTo(_hasCriteria) { + stack.unwindTo = function(_hasCriteria) { if (stack.length === 0) { return null; } @@ -412,35 +221,23 @@ function $X(xpath, contextNode, resultType) { } return stack.top(); }; - stack.isHere = function isHere() { + stack.isHere = function() { return (stack.length > 0 && stack.top().idx === idxHere()); }; return stack; } // Determine if the given stack frame is one of the given block kinds - Stack.isTryBlock = function (stackFrame) { - return (blkDefFor(stackFrame).nature === "try"); - }; - Stack.isLoopBlock = function (stackFrame) { - return (blkDefFor(stackFrame).nature === "loop"); - }; - Stack.isFunctionBlock = function (stackFrame) { - return (blkDefFor(stackFrame).nature === "function"); - }; + Stack.isTryBlock = function(stackFrame) { return (blkDefFor(stackFrame).nature === "try"); }; + Stack.isLoopBlock = function(stackFrame) { return (blkDefFor(stackFrame).nature === "loop"); }; + Stack.isFunctionBlock = function(stackFrame) { return (blkDefFor(stackFrame).nature === "function"); }; + // Flow control - we don't just alter debugIndex on the fly, because the command // preceding the destination would falsely get marked as successfully executed - // SelBLocksGlobal: This is a global index of the next command - set to a result of globIdx() var branchIdx = null; // if testCase.nextCommand() ever changes, this will need to be revisited // (current as of: selenium-ide-2.4.0) - // See Selenium's {a6fd85ed-e919-4a43-a5af-8da18bda539f}/chrome/content/testCase.js - // This is for a head-intercept of TestCaseDebugContext.prototype.nextCommand(), and it adds support for SelBlocksGlobal branches (across test cases). - // We can't redefine/tail-intercept testCase.debugContext.nextCommand() at the time - // this SelBlocksGlobal source file is loaded, because testCase is not defined yet. Therefore we do it here - // on the first run of the enclosing tail intercept of Selenium.prototype.reset() below. - // And we intercept do it on the prototype, so that it applies to any test cases. function nextCommand() { if (!this.started) { this.started = true; @@ -449,14 +246,11 @@ function $X(xpath, contextNode, resultType) { else { if (branchIdx !== null) { $$.LOG.info("branch => " + fmtCmdRef(branchIdx)); - this.debugIndex = localIdx(branchIdx); - - testCase= this.testCase= localCase(branchIdx); - testCase.debugContext= this; + this.debugIndex = branchIdx; branchIdx = null; } else { - this.debugIndex++; // global removed this + this.debugIndex++; } } // skip over comments @@ -469,17 +263,12 @@ function $X(xpath, contextNode, resultType) { } return null; } - /** @param {string} globIdx value */ function setNextCommand(cmdIdx) { - var idx= localIdx(cmdIdx); - var localTestCase= localCase(cmdIdx); - assert( idx >= 0 && idx < localTestCase.commands.length, - " Cannot branch to non-existent command @" +cmdIdx ); + assert(cmdIdx >= 0 && cmdIdx < testCase.commands.length, + " Cannot branch to non-existent command @" + (cmdIdx+1)); branchIdx = cmdIdx; } -(function () { // wrapper makes testCaseDebugContextWasIntercepted private - var testCaseDebugContextWasIntercepted; // undefined or true // Selenium calls reset(): // * before each single (double-click) command execution // * before a testcase is run @@ -498,98 +287,37 @@ function $X(xpath, contextNode, resultType) { callStack.push({ blockStack: new Stack() }); // top-level execution state $$.tcf = { nestingLevel: -1 }; // try/catch/finally nesting + // customize flow control logic + // TBD: this should be a tail intercept rather than brute force replace $$.LOG.debug("Configuring tail intercept: testCase.debugContext.nextCommand()"); $$.fn.interceptReplace(testCase.debugContext, "nextCommand", nextCommand); - - //if( testCaseDebugContextWasIntercepted===undefined ) { - // // SelBlocksGlobal: This is a head-intercept, rather than interceptReplace as in SelBlocks 2.0.1, - // // and I'm intercepting TestCaseDebugContext.prototype.nextCommand() rather than testCase.debugContext.nextCommand() - // $$.LOG.debug("Configuring head intercept: TestCaseDebugContext.prototype.nextCommand()"); - // $$.fn.interceptBefore(TestCaseDebugContext.prototype, "nextCommand", nextCommand); - // - // testCaseDebugContextWasIntercepted= true; - //} }); -}()); // get the blockStack for the currently active callStack function activeBlockStack() { return callStack.top().blockStack; } + // ================================================================================ // Assemble block relationships and symbol locations function compileSelBlocks() { - symbols= {}; // Let's clear symbols - // Currently, this is called multiple times when Se IDE runs the whole test suite - // - once per each test case. No harm in that, only a bit of wasted CPU. - - //alert( 'testCase===editor.suiteTreeView.getCurrentTestCase(): ' +(testCase===editor.suiteTreeView.getCurrentTestCase()) ); // --> false! - //alert( 'testCase===editor.getTestCase(): ' +(testCase===editor.getTestCase()) ); //--> true! - var testCaseOriginal= testCase; - testCaseOriginal= editor.getTestCase(); - var testCaseOriginalIndex= -1; - testCases= []; - //alert( 'editor.app.getTestSuite()===editor.app.testSuite: ' +editor.app.getTestSuite()===editor.app.testSuite ); // => false - //alert( 'editor.app.testSuite.tests.indexOf( testCase): ' +editor.app.testSuite.tests.indexOf( testCase) ); // => -1 - // SelBlocksGlobal: I set blockDefs before looping through test cases, because it's global - for all test cases blockDefs = new BlockDefs(); - for( var testCaseIndex=0; testCaseIndex=0, "testCaseOriginalIndex mut be non-negative!"); - // In the following, do not pass testCases[testCaseOriginalIndex]. - // is not the same as editor.app.getTestSuite().tests[testCaseOriginalIndex], because - // Application.prototype.showTestCaseFromSuite(givenTestCase) calls this.setTestCase(givenTestCase.content) - editor.app.showTestCaseFromSuite( editor.app.getTestSuite().tests[testCaseOriginalIndex] ); - testCase.debugContext.testCase= testCase; - } - // end of compileSelBlocks() - // SelBlocksGlobal: Following three functions were inside compileSelBlocksTestCase(), but that doesn't follow JS strict mode. Two of them didn't have to be closures. assertBlockIsPending() used to be a closure, now it received parameter lexStack and is not a closure anymore. - //- command validation - function assertNotAndWaitSuffix(cmdIdx) { - assertCmd(cmdIdx, localCase(cmdIdx).commands[ localIdx(cmdIdx) ].command.indexOf("AndWait") === -1, - ", AndWait suffix is not valid for SelBlocks commands"); - } - //- active block validation - function assertBlockIsPending(lexStack, expectedCmd, cmdIdx, desc) { - assertCmd(cmdIdx, !lexStack.isEmpty(), desc || ", without an beginning [" + expectedCmd + "]"); - } - //- command-pairing validation - function assertMatching(curCmd, expectedCmd, cmdIdx, pendIdx) { - assertCmd(cmdIdx, curCmd === expectedCmd, ", does not match command " + fmtCmdRef(pendIdx)); - } - // SelBlocksGlobal factored the following out of SelBlocks' compileSelBlocks(), to make 'testCase' not refer - // to global testCase - function compileSelBlocksTestCase( testCase ) { - // SelBlocksGlobal: I set lexStack here, since it's local stack per testCase (and only used during compilation) var lexStack = new Stack(); - // SelBlocksGlobal: Following loop variable commandIndex was renamed from 'i' in SelBlocks. - // I set variable 'i' to globIdx() value of commandIndex (i.e. of the original intended value of 'i'). This way - // the original SelBlocks code still uses variable 'i', so there are less merge conflicts. - var commandIndex, i; - for (commandIndex = 0; commandIndex < testCase.commands.length; commandIndex++) + var i; + for (i = 0; i < testCase.commands.length; i++) { - if (testCase.commands[commandIndex].type === "command") + if (testCase.commands[i].type === "command") { - var curCmd = testCase.commands[commandIndex].command; + var curCmd = testCase.commands[i].command; var aw = curCmd.indexOf("AndWait"); if (aw !== -1) { // just ignore the suffix for now, this may or may not be a SelBlocks commands curCmd = curCmd.substring(0, aw); } - var cmdTarget = testCase.commands[commandIndex].target; - i= globIdx(commandIndex, testCase); + var cmdTarget = testCase.commands[i].target; + var ifDef; var tryDef; var expectedCmd; @@ -597,7 +325,7 @@ function $X(xpath, contextNode, resultType) { { case "label": assertNotAndWaitSuffix(i); - symbols[ labelIdx(cmdTarget, testCase) ] = i; + symbols[cmdTarget] = i; break; case "goto": case "gotoIf": case "skipNext": assertNotAndWaitSuffix(i); @@ -609,7 +337,7 @@ function $X(xpath, contextNode, resultType) { break; case "elseIf": assertNotAndWaitSuffix(i); - assertBlockIsPending(lexStack, "elseIf", i, ", is not valid outside of an if/endIf block"); + assertBlockIsPending("elseIf", i, ", is not valid outside of an if/endIf block"); ifDef = lexStack.top(); assertMatching(ifDef.cmdName, "if", i, ifDef.idx); var eIdx = blkDefFor(ifDef).elseIdx; @@ -621,7 +349,7 @@ function $X(xpath, contextNode, resultType) { break; case "else": assertNotAndWaitSuffix(i); - assertBlockIsPending(lexStack, "if", i, ", is not valid outside of an if/endIf block"); + assertBlockIsPending("if", i, ", is not valid outside of an if/endIf block"); ifDef = lexStack.top(); assertMatching(ifDef.cmdName, "if", i, ifDef.idx); if (blkDefFor(ifDef).elseIdx) { @@ -632,7 +360,7 @@ function $X(xpath, contextNode, resultType) { break; case "endIf": assertNotAndWaitSuffix(i); - assertBlockIsPending(lexStack, "if", i); + assertBlockIsPending("if", i); ifDef = lexStack.pop(); assertMatching(ifDef.cmdName, "if", i, ifDef.idx); blockDefs.init(i, { ifIdx: ifDef.idx }); // endIf -> if @@ -648,7 +376,7 @@ function $X(xpath, contextNode, resultType) { break; case "catch": assertNotAndWaitSuffix(i); - assertBlockIsPending(lexStack, "try", i, ", is not valid without a try block"); + assertBlockIsPending("try", i, ", is not valid without a try block"); tryDef = lexStack.top(); assertMatching(tryDef.cmdName, "try", i, tryDef.idx); if (blkDefFor(tryDef).catchIdx) { @@ -663,7 +391,7 @@ function $X(xpath, contextNode, resultType) { break; case "finally": assertNotAndWaitSuffix(i); - assertBlockIsPending(lexStack, "try", i); + assertBlockIsPending("try", i); tryDef = lexStack.top(); assertMatching(tryDef.cmdName, "try", i, tryDef.idx); if (blkDefFor(tryDef).finallyIdx) { @@ -677,7 +405,7 @@ function $X(xpath, contextNode, resultType) { break; case "endTry": assertNotAndWaitSuffix(i); - assertBlockIsPending(lexStack, "try", i); + assertBlockIsPending("try", i); tryDef = lexStack.pop(); assertMatching(tryDef.cmdName, "try", i, tryDef.idx); if (cmdTarget) { @@ -702,7 +430,7 @@ function $X(xpath, contextNode, resultType) { case "endWhile": case "endFor": case "endForeach": case "endForJson": case "endForXml": assertNotAndWaitSuffix(i); expectedCmd = curCmd.substr(3).toLowerCase(); - assertBlockIsPending(lexStack, expectedCmd, i); + assertBlockIsPending(expectedCmd, i); var beginDef = lexStack.pop(); assertMatching(beginDef.cmdName.toLowerCase(), expectedCmd, i, beginDef.idx); blkDefFor(beginDef).endIdx = i; // begin -> end @@ -724,14 +452,14 @@ function $X(xpath, contextNode, resultType) { break; case "return": assertNotAndWaitSuffix(i); - assertBlockIsPending(lexStack, "function", i, ", is not valid outside of a function/endFunction block"); + assertBlockIsPending("function", i, ", is not valid outside of a function/endFunction block"); var funcCmd = lexStack.findEnclosing(Stack.isFunctionBlock); blockDefs.init(i, { funcIdx: funcCmd.idx }); // return -> function break; case "endFunction": case "endScript": assertNotAndWaitSuffix(i); expectedCmd = curCmd.substr(3).toLowerCase(); - assertBlockIsPending(lexStack, expectedCmd, i); + assertBlockIsPending(expectedCmd, i); var funcDef = lexStack.pop(); assertMatching(funcDef.cmdName.toLowerCase(), expectedCmd, i, funcDef.idx); if (cmdTarget) { @@ -759,7 +487,20 @@ function $X(xpath, contextNode, resultType) { } throw new SyntaxError(cmdErrors.join("; ")); } - } // end of compileSelBlocksTestCase() + //- command validation + function assertNotAndWaitSuffix(cmdIdx) { + assertCmd(cmdIdx, (testCase.commands[cmdIdx].command.indexOf("AndWait") === -1), + ", AndWait suffix is not valid for SelBlocks commands"); + } + //- active block validation + function assertBlockIsPending(expectedCmd, cmdIdx, desc) { + assertCmd(cmdIdx, !lexStack.isEmpty(), desc || ", without an beginning [" + expectedCmd + "]"); + } + //- command-pairing validation + function assertMatching(curCmd, expectedCmd, cmdIdx, pendIdx) { + assertCmd(cmdIdx, curCmd === expectedCmd, ", does not match command " + fmtCmdRef(pendIdx)); + } + } // -------------------------------------------------------------------------------- @@ -857,7 +598,7 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doSkipNext = function(spec) { assertRunning(); - var n = parseInt(this.evalWithVars(spec), 10); + var n = parseInt(evalWithVars(spec), 10); if (isNaN(n)) { if (spec.trim() === "") { n = 1; } else { notifyFatalHere(" Requires a numeric value"); } @@ -867,8 +608,8 @@ function $X(xpath, contextNode, resultType) { } if (n !== 0) { // if n=0, execute the next command as usual - destIdx = globIdx(localIdxHere()+n+1); - assertIntraBlockJumpRestriction(localIdxHere(), localIdxHere()+n+1); + destIdx = idxHere() + n + 1; + assertIntraBlockJumpRestriction(idxHere(), destIdx); setNextCommand(destIdx); } }; @@ -876,16 +617,15 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doGoto = function(label) { assertRunning(); - var symbolIndex= labelIdx(label); - assert(symbols[symbolIndex], " Target label '" + label + "' is not found."); - assertIntraBlockJumpRestriction(localIdxHere(), localIdx(symbols[symbolIndex])); - setNextCommand(symbols[symbolIndex]); + assert(symbols[label]!==undefined, " Target label '" + label + "' is not found."); + assertIntraBlockJumpRestriction(idxHere(), symbols[label]); + setNextCommand(symbols[label]); }; Selenium.prototype.doGotoIf = function(condExpr, label) { assertRunning(); - if (this.evalWithVars(condExpr)) { + if (evalWithVars(condExpr)) { this.doGoto(label); } }; @@ -897,7 +637,7 @@ function $X(xpath, contextNode, resultType) { var ifDef = blkDefHere(); var ifState = { idx: idxHere(), elseIfItr: arrayIterator(ifDef.elseIfIdxs) }; activeBlockStack().push(ifState); - this.cascadeElseIf(ifState, condExpr); + cascadeElseIf(ifState, condExpr); }; Selenium.prototype.doElseIf = function(condExpr) { @@ -908,7 +648,7 @@ function $X(xpath, contextNode, resultType) { setNextCommand(blkDefAt(blkDefHere().ifIdx).endIdx); } else { - this.cascadeElseIf(ifState, condExpr); + cascadeElseIf(ifState, condExpr); } }; Selenium.prototype.doElse = function() @@ -928,9 +668,9 @@ function $X(xpath, contextNode, resultType) { // fall out of if-endIf }; - Selenium.prototype.cascadeElseIf = function cascadeElseIf(ifState, condExpr) { - this.assertCompilable("", condExpr, ";", "Invalid condition"); - if (!this.evalWithVars(condExpr)) { + function cascadeElseIf(ifState, condExpr) { + assertCompilable("", condExpr, ";", "Invalid condition"); + if (!evalWithVars(condExpr)) { // jump to next elseIf or else or endif var ifDef = blkDefFor(ifState); if (ifState.elseIfItr.hasNext()) { setNextCommand(ifState.elseIfItr.next()); } @@ -941,13 +681,13 @@ function $X(xpath, contextNode, resultType) { ifState.skipElseBlocks = true; // continue into if/elseIf block } - }; + } // ================================================================================ // throw the given Error Selenium.prototype.doThrow = function(err) { - err = this.evalWithVars(err); + err = evalWithVars(err); if (!(err instanceof Error)) { err = new SelblocksError(idxHere(), err); } @@ -971,7 +711,7 @@ function $X(xpath, contextNode, resultType) { // log an advisory about the active catch block if (tryDef.catchIdx) { - var errDcl = localCommand( tryDef.catchIdx ).target; + var errDcl = testCase.commands[tryDef.catchIdx].target; $$.LOG.debug(tryName + " catchable: " + (errDcl || "ANY")); } @@ -981,7 +721,7 @@ function $X(xpath, contextNode, resultType) { if ($$.tcf.nestingLevel === 0) { // enable special command handling $$.fn.interceptPush(editor.selDebugger.runner.currentTest, "resume", - $$.handleAsTryBlock, { manageError: this.handleCommandError }); + $$.handleAsTryBlock, { manageError: handleCommandError }); } $$.LOG.debug("++ try nesting: " + $$.tcf.nestingLevel); // continue into try-block @@ -1027,7 +767,7 @@ function $X(xpath, contextNode, resultType) { // $$.tcf.bubbling = null; } if ($$.tcf.bubbling) { - this.reBubble(); + reBubble(); } else { $$.LOG.debug("no bubbling in process"); @@ -1042,14 +782,14 @@ function $X(xpath, contextNode, resultType) { // alter the behavior of Selenium error handling // returns true if catch/finally bubbling is active - Selenium.prototype.handleCommandError = function handleCommandError(err) + function handleCommandError(err) { var tryState = bubbleToTryBlock(Stack.isTryBlock); var tryDef = blkDefFor(tryState); if (tryState) { $$.LOG.debug("error encountered while: " + tryState.execPhase); - if (this.hasUnspentCatch(tryState)) { - if (this.isMatchingCatch(err, tryDef.catchIdx)) { + if (hasUnspentCatch(tryState)) { + if (isMatchingCatch(err, tryDef.catchIdx)) { // an expected kind of error has been caught $$.LOG.info("@" + (idxHere()+1) + ", error has been caught" + fmtCatching(tryState)); tryState.hasCaught = true; @@ -1064,7 +804,7 @@ function $X(xpath, contextNode, resultType) { // error not caught .. instigate bubbling $$.LOG.debug("error not caught, bubbling error: '" + err.message + "'"); $$.tcf.bubbling = { mode: "error", error: err, srcIdx: idxHere() }; - if (self.hasUnspentFinally(tryState)) { + if (hasUnspentFinally(tryState)) { $$.LOG.info("Bubbling suspended while finally block runs"); tryState.execPhase = "finallying"; tryState.hasFinaled = true; @@ -1078,30 +818,15 @@ function $X(xpath, contextNode, resultType) { } $$.LOG.info("No handling provided in this try section for this error: '" + err.message + "'"); return false; // stop test - }; + } // execute any enclosing finally block(s) until reaching the given type of enclosing block - Selenium.prototype.bubbleCommand = function bubbleCommand(cmdIdx, _isContextBlockType) + function bubbleCommand(cmdIdx, _isContextBlockType) { - var self= this; - //- determine if catch matches an error, or there is a finally, or the ceiling block has been reached - function isTryWithMatchingOrFinally(stackFrame) { - if (_isContextBlockType && _isContextBlockType(stackFrame)) { - return true; - } - if ($$.tcf.bubbling && $$.tcf.bubbling.mode === "error" && self.hasUnspentCatch(stackFrame)) { - var tryDef = blkDefFor(stackFrame); - if (self.isMatchingCatch($$.tcf.bubbling.error, tryDef.catchIdx)) { - return true; - } - } - return self.hasUnspentFinally(stackFrame); - } - var tryState = bubbleToTryBlock(isTryWithMatchingOrFinally); var tryDef = blkDefFor(tryState); $$.tcf.bubbling = { mode: "command", srcIdx: cmdIdx, _isStopCriteria: _isContextBlockType }; - if (self.hasUnspentFinally(tryState)) { + if (hasUnspentFinally(tryState)) { $$.LOG.info("Command " + fmtCmdRef(cmdIdx) + ", suspended while finally block runs"); tryState.execPhase = "finallying"; tryState.hasFinaled = true; @@ -1113,21 +838,35 @@ function $X(xpath, contextNode, resultType) { setNextCommand(tryDef.endIdx); // jump out of try section } - }; + + //- determine if catch matches an error, or there is a finally, or the ceiling block has been reached + function isTryWithMatchingOrFinally(stackFrame) { + if (_isContextBlockType && _isContextBlockType(stackFrame)) { + return true; + } + if ($$.tcf.bubbling && $$.tcf.bubbling.mode === "error" && hasUnspentCatch(stackFrame)) { + var tryDef = blkDefFor(stackFrame); + if (isMatchingCatch($$.tcf.bubbling.error, tryDef.catchIdx)) { + return true; + } + } + return hasUnspentFinally(stackFrame); + } + } //- error message matcher - Selenium.prototype.isMatchingCatch = function isMatchingCatch(e, catchIdx) { - var errDcl = localCommand( catchIdx ).target; + function isMatchingCatch(e, catchIdx) { + var errDcl = testCase.commands[catchIdx].target; if (!errDcl) { return true; // no error specified means catch all errors } - var errExpr = this.evalWithVars(errDcl); + var errExpr = evalWithVars(errDcl); var errMsg = e.message; if (errExpr instanceof RegExp) { return (errMsg.match(errExpr)); } return (errMsg.indexOf(errExpr) !== -1); - }; + } // unwind the blockStack, and callStack (ie, aborting functions), until reaching the given criteria function bubbleToTryBlock(_hasCriteria) { @@ -1137,12 +876,6 @@ function $X(xpath, contextNode, resultType) { var tryState = unwindToBlock(_hasCriteria); while (!tryState && $$.tcf.nestingLevel > -1 && callStack.length > 1) { var callFrame = callStack.pop(); - var _result= storedVars._result; - storedVars= callFrame.savedVars; - storedVars._result= _result; - testCase= callFrame.testCase; - testCase.debugContext.testCase= testCase; - testCase.debugContext.debugIndex = localIdx( callFrame.returnIdx ); $$.LOG.info("function '" + callFrame.name + "' aborting due to error"); restoreVarState(callFrame.savedVars); tryState = unwindToBlock(_hasCriteria); @@ -1160,11 +893,11 @@ function $X(xpath, contextNode, resultType) { } // resume or conclude command/error bubbling - Selenium.prototype.reBubble = function reBubble() { + function reBubble() { if ($$.tcf.bubbling.mode === "error") { if ($$.tcf.nestingLevel > -1) { $$.LOG.debug("error-bubbling continuing..."); - this.handleCommandError($$.tcf.bubbling.error); + handleCommandError($$.tcf.bubbling.error); } else { $$.LOG.error("Error was not caught: '" + $$.tcf.bubbling.error.message + "'"); @@ -1175,7 +908,7 @@ function $X(xpath, contextNode, resultType) { else { // mode == "command" if (isBubblable()) { $$.LOG.debug("command-bubbling continuing..."); - this.bubbleCommand($$.tcf.bubbling.srcIdx, $$.tcf.bubbling._isStopCriteria); + bubbleCommand($$.tcf.bubbling.srcIdx, $$.tcf.bubbling._isStopCriteria); } else { $$.LOG.info("command-bubbling complete - suspended command executing now " + fmtCmdRef($$.tcf.bubbling.srcIdx)); @@ -1183,10 +916,10 @@ function $X(xpath, contextNode, resultType) { $$.tcf.bubbling = null; } } - }; + } // instigate or transform bubbling, as appropriate - Selenium.prototype.transitionBubbling = function transitionBubbling(_isContextBlockType) + function transitionBubbling(_isContextBlockType) { if ($$.tcf.bubbling) { // transform bubbling if ($$.tcf.bubbling.mode === "error") { @@ -1202,35 +935,35 @@ function $X(xpath, contextNode, resultType) { return true; } if (isBubblable(_isContextBlockType)) { // instigate bubbling - this.bubbleCommand(idxHere(), _isContextBlockType); + bubbleCommand(idxHere(), _isContextBlockType); return true; } // no change to bubbling return false; - }; + } // determine if bubbling is possible from this point outward function isBubblable(_isContextBlockType) { var canBubble = ($$.tcf.nestingLevel > -1); if (canBubble) { - var blkState = activeBlockStack().findEnclosing( - //- determine if stackFrame is a try-block or the given type of block - function isTryOrContextBlockType(stackFrame) { - if (_isContextBlockType && _isContextBlockType(stackFrame)) { - return true; - } - return Stack.isTryBlock(stackFrame); - } - ); + var blkState = activeBlockStack().findEnclosing(isTryOrContextBlockType); return (blkDefFor(blkState).nature === "try"); } return canBubble; + + //- determine if stackFrame is a try-block or the given type of block + function isTryOrContextBlockType(stackFrame) { + if (_isContextBlockType && _isContextBlockType(stackFrame)) { + return true; + } + return Stack.isTryBlock(stackFrame); + } } - Selenium.prototype.hasUnspentCatch = function hasUnspentCatch(tryState) { + function hasUnspentCatch(tryState) { return (tryState && blkDefFor(tryState).catchIdx && !tryState.hasCaught); } - Selenium.prototype.hasUnspentFinally = function hasUnspentFinally(tryState) { + function hasUnspentFinally(tryState) { return (tryState && blkDefFor(tryState).finallyIdx && !tryState.hasFinaled); } @@ -1255,21 +988,21 @@ function $X(xpath, contextNode, resultType) { bbl = "@" + ($$.tcf.bubbling.srcIdx+1) + " "; } var tryDef = blkDefFor(tryState); - var catchDcl = localCommand( tryDef.catchIdx ).target; + var catchDcl = testCase.commands[tryDef.catchIdx].target; return " :: " + bbl + catchDcl; } + // ================================================================================ Selenium.prototype.doWhile = function(condExpr) { - var self= this; enterLoop( function() { // validate assert(condExpr, " 'while' requires a condition expression."); - self.assertCompilable("", condExpr, ";", "Invalid condition"); + assertCompilable("", condExpr, ";", "Invalid condition"); return null; } ,function() { } // initialize - ,function() { return (self.evalWithVars(condExpr)); } // continue? + ,function() { return (evalWithVars(condExpr)); } // continue? ,function() { } // iterate ); }; @@ -1280,11 +1013,10 @@ function $X(xpath, contextNode, resultType) { // ================================================================================ Selenium.prototype.doFor = function(forSpec) { - var self= this; enterLoop( function(loop) { // validate assert(forSpec, " 'for' requires: ; ; ."); - self.assertCompilable("for ( ", forSpec, " );", "Invalid loop parameters"); + assertCompilable("for ( ", forSpec, " );", "Invalid loop parameters"); var specs = iexpr.splitList(forSpec, ";"); assert(specs.length === 3, " 'for' requires ; ; ."); loop.initStmt = specs[0]; @@ -1295,9 +1027,9 @@ function $X(xpath, contextNode, resultType) { validateNames(localVarNames, "variable"); return localVarNames; } - ,function(loop) { self.evalWithVars(loop.initStmt); } // initialize - ,function(loop) { return (self.evalWithVars(loop.condExpr)); } // continue? - ,function(loop) { self.evalWithVars(loop.iterStmt); } // iterate + ,function(loop) { evalWithVars(loop.initStmt); } // initialize + ,function(loop) { return (evalWithVars(loop.condExpr)); } // continue? + ,function(loop) { evalWithVars(loop.iterStmt); } // iterate ); }; Selenium.prototype.doEndFor = function() { @@ -1316,16 +1048,16 @@ function $X(xpath, contextNode, resultType) { } return varNames; } + // ================================================================================ Selenium.prototype.doForeach = function(varName, valueExpr) { - var self= this; enterLoop( function(loop) { // validate assert(varName, " 'foreach' requires a variable name."); assert(valueExpr, " 'foreach' requires comma-separated values."); - self.assertCompilable("[ ", valueExpr, " ];", "Invalid value list"); - loop.values = self.evalWithVars("[" + valueExpr + "]"); + assertCompilable("[ ", valueExpr, " ];", "Invalid value list"); + loop.values = evalWithVars("[" + valueExpr + "]"); if (loop.values.length === 1 && loop.values[0] instanceof Array) { loop.values = loop.values[0]; // if sole element is an array, than use it } @@ -1343,18 +1075,19 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doEndForeach = function() { iterateLoop(); }; + // ================================================================================ Selenium.prototype.doLoadJsonVars = function(filepath, selector) { assert(filepath, " Requires a JSON file path or URL."); var jsonReader = new JSONReader(filepath); - this.loadVars(jsonReader, "JSON object", filepath, selector); + loadVars(jsonReader, "JSON object", filepath, selector); }; Selenium.prototype.doLoadXmlVars = function(filepath, selector) { assert(filepath, " Requires an XML file path or URL."); var xmlReader = new XmlReader(filepath); - this.loadVars(xmlReader, "XML element", filepath, selector); + loadVars(xmlReader, "XML element", filepath, selector); }; Selenium.prototype.doLoadVars = function(filepath, selector) { @@ -1363,10 +1096,10 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doLoadXmlVars(filepath, selector); }; - Selenium.prototype.loadVars = function loadVars(reader, desc, filepath, selector) + function loadVars(reader, desc, filepath, selector) { if (selector) { - this.assertCompilable("", selector, ";", "Invalid selector condition"); + assertCompilable("", selector, ";", "Invalid selector condition"); } reader.load(filepath); reader.next(); // read first varset and set values on storedVars @@ -1375,23 +1108,23 @@ function $X(xpath, contextNode, resultType) { + ' (A specific ' + desc + ' can be selected by specifying: name="value".)'); } - var result = this.evalWithVars(selector); + var result = evalWithVars(selector); if (typeof result !== "boolean") { notifyFatalHere(", " + selector + " is not a boolean expression"); } // read until specified set found var isEof = reader.EOF(); - while (!isEof && this.evalWithVars(selector) !== true) { + while (!isEof && evalWithVars(selector) !== true) { reader.next(); // read next varset and set values on storedVars isEof = reader.EOF(); } - if (!this.evalWithVars(selector)) { + if (!evalWithVars(selector)) { notifyFatalHere(desc + " not found for selector expression: " + selector + "; in input file " + filepath); } - }; + } // ================================================================================ @@ -1490,7 +1223,7 @@ function $X(xpath, contextNode, resultType) { // ================================================================================ Selenium.prototype.doContinue = function(condExpr) { - var loopState = this.dropToLoop(condExpr); + var loopState = dropToLoop(condExpr); if (loopState) { // jump back to top of loop for next iteration, if any var endCmd = blkDefFor(loopState); @@ -1498,7 +1231,7 @@ function $X(xpath, contextNode, resultType) { } }; Selenium.prototype.doBreak = function(condExpr) { - var loopState = this.dropToLoop(condExpr); + var loopState = dropToLoop(condExpr); if (loopState) { loopState.isComplete = true; // jump to bottom of loop for exit @@ -1508,69 +1241,46 @@ function $X(xpath, contextNode, resultType) { // Unwind the command stack to the inner-most active loop block // (unless the optional condition evaluates to false) - Selenium.prototype.dropToLoop = function dropToLoop(condExpr) + function dropToLoop(condExpr) { assertRunning(); if (condExpr) { - this.assertCompilable("", condExpr, ";", "Invalid condition"); + assertCompilable("", condExpr, ";", "Invalid condition"); } - if (this.transitionBubbling(Stack.isLoopBlock)) { + if (transitionBubbling(Stack.isLoopBlock)) { return; } - if (condExpr && !this.evalWithVars(condExpr)) { + if (condExpr && !evalWithVars(condExpr)) { return; } var loopState = activeBlockStack().unwindTo(Stack.isLoopBlock); return loopState; - }; + } // ================================================================================ Selenium.prototype.doCall = function(funcName, argSpec) { - var loop = currentTest || htmlTestRunner.currentTest; // See Selenium.prototype.doRollup() assertRunning(); // TBD: can we do single execution, ie, run from this point then break on return? if (argSpec) { - this.assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)"); + assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)"); } var funcIdx = symbols[funcName]; - assert(funcIdx, " Function does not exist: " + funcName + "."); + assert(funcIdx!==undefined, " Function does not exist: " + funcName + "."); var activeCallFrame = callStack.top(); if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) { // returning from completed function - var popped= callStack.pop(); - loop.commandError= popped.originalCommandError; - var _result= storedVars._result; - storedVars= popped.savedVars; //restoreVarState( popped.savedVars ); - storedVars._result= _result; - assert( testCase===popped.testCase, "The popped testCase is different." ); // Not sure why, but this seems to be true. + restoreVarState(callStack.pop().savedVars); } else { // save existing variable state and set args as local variables - var args = this.parseArgs(argSpec); - var savedVars = storedVars; - storedVars = args; - - var originalCommandError= loop.commandError; - // There can be several cascading layers of these calls - one per function call level. - loop.commandError= function doCallCommandError( result ) { - this.commandError= originalCommandError; - // See also bubbleToTryBlock(..) - editor.selDebugger.pause(); - originalCommandError.call( this, result ); // I've restored this.commandError above *before* calling originalCommandError(), because: if this was a deeper function call then originalCommandError() will restore any previous version of this.commandError, and I don't want to step on its feet here - }; - - callStack.push( { - funcIdx: funcIdx, - name: funcName, - args: args, - returnIdx: idxHere(), - savedVars: savedVars, - blockStack: new Stack(), - testCase: testCase, - originalCommandError: originalCommandError - }); + var args = parseArgs(argSpec); + var savedVars = getVarStateFor(args); + setVars(args); + + callStack.push({ funcIdx: funcIdx, name: funcName, args: args, returnIdx: idxHere(), + savedVars: savedVars, blockStack: new Stack() }); // jump to function body setNextCommand(funcIdx); } @@ -1582,7 +1292,8 @@ function $X(xpath, contextNode, resultType) { var funcDef = blkDefHere(); var activeCallFrame = callStack.top(); if (activeCallFrame.funcIdx === idxHere()) { - //SelBlocks used to call setVars(activeCallFrame.args); here. But this was already handled in doCall(). + // get parameter values + setVars(activeCallFrame.args); } else { // no active call, skip around function body @@ -1596,19 +1307,19 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doFunction(scrName); }; Selenium.prototype.doReturn = function(value) { - this.returnFromFunction(null, value); + returnFromFunction(null, value); }; Selenium.prototype.doEndFunction = function(funcName) { - this.returnFromFunction(funcName); + returnFromFunction(funcName); }; Selenium.prototype.doEndScript = function(scrName) { - this.returnFromFunction(scrName); + returnFromFunction(scrName); }; - Selenium.prototype.returnFromFunction = function(funcName, returnVal) + function returnFromFunction(funcName, returnVal) { assertRunning(); - if (this.transitionBubbling(Stack.isFunctionBlock)) { + if (transitionBubbling(Stack.isFunctionBlock)) { return; } var endDef = blkDefHere(); @@ -1617,17 +1328,17 @@ function $X(xpath, contextNode, resultType) { // no active call, we're just skipping around a function block } else { - if (returnVal) { storedVars._result = this.evalWithVars(returnVal); } + if (returnVal) { storedVars._result = evalWithVars(returnVal); } activeCallFrame.isReturning = true; // jump back to call command setNextCommand(activeCallFrame.returnIdx); } - }; + } // ================================================================================ Selenium.prototype.doExitTest = function() { - if (this.transitionBubbling()) { + if (transitionBubbling()) { return; } // intercept command processing and simply stop test execution instead of executing the next command @@ -1636,7 +1347,8 @@ function $X(xpath, contextNode, resultType) { // ========= storedVars management ========= - Selenium.prototype.evalWithVars = function evalWithVars(expr) { + + function evalWithVars(expr) { var result = null; try { // EXTENSION REVIEWERS: Use of eval is consistent with the Selenium extension itself. @@ -1647,18 +1359,19 @@ function $X(xpath, contextNode, resultType) { notifyFatalErr(" While evaluating Javascript expression: " + expr, e); } return result; - }; - Selenium.prototype.parseArgs = function parseArgs(argSpec) { // comma-sep -> new prop-set + } + + function parseArgs(argSpec) { // comma-sep -> new prop-set var args = {}; var parms = iexpr.splitList(argSpec, ","); var i; for (i = 0; i < parms.length; i++) { var keyValue = iexpr.splitList(parms[i], "="); validateName(keyValue[0], "parameter"); - args[keyValue[0].trim()] = this.evalWithVars(keyValue[1]); + args[keyValue[0]] = evalWithVars(keyValue[1]); } return args; - }; + } function initVarState(names) { // new -> storedVars(names) if (names) { var i; @@ -1727,21 +1440,7 @@ function $X(xpath, contextNode, resultType) { throw err; } function notifyFatalCmdRef(idx, msg) { notifyFatal(fmtCmdRef(idx) + msg); } - function notifyFatalHere(msg) { - // This may be called before testCase is set - var commandRef; - if( testCase===undefined ) { - commandRef= 'unknown step: '; - } - else { - // SelBlocks used fmtCurCmd() here. However, this - // may be called right after TestCaseDebugContext's nextCommand(), which (as intercepted by SelBlocksGlobal) sets testCase.debugContext.debugIndex to -1. Then - // fmtCurCmd() would fail (as it invokes idxHere() -> globIdx(-1). - var stepLocalIdx= localIdxHere(); - commandRef= fmtCmdRef( globIdx( Math.max(stepLocalIdx, 0) ) )+ ': '; - } - notifyFatal( commandRef+msg ); - } + function notifyFatalHere(msg) { notifyFatal(fmtCurCmd() + msg); } function assertCmd(idx, cond, msg) { if (!cond) { notifyFatalCmdRef(idx, msg); } } function assert(cond, msg) { if (!cond) { notifyFatalHere(msg); } } @@ -1755,29 +1454,20 @@ function $X(xpath, contextNode, resultType) { assert(activeIdx === expectedIdx, " unexpected command, active command was " + fmtCmdRef(activeIdx)); } - Selenium.prototype.assertCompilable = function assertCompilable(left, stmt, right, explanation) { + function assertCompilable(left, stmt, right, explanation) { try { - this.evalWithVars("function selblocksTemp() { " + left + stmt + right + " }"); + evalWithVars("function selblocksTemp() { " + left + stmt + right + " }"); } catch (e) { throw new SyntaxError(fmtCmdRef(idxHere()) + " " + explanation + " '" + stmt + "': " + e.message); } - }; + } function fmtCurCmd() { return fmtCmdRef(idxHere()); } function fmtCmdRef(idx) { - var test= localCase(idx); - var commandIdx= localIdx(idx); - - return "@" +test.filename+ ': ' +(commandIdx+1) + ": " + fmtCommand( test.commands[commandIdx] ); - } - function fmtCommand(cmd) { - var c = cmd.command; - if (cmd.target) { c += "|" + cmd.target; } - if (cmd.value) { c += "|" + cmd.value; } - return '[' + c + ']'; + return ("@" + (idx+1) + ": [" + $$.fmtCmd(testCase.commands[idx]) + "]"); } //================= Utils =============== @@ -1792,13 +1482,12 @@ function $X(xpath, contextNode, resultType) { // produce an iterator object for the given array function arrayIterator(arrayObject) { - function ArrayIteratorClosure(ary) { + return new function(ary) { var cur = 0; this.hasNext = function() { return (cur < ary.length); }; this.next = function() { if (this.hasNext()) { return ary[cur++]; } }; - } - return new ArrayIteratorClosure(arrayObject); - } + }(arrayObject); + }; // ==================== Data Files ==================== // Adapted from the datadriven plugin @@ -1826,7 +1515,7 @@ function $X(xpath, contextNode, resultType) { } curVars = 0; - varNames = XmlReader.attrNamesFor(varsets[0]); + varNames = attrNamesFor(varsets[0]); return varNames; }; @@ -1841,23 +1530,22 @@ function $X(xpath, contextNode, resultType) { return; } varsetIdx++; - $$.LOG.debug(varsetIdx + ") " + XmlReader.serializeXml(varsets[curVars])); // log each name & value + $$.LOG.debug(varsetIdx + ") " + serializeXml(varsets[curVars])); // log each name & value - var expected = XmlReader.countAttrs(varsets[0]); - var found = XmlReader.countAttrs(varsets[curVars]); + var expected = countAttrs(varsets[0]); + var found = countAttrs(varsets[curVars]); if (found !== expected) { throw new Error("Inconsistent at element #" + varsetIdx + "; expected " + expected + " attributes, but found " + found + "." + " Each element must have the same set of attributes." ); } - XmlReader.setupStoredVars(varsets, varsetIdx, varsets[curVars]); + setupStoredVars(varsets[curVars]); curVars++; }; - } // end of XmlReader //- retrieve the names of each attribute on the given XML node - XmlReader.attrNamesFor = function attrNamesFor(node) { + function attrNamesFor(node) { var attrNames = []; var varAttrs = node.attributes; // NamedNodeMap var v; @@ -1865,16 +1553,15 @@ function $X(xpath, contextNode, resultType) { attrNames.push(varAttrs[v].nodeName); } return attrNames; - }; - + } //- determine how many attributes are present on the given node - XmlReader.countAttrs = function countAttrs(node) { + function countAttrs(node) { return node.attributes.length; - }; - + } + //- set selenium variables from given XML attributes - XmlReader.setupStoredVars= function setupStoredVars(varsets, varsetIdx, node) { + function setupStoredVars(node) { var varAttrs = node.attributes; // NamedNodeMap var v; for (v = 0; v < varAttrs.length; v++) { @@ -1887,16 +1574,17 @@ function $X(xpath, contextNode, resultType) { } storedVars[attr.nodeName] = attr.nodeValue; } - }; + } //- format the given XML node for display - XmlReader.serializeXml = function serializeXml(node) { + function serializeXml(node) { if (XMLSerializer !== "undefined") { return (new XMLSerializer()).serializeToString(node) ; } if (node.xml) { return node.xml; } throw "XMLSerializer is not supported or can't serialize " + node; - }; + } + } function JSONReader() @@ -1921,7 +1609,7 @@ function $X(xpath, contextNode, resultType) { } curVars = 0; - varNames = JSONReader.attrNamesFor(varsets[0]); + varNames = attrNamesFor(varsets[0]); return varNames; }; @@ -1936,43 +1624,42 @@ function $X(xpath, contextNode, resultType) { return; } varsetIdx++; - $$.LOG.debug(varsetIdx + ") " + JSONReader.serializeJson(varsets[curVars])); // log each name & value + $$.LOG.debug(varsetIdx + ") " + serializeJson(varsets[curVars])); // log each name & value - var expected = JSONReader.countAttrs(varsets[0]); - var found = JSONReader.countAttrs(varsets[curVars]); + var expected = countAttrs(varsets[0]); + var found = countAttrs(varsets[curVars]); if (found !== expected) { throw new Error("Inconsistent JSON object #" + varsetIdx + "; expected " + expected + " attributes, but found " + found + "." + " Each JSON object must have the same set of attributes." ); } - JSONReader.setupStoredVars(varsets, varsetIdx, varsets[curVars]); + setupStoredVars(varsets[curVars]); curVars++; }; - } // end of JSONReader - //- retrieve the names of each attribute on the given object - JSONReader.attrNamesFor = function attrNamesFor(obj) { + //- retrieve the names of each attribute on the given object + function attrNamesFor(obj) { var attrNames = []; var attrName; for (attrName in obj) { attrNames.push(attrName); } return attrNames; - }; + } //- determine how many attributes are present on the given obj - JSONReader.countAttrs = function countAttrs(obj) { + function countAttrs(obj) { var n = 0; var attrName; for (attrName in obj) { n++; } return n; - }; + } //- set selenium variables from given JSON attributes - JSONReader.setupStoredVars = function setupStoredVars(varsets, varsetIdx, obj) { + function setupStoredVars(obj) { var attrName; for (attrName in obj) { if (null === varsets[0][attrName]) { @@ -1983,19 +1670,20 @@ function $X(xpath, contextNode, resultType) { } storedVars[attrName] = obj[attrName]; } - }; + } //- format the given JSON object for display - JSONReader.serializeJson = function serializeJson(obj) { + function serializeJson(obj) { var json = uneval(obj); return json.substring(1, json.length-1); - }; + } + } function urlFor(filepath) { var URL_PFX = "file://"; var url = filepath; if (filepath.substring(0, URL_PFX.length).toLowerCase() !== URL_PFX) { - var testCasePath = testCase.file.path.replace("\\", "/", "g"); + testCasePath = testCase.file.path.replace("\\", "/", "g"); var i = testCasePath.lastIndexOf("/"); url = URL_PFX + testCasePath.substr(0, i) + "/" + filepath; } From 9358b32d27fc43580b2e96994817eea0ce3e8e51 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 1 Jan 2015 18:01:36 -0500 Subject: [PATCH 09/47] issue fixed: functions aren't global Since the blockdefs and stack are reset on a per testCase basis, the symbols need to be reset as well. If they're not, then the function names persist between test cases but the blockdefs don't. This causes selblocks to execute the range of indexes stored for the function, but using the commands array from the current case, which is obviously wrong. Resetting the symbols makes the behavior "functions aren't global", correct and fixes the bug. --- sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js | 1 + 1 file changed, 1 insertion(+) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index f6ba739..0814d7d 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -303,6 +303,7 @@ function $X(xpath, contextNode, resultType) { // Assemble block relationships and symbol locations function compileSelBlocks() { + symbols = {}; blockDefs = new BlockDefs(); var lexStack = new Stack(); var i; From 4ec8d62ffecfe76d9b263e91e63fabe110ba16ce Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 1 Jan 2015 19:06:35 -0500 Subject: [PATCH 10/47] Caching funcs from previous testCases This holds a naive clone of the symbols, commands, and blockDefs for each test suite as it is run through "compileSelBlocks". The cache is private to the closure where selblocks is built so, it can't be tampered with from the outside. --- .../chrome/content/extensions/selblocks.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 0814d7d..8a57ec7 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -163,6 +163,11 @@ function $X(xpath, contextNode, resultType) { var symbols = {}; // command indexes stored by name: function names var blockDefs = null; // static command definitions stored by command index var callStack = null; // command execution stack + /** + * selblocks test case functions cache, to allow calling funcs across the + * entire suite. + */ + var cachedCommands = Object.create(null); // the idx of the currently executing command function idxHere() { @@ -488,6 +493,15 @@ function $X(xpath, contextNode, resultType) { } throw new SyntaxError(cmdErrors.join("; ")); } + (function cacheCommands() { + var _mytitle = (testCase.title) ? testCase.title : "untitled"; + cachedCommands[_mytitle] = { + 'symbols' : naiveClone(symbols), + 'commands' : naiveClone(testCase.commands), + 'blockDefs' : naiveClone(blockDefs) + }; + }()); + //console.dir(cachedCommands); //- command validation function assertNotAndWaitSuffix(cmdIdx) { assertCmd(cmdIdx, (testCase.commands[cmdIdx].command.indexOf("AndWait") === -1), @@ -501,6 +515,9 @@ function $X(xpath, contextNode, resultType) { function assertMatching(curCmd, expectedCmd, cmdIdx, pendIdx) { assertCmd(cmdIdx, curCmd === expectedCmd, ", does not match command " + fmtCmdRef(pendIdx)); } + function naiveClone(symbols) { + return JSON.parse(JSON.stringify(symbols)); + } } // -------------------------------------------------------------------------------- From a7efd2f44368afbc87891882178c511973fdcf66 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 1 Jan 2015 19:33:16 -0500 Subject: [PATCH 11/47] Beginning specs for calling caseName.fn calling functions from other test cases should require qualifying the function name with the test case title, that way two cases could have a function with the same name and not collide. Think of it like two namespaces. Currently, this doesn't execute functions in other cases, it just uses the cache to lookup the symbol for the current case. --- .../chrome/content/extensions/selblocks.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 8a57ec7..26f30ae 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -495,6 +495,7 @@ function $X(xpath, contextNode, resultType) { } (function cacheCommands() { var _mytitle = (testCase.title) ? testCase.title : "untitled"; + cachedCommands.currentCaseTitle = _mytitle; cachedCommands[_mytitle] = { 'symbols' : naiveClone(symbols), 'commands' : naiveClone(testCase.commands), @@ -1279,11 +1280,18 @@ function $X(xpath, contextNode, resultType) { // ================================================================================ Selenium.prototype.doCall = function(funcName, argSpec) { + var funcIdx, caseName; assertRunning(); // TBD: can we do single execution, ie, run from this point then break on return? if (argSpec) { assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)"); } - var funcIdx = symbols[funcName]; + if(funcName.match(/[.]/)) { + caseName = funcName.split(".")[0]; + funcName = funcName.split(".")[1]; + } else { + caseName = cachedCommands.currentCaseTitle; + } + funcIdx = cachedCommands[caseName].symbols[funcName]; assert(funcIdx!==undefined, " Function does not exist: " + funcName + "."); var activeCallFrame = callStack.top(); From 071298bc77484779db0a8d7d2aaf5769f0cb8748 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 1 Jan 2015 19:46:40 -0500 Subject: [PATCH 12/47] updates test to use namespaced funcs --- sel-blocksTests/CallGlobalFunction.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sel-blocksTests/CallGlobalFunction.html b/sel-blocksTests/CallGlobalFunction.html index 108bf33..cb40fb0 100644 --- a/sel-blocksTests/CallGlobalFunction.html +++ b/sel-blocksTests/CallGlobalFunction.html @@ -27,7 +27,7 @@ call - setGlobalVar + GlobalFunctions.setGlobalVar @@ -37,7 +37,7 @@ call - doStuff + GlobalFunctions 2.doStuff From 789c42bd0deb8ae6e041f49c16ca64987b5072d5 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 1 Jan 2015 19:48:45 -0500 Subject: [PATCH 13/47] reintroduces cross case bug calling a function in the current case works as expected. Calling a function with it's qualified name in another test case will exhibit the same behavior as was happening before we reset the symbols for each case. --- sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 26f30ae..36d6b7a 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -1280,19 +1280,20 @@ function $X(xpath, contextNode, resultType) { // ================================================================================ Selenium.prototype.doCall = function(funcName, argSpec) { - var funcIdx, caseName; + var funcIdx, fName, caseName; assertRunning(); // TBD: can we do single execution, ie, run from this point then break on return? if (argSpec) { assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)"); } if(funcName.match(/[.]/)) { caseName = funcName.split(".")[0]; - funcName = funcName.split(".")[1]; + fName = funcName.split(".")[1]; } else { caseName = cachedCommands.currentCaseTitle; + fName = funcName; } - funcIdx = cachedCommands[caseName].symbols[funcName]; - assert(funcIdx!==undefined, " Function does not exist: " + funcName + "."); + funcIdx = cachedCommands[caseName].symbols[fName]; + assert(funcIdx!==undefined, " Function does not exist: " + funcName); var activeCallFrame = callStack.top(); if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) { From 38c40460eaab8a05f81522cb5e3021f3ece7ff74 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Thu, 1 Jan 2015 20:02:50 -0500 Subject: [PATCH 14/47] adding active case property to cache --- .../chrome/content/extensions/selblocks.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 36d6b7a..085d68c 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -244,6 +244,7 @@ function $X(xpath, contextNode, resultType) { // if testCase.nextCommand() ever changes, this will need to be revisited // (current as of: selenium-ide-2.4.0) function nextCommand() { + var activeCase = cachedCommands.activeCase; if (!this.started) { this.started = true; this.debugIndex = testCase.startPoint ? testCase.commands.indexOf(testCase.startPoint) : 0; @@ -1286,19 +1287,21 @@ function $X(xpath, contextNode, resultType) { assertCompilable("var ", argSpec, ";", "Invalid call parameter(s)"); } if(funcName.match(/[.]/)) { - caseName = funcName.split(".")[0]; - fName = funcName.split(".")[1]; + caseName = String(funcName.split(".")[0]); + fName = String(funcName.split(".")[1]); } else { - caseName = cachedCommands.currentCaseTitle; - fName = funcName; + caseName = String(cachedCommands.currentCaseTitle); + fName = String(funcName); } funcIdx = cachedCommands[caseName].symbols[fName]; assert(funcIdx!==undefined, " Function does not exist: " + funcName); + cachedCommands.activeCase = cachedCommands[String(caseName)]; var activeCallFrame = callStack.top(); if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) { // returning from completed function restoreVarState(callStack.pop().savedVars); + cachedCommands.activeCase = cachedCommands.currentCaseTitle; } else { // save existing variable state and set args as local variables From 89a7571c1e91e88b0d7eb6e8a2e8ff7c73021bd6 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 01:52:08 -0500 Subject: [PATCH 15/47] offsetting command indexes I don't want the "function" or "endfunction" to be in the same row as the "call". --- sel-blocksTests/CallGlobalFunction.html | 10 ++++++++++ sel-blocksTests/CreateGlobalVars.html | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/sel-blocksTests/CallGlobalFunction.html b/sel-blocksTests/CallGlobalFunction.html index cb40fb0..5066a9a 100644 --- a/sel-blocksTests/CallGlobalFunction.html +++ b/sel-blocksTests/CallGlobalFunction.html @@ -15,6 +15,16 @@ globalStoredVars.aGlobalVar = "reset"; + + getEval + globalStoredVars.aGlobalVar = "reset"; + + + + getEval + globalStoredVars.aGlobalVar = "reset"; + + assertNotEval globalStoredVars.aGlobalVar; diff --git a/sel-blocksTests/CreateGlobalVars.html b/sel-blocksTests/CreateGlobalVars.html index 969fed7..af27f19 100644 --- a/sel-blocksTests/CreateGlobalVars.html +++ b/sel-blocksTests/CreateGlobalVars.html @@ -15,7 +15,16 @@ globalStoredVars = {}; - + + getEval + globalStoredVars = {}; + + + + getEval + globalStoredVars = {}; + + From 69d9d1cf8ab535fb68467b3bdd9c28ec97020486 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 01:53:19 -0500 Subject: [PATCH 16/47] It works! --- .../chrome/content/extensions/selblocks.js | 108 ++++++++++++------ 1 file changed, 76 insertions(+), 32 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 085d68c..7004770 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -82,6 +82,9 @@ function $X(xpath, contextNode, resultType) { // selbocks name-space (function($$){ + function naiveClone(symbols) { + return JSON.parse(JSON.stringify(symbols)); + } // =============== Javascript extensions as script helpers =============== // EXTENSION REVIEWERS: @@ -157,21 +160,56 @@ function $X(xpath, contextNode, resultType) { return null; }; - //=============== Call/Scope Stack handling =============== var symbols = {}; // command indexes stored by name: function names var blockDefs = null; // static command definitions stored by command index var callStack = null; // command execution stack /** - * selblocks test case functions cache, to allow calling funcs across the - * entire suite. + * State information about the cache. + * currentCaseTitle is the current case displayed in the UI + * activeCaseTitle is the cashed case where commands are actually being pulled + * from. + * commandIndex is the current index of the commands array in the active case + * suites are the cached test suites commands, symbols, and blockDefs */ - var cachedCommands = Object.create(null); + var cachedCommandsData = { + initializing : true, + currentCaseTitle : '', + activeCaseTitle : '', + commandIndex : 0, + suites : Object.create(null), + /** + * Caches information about the current test case + */ + cacheCommands : function cacheCacheCommands(symbols, commands, blockDefs) { + cachedCommandsData.initializing = false; + cachedCommandsData.suites[cachedCommandsData.currentCaseTitle] = { + 'symbols' : symbols, + 'commands' : commands, + 'blockDefs' : blockDefs + } + }, + /** + * Starts a new suite cache. + */ + init : function cacheInit() { + cachedCommandsData.initializing = true; + var _mytitle = testCase.title || "untitiled"; + // the current case displayed in the IDE + cachedCommandsData.currentCaseTitle = String(_mytitle); + // the current case for looking up functions + cachedCommandsData.activeCaseTitle = String(_mytitle); + // the index used for executing commands, this is decoupled from the + // testCase.debugIndex because what's being executed doesn't always + // move the row cursor in the UI anymore. + cachedCommandsData.commandIndex = 0; + } + }; // the idx of the currently executing command function idxHere() { - return testCase.debugContext.debugIndex; + return cachedCommandsData.commandIndex; } // Command structure definitions, stored by command index @@ -189,7 +227,13 @@ function $X(xpath, contextNode, resultType) { // retrieve the blockDef at the given command idx function blkDefAt(idx) { - return blockDefs[idx]; + var where; + if(cachedCommandsData.initializing === true) { + where = blockDefs[idx]; + } else { + where = cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].blockDefs[idx]; + } + return where; } // retrieve the blockDef for the currently executing command function blkDefHere() { @@ -244,34 +288,44 @@ function $X(xpath, contextNode, resultType) { // if testCase.nextCommand() ever changes, this will need to be revisited // (current as of: selenium-ide-2.4.0) function nextCommand() { - var activeCase = cachedCommands.activeCase; if (!this.started) { this.started = true; this.debugIndex = testCase.startPoint ? testCase.commands.indexOf(testCase.startPoint) : 0; + cachedCommandsData.commandIndex = 0 + this.debugIndex; + cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle); } else { if (branchIdx !== null) { $$.LOG.info("branch => " + fmtCmdRef(branchIdx)); - this.debugIndex = branchIdx; + if(cachedCommandsData.activeCaseTitle === cachedCommandsData.currentCaseTitle) { + this.debugIndex = branchIdx; + } + cachedCommandsData.commandIndex = branchIdx; branchIdx = null; } else { - this.debugIndex++; + if(cachedCommandsData.activeCaseTitle === cachedCommandsData.currentCaseTitle) { + this.debugIndex++; + } + cachedCommandsData.commandIndex++; } } // skip over comments - while (this.debugIndex < testCase.commands.length) { - var command = testCase.commands[this.debugIndex]; + while (cachedCommandsData.commandIndex < cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].commands.length) { + var command = cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].commands[cachedCommandsData.commandIndex]; if (command.type === "command") { return command; } - this.debugIndex++; + if(cachedCommandsData.activeCaseTitle === cachedCommandsData.currentCaseTitle) { + this.debugIndex++; + } + cachedCommandsData.commandIndex++; } return null; } function setNextCommand(cmdIdx) { - assert(cmdIdx >= 0 && cmdIdx < testCase.commands.length, - " Cannot branch to non-existent command @" + (cmdIdx+1)); + assert(cmdIdx >= 0 && cmdIdx < cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)].commands.length, + " Cannot branch to non-existent command " + String(cachedCommandsData.activeCaseTitle) + " @" + (cmdIdx+1)); branchIdx = cmdIdx; } @@ -309,6 +363,7 @@ function $X(xpath, contextNode, resultType) { // Assemble block relationships and symbol locations function compileSelBlocks() { + cachedCommandsData.init(); symbols = {}; blockDefs = new BlockDefs(); var lexStack = new Stack(); @@ -494,16 +549,7 @@ function $X(xpath, contextNode, resultType) { } throw new SyntaxError(cmdErrors.join("; ")); } - (function cacheCommands() { - var _mytitle = (testCase.title) ? testCase.title : "untitled"; - cachedCommands.currentCaseTitle = _mytitle; - cachedCommands[_mytitle] = { - 'symbols' : naiveClone(symbols), - 'commands' : naiveClone(testCase.commands), - 'blockDefs' : naiveClone(blockDefs) - }; - }()); - //console.dir(cachedCommands); + cachedCommandsData.cacheCommands(symbols, testCase.commands, blockDefs); //- command validation function assertNotAndWaitSuffix(cmdIdx) { assertCmd(cmdIdx, (testCase.commands[cmdIdx].command.indexOf("AndWait") === -1), @@ -517,9 +563,6 @@ function $X(xpath, contextNode, resultType) { function assertMatching(curCmd, expectedCmd, cmdIdx, pendIdx) { assertCmd(cmdIdx, curCmd === expectedCmd, ", does not match command " + fmtCmdRef(pendIdx)); } - function naiveClone(symbols) { - return JSON.parse(JSON.stringify(symbols)); - } } // -------------------------------------------------------------------------------- @@ -1290,18 +1333,18 @@ function $X(xpath, contextNode, resultType) { caseName = String(funcName.split(".")[0]); fName = String(funcName.split(".")[1]); } else { - caseName = String(cachedCommands.currentCaseTitle); + caseName = String(cachedCommandsData.activeCaseTitle); fName = String(funcName); } - funcIdx = cachedCommands[caseName].symbols[fName]; + funcIdx = cachedCommandsData.suites[caseName].symbols[fName]; assert(funcIdx!==undefined, " Function does not exist: " + funcName); - cachedCommands.activeCase = cachedCommands[String(caseName)]; + cachedCommandsData.activeCaseTitle = String(caseName); var activeCallFrame = callStack.top(); if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) { // returning from completed function + cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle); restoreVarState(callStack.pop().savedVars); - cachedCommands.activeCase = cachedCommands.currentCaseTitle; } else { // save existing variable state and set args as local variables @@ -1309,7 +1352,7 @@ function $X(xpath, contextNode, resultType) { var savedVars = getVarStateFor(args); setVars(args); - callStack.push({ funcIdx: funcIdx, name: funcName, args: args, returnIdx: idxHere(), + callStack.push({ funcIdx: funcIdx, name: fName, args: args, returnIdx: idxHere(), savedVars: savedVars, blockStack: new Stack() }); // jump to function body setNextCommand(funcIdx); @@ -1361,6 +1404,7 @@ function $X(xpath, contextNode, resultType) { if (returnVal) { storedVars._result = evalWithVars(returnVal); } activeCallFrame.isReturning = true; // jump back to call command + cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle); setNextCommand(activeCallFrame.returnIdx); } } From 9ac20361f216aa1da03eba08f1354cf2ff472687 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 01:59:19 -0500 Subject: [PATCH 17/47] removing unused function I'd rather return clones of the commands, since we're running rows from functions more than once. My naive clone function destroyed command objects though. --- sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 7004770..d298d16 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -82,9 +82,6 @@ function $X(xpath, contextNode, resultType) { // selbocks name-space (function($$){ - function naiveClone(symbols) { - return JSON.parse(JSON.stringify(symbols)); - } // =============== Javascript extensions as script helpers =============== // EXTENSION REVIEWERS: From a557cf0159d084ccb123ac337befa6ad7022375c Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 15:18:52 -0500 Subject: [PATCH 18/47] Draft 1: define fn params bugged, when parameter defaults to undefined and fn is called without specifying said param, storedVars will not be restored on exiting fn execution in some cases. --- .../chrome/content/extensions/selblocks.js | 61 ++++++++++++-- .../DefineFunctionParameters - Base.html | 83 +++++++++++++++++++ sel-blocksTests/GlobalFunctionsTestSuite.html | 1 + 3 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 sel-blocksTests/DefineFunctionParameters - Base.html diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index d298d16..d1f08bc 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -1355,14 +1355,17 @@ function $X(xpath, contextNode, resultType) { setNextCommand(funcIdx); } }; - Selenium.prototype.doFunction = function(funcName) + Selenium.prototype.doFunction = function(funcName, paramString) { assertRunning(); - + var params; var funcDef = blkDefHere(); var activeCallFrame = callStack.top(); if (activeCallFrame.funcIdx === idxHere()) { - // get parameter values + params = parseArgs(paramString, "suppress variable expansion"); + // set default values supplied in function definition + setVars(params); + // overwrite local variables with the supplied values from the call setVars(activeCallFrame.args); } else { @@ -1431,15 +1434,37 @@ function $X(xpath, contextNode, resultType) { } return result; } - - function parseArgs(argSpec) { // comma-sep -> new prop-set + /** + * Parses a string of comma separated args that may contain assignments with + * equal signs. + * @param {String} argSpec The string representing arguments. + * @param {Null|Any} suppressEval If this is set to any truthy value then the + * values side of any arguments will not be evaluated with respect to the + * stored variables. + * @returns {Object} Returns an object whose properties are argument names and + * whose property values are the arguments values. If no value was supplied + * then the property will exist but it will be set to undefined. + * @example + * // storedVariables.bat = "XXXXXXX"; + * parseArgs('foo="bar",baz=bat'); + * // returns {foo:"bar", baz:"XXXXXXX"} + * parseArgs('foo="bar",baz=bat', "suppress eval"); + * // returns {foo:"bar", baz:"bat"} + */ + function parseArgs(argSpec, suppressEval) { // comma-sep -> new prop-set var args = {}; var parms = iexpr.splitList(argSpec, ","); var i; for (i = 0; i < parms.length; i++) { var keyValue = iexpr.splitList(parms[i], "="); validateName(keyValue[0], "parameter"); - args[keyValue[0]] = evalWithVars(keyValue[1]); + // boolean values suck, let me put any arbitrary string as the arg + // like "suppress variable expansion" so I can be clear about my intent. + if(suppressEval) { + args[keyValue[0]] = keyValue[1]; + } else { + args[keyValue[0]] = evalWithVars(keyValue[1]); + } } return args; } @@ -1453,6 +1478,15 @@ function $X(xpath, contextNode, resultType) { } } } + /** + * Sets the values of existing `args` properties to match `storedVars` + * @param {Object} args A simple value store where properties represent the + * the names of arguments and their values are the default values of those + * properties. If those same properties exist on `storedVars` they will be + * overwritten in the output object. + * @returns {Object} returns a simple object that can be used as `this` in + * `fn.call` and `fn.apply` etc. + */ function getVarStateFor(args) { // storedVars(prop-set) -> new prop-set var savedVars = {}; var varname; @@ -1461,6 +1495,13 @@ function $X(xpath, contextNode, resultType) { } return savedVars; } + /** + * Gets links to the storedVars specified. + * @param {Array} names An array of strings representing the storedVars + * properties that you want to copy/link to. + * @returns {Object} returns a simple object that can be used as `this` in + * `fn.call` and `fn.apply` etc. + */ function getVarState(names) { // storedVars(names) -> new prop-set var savedVars = {}; if (names) { @@ -1471,6 +1512,14 @@ function $X(xpath, contextNode, resultType) { } return savedVars; } + /** + * Overwrites values in storedVars with values supplied. + * @param {Object} args A simple value store where properties represent the + * the names of arguments and their values are the default values of those + * properties. If those same properties exist on `storedVars` they will be + * overwritten. + * @returns {Null} returns nothing. + */ function setVars(args) { // prop-set -> storedVars var varname; for (varname in args) { diff --git a/sel-blocksTests/DefineFunctionParameters - Base.html b/sel-blocksTests/DefineFunctionParameters - Base.html new file mode 100644 index 0000000..0fe1f85 --- /dev/null +++ b/sel-blocksTests/DefineFunctionParameters - Base.html @@ -0,0 +1,83 @@ + + + + + + +DefineFunctionParameters - Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DefineFunctionParameters - Base
storeoriginalx
storeoriginaly
assertEval"${x}" === "original";true
assertEval"${y}" === "original";true
functionbobx,y
assertEval"${x}" === "overwritten"true
assertEvalstoredVars.y === undefinedtrue
endFunction
callbobx="overwritten"
assertEval"${x}" === "overwritten"false
assertEval"${x}" === "original";true
assertEvalstoredVars.y === undefinedfalse
assertEval"${y}" === "original";true
+ + diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index 2a581a0..de6ac1a 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -12,6 +12,7 @@ GlobalFunctions GlobalFunctions 2 CallGlobalFunction +DefineFunctionParameters - Base From 2226f6e49be968759741744f8e5e049712b47112 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 16:58:12 -0500 Subject: [PATCH 19/47] moved saving vars into doFunction we have to cache the values of all storedVars that exist in the function signature, since we're initializing them to undefined for the function block. If we don't cache all the storedVars spec'd in the sig., then when the call completes only the args specified in the call will be restored while the unspecified params will be left as undefined on the storedVars object, or leak whatever value may have been assigned to them in the function call. --- .../chrome/content/extensions/selblocks.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index d1f08bc..a1b10c7 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -1344,13 +1344,12 @@ function $X(xpath, contextNode, resultType) { restoreVarState(callStack.pop().savedVars); } else { - // save existing variable state and set args as local variables + // pass supplied args to the function call through the call stack var args = parseArgs(argSpec); - var savedVars = getVarStateFor(args); - setVars(args); + // saved vars will be populated by the function call callStack.push({ funcIdx: funcIdx, name: fName, args: args, returnIdx: idxHere(), - savedVars: savedVars, blockStack: new Stack() }); + savedVars: {}, blockStack: new Stack() }); // jump to function body setNextCommand(funcIdx); } @@ -1358,14 +1357,18 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doFunction = function(funcName, paramString) { assertRunning(); - var params; + var params, savedVars; var funcDef = blkDefHere(); var activeCallFrame = callStack.top(); if (activeCallFrame.funcIdx === idxHere()) { params = parseArgs(paramString, "suppress variable expansion"); + // caching the values of all storedVars specified in function signature. + savedVars = getVarStateFor(params); + activeCallFrame.savedVars = savedVars; // set default values supplied in function definition setVars(params); - // overwrite local variables with the supplied values from the call + // overwrite local variables and defaults with the supplied values from + // the call setVars(activeCallFrame.args); } else { From 1939111b2621d1300033ec8b8e89ed13aea08144 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 17:27:15 -0500 Subject: [PATCH 20/47] removed comment about fixed bug --- sel-blocksTests/DefineFunctionParameters - Base.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/sel-blocksTests/DefineFunctionParameters - Base.html b/sel-blocksTests/DefineFunctionParameters - Base.html index 0fe1f85..a7b8cf5 100644 --- a/sel-blocksTests/DefineFunctionParameters - Base.html +++ b/sel-blocksTests/DefineFunctionParameters - Base.html @@ -66,8 +66,6 @@ "${x}" === "original"; true - - assertEval storedVars.y === undefined From 5efbc6b02527b730f6ed27f2beca2092246b03f6 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 17:27:44 -0500 Subject: [PATCH 21/47] adds test for funcs with default values --- ...ctionParameters - With Default Values.html | 206 ++++++++++++++++++ sel-blocksTests/GlobalFunctionsTestSuite.html | 1 + 2 files changed, 207 insertions(+) create mode 100644 sel-blocksTests/DefineFunctionParameters - With Default Values.html diff --git a/sel-blocksTests/DefineFunctionParameters - With Default Values.html b/sel-blocksTests/DefineFunctionParameters - With Default Values.html new file mode 100644 index 0000000..2e10e74 --- /dev/null +++ b/sel-blocksTests/DefineFunctionParameters - With Default Values.html @@ -0,0 +1,206 @@ + + + + + + +DefineFunctionParameters - With Default Values + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DefineFunctionParameters - With Default Values
storeoriginalx
storeoriginaly
assertEval"${x}" === "original";true
assertEval"${y}" === "original";true
functionleakTestx="fn default x",y="fn default y"
storeoverwrittenx
storeoverwritteny
storenonlocalleak
endFunction
callleakTest
assertEval"${x}" === "fn default x"false
assertEval"${x}" === "overwritten"false
assertEval"${x}" === "original";true
assertEval"${y}" === "fn default y"false
assertEval"${y}" === "overwritten"false
assertEval"${y}" === "original";true
callleakTesty="overwritten"
assertEval"${x}" === "fn default x"false
assertEval"${x}" === "overwritten"false
assertEval"${x}" === "original";true
assertEval"${y}" === "fn default y"false
assertEval"${y}" === "overwritten"false
assertEval"${y}" === "original";true
callleakTestx="overwritten"
assertEval"${x}" === "fn default x"false
assertEval"${x}" === "overwritten"false
assertEval"${x}" === "original";true
assertEval"${y}" === "fn default y"false
assertEval"${y}" === "overwritten"false
assertEval"${y}" === "original";true
callleakTestx = "overwritten", y = "overwritten"
assertEval"${x}" === "fn default x"false
assertEval"${x}" === "overwritten"false
assertEval"${x}" === "original";true
assertEval"${y}" === "fn default y"false
assertEval"${y}" === "overwritten"false
assertEval"${y}" === "original";true
+ + diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index de6ac1a..8a15ec9 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -13,6 +13,7 @@ GlobalFunctions 2 CallGlobalFunction DefineFunctionParameters - Base +DefineFunctionParameters - With Default Values From a4f57d375ae533743e945044c40f53fd6b6e819c Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 17:40:49 -0500 Subject: [PATCH 22/47] tests that default values respected the tests now check that parameters can have default values specified, that those values will be respected, and that parameters automatically become function scoped variables (not just the specified args). --- ...ctionParameters - With Default Values.html | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/sel-blocksTests/DefineFunctionParameters - With Default Values.html b/sel-blocksTests/DefineFunctionParameters - With Default Values.html index 2e10e74..cbf9f1d 100644 --- a/sel-blocksTests/DefineFunctionParameters - With Default Values.html +++ b/sel-blocksTests/DefineFunctionParameters - With Default Values.html @@ -31,6 +31,7 @@ "${y}" === "original"; true + function leakTest @@ -46,16 +47,58 @@ overwritten y + + endFunction + + + + + + function + defaultsTest + x="fn default x",y="fn default y" + + + assertEval + ${x} === "fn default x" + true + + + store + "overwritten" + x + + + assertEval + ${x} === "overwritten" + true + + + assertEval + ${y} === "fn default y" + true + store - nonlocal - leak + "overwritten" + y + + + assertEval + ${y} === "overwritten" + true endFunction + + + call + defaultsTest + + call @@ -96,7 +139,7 @@ call leakTest - y="overwritten" + y = "overwritten" assertEval @@ -132,7 +175,7 @@ call leakTest - x="overwritten" + x = "overwritten" assertEval From a17b481f0c821b8b09b8ac18560a2673c65ca336 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 19:00:35 -0500 Subject: [PATCH 23/47] overriding current command current command is supposed to return the current command being executed. This has to be overridden in order to return the correct command object. --- .../chrome/content/extensions/selblocks.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index a1b10c7..fd90bb1 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -325,7 +325,15 @@ function $X(xpath, contextNode, resultType) { " Cannot branch to non-existent command " + String(cachedCommandsData.activeCaseTitle) + " @" + (cmdIdx+1)); branchIdx = cmdIdx; } - + function currentCommand () { + var theCase = cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)]; + var theIndex = cachedCommandsData.commandIndex; + var command = theCase.commands[theIndex]; + if (!command) { + testCase.log.warn("currentCommand() not found: commands.length=" + theCase.commands.length + ", debugIndex=" + theIndex); + } + return command; + } // Selenium calls reset(): // * before each single (double-click) command execution // * before a testcase is run @@ -349,6 +357,7 @@ function $X(xpath, contextNode, resultType) { // TBD: this should be a tail intercept rather than brute force replace $$.LOG.debug("Configuring tail intercept: testCase.debugContext.nextCommand()"); $$.fn.interceptReplace(testCase.debugContext, "nextCommand", nextCommand); + $$.fn.interceptReplace(testCase.debugContext, "currentCommand", currentCommand); }); // get the blockStack for the currently active callStack From 64ba36e837b64de9118965657254ee7bb8b20920 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 19:18:21 -0500 Subject: [PATCH 24/47] changed test for leaking values Functions scope their defined parameters by name, all other variables leak to the containing scope. The current behavior in master branch is that all arguments passed in are arbitrarily scoped but, any new variables defined inside of the function block will leak to the global storedVariables and persist after the function returns. This new behavior is more consistent with the model presented by JavaScript, where defining variables without the var keyword will leak them into the containing scope. Given that the parameters are automatically scoped to the function, there is no need to define them and they will not leak. This is the exact behavior exhibited by selblocks functions now. We can implement block scoping for all variables by cloning storedVars at the beginning of function execution, then replacing it afterward. This is not the current behavior though, and would more likely break existing tests that count on being able to set global variables from within a function. --- sel-blocksTests/function.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sel-blocksTests/function.html b/sel-blocksTests/function.html index 5642472..a4112c0 100644 --- a/sel-blocksTests/function.html +++ b/sel-blocksTests/function.html @@ -83,7 +83,7 @@ assertEmitted - "START $" + "{sname} $" + "{srole}~sname=dilbert,Q,srole=goof~sname=dogbert,srole=woof~sname=ratbert,srole=squeak~sname=asok,srole=super=user~sname=alice,srole=super=user~DONE $" + "{sname} $" + "{srole}" + "START $" + "{sname} $" + "{srole}~sname=dilbert,Q,srole=goof~sname=dogbert,srole=woof~sname=ratbert,srole=squeak~sname=asok,srole=super=user~sname=alice,srole=super=user~DONE $" + "{sname} super=user" From 9df5a973e7043a9e5b21c36ae4a0773934e2bbef Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 19:26:49 -0500 Subject: [PATCH 25/47] update doScript, since it's an alias to doFunction doScript now passes the second argument from the command row to doFunction. Without passing this empty string, an error is thrown. With this argument available, scripts could have defined parameters like functions do... but they're the same thing really so... --- sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index fd90bb1..1dc4ed5 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -1385,11 +1385,11 @@ function $X(xpath, contextNode, resultType) { setNextCommand(funcDef.endIdx); } }; - Selenium.prototype.doScript = function(scrName) + Selenium.prototype.doScript = function(scrName, paramString) { $$.LOG.warn("The script command has been deprecated and will be removed in future releases." + " Please use function instead."); - Selenium.prototype.doFunction(scrName); + Selenium.prototype.doFunction(scrName, paramString); }; Selenium.prototype.doReturn = function(value) { returnFromFunction(null, value); From 1fdc86c30d42a339b8b8ed6f7cb258b6bcdf55ec Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 19:44:16 -0500 Subject: [PATCH 26/47] parameters must be named to be scoped --- sel-blocksTests/try - bubble out of nested function calls.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sel-blocksTests/try - bubble out of nested function calls.html b/sel-blocksTests/try - bubble out of nested function calls.html index f11aa4e..bd12a70 100644 --- a/sel-blocksTests/try - bubble out of nested function calls.html +++ b/sel-blocksTests/try - bubble out of nested function calls.html @@ -91,7 +91,7 @@ function subBub - + n @@ -175,7 +175,6 @@ "([/~trying~calling~0) trying-inner~0) calling~1) trying-inner~1) calling~2) trying-inner~2) throwing~2) finally~1) finally~0) finally~caught 'blamo'~finally~/])" - From e47b197e2e4c21095ca4b92f9bfb672858a1c942 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 20:02:09 -0500 Subject: [PATCH 27/47] currentCommand should not be overridden This function just marks the status of the current row in the IDE. It should follow the debug index and be called normally. I thought it was doing something else earlier. --- .../chrome/content/extensions/selblocks.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 1dc4ed5..ffda20b 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -325,15 +325,6 @@ function $X(xpath, contextNode, resultType) { " Cannot branch to non-existent command " + String(cachedCommandsData.activeCaseTitle) + " @" + (cmdIdx+1)); branchIdx = cmdIdx; } - function currentCommand () { - var theCase = cachedCommandsData.suites[String(cachedCommandsData.activeCaseTitle)]; - var theIndex = cachedCommandsData.commandIndex; - var command = theCase.commands[theIndex]; - if (!command) { - testCase.log.warn("currentCommand() not found: commands.length=" + theCase.commands.length + ", debugIndex=" + theIndex); - } - return command; - } // Selenium calls reset(): // * before each single (double-click) command execution // * before a testcase is run @@ -357,7 +348,6 @@ function $X(xpath, contextNode, resultType) { // TBD: this should be a tail intercept rather than brute force replace $$.LOG.debug("Configuring tail intercept: testCase.debugContext.nextCommand()"); $$.fn.interceptReplace(testCase.debugContext, "nextCommand", nextCommand); - $$.fn.interceptReplace(testCase.debugContext, "currentCommand", currentCommand); }); // get the blockStack for the currently active callStack From 3b15b1781e121efacfae0f928cde7cf0274982cc Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Fri, 2 Jan 2015 20:25:49 -0500 Subject: [PATCH 28/47] tests that "store" is not block scoped Store adds any var you want to the storedVariables object. The only properties that will be reverted / deleted after function calls are the explicitly named parameters of the function. --- sel-blocksTests/GlobalFunctionsTestSuite.html | 1 + .../function - store not block scoped.html | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 sel-blocksTests/function - store not block scoped.html diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index 8a15ec9..bd2b0dd 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -14,6 +14,7 @@ CallGlobalFunction DefineFunctionParameters - Base DefineFunctionParameters - With Default Values +function - store not block scoped diff --git a/sel-blocksTests/function - store not block scoped.html b/sel-blocksTests/function - store not block scoped.html new file mode 100644 index 0000000..dae0d86 --- /dev/null +++ b/sel-blocksTests/function - store not block scoped.html @@ -0,0 +1,52 @@ + + + + + + +function - store not block scoped + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
function - store not block scoped
functionnoBlockScopescoped
storeI will escape the blockabsconder
storeI will not escape the blockscoped
endFunction
callnoBlockScopescoped="original"
assertEvalstoredVars.scoped === undefinedtrue
assertEvalstoredVars.absconder === "I will escape the block"true
+ + From 49bf74aae470822899565752ca2078d10919da6f Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 14:11:04 -0500 Subject: [PATCH 29/47] Create basic test for SetLocal Adding a new selenium command "SetLocal" to set variables that will be discarded when the block completes. --- sel-blocksTests/GlobalFunctionsTestSuite.html | 1 + sel-blocksTests/StoreLocal - Base.html | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 sel-blocksTests/StoreLocal - Base.html diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index bd2b0dd..e30c20d 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -15,6 +15,7 @@ DefineFunctionParameters - Base DefineFunctionParameters - With Default Values function - store not block scoped +StoreLocal - Base diff --git a/sel-blocksTests/StoreLocal - Base.html b/sel-blocksTests/StoreLocal - Base.html new file mode 100644 index 0000000..cca78b6 --- /dev/null +++ b/sel-blocksTests/StoreLocal - Base.html @@ -0,0 +1,62 @@ + + + + + + +StoreLocal - Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StoreLocal - Base
functionblockScopeVars
storethis is stored globallyg
storeLocalthis is stored locallyl
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVars.l === "this is stored locally"true
endFunction
callblockScopeVars
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVars.l === "this is stored locally"false
+ + From baf71da3a8a44c94d33d6f6fe6a6360e9e07cb6e Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 16:17:02 -0500 Subject: [PATCH 30/47] Adds local storage fns and ContextManager Local storage is implemented through the ContextManager. Currently it does not enter and exit contexts though, so nothing would appear to change... unless I've got a typo in the code or forgot to wire blockVars, storedVars, and _oStoredVars in... Just checking in the new additions mostly. --- .../chrome/content/extensions/selblocks.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index ffda20b..7db5c3f 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -162,6 +162,44 @@ function $X(xpath, contextNode, resultType) { var symbols = {}; // command indexes stored by name: function names var blockDefs = null; // static command definitions stored by command index var callStack = null; // command execution stack + var _oStoredVars, blockVars, contextManager; // makes block scoped variables work + + /** + * Manages variable scope for functions, blocks, etc. + * @class ContextManager + */ + function ContextManager() { + _oStoredVars = storedVars; + this.contexts = []; + this.enter(); + } + /** + * Enters a new variable context. + * @methodOf ContextManager. + */ + ContextManager.prototype.enter = function enterContext() { + var context = { + here : Object.create(storedVars), + back : storedVars + }; + this.contexts.push(context); + storedVars = context.here; + blockVars = context.here; + }; + /** + * Exits to the previous variable context. + * @methodOf ContextManager. + */ + ContextManager.prototype.exit = function exitContext() { + if (this.contexts.length > 0) { + var context = this.contexts.pop(); + storedVars = context.back; + blockVars = context.back; + } else { + throw new Error("No context to exit from"); + } + }; + contextManager = new ContextManager(); /** * State information about the cache. * currentCaseTitle is the current case displayed in the UI @@ -648,7 +686,33 @@ function $X(xpath, contextNode, resultType) { notifyFatal("Invalid character(s) in " + desc + " name: '" + name + "'"); } } + + Selenium.prototype.doStore = function(value, varName) { + _oStoredVars[varName] = value; + }; + + Selenium.prototype.doStoreText = function(target, varName) { + var element = this.page().findElement(target); + _oStoredVars[varName] = getText(element); + }; + Selenium.prototype.doStoreAttribute = function(target, varName) { + _oStoredVars[varName] = this.page().findAttribute(target); + }; + + Selenium.prototype.doStoreLocal = function(value, varName) { + blockVars[varName] = value; + }; + + Selenium.prototype.doStoreLocalText = function(target, varName) { + var element = this.page().findElement(target); + blockVars[varName] = getText(element); + }; + + Selenium.prototype.doStoreLocalAttribute = function(target, varName) { + blockVars[varName] = this.page().findAttribute(target); + }; + Selenium.prototype.doLabel = function() { // noop }; From 6c5ed81bbff51a543e4b67013edc79516a764be1 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 17:54:53 -0500 Subject: [PATCH 31/47] Updates tests --- sel-blocksTests/GlobalFunctionsTestSuite.html | 4 +- .../Store - Behaves as StoreLocal.html | 102 ++++++++++++++++++ .../StoreGlobal - Function Scope.html | 89 +++++++++++++++ .../StoreLocal - Function Scope.html | 101 +++++++++++++++++ 4 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 sel-blocksTests/Store - Behaves as StoreLocal.html create mode 100644 sel-blocksTests/StoreGlobal - Function Scope.html create mode 100644 sel-blocksTests/StoreLocal - Function Scope.html diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index e30c20d..8da9fe0 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -15,7 +15,9 @@ DefineFunctionParameters - Base DefineFunctionParameters - With Default Values function - store not block scoped -StoreLocal - Base +StoreLocal - Function Scope +StoreGlobal - Function Scope +Store - Behaves as StoreLocal diff --git a/sel-blocksTests/Store - Behaves as StoreLocal.html b/sel-blocksTests/Store - Behaves as StoreLocal.html new file mode 100644 index 0000000..5607932 --- /dev/null +++ b/sel-blocksTests/Store - Behaves as StoreLocal.html @@ -0,0 +1,102 @@ + + + + + + +Store - Behaves as StoreLocal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Store - Behaves as StoreLocal
functionblockScopeVars
storethis is stored locallyl
assertEval_oStoredVars.l === "this is stored locally"false
assertEvalstoredVars.l === "this is stored locally"true
assertEvalblockVars.l === "this is stored locally"true
assertEval"${l}" === "this is stored locally"true
endFunction
getEval_oStoredVars.l = null;
getEvalstoredVars.l = null;
assertEvalstoredVars.l = null;null
assertEval_oStoredVars.l = null;null
callblockScopeVars
assertEval_oStoredVars.l === "this is stored locally"false
assertEvalstoredVars.l === "this is stored locally"false
assertEvalblockVars.l === "this is stored locally"false
assertEval"${l}" === "this is stored locally"false
+ + diff --git a/sel-blocksTests/StoreGlobal - Function Scope.html b/sel-blocksTests/StoreGlobal - Function Scope.html new file mode 100644 index 0000000..eba628a --- /dev/null +++ b/sel-blocksTests/StoreGlobal - Function Scope.html @@ -0,0 +1,89 @@ + + + + + + +StoreGlobal - Function Scope + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StoreGlobal - Function Scope
functionblockScopeVars
storeGlobalthis is stored globallyg
assertEval_oStoredVars.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalblockVars.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
endFunction
getEval_oStoredVars.g = null;
assertEval_oStoredVars.g = null;null
callblockScopeVars
assertEval_oStoredVars.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalblockVars.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
+ + diff --git a/sel-blocksTests/StoreLocal - Function Scope.html b/sel-blocksTests/StoreLocal - Function Scope.html new file mode 100644 index 0000000..09ef6e6 --- /dev/null +++ b/sel-blocksTests/StoreLocal - Function Scope.html @@ -0,0 +1,101 @@ + + + + + + +StoreLocal - Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StoreLocal - Base
functionblockScopeVars
storeLocalthis is stored locallyl
assertEval_oStoredVars.l === "this is stored locally"false
assertEvalstoredVars.l === "this is stored locally"true
assertEvalblockVars.l === "this is stored locally"true
assertEval"${l}" === "this is stored locally"true
endFunction
getEval_oStoredVars.l = null;
getEvalstoredVars.l = null;
assertEvalstoredVars.l = null;null
assertEval_oStoredVars.l = null;null
callblockScopeVars
assertEval_oStoredVars.l === "this is stored locally"false
assertEvalstoredVars.l === "this is stored locally"false
assertEvalblockVars.l === "this is stored locally"false
assertEval"${l}" === "this is stored locally"false
+ + From d4322412ebb78c4073a26b1fa797ea781ad8b7fb Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 18:00:46 -0500 Subject: [PATCH 32/47] storeLocal and storeGlobal work, funcs have blocks I've implemented block scope for function variables. storeLocal stores local to the block, storeGlobal stores to the global variables. Both local and global variables can be expanded when using the ${} notation. The original store commands, or anything that tries to write directly to storedVars will be subject to the new block scope mechanism. To start a new block scope just write "contextManager.enter()", to return to the parent block scope just write "contextManager.exit()" They should be able to nest for as long and large as a javascript prototype chain is allowed to be. --- .../chrome/content/extensions/selblocks.js | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 7db5c3f..6b0c602 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -162,14 +162,17 @@ function $X(xpath, contextNode, resultType) { var symbols = {}; // command indexes stored by name: function names var blockDefs = null; // static command definitions stored by command index var callStack = null; // command execution stack - var _oStoredVars, blockVars, contextManager; // makes block scoped variables work + var contextManager; // makes block scoped variables work /** * Manages variable scope for functions, blocks, etc. * @class ContextManager */ function ContextManager() { + // intentionally global var _oStoredVars = storedVars; + // intentionally global var + blockVars = Object.create(storedVars); this.contexts = []; this.enter(); } @@ -687,32 +690,6 @@ function $X(xpath, contextNode, resultType) { } } - Selenium.prototype.doStore = function(value, varName) { - _oStoredVars[varName] = value; - }; - - Selenium.prototype.doStoreText = function(target, varName) { - var element = this.page().findElement(target); - _oStoredVars[varName] = getText(element); - }; - - Selenium.prototype.doStoreAttribute = function(target, varName) { - _oStoredVars[varName] = this.page().findAttribute(target); - }; - - Selenium.prototype.doStoreLocal = function(value, varName) { - blockVars[varName] = value; - }; - - Selenium.prototype.doStoreLocalText = function(target, varName) { - var element = this.page().findElement(target); - blockVars[varName] = getText(element); - }; - - Selenium.prototype.doStoreLocalAttribute = function(target, varName) { - blockVars[varName] = this.page().findAttribute(target); - }; - Selenium.prototype.doLabel = function() { // noop }; @@ -1404,9 +1381,11 @@ function $X(xpath, contextNode, resultType) { if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) { // returning from completed function cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle); - restoreVarState(callStack.pop().savedVars); + //restoreVarState(callStack.pop().savedVars); + contextManager.exit(); } else { + contextManager.enter(); // pass supplied args to the function call through the call stack var args = parseArgs(argSpec); // saved vars will be populated by the function call @@ -1487,7 +1466,33 @@ function $X(xpath, contextNode, resultType) { // ========= storedVars management ========= + + Selenium.prototype.doStoreGlobal = function(value, varName) { + _oStoredVars[varName] = value; + }; + + Selenium.prototype.doStoreGlobalText = function(target, varName) { + var element = this.page().findElement(target); + _oStoredVars[varName] = getText(element); + }; + Selenium.prototype.doStoreGlobalAttribute = function(target, varName) { + _oStoredVars[varName] = this.page().findAttribute(target); + }; + + Selenium.prototype.doStoreLocal = function(value, varName) { + blockVars[varName] = value; + }; + + Selenium.prototype.doStoreLocalText = function(target, varName) { + var element = this.page().findElement(target); + blockVars[varName] = getText(element); + }; + + Selenium.prototype.doStoreLocalAttribute = function(target, varName) { + blockVars[varName] = this.page().findAttribute(target); + }; + function evalWithVars(expr) { var result = null; try { From 0062cc976a5dd1874f5ee86e0734bd7f50a74db7 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 19:24:16 -0500 Subject: [PATCH 33/47] can't override doStore for some stupid reason you can't change the store methods on the selenium prototype. Probably because they're cut and pasted over and over and over all over the IDE codebase and reloaded from who knows where, who knows when... --- .../chrome/content/extensions/selblocks.js | 24 ++--- sel-blocksTests/GlobalFunctionsTestSuite.html | 1 + .../Store - Behaves as StoreGlobal.html | 90 +++++++++++++++++++ .../Store - Behaves as StoreLocal.html | 12 +-- .../StoreGlobal - Function Scope.html | 12 +-- .../StoreLocal - Function Scope.html | 12 +-- 6 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 sel-blocksTests/Store - Behaves as StoreGlobal.html diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 6b0c602..e336727 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -170,9 +170,9 @@ function $X(xpath, contextNode, resultType) { */ function ContextManager() { // intentionally global var - _oStoredVars = storedVars; + storedVarsGlobal = storedVars; // intentionally global var - blockVars = Object.create(storedVars); + storedVarsLocal = Object.create(storedVars); this.contexts = []; this.enter(); } @@ -187,7 +187,7 @@ function $X(xpath, contextNode, resultType) { }; this.contexts.push(context); storedVars = context.here; - blockVars = context.here; + storedVarsLocal = context.here; }; /** * Exits to the previous variable context. @@ -197,7 +197,7 @@ function $X(xpath, contextNode, resultType) { if (this.contexts.length > 0) { var context = this.contexts.pop(); storedVars = context.back; - blockVars = context.back; + storedVarsLocal = context.back; } else { throw new Error("No context to exit from"); } @@ -1468,29 +1468,33 @@ function $X(xpath, contextNode, resultType) { // ========= storedVars management ========= Selenium.prototype.doStoreGlobal = function(value, varName) { - _oStoredVars[varName] = value; + storedVarsGlobal[varName] = value; }; Selenium.prototype.doStoreGlobalText = function(target, varName) { var element = this.page().findElement(target); - _oStoredVars[varName] = getText(element); + storedVarsGlobal[varName] = getText(element); }; Selenium.prototype.doStoreGlobalAttribute = function(target, varName) { - _oStoredVars[varName] = this.page().findAttribute(target); + storedVarsGlobal[varName] = this.page().findAttribute(target); }; + Selenium.prototype.doStore = Selenium.prototype.doStoreGlobal; + Selenium.prototype.doStoreText = Selenium.prototype.doStoreGlobalText; + Selenium.prototype.doStoreAttribute = Selenium.prototype.doStoreGlobalAttribute; + Selenium.prototype.doStoreLocal = function(value, varName) { - blockVars[varName] = value; + storedVarsLocal[varName] = value; }; Selenium.prototype.doStoreLocalText = function(target, varName) { var element = this.page().findElement(target); - blockVars[varName] = getText(element); + storedVarsLocal[varName] = getText(element); }; Selenium.prototype.doStoreLocalAttribute = function(target, varName) { - blockVars[varName] = this.page().findAttribute(target); + storedVarsLocal[varName] = this.page().findAttribute(target); }; function evalWithVars(expr) { diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index 8da9fe0..8bd2980 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -18,6 +18,7 @@ StoreLocal - Function Scope StoreGlobal - Function Scope Store - Behaves as StoreLocal +Store - Behaves as StoreGlobal diff --git a/sel-blocksTests/Store - Behaves as StoreGlobal.html b/sel-blocksTests/Store - Behaves as StoreGlobal.html new file mode 100644 index 0000000..1b8de1c --- /dev/null +++ b/sel-blocksTests/Store - Behaves as StoreGlobal.html @@ -0,0 +1,90 @@ + + + + + + +Store - Behaves as StoreGlobal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Store - Behaves as StoreGlobal
functionblockScopeVars
storethis is stored globallyg
assertEvalstoredVarsGlobal.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVarsLocal.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
endFunction
getEvalstoredVarsGlobal.g = null;
assertEvalstoredVarsGlobal.g = null;null
callblockScopeVars
assertEvalstoredVarsGlobal.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVarsLocal.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
+ + diff --git a/sel-blocksTests/Store - Behaves as StoreLocal.html b/sel-blocksTests/Store - Behaves as StoreLocal.html index 5607932..12905a4 100644 --- a/sel-blocksTests/Store - Behaves as StoreLocal.html +++ b/sel-blocksTests/Store - Behaves as StoreLocal.html @@ -25,7 +25,7 @@ assertEval - _oStoredVars.l === "this is stored locally" + storedVarsGlobal.l === "this is stored locally" false @@ -37,7 +37,7 @@ assertEval - blockVars.l === "this is stored locally" + storedVarsLocal.l === "this is stored locally" true @@ -53,7 +53,7 @@ getEval - _oStoredVars.l = null; + storedVarsGlobal.l = null; @@ -68,7 +68,7 @@ assertEval - _oStoredVars.l = null; + storedVarsGlobal.l = null; null @@ -78,7 +78,7 @@ assertEval - _oStoredVars.l === "this is stored locally" + storedVarsGlobal.l === "this is stored locally" false @@ -88,7 +88,7 @@ assertEval - blockVars.l === "this is stored locally" + storedVarsLocal.l === "this is stored locally" false diff --git a/sel-blocksTests/StoreGlobal - Function Scope.html b/sel-blocksTests/StoreGlobal - Function Scope.html index eba628a..b092889 100644 --- a/sel-blocksTests/StoreGlobal - Function Scope.html +++ b/sel-blocksTests/StoreGlobal - Function Scope.html @@ -25,7 +25,7 @@ assertEval - _oStoredVars.g === "this is stored globally" + storedVarsGlobal.g === "this is stored globally" true @@ -35,7 +35,7 @@ assertEval - blockVars.g === "this is stored globally" + storedVarsLocal.g === "this is stored globally" true @@ -51,12 +51,12 @@ getEval - _oStoredVars.g = null; + storedVarsGlobal.g = null; assertEval - _oStoredVars.g = null; + storedVarsGlobal.g = null; null @@ -66,7 +66,7 @@ assertEval - _oStoredVars.g === "this is stored globally" + storedVarsGlobal.g === "this is stored globally" true @@ -76,7 +76,7 @@ assertEval - blockVars.g === "this is stored globally" + storedVarsLocal.g === "this is stored globally" true diff --git a/sel-blocksTests/StoreLocal - Function Scope.html b/sel-blocksTests/StoreLocal - Function Scope.html index 09ef6e6..3873cde 100644 --- a/sel-blocksTests/StoreLocal - Function Scope.html +++ b/sel-blocksTests/StoreLocal - Function Scope.html @@ -25,7 +25,7 @@ assertEval - _oStoredVars.l === "this is stored locally" + storedVarsGlobal.l === "this is stored locally" false @@ -37,7 +37,7 @@ assertEval - blockVars.l === "this is stored locally" + storedVarsLocal.l === "this is stored locally" true @@ -53,7 +53,7 @@ getEval - _oStoredVars.l = null; + storedVarsGlobal.l = null; @@ -68,7 +68,7 @@ assertEval - _oStoredVars.l = null; + storedVarsGlobal.l = null; null @@ -78,7 +78,7 @@ assertEval - _oStoredVars.l === "this is stored locally" + storedVarsGlobal.l === "this is stored locally" false @@ -88,7 +88,7 @@ assertEval - blockVars.l === "this is stored locally" + storedVarsLocal.l === "this is stored locally" false From e267e437087e2a431eb124eb7f5803b4eeccd5cd Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 21:11:44 -0500 Subject: [PATCH 34/47] fuck it --- sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index e336727..485c4e7 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -1480,10 +1480,6 @@ function $X(xpath, contextNode, resultType) { storedVarsGlobal[varName] = this.page().findAttribute(target); }; - Selenium.prototype.doStore = Selenium.prototype.doStoreGlobal; - Selenium.prototype.doStoreText = Selenium.prototype.doStoreGlobalText; - Selenium.prototype.doStoreAttribute = Selenium.prototype.doStoreGlobalAttribute; - Selenium.prototype.doStoreLocal = function(value, varName) { storedVarsLocal[varName] = value; }; From 29d1e89616371758f4b57fa7f4c17852d07eb45a Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 21:19:21 -0500 Subject: [PATCH 35/47] Breaking change: store is block scoped There's no way to make "store" store variables in global scope and implement block scoping at the same time. They're reloading selenium-runner after loading the plugins and refreshing the default definition for doStore before every command is executed. All we'd need to do is point "doStore" at the new "storedVarsGlobal" and things wouldn't break but.. yeah. --- sel-blocksTests/GlobalFunctionsTestSuite.html | 2 - .../Store - Behaves as StoreGlobal.html | 90 ------------------- .../function - store not block scoped.html | 52 ----------- 3 files changed, 144 deletions(-) delete mode 100644 sel-blocksTests/Store - Behaves as StoreGlobal.html delete mode 100644 sel-blocksTests/function - store not block scoped.html diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index 8bd2980..7b2f42d 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -14,11 +14,9 @@ CallGlobalFunction DefineFunctionParameters - Base DefineFunctionParameters - With Default Values -function - store not block scoped StoreLocal - Function Scope StoreGlobal - Function Scope Store - Behaves as StoreLocal -Store - Behaves as StoreGlobal diff --git a/sel-blocksTests/Store - Behaves as StoreGlobal.html b/sel-blocksTests/Store - Behaves as StoreGlobal.html deleted file mode 100644 index 1b8de1c..0000000 --- a/sel-blocksTests/Store - Behaves as StoreGlobal.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - -Store - Behaves as StoreGlobal - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Store - Behaves as StoreGlobal
functionblockScopeVars
storethis is stored globallyg
assertEvalstoredVarsGlobal.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVarsLocal.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
endFunction
getEvalstoredVarsGlobal.g = null;
assertEvalstoredVarsGlobal.g = null;null
callblockScopeVars
assertEvalstoredVarsGlobal.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVarsLocal.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
- - diff --git a/sel-blocksTests/function - store not block scoped.html b/sel-blocksTests/function - store not block scoped.html deleted file mode 100644 index dae0d86..0000000 --- a/sel-blocksTests/function - store not block scoped.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - -function - store not block scoped - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function - store not block scoped
functionnoBlockScopescoped
storeI will escape the blockabsconder
storeI will not escape the blockscoped
endFunction
callnoBlockScopescoped="original"
assertEvalstoredVars.scoped === undefinedtrue
assertEvalstoredVars.absconder === "I will escape the block"true
- - From 574d414d632565b4acc071aecc92e904d213164b Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 22:27:54 -0500 Subject: [PATCH 36/47] adds test for function can return value --- .../Function - Can Return Value.html | 41 +++++++++++++++++++ sel-blocksTests/GlobalFunctionsTestSuite.html | 1 + 2 files changed, 42 insertions(+) create mode 100644 sel-blocksTests/Function - Can Return Value.html diff --git a/sel-blocksTests/Function - Can Return Value.html b/sel-blocksTests/Function - Can Return Value.html new file mode 100644 index 0000000..878ec6d --- /dev/null +++ b/sel-blocksTests/Function - Can Return Value.html @@ -0,0 +1,41 @@ + + + + + + +Function - Can Return Value + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Function - Can Return Value
functionreturnsValue
return"banana"
endFunction
callreturnsValue
assertEval"${_result}" === "banana"true
+ + diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index 7b2f42d..51893c9 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -17,6 +17,7 @@ StoreLocal - Function Scope StoreGlobal - Function Scope Store - Behaves as StoreLocal +Function - Can Return Value From ba85c771b705cde74f40d9cfc3c38a5d9cb3a012 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 22:29:25 -0500 Subject: [PATCH 37/47] pops callstack again, fn context trigger change Entering and exiting context in doCall was too early and too late. Moved context change into doFunction and doEndFunction. --- .../chrome/content/extensions/selblocks.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 485c4e7..b099953 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -173,6 +173,7 @@ function $X(xpath, contextNode, resultType) { storedVarsGlobal = storedVars; // intentionally global var storedVarsLocal = Object.create(storedVars); + storedVars = storedVarsLocal; this.contexts = []; this.enter(); } @@ -1381,11 +1382,10 @@ function $X(xpath, contextNode, resultType) { if (activeCallFrame.isReturning && activeCallFrame.returnIdx === idxHere()) { // returning from completed function cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle); + callStack.pop() //restoreVarState(callStack.pop().savedVars); - contextManager.exit(); } else { - contextManager.enter(); // pass supplied args to the function call through the call stack var args = parseArgs(argSpec); // saved vars will be populated by the function call @@ -1403,10 +1403,12 @@ function $X(xpath, contextNode, resultType) { var funcDef = blkDefHere(); var activeCallFrame = callStack.top(); if (activeCallFrame.funcIdx === idxHere()) { + contextManager.enter(); params = parseArgs(paramString, "suppress variable expansion"); // caching the values of all storedVars specified in function signature. - savedVars = getVarStateFor(params); - activeCallFrame.savedVars = savedVars; + //savedVars = getVarStateFor(params); + //activeCallFrame.savedVars = savedVars; + // set default values supplied in function definition setVars(params); // overwrite local variables and defaults with the supplied values from @@ -1446,6 +1448,7 @@ function $X(xpath, contextNode, resultType) { // no active call, we're just skipping around a function block } else { + contextManager.exit(); if (returnVal) { storedVars._result = evalWithVars(returnVal); } activeCallFrame.isReturning = true; // jump back to call command From fbab35b91ca30807e31387a4ce61091d1d41f308 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sat, 3 Jan 2015 22:48:27 -0500 Subject: [PATCH 38/47] Refactor function test for local vars selbench stores "emit" in the storedVars. storedVars is actually block scoped now so the emits are wiped out when the function returns. I'm returning the values that were emitted before and asserting their correctness immediately. --- sel-blocksTests/function.html | 83 +++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/sel-blocksTests/function.html b/sel-blocksTests/function.html index a4112c0..22c8074 100644 --- a/sel-blocksTests/function.html +++ b/sel-blocksTests/function.html @@ -11,20 +11,15 @@ subtest - - resetEmitted - - - deleteVars sname, srole - emit - "START ${sname} ${srole}" - + assertEval + "START ${sname} ${srole}" === "START $" + "{sname} $" + "{srole}" + true @@ -32,6 +27,16 @@ doSomething sname = "dilbert,Q", srole = "goof" + + assertEval + "${_result}" === "sname=dilbert,Q,srole=goof" + true + + + deleteVars + sname, srole + + function @@ -39,7 +44,7 @@ sname - emit + return "sname=${sname},srole=${srole}" @@ -49,16 +54,41 @@ + + deleteVars + sname, srole + + call doSomething sname = "dogbert", srole = "woof" + + assertEval + "${_result}" === "sname=dogbert,srole=woof" + true + + + deleteVars + sname, srole + + call doSomething sname = "ratbert", srole = "squeak" + + assertEval + "${_result}" === "sname=ratbert,srole=squeak" + true + + + deleteVars + sname, srole + + foreach @@ -71,21 +101,25 @@ sname = sname, srole = "super=user" - endForeach - - + assertEval + "${_result}" === "sname=${sname},srole=super=user" + true - emit - "DONE ${sname} ${srole}" + deleteVars + sname, srole - - assertEmitted - "START $" + "{sname} $" + "{srole}~sname=dilbert,Q,srole=goof~sname=dogbert,srole=woof~sname=ratbert,srole=squeak~sname=asok,srole=super=user~sname=alice,srole=super=user~DONE $" + "{sname} super=user" + endForeach + + + assertEval + "DONE ${sname} ${srole}" === "DONE $" + "{sname} $" + "{srole}" + true + @@ -100,8 +134,8 @@ - resetEmitted - + deleteVars + sname, srole @@ -110,7 +144,7 @@ sname - emit + return "sname=${sname},srole=${srole}" @@ -125,8 +159,13 @@ sname = "wally", srole = "lazy" - assertEmitted - "sname=wally,srole=lazy" + assertEval + "${_result}" === "sname=wally,srole=lazy" + true + + + deleteVars + sname, srole From 3c8175d0ee739961684122d7cb8e91f32b0b28fe Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 03:04:46 -0500 Subject: [PATCH 39/47] adding block scope to loops --- .../chrome/content/extensions/selblocks.js | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index b099953..9c0d3fa 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -163,16 +163,15 @@ function $X(xpath, contextNode, resultType) { var blockDefs = null; // static command definitions stored by command index var callStack = null; // command execution stack var contextManager; // makes block scoped variables work - + // intentionally global var + storedVarsGlobal = storedVars; /** * Manages variable scope for functions, blocks, etc. * @class ContextManager */ function ContextManager() { // intentionally global var - storedVarsGlobal = storedVars; - // intentionally global var - storedVarsLocal = Object.create(storedVars); + storedVarsLocal = Object.create(storedVarsGlobal); storedVars = storedVarsLocal; this.contexts = []; this.enter(); @@ -182,9 +181,10 @@ function $X(xpath, contextNode, resultType) { * @methodOf ContextManager. */ ContextManager.prototype.enter = function enterContext() { + var _o = storedVars; var context = { here : Object.create(storedVars), - back : storedVars + back : _o }; this.contexts.push(context); storedVars = context.here; @@ -203,6 +203,20 @@ function $X(xpath, contextNode, resultType) { throw new Error("No context to exit from"); } }; + /** + * Resets to the top variable context. + * @methodOf ContextManager. + */ + ContextManager.prototype.reset = function exitContext() { + if (this.contexts.length > 0) { + storedVars = storedVarsGlobal; + storedVarsLocal = storedVarsGlobal; + this.contexts = []; + this.enter(); + } else { + throw new Error("No context to exit from"); + } + }; contextManager = new ContextManager(); /** * State information about the cache. @@ -376,6 +390,7 @@ function $X(xpath, contextNode, resultType) { { $$.LOG.trace("In tail intercept :: Selenium.reset()"); try { + contextManager.reset(); compileSelBlocks(); } catch (err) { @@ -978,7 +993,8 @@ function $X(xpath, contextNode, resultType) { while (!tryState && $$.tcf.nestingLevel > -1 && callStack.length > 1) { var callFrame = callStack.pop(); $$.LOG.info("function '" + callFrame.name + "' aborting due to error"); - restoreVarState(callFrame.savedVars); + contextManager.exit(); + //restoreVarState(callFrame.savedVars); tryState = unwindToBlock(_hasCriteria); } return tryState; @@ -1290,6 +1306,7 @@ function $X(xpath, contextNode, resultType) { activeBlockStack().push(loopState); var localVars = _validateFunc(loopState); loopState.savedVars = getVarState(localVars); + contextManager.enter(); initVarState(localVars); // because with-scope can reference storedVars only once they exist _initFunc(loopState); } @@ -1312,7 +1329,8 @@ function $X(xpath, contextNode, resultType) { assertActiveScope(blkDefHere().beginIdx); var loopState = activeBlockStack().top(); if (loopState.isComplete) { - restoreVarState(loopState.savedVars); + contextManager.exit(); + //restoreVarState(loopState.savedVars); activeBlockStack().pop(); // done, fall out of loop } @@ -1547,7 +1565,7 @@ function $X(xpath, contextNode, resultType) { var i; for (i = 0; i < names.length; i++) { if (!storedVars[names[i]]) { - storedVars[names[i]] = null; + storedVars[names[i]] = undefined; } } } From 2c47b8e51c3ff71c54b56fb3118cf59a31eaeb91 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 10:53:01 -0500 Subject: [PATCH 40/47] improves return, storeEval in scope The built in storeEval makes variables that are local to the block scope. This adds the ability to do the same but in global scope. I had exited the context too early in returns, so variable expansion in return values was broken for a minute. --- .../chrome/content/extensions/selblocks.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 9c0d3fa..10befe1 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -1460,14 +1460,16 @@ function $X(xpath, contextNode, resultType) { if (transitionBubbling(Stack.isFunctionBlock)) { return; } + var ret; var endDef = blkDefHere(); var activeCallFrame = callStack.top(); if (activeCallFrame.funcIdx !== endDef.funcIdx) { // no active call, we're just skipping around a function block } else { + if (returnVal) { ret = evalWithVars(returnVal); } contextManager.exit(); - if (returnVal) { storedVars._result = evalWithVars(returnVal); } + storedVars._result = ret; activeCallFrame.isReturning = true; // jump back to call command cachedCommandsData.activeCaseTitle = String(cachedCommandsData.currentCaseTitle); @@ -1491,6 +1493,11 @@ function $X(xpath, contextNode, resultType) { Selenium.prototype.doStoreGlobal = function(value, varName) { storedVarsGlobal[varName] = value; }; + + Selenium.prototype.doStoreEvalGlobal = function(value, varName) { + var val = evalWithVars(String(value)); + storedVarsGlobal[varName] = val; + }; Selenium.prototype.doStoreGlobalText = function(target, varName) { var element = this.page().findElement(target); @@ -1505,6 +1512,11 @@ function $X(xpath, contextNode, resultType) { storedVarsLocal[varName] = value; }; + Selenium.prototype.doStoreEvalLocal = function(value, varName) { + var val = evalWithVars(String(value)); + storedVarsLocal[varName] = val; + }; + Selenium.prototype.doStoreLocalText = function(target, varName) { var element = this.page().findElement(target); storedVarsLocal[varName] = getText(element); From 1e7bcc14dd17b2e3c381a47b6babba4366798bb3 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 11:04:01 -0500 Subject: [PATCH 41/47] update test for block scope The iterator value "w" is evaluated and stored inside the "with" block. The iterator emits the right values but after the block is completed, emitting "end ${w}" gets the value of w in the scope above the "with" block, which hasn't been changed. The variable is automatically shadowed in local scope, so storing the iterator value globally doesn't work because the updated value isn't visible locally. I don't know how that works exactly, it's something with the way arguments are being parsed. --- sel-blocksTests/while.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sel-blocksTests/while.html b/sel-blocksTests/while.html index f3178df..beb7b20 100644 --- a/sel-blocksTests/while.html +++ b/sel-blocksTests/while.html @@ -76,7 +76,7 @@ assertEmitted - "START: 3~iter=3~iter=2~iter=1~END: 0" + "START: 3~iter=3~iter=2~iter=1~END: 3" From 38f795b44e60ef9192e9db8bafd5ac01811ebd86 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 11:25:10 -0500 Subject: [PATCH 42/47] update test for block scope the value for i will be the same at the start and end because it's incremented in block scope to the for loops. --- sel-blocksTests/nested-loops.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sel-blocksTests/nested-loops.html b/sel-blocksTests/nested-loops.html index b682354..b2c623d 100644 --- a/sel-blocksTests/nested-loops.html +++ b/sel-blocksTests/nested-loops.html @@ -119,7 +119,7 @@ assertEmitted - "START 2~while=2~foreach=dilbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~foreach=dogbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~while=1~foreach=dilbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~foreach=dogbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~END 0" + "START 2~while=2~foreach=dilbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~foreach=dogbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~while=1~foreach=dilbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~foreach=dogbert~FOR=2~for=7~for=8~FOR=3~for=7~for=8~endFor ${n}~END 2" From 22673d089b7fd04ebce52c9ec0d80b9355b0026e Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 12:03:52 -0500 Subject: [PATCH 43/47] bubbles any _result through contexts --- sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 10befe1..2814aed 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -990,10 +990,13 @@ function $X(xpath, contextNode, resultType) { $$.LOG.warn("bubbleToTryBlock() called outside of any try nesting"); } var tryState = unwindToBlock(_hasCriteria); + var ret; while (!tryState && $$.tcf.nestingLevel > -1 && callStack.length > 1) { var callFrame = callStack.pop(); $$.LOG.info("function '" + callFrame.name + "' aborting due to error"); + if (storedVars._result) { ret = storedVars._result; } contextManager.exit(); + storedVars._result = ret; //restoreVarState(callFrame.savedVars); tryState = unwindToBlock(_hasCriteria); } @@ -1328,8 +1331,12 @@ function $X(xpath, contextNode, resultType) { assertRunning(); assertActiveScope(blkDefHere().beginIdx); var loopState = activeBlockStack().top(); + var ret; if (loopState.isComplete) { + if (storedVars._result) { ret = storedVars._result; } contextManager.exit(); + storedVars._result = ret; + //restoreVarState(loopState.savedVars); activeBlockStack().pop(); // done, fall out of loop From a6dcf55b880d3c898fc85213fa9cc50dd7d32a4a Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 12:06:08 -0500 Subject: [PATCH 44/47] updated test this test is really convoluted. It looks like "finally" is supposed to execute even after a return statement. That finally throws, so nothing is being returned. --- .../try - command bubbling return replaced by error.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sel-blocksTests/try - command bubbling return replaced by error.html b/sel-blocksTests/try - command bubbling return replaced by error.html index 165cd6a..139c160 100644 --- a/sel-blocksTests/try - command bubbling return replaced by error.html +++ b/sel-blocksTests/try - command bubbling return replaced by error.html @@ -147,10 +147,9 @@ assertEmitted - "([/~calling~in cmdBubC~trying~returning (1)~finallying~throwing~caught 'this-error-replaces-return-1'~returned 2~/])" + "([/~calling~in cmdBubC~trying~returning (1)~finallying~throwing~caught 'this-error-replaces-return-1'~returned ${_result}~/])" - From d0c23167d7199ad5319ab37427be3f4f0dabd0b2 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 12:06:41 -0500 Subject: [PATCH 45/47] Adds tests for while scoping --- sel-blocksTests/GlobalFunctionsTestSuite.html | 2 + .../StoreGlobal - While Scope.html | 96 ++++++++++++++++ sel-blocksTests/StoreLocal - While Scope.html | 108 ++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 sel-blocksTests/StoreGlobal - While Scope.html create mode 100644 sel-blocksTests/StoreLocal - While Scope.html diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index 51893c9..1ace39f 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -16,6 +16,8 @@ DefineFunctionParameters - With Default Values StoreLocal - Function Scope StoreGlobal - Function Scope +StoreGlobal - While Scope +StoreLocal - While Scope Store - Behaves as StoreLocal Function - Can Return Value diff --git a/sel-blocksTests/StoreGlobal - While Scope.html b/sel-blocksTests/StoreGlobal - While Scope.html new file mode 100644 index 0000000..c0b8d30 --- /dev/null +++ b/sel-blocksTests/StoreGlobal - While Scope.html @@ -0,0 +1,96 @@ + + + + + + +StoreGlobal - While Scope + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StoreGlobal - While Scope
getEvalstoredVarsGlobal.g = null;
assertEvalstoredVarsGlobal.g = null;null
store1i
whilei > 0
store0i
storeGlobalthis is stored globallyg
assertEvalstoredVarsGlobal.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVarsLocal.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
endWhile
assertEvalstoredVarsGlobal.g === "this is stored globally"true
assertEvalstoredVars.g === "this is stored globally"true
assertEvalstoredVarsLocal.g === "this is stored globally"true
assertEval"${g}" === "this is stored globally"true
+ + diff --git a/sel-blocksTests/StoreLocal - While Scope.html b/sel-blocksTests/StoreLocal - While Scope.html new file mode 100644 index 0000000..08012f9 --- /dev/null +++ b/sel-blocksTests/StoreLocal - While Scope.html @@ -0,0 +1,108 @@ + + + + + + +StoreLocal - While Scope + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StoreLocal - While Scope
getEvalstoredVarsGlobal.l = null;
getEvalstoredVars.l = null;
assertEvalstoredVars.l = null;null
assertEvalstoredVarsGlobal.l = null;null
store1i
whilei > 0
store0i
storeLocalthis is stored locallyl
assertEvalstoredVarsGlobal.l === "this is stored locally"false
assertEvalstoredVars.l === "this is stored locally"true
assertEvalstoredVarsLocal.l === "this is stored locally"true
assertEval"${l}" === "this is stored locally"true
endWhile
assertEvalstoredVarsGlobal.l === "this is stored locally"false
assertEvalstoredVars.l === "this is stored locally"false
assertEvalstoredVarsLocal.l === "this is stored locally"false
assertEval"${l}" === "this is stored locally"false
+ + From a9ab71fd566e3ca33af0a80f136604ecd583d878 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 19:03:04 -0500 Subject: [PATCH 46/47] updates getEval on a few tests I was setting and verifying at the same time, which is wrong. --- sel-blocksTests/GlobalFunctionsTestSuite.html | 2 ++ sel-blocksTests/Store - Behaves as StoreLocal.html | 9 ++++----- sel-blocksTests/StoreGlobal - Function Scope.html | 4 ++-- sel-blocksTests/StoreGlobal - While Scope.html | 5 ++--- sel-blocksTests/StoreLocal - Function Scope.html | 8 ++++---- sel-blocksTests/StoreLocal - While Scope.html | 9 ++++----- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/sel-blocksTests/GlobalFunctionsTestSuite.html b/sel-blocksTests/GlobalFunctionsTestSuite.html index 1ace39f..c171f0e 100644 --- a/sel-blocksTests/GlobalFunctionsTestSuite.html +++ b/sel-blocksTests/GlobalFunctionsTestSuite.html @@ -20,6 +20,8 @@ StoreLocal - While Scope Store - Behaves as StoreLocal Function - Can Return Value +StoreAt - Function Scope Nesting +StoreAt - While Scope Nesting diff --git a/sel-blocksTests/Store - Behaves as StoreLocal.html b/sel-blocksTests/Store - Behaves as StoreLocal.html index 12905a4..a917370 100644 --- a/sel-blocksTests/Store - Behaves as StoreLocal.html +++ b/sel-blocksTests/Store - Behaves as StoreLocal.html @@ -63,13 +63,13 @@ assertEval - storedVars.l = null; - null + storedVars.l === null; + true assertEval - storedVarsGlobal.l = null; - null + storedVarsGlobal.l === null; + true call @@ -96,7 +96,6 @@ "${l}" === "this is stored locally" false - diff --git a/sel-blocksTests/StoreGlobal - Function Scope.html b/sel-blocksTests/StoreGlobal - Function Scope.html index b092889..5c61fc5 100644 --- a/sel-blocksTests/StoreGlobal - Function Scope.html +++ b/sel-blocksTests/StoreGlobal - Function Scope.html @@ -56,8 +56,8 @@ assertEval - storedVarsGlobal.g = null; - null + storedVarsGlobal.g === null; + true call diff --git a/sel-blocksTests/StoreGlobal - While Scope.html b/sel-blocksTests/StoreGlobal - While Scope.html index c0b8d30..80d8560 100644 --- a/sel-blocksTests/StoreGlobal - While Scope.html +++ b/sel-blocksTests/StoreGlobal - While Scope.html @@ -19,8 +19,8 @@ assertEval - storedVarsGlobal.g = null; - null + storedVarsGlobal.g === null; + true store @@ -90,7 +90,6 @@ "${g}" === "this is stored globally" true - diff --git a/sel-blocksTests/StoreLocal - Function Scope.html b/sel-blocksTests/StoreLocal - Function Scope.html index 3873cde..944f809 100644 --- a/sel-blocksTests/StoreLocal - Function Scope.html +++ b/sel-blocksTests/StoreLocal - Function Scope.html @@ -63,13 +63,13 @@ assertEval - storedVars.l = null; - null + storedVars.l === null; + true assertEval - storedVarsGlobal.l = null; - null + storedVarsGlobal.l === null; + true call diff --git a/sel-blocksTests/StoreLocal - While Scope.html b/sel-blocksTests/StoreLocal - While Scope.html index 08012f9..cf71f8f 100644 --- a/sel-blocksTests/StoreLocal - While Scope.html +++ b/sel-blocksTests/StoreLocal - While Scope.html @@ -24,13 +24,13 @@ assertEval - storedVars.l = null; - null + storedVars.l === null; + true assertEval - storedVarsGlobal.l = null; - null + storedVarsGlobal.l === null; + true store @@ -102,7 +102,6 @@ "${l}" === "this is stored locally" false - From b207dac10e65d806a426ff11002f6414db032632 Mon Sep 17 00:00:00 2001 From: Matthew Kastor Date: Sun, 4 Jan 2015 19:06:53 -0500 Subject: [PATCH 47/47] adds storeAt command and tests The tests explain it better than I can. Basically, without this you only have the choice of shadowing variables in the child scope, or setting globals all the time to pass data around. storeAt allows you to name a variable defined in any preceeding scope and bubble the value to set on it. The changed value will be available after you exit the current scope, so you can do things like "while" and "for" loops without bouncing return values off the global scope. . . Just storeAt whatever receiving variable you have set up in the parent and it's like magic! --- .../chrome/content/extensions/selblocks.js | 45 ++++++ .../StoreAt - Function Scope Nesting.html | 129 ++++++++++++++++ .../StoreAt - While Scope Nesting.html | 140 ++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 sel-blocksTests/StoreAt - Function Scope Nesting.html create mode 100644 sel-blocksTests/StoreAt - While Scope Nesting.html diff --git a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js index 2814aed..e23f87a 100644 --- a/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js +++ b/sel-blocks-fx_xpi/chrome/content/extensions/selblocks.js @@ -217,6 +217,31 @@ function $X(xpath, contextNode, resultType) { throw new Error("No context to exit from"); } }; + /** + * Stores variable into the context chain in the last parent where it was + * defined. This allows values to bubble up to their relevant parent so that + * you don't have to pass everything in global scope. The variable must exist + * in a parent scope in order to be caught there, otherwise it will get to + * the global scope and be set there. + * @param {String} propName The name of the variable. + * @param {Mixed} val Any value you could set for a variable. + */ + ContextManager.prototype.storeAtClosestContextWithPropName = + function storeAtClosestContextWithPropName (propName, val) { + var out = undefined; + var idx = this.contexts.length - 1; + while (idx >= 0 && out === undefined) { + if(this.contexts[idx].here.hasOwnProperty(propName)) { + this.contexts[idx].here[String(propName)] = val; + out = true; + } + idx--; + } + if(out === undefined) { + storedVarsGlobal[String(propName)] = val; + $$.LOG.warn(String(propName) + " not found, setting global variable"); + } + }; contextManager = new ContextManager(); /** * State information about the cache. @@ -1533,6 +1558,26 @@ function $X(xpath, contextNode, resultType) { storedVarsLocal[varName] = this.page().findAttribute(target); }; + Selenium.prototype.doStoreAt = function(value, varName) { + contextManager.storeAtClosestContextWithPropName(varName, value); + }; + + Selenium.prototype.doStoreEvalAt = function(value, varName) { + var val = evalWithVars(String(value)); + contextManager.storeAtClosestContextWithPropName(varName, val); + }; + + Selenium.prototype.doStoreAtText = function(target, varName) { + var element = this.page().findElement(target); + var value = getText(element); + contextManager.storeAtClosestContextWithPropName(varName, value); + }; + + Selenium.prototype.doStoreAtAttribute = function(target, varName) { + var value = this.page().findAttribute(target); + contextManager.storeAtClosestContextWithPropName(varName, value); + }; + function evalWithVars(expr) { var result = null; try { diff --git a/sel-blocksTests/StoreAt - Function Scope Nesting.html b/sel-blocksTests/StoreAt - Function Scope Nesting.html new file mode 100644 index 0000000..d428d44 --- /dev/null +++ b/sel-blocksTests/StoreAt - Function Scope Nesting.html @@ -0,0 +1,129 @@ + + + + + + +StoreAt - Function Scope Nesting + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StoreAt - Function Scope Nesting
functionstorel
storeAtthis ends up globalnotDefined
storeAtthis is stored in an ancestorl
assertEvalstoredVarsGlobal.notDefined === "this ends up global"true
assertEvalstoredVarsGlobal.l === "this is stored in an ancestor"false
assertEvalstoredVars.l === "this is stored in an ancestor"true
assertEvalstoredVarsLocal.l === "this is stored in an ancestor"true
assertEval"${l}" === "this is stored in an ancestor"true
endFunction
getEvalstoredVarsGlobal.notDefined = null
assertEvalstoredVarsGlobal.notDefined === nulltrue
getEvalstoredVarsGlobal.l = null;
assertEvalstoredVarsGlobal.l === null;true
storeLocalexists in parentl
assertEvalstoredVars.l === "exists in parent";true
callstorel
assertEvalstoredVarsGlobal.notDefined === "this ends up global"true
assertEvalstoredVarsGlobal.l === "this is stored in an ancestor"false
assertEvalstoredVars.l === "this is stored in an ancestor"true
assertEvalstoredVarsLocal.l === "this is stored in an ancestor"true
assertEval"${l}" === "this is stored in an ancestor"true
+ + diff --git a/sel-blocksTests/StoreAt - While Scope Nesting.html b/sel-blocksTests/StoreAt - While Scope Nesting.html new file mode 100644 index 0000000..7185ae6 --- /dev/null +++ b/sel-blocksTests/StoreAt - While Scope Nesting.html @@ -0,0 +1,140 @@ + + + + + + +StoreAt - While Scope Nesting + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StoreAt - While Scope Nesting
getEvalstoredVarsGlobal.notDefined = null
assertEvalstoredVarsGlobal.notDefined === nulltrue
getEvalstoredVarsGlobal.l = null;
assertEvalstoredVarsGlobal.l === null;true
storeLocalexists in parentl
assertEvalstoredVars.l === "exists in parent";true
storeLocal3x
whilex !== 0
storeAtthis ends up globalnotDefined
storeAtthis is stored in an ancestorl
assertEvalstoredVarsGlobal.notDefined === "this ends up global"true
assertEvalstoredVarsGlobal.l === "this is stored in an ancestor"false
assertEvalstoredVars.l === "this is stored in an ancestor"true
assertEvalstoredVarsLocal.l === "this is stored in an ancestor"true
assertEval"${l}" === "this is stored in an ancestor"true
storeEvalLocalx -= 1
endWhile
deleteVarx
assertEvalstoredVarsGlobal.notDefined === "this ends up global"true
assertEvalstoredVarsGlobal.l === "this is stored in an ancestor"false
assertEvalstoredVars.l === "this is stored in an ancestor"true
assertEvalstoredVarsLocal.l === "this is stored in an ancestor"true
assertEval"${l}" === "this is stored in an ancestor"true
+ +