siesta 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  /*
2
2
 
3
- Siesta 1.1.0
3
+ Siesta 1.1.5
4
4
  Copyright(c) 2009-2012 Bryntum AB
5
5
  http://bryntum.com/contact
6
6
  http://bryntum.com/products/siesta/license
@@ -6114,7 +6114,9 @@ Class('Siesta.Content.Manager', {
6114
6114
  require : true
6115
6115
  },
6116
6116
 
6117
- urls : Joose.I.Object
6117
+ urls : Joose.I.Object,
6118
+
6119
+ maxLoads : 5
6118
6120
  },
6119
6121
 
6120
6122
 
@@ -6142,29 +6144,42 @@ Class('Siesta.Content.Manager', {
6142
6144
  var errorCount = 0
6143
6145
 
6144
6146
  var total = 0
6145
- Joose.O.each(urls, function () { total++ })
6147
+ var urlsArray = []
6148
+ Joose.O.each(urls, function (value, url) { total++; urlsArray.push(url) })
6146
6149
 
6147
- if (total)
6148
- Joose.O.each(urls, function (value, url) {
6150
+ if (total) {
6151
+
6152
+ var loadSingle = function (url) {
6153
+ if (!url) return
6149
6154
 
6150
6155
  me.load(url, function (content) {
6151
6156
  if (errorCount) return
6152
6157
 
6153
6158
  urls[ url ] = content
6154
6159
 
6155
- if (++loadCount == total) callback && callback()
6160
+ if (++loadCount == total)
6161
+ callback && callback()
6162
+ else
6163
+ loadSingle(urlsArray.shift())
6156
6164
 
6157
6165
  }, ignoreErrors ? function () {
6158
6166
 
6159
- if (++loadCount == total) callback && callback()
6167
+ if (++loadCount == total)
6168
+ callback && callback()
6169
+ else
6170
+ loadSingle(urlsArray.shift())
6160
6171
 
6161
6172
  } : function () {
6162
6173
  errorCount++
6163
6174
 
6164
6175
  errback && errback(url)
6165
6176
  })
6166
- })
6167
- else
6177
+ }
6178
+
6179
+ // running only `maxLoads` "loading threads" at the same time
6180
+ for (var i = 0; i < this.maxLoads; i++) loadSingle(urlsArray.shift())
6181
+
6182
+ } else
6168
6183
  callback && callback()
6169
6184
  },
6170
6185
 
@@ -6206,6 +6221,8 @@ Class('Siesta.Result.Diagnostic', {
6206
6221
  isa : Siesta.Result,
6207
6222
 
6208
6223
  has : {
6224
+ isWarning : false,
6225
+
6209
6226
  isSimulatedEvent : false,
6210
6227
 
6211
6228
  // Used by simulated events
@@ -6217,8 +6234,7 @@ Class('Siesta.Result.Diagnostic', {
6217
6234
  methods : {
6218
6235
 
6219
6236
  toString : function () {
6220
- var message = '# ' + this.description;
6221
- return message;
6237
+ return '# ' + this.description
6222
6238
  }
6223
6239
  }
6224
6240
  });
@@ -6400,6 +6416,7 @@ Role('Siesta.Test.More', {
6400
6416
  'onload',
6401
6417
  'onerror',
6402
6418
  'StartTest',
6419
+ 'startTest',
6403
6420
  // will be reported in IE8 after overriding
6404
6421
  'setTimeout',
6405
6422
  'clearTimeout'
@@ -6636,7 +6653,7 @@ Role('Siesta.Test.More', {
6636
6653
 
6637
6654
  desc = desc || 'throwsOk';
6638
6655
 
6639
- var e = this.global.StartTest.exceptionCatcher(func)
6656
+ var e = this.getExceptionCatcher()(func)
6640
6657
 
6641
6658
  // assuming no one will throw undefined exception..
6642
6659
  if (e === undefined) {
@@ -6648,7 +6665,7 @@ Role('Siesta.Test.More', {
6648
6665
  return
6649
6666
  }
6650
6667
 
6651
- if (e instanceof this.global.StartTest.testError)
6668
+ if (e instanceof this.getTestErrorClass())
6652
6669
  //IE uses non-standard 'description' property for error msg
6653
6670
  e = e.message || e.description
6654
6671
 
@@ -6703,7 +6720,7 @@ Role('Siesta.Test.More', {
6703
6720
  func = [ desc, desc = func][ 0 ]
6704
6721
  }
6705
6722
 
6706
- var e = this.global.StartTest.exceptionCatcher(func)
6723
+ var e = this.getExceptionCatcher()(func)
6707
6724
 
6708
6725
  if (e === undefined)
6709
6726
  this.pass(desc)
@@ -6892,9 +6909,9 @@ Role('Siesta.Test.More', {
6892
6909
  *
6893
6910
  * This method has a synonym with singular name: `expectGlobal`
6894
6911
  *
6895
- * @param {String} name1 The name of global property
6896
- * @param {String} name2 The name of global property
6897
- * @param {String} nameN The name of global property
6912
+ * @param {String/RegExp} name1 The name of global property or the regular expression to match several properties
6913
+ * @param {String/RegExp} name2 The name of global property or the regular expression to match several properties
6914
+ * @param {String/RegExp} nameN The name of global property or the regular expression to match several properties
6898
6915
  */
6899
6916
  expectGlobals : function () {
6900
6917
  this.expectedGlobals.push.apply(this.expectedGlobals, arguments)
@@ -6908,9 +6925,9 @@ Role('Siesta.Test.More', {
6908
6925
  *
6909
6926
  * You can enable this assertion to automatically happen at the end of each test, using {@link Siesta.Harness#autoCheckGlobals autoCheckGlobals} option of the harness.
6910
6927
  *
6911
- * @param {String} name1 The name of global property
6912
- * @param {String} name2 The name of global property
6913
- * @param {String} nameN The name of global property
6928
+ * @param {String/RegExp} name1 The name of global property or the regular expression to match several properties
6929
+ * @param {String/RegExp} name2 The name of global property or the regular expression to match several properties
6930
+ * @param {String/RegExp} nameN The name of global property or the regular expression to match several properties
6914
6931
  */
6915
6932
  verifyGlobals : function () {
6916
6933
  if (this.disableGlobalsCheck) {
@@ -6922,16 +6939,33 @@ Role('Siesta.Test.More', {
6922
6939
  this.expectGlobals.apply(this, arguments)
6923
6940
 
6924
6941
  var me = this
6925
- var expectedGlobals = {}
6926
- var failed = false
6942
+ var expectedStrings = {}
6943
+ var expectedRegExps = []
6927
6944
 
6928
- Joose.A.each(this.expectedGlobals.concat(this.browserGlobals), function (name) { expectedGlobals[ name ] = true })
6945
+ Joose.A.each(this.expectedGlobals.concat(this.browserGlobals), function (value) {
6946
+ if (me.typeOf(value) == 'RegExp')
6947
+ expectedRegExps.push(value)
6948
+ else
6949
+ expectedStrings[ value ] = true
6950
+ })
6929
6951
 
6930
6952
  this.diag('Global variables')
6931
6953
 
6954
+ var failed = false
6955
+
6932
6956
  for (var name in this.global) {
6957
+ if (expectedStrings[ name ]) continue
6958
+
6959
+ var isExpected = false
6960
+
6961
+ for (var i = 0; i < expectedRegExps.length; i++) {
6962
+ if (expectedRegExps[ i ].test(name)) {
6963
+ isExpected = true
6964
+ break
6965
+ }
6966
+ }
6933
6967
 
6934
- if (!expectedGlobals[ name ]) {
6968
+ if (!isExpected) {
6935
6969
  me.fail('Unexpected global found', 'Global name: ' + name + ', value: ' + Siesta.Util.Serializer.stringify(this.global[name]))
6936
6970
 
6937
6971
  failed = true
@@ -6968,7 +7002,7 @@ Role('Siesta.Test.More', {
6968
7002
  else
6969
7003
  this.fail(desc, annotation, result);
6970
7004
  },
6971
-
7005
+
6972
7006
 
6973
7007
  /**
6974
7008
  * Waits for passed checker method to return true (or any non-false value, like for example DOM element or array), and calls the callback when this happens.
@@ -6980,8 +7014,9 @@ Role('Siesta.Test.More', {
6980
7014
  * @param {Function} callback A function to call when the condition has been met. Will receive a result from checker function.
6981
7015
  * @param {Object} scope The scope for the callback
6982
7016
  * @param {Int} timeout The maximum amount of time (in milliseconds) to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
7017
+ * @param {Int} interval The polling interval (in milliseconds)
6983
7018
  */
6984
- waitFor : function (method, callback, scope, timeout) {
7019
+ waitFor : function (method, callback, scope, timeout, interval) {
6985
7020
  var description = this.typeOf(method) == 'Number' ? (method + ' ms') : ' condition to be fullfilled';
6986
7021
  var assertionName = 'waitFor';
6987
7022
 
@@ -6992,11 +7027,17 @@ Role('Siesta.Test.More', {
6992
7027
  callback = options.callback;
6993
7028
  scope = options.scope;
6994
7029
  timeout = options.timeout;
7030
+ interval = options.interval
7031
+
6995
7032
  description = options.description;
6996
7033
  assertionName = options.assertionName || assertionName;
7034
+
6997
7035
  }
6998
7036
 
6999
- var async = this.beginAsync(),
7037
+ interval = interval || this.waitForPollInterval
7038
+ timeout = timeout || this.waitForTimeout
7039
+
7040
+ var async = this.beginAsync(timeout + 2 * interval),
7000
7041
  me = this;
7001
7042
 
7002
7043
  var sourceLine = me.getSourceLine();
@@ -7015,13 +7056,12 @@ Role('Siesta.Test.More', {
7015
7056
  if (this.typeOf(method) == 'Number') {
7016
7057
  pollTimeout = originalSetTimeout(function() {
7017
7058
  me.endAsync(async);
7018
- callback.call(scope || me);
7059
+ me.processCallbackFromTest(callback, [], scope || me)
7060
+
7019
7061
  }, method);
7020
7062
 
7021
7063
  me.finalizeWaiting(waitAssertion, true, 'Waited ' + method + ' ms');
7022
7064
  } else {
7023
-
7024
- timeout = timeout || this.waitForTimeout
7025
7065
 
7026
7066
  var startDate = new Date()
7027
7067
 
@@ -7058,9 +7098,10 @@ Role('Siesta.Test.More', {
7058
7098
  me.endAsync(async);
7059
7099
 
7060
7100
  me.finalizeWaiting(waitAssertion, true, 'Waited ' + time + ' ms for ' + description);
7061
- callback.call(scope || me, result);
7101
+
7102
+ me.processCallbackFromTest(callback, [ result ], scope || me)
7062
7103
  } else
7063
- pollTimeout = originalSetTimeout(pollFunc, me.waitForPollInterval)
7104
+ pollTimeout = originalSetTimeout(pollFunc, interval)
7064
7105
  }
7065
7106
 
7066
7107
  pollFunc()
@@ -7072,8 +7113,8 @@ Role('Siesta.Test.More', {
7072
7113
  // returns "true" if "next" is used,
7073
7114
  analyzeChainStep : function (func) {
7074
7115
  var sources = func.toString()
7075
- var firstArg = sources.match(/function\s*[^(]*\((.*?)(?:,|\))/)[ 1 ]
7076
-
7116
+ var firstArg = sources.match(/function\s*[^(]*\(\s*(.*?)\s*(?:,|\))/)[ 1 ]
7117
+
7077
7118
  if (!firstArg) return false
7078
7119
 
7079
7120
  var body = sources.match(/\{([\s\S]*)\}/)[ 1 ]
@@ -7158,11 +7199,13 @@ Role('Siesta.Test.More', {
7158
7199
  observeTest : this
7159
7200
  })
7160
7201
 
7202
+ var sourceLine = me.getSourceLine();
7203
+
7161
7204
  // inline any arrays in the arguments into one array
7162
- var steps = Array.prototype.concat.apply([], arguments)
7205
+ var steps = Array.prototype.concat.apply([], arguments)
7163
7206
 
7164
- var len = steps.length
7165
- var args = []
7207
+ var len = steps.length
7208
+ var args = []
7166
7209
 
7167
7210
  Joose.A.each(steps, function (step, index) {
7168
7211
 
@@ -7170,10 +7213,29 @@ Role('Siesta.Test.More', {
7170
7213
 
7171
7214
  queue.addAsyncStep({
7172
7215
  processor : function (data) {
7173
- var async = me.beginAsync()
7216
+ var isWaitStep = step.action == 'wait' || step.waitFor
7217
+
7218
+ if (!isWaitStep) {
7219
+ var timeout = step.timeout || me.defaultTimeout
7220
+
7221
+ // + 100 to allow `waitFor` steps (which will be waiting the `timeout` time) to
7222
+ // generate their own failures
7223
+ var async = me.beginAsync(timeout + 100, function () {
7224
+ me.fail(
7225
+ 'The step in `t.chain()` call did not complete within required timeframe, chain can not proceed',
7226
+ {
7227
+ sourceLine : sourceLine,
7228
+ annotation : 'Step number: ' + (index + 1) + ' (1-based)' + (sourceLine ? '\nAt line : ' + sourceLine : ''),
7229
+ ownTextOnly : true
7230
+ }
7231
+ )
7232
+
7233
+ return true
7234
+ })
7235
+ }
7174
7236
 
7175
7237
  var nextFunc = function () {
7176
- me.endAsync(async)
7238
+ if (!isWaitStep) me.endAsync(async)
7177
7239
 
7178
7240
  args = Array.prototype.slice.call(arguments);
7179
7241
 
@@ -7194,9 +7256,23 @@ Role('Siesta.Test.More', {
7194
7256
  // and finalize the async frame manually, as the "nextFunc" for last step will never be called
7195
7257
  isLast && me.endAsync(async)
7196
7258
 
7259
+ } else if (me.typeOf(step) == 'String') {
7260
+ var action = new Siesta.Test.Action.Eval({
7261
+ actionString : step,
7262
+ next : nextFunc,
7263
+ test : me
7264
+ })
7265
+
7266
+ action.process()
7267
+
7197
7268
  } else {
7198
7269
  if (!step.args) step.args = args
7199
7270
 
7271
+ // Don't pass target to next step if it is a waitFor action, it does not make sense and messes up the arguments
7272
+ if (!isLast && (steps[ index + 1 ].waitFor || steps[ index + 1 ].action == 'wait')) {
7273
+ step.passTargetToNext = false;
7274
+ }
7275
+
7200
7276
  step.next = nextFunc
7201
7277
  step.test = me
7202
7278
 
@@ -7303,6 +7379,9 @@ Class('Siesta.Test', {
7303
7379
  results : Joose.I.Array,
7304
7380
 
7305
7381
  run : { required : true },
7382
+ startTestAnchor : null,
7383
+ exceptionCatcher : null,
7384
+ testErrorClass : null,
7306
7385
 
7307
7386
  harness : { required : true },
7308
7387
 
@@ -7428,9 +7507,16 @@ Class('Siesta.Test', {
7428
7507
  * @return {Object} Object with properties `{ ready : true/false, reason : 'description' }`
7429
7508
  */
7430
7509
  isReady: function() {
7510
+ // this should allow us to wait until the presense of "run" function
7511
+ // it will become available after call to StartTest method
7512
+ // which some users may call asynchronously, after some delay
7513
+ // see https://www.assembla.com/spaces/bryntum/tickets/379
7514
+ // in this case test can not be configured using object as 1st argument for StartTest
7515
+ this.run = this.run || this.getStartTestAnchor().args && this.getStartTestAnchor().args[ 0 ]
7516
+
7431
7517
  return {
7432
- ready : true,
7433
- reason : ''
7518
+ ready : this.typeOf(this.run) == 'Function',
7519
+ reason : 'No code provided to test'
7434
7520
  }
7435
7521
  },
7436
7522
 
@@ -7561,7 +7647,7 @@ Class('Siesta.Test', {
7561
7647
  var gotDesc = params.gotDesc || 'Got'
7562
7648
  var needDesc = params.needDesc || 'Need'
7563
7649
 
7564
- if (assertionName || sourceLine) strings.push(
7650
+ if (!params.ownTextOnly && (assertionName || sourceLine)) strings.push(
7565
7651
  'Failed assertion ' + (assertionName ? '[' + assertionName + '] ' : '') + this.formatSourceLine(sourceLine)
7566
7652
  )
7567
7653
 
@@ -7630,23 +7716,83 @@ Class('Siesta.Test', {
7630
7716
  },
7631
7717
 
7632
7718
 
7719
+ getStartTestAnchor : function () {
7720
+ return this.startTestAnchor
7721
+ },
7722
+
7723
+
7724
+ getExceptionCatcher : function () {
7725
+ return this.exceptionCatcher
7726
+ },
7727
+
7728
+
7729
+ getTestErrorClass : function () {
7730
+ return this.testErrorClass
7731
+ },
7732
+
7733
+
7734
+ processCallbackFromTest : function (callback, args, scope) {
7735
+ var me = this
7736
+
7737
+ if (this.transparentEx) {
7738
+ callback.apply(scope || this.global, args || [])
7739
+ } else {
7740
+ var e = this.getExceptionCatcher()(function(){
7741
+ callback.apply(scope || me.global, args || [])
7742
+ })
7743
+
7744
+ if (e) {
7745
+ this.failWithException(e)
7746
+
7747
+ // flow should be interrupted - exception detected
7748
+ return false
7749
+ }
7750
+ }
7751
+
7752
+ // flow can be continued
7753
+ return true
7754
+ },
7755
+
7756
+
7633
7757
  getStackTrace : function (e) {
7634
7758
  if (Object(e) !== e) return null
7635
7759
  if (!e.stack) return null
7636
7760
 
7637
- var text = e.stack
7638
- var traceLineRegex = /\((.*?)\)@(.*?):(\d+)\n/g;
7761
+ var text = e.stack + '';
7762
+ var isFirefox = /^@/.test(text)
7763
+ var lines = text.split('\n')
7764
+
7765
+ var result = []
7639
7766
  var match
7640
7767
 
7641
- var result = []
7768
+ for (var i = 0; i < lines.length; i++) {
7769
+ if (!lines[ i ]) continue
7770
+
7771
+ if (!i) {
7772
+ if (isFirefox)
7773
+ result.push(e + '')
7774
+ else {
7775
+ result.push(lines[ i ])
7776
+ continue;
7777
+ }
7778
+ }
7642
7779
 
7643
- for (var i = 0; match = traceLineRegex.exec(text); i++)
7644
- if (i)
7645
- // other lines
7646
- result.push('at line ' + match[ 3 ] + ' of ' + match[ 2 ])
7647
- else
7648
- // first line
7649
- result.push(match[ 1 ] + ' at line ' + match[ 3 ] + ' of ' + match[ 2 ])
7780
+ if (isFirefox) {
7781
+ match = /@(.*?):(\d+)/.exec(lines[ i ]);
7782
+
7783
+ // the format of stack trace in Firefox has changed, 080_exception_parsing should fail
7784
+ if (!match) return null
7785
+
7786
+ result.push(' at line ' + match[ 2 ] + ' of ' + match[ 1 ])
7787
+ } else {
7788
+ match = /\s*at\s(.*?):(\d+):(\d+)/.exec(lines[ i ]);
7789
+
7790
+ // the format of stack trace in Chrome has changed, 080_exception_parsing should fail
7791
+ if (!match) return null
7792
+
7793
+ result.push(' at line ' + match[ 2 ] + ', character ' + match[ 3 ] + ', of ' + match[ 1 ])
7794
+ }
7795
+ }
7650
7796
 
7651
7797
  if (!result.length) return null
7652
7798
 
@@ -7730,7 +7876,7 @@ Class('Siesta.Test', {
7730
7876
  * @param {String} desc The description of the assertion
7731
7877
  */
7732
7878
  is : function (got, expected, desc) {
7733
- if (got instanceof this.global.Date) {
7879
+ if (expected && got instanceof this.global.Date) {
7734
7880
  this.isDateEqual(got, expected, desc);
7735
7881
  } else if (got == expected)
7736
7882
  this.pass(desc)
@@ -7863,7 +8009,7 @@ Class('Siesta.Test', {
7863
8009
 
7864
8010
  /**
7865
8011
  * This method starts the "asynchronous frame". The test will wait for all asynchronous frames to complete before it will finalize.
7866
- * The frame can be finished with the {@link #endAsync} call.
8012
+ * The frame should be finished with the {@link #endAsync} call within the provided `time`, otherwise a failure will be reported.
7867
8013
  *
7868
8014
  * For example:
7869
8015
  *
@@ -7878,27 +8024,42 @@ Class('Siesta.Test', {
7878
8024
  *
7879
8025
  *
7880
8026
  * @param {Number} time The maximum time (in ms) to wait until force the finalization of this async frame. Optional. Default time is 15000 ms.
8027
+ * @param {Function} errback Optional. The function to call in case the call to {@link #endAsync} was not detected withing `time`. If function
8028
+ * will return any "truthy" value, the failure will not be reported (you can report own failure with this errback).
8029
+ *
7881
8030
  * @return {Object} The frame object, which can be used in {@link #endAsync} call
7882
8031
  */
7883
- beginAsync : function (time) {
8032
+ beginAsync : function (time, errback) {
8033
+ time = time || this.defaultTimeout
8034
+
7884
8035
  var me = this
7885
8036
  var originalSetTimeout = this.originalSetTimeout
7886
8037
 
8038
+ var index = this.timeoutsCount++
8039
+
7887
8040
  // in NodeJS `setTimeout` returns an object and not a simple ID, so we try hard to store that object under unique index
7888
8041
  // also using `setTimeout` from the scope of test - as timeouts in different scopes in browsers are mis-synchronized
7889
8042
  // can't just use `this.originalSetTimeout` because of scoping issues
7890
- var timeoutId = originalSetTimeout(function () {
7891
- me.endAsync(index)
7892
- }, time || this.defaultTimeout)
7893
-
7894
- var index = this.timeoutsCount++
8043
+ var timeoutId = originalSetTimeout(function () {
8044
+
8045
+ if (me.hasAsyncFrame(index)) {
8046
+ if (!errback || !errback.call(me, me)) me.fail('No matching `endAsync` call within ' + time + 'ms')
8047
+
8048
+ me.endAsync(index)
8049
+ }
8050
+ }, time)
7895
8051
 
7896
- this.timeoutIds[ index ] = timeoutId
8052
+ this.timeoutIds[ index ] = timeoutId
7897
8053
 
7898
8054
  return index
7899
8055
  },
7900
8056
 
7901
8057
 
8058
+ hasAsyncFrame : function (index) {
8059
+ return this.timeoutIds.hasOwnProperty(index)
8060
+ },
8061
+
8062
+
7902
8063
  /**
7903
8064
  * This method finalize the "asynchronous frame" started with {@link #beginAsync}.
7904
8065
  *
@@ -7931,7 +8092,6 @@ Class('Siesta.Test', {
7931
8092
 
7932
8093
 
7933
8094
  clearTimeouts : function () {
7934
- var me = this
7935
8095
  var originalClearTimeout = this.originalClearTimeout
7936
8096
 
7937
8097
  Joose.O.each(this.timeoutIds, function (value, id) {
@@ -8008,10 +8168,14 @@ Class('Siesta.Test', {
8008
8168
  originalClearTimeout : this.originalClearTimeout
8009
8169
  })
8010
8170
 
8011
- var exception = this.global.StartTest.exceptionCatcher(function(){
8171
+ var exception = this.getExceptionCatcher()(function(){
8012
8172
  code(todo)
8013
8173
  })
8014
8174
 
8175
+ todo.global = null
8176
+ todo.originalSetTimeout = null
8177
+ todo.originalClearTimeout = null
8178
+
8015
8179
  if (exception !== undefined) this.diag("TODO section threw an exception: [" + exception + "]")
8016
8180
  },
8017
8181
 
@@ -8049,7 +8213,8 @@ Class('Siesta.Test', {
8049
8213
  this.harness.onTestStart(this)
8050
8214
 
8051
8215
  /**
8052
- * This event is fired when the individual test case starts. When started, test may still be waiting for the
8216
+ * This event is fired when the individual test case starts. When *started*, test may still be waiting for the {@link #isReady} conditions
8217
+ * to be fullfilled. Once all conditions will be fullfilled, test will be *launched*.
8053
8218
  *
8054
8219
  * This event bubbles up to the {@link Siesta.Harness harness}, you can observe it on harness as well.
8055
8220
  *
@@ -8061,7 +8226,7 @@ Class('Siesta.Test', {
8061
8226
  this.fireEvent('teststart', this);
8062
8227
 
8063
8228
  if (alreadyFailedWithException) {
8064
- this.failWithException(alreadyFailedWithException)
8229
+ this.failWithException(alreadyFailedWithException)
8065
8230
 
8066
8231
  return
8067
8232
  }
@@ -8141,7 +8306,7 @@ Class('Siesta.Test', {
8141
8306
  else
8142
8307
  // in browser (where `timeoutId` is a number) - to the `idsToIndex` hash
8143
8308
  me.idsToIndex[ timeoutId ] = index
8144
-
8309
+
8145
8310
  return me.timeoutIds[ index ] = timeoutId
8146
8311
  }
8147
8312
 
@@ -8181,10 +8346,13 @@ Class('Siesta.Test', {
8181
8346
  global.clearTimeout = originalClearTimeout
8182
8347
  }
8183
8348
 
8184
- originalSetTimeout = me.originalSetTimeout = null
8185
- originalClearTimeout = me.originalClearTimeout = null
8349
+ originalSetTimeout = me.originalSetTimeout = null
8350
+ originalClearTimeout = me.originalClearTimeout = null
8186
8351
 
8187
- me.global = global = null
8352
+ me.global = global = null
8353
+ me.run = run = null
8354
+ me.exceptionCatcher = me.testErrorClass = null
8355
+ me.startTestAnchor = null
8188
8356
  }
8189
8357
 
8190
8358
  var run = this.run
@@ -8192,7 +8360,7 @@ Class('Siesta.Test', {
8192
8360
  if (this.transparentEx)
8193
8361
  run(me)
8194
8362
  else
8195
- var e = global.StartTest.exceptionCatcher(function(){
8363
+ var e = this.getExceptionCatcher()(function(){
8196
8364
  run(me)
8197
8365
  })
8198
8366
 
@@ -8214,11 +8382,12 @@ Class('Siesta.Test', {
8214
8382
  if (force) this.clearTimeouts()
8215
8383
 
8216
8384
  if (!Joose.O.isEmpty(this.timeoutIds)) {
8217
- if (!this.__timeoutWarning && this.overrideSetTimeout && this.lastActivityDate &&
8218
- new Date() - this.lastActivityDate > this.defaultTimeout)
8219
- {
8385
+ if (
8386
+ !this.__timeoutWarning && this.overrideSetTimeout && this.lastActivityDate &&
8387
+ new Date() - this.lastActivityDate > this.defaultTimeout * 2
8388
+ ) {
8220
8389
  this.diag('Your test is still considered to be running, if this is unexpected please see console for more information');
8221
- this.warn('Warning, your test has not finalized, most likely since a timer (setTimeout) is still active ' +
8390
+ this.warn('Your test [' + this.url + '] has not finalized, most likely since a timer (setTimeout) is still active. ' +
8222
8391
  'If this is the expected behavior, try setting "overrideSetTimeout : false" on your Harness configuration.');
8223
8392
  this.__timeoutWarning = true;
8224
8393
  }
@@ -8259,25 +8428,6 @@ Class('Siesta.Test', {
8259
8428
  this.fireEvent('testfinalize', this);
8260
8429
 
8261
8430
  this.callback && this.callback()
8262
-
8263
- // // attempting to clear all references to scope, but with delay, to allow
8264
- // // other potentially delayed actions to access `global`
8265
- // var me = this
8266
- //
8267
- // var originalSetTimeout = me.originalSetTimeout
8268
- //
8269
- // // setTimeout from the scope of harness
8270
- // originalSetTimeout(function () {
8271
- // if (me.overrideSetTimeout) {
8272
- // me.global.setTimeout = me.originalSetTimeout
8273
- // me.global.clearTimeout = me.originalClearTimeout
8274
- // }
8275
- //
8276
- // originalSetTimeout = me.originalSetTimeout = null
8277
- // me.originalClearTimeout = null
8278
- //
8279
- // me.global = null
8280
- // }, 700)
8281
8431
  },
8282
8432
 
8283
8433
 
@@ -8454,9 +8604,10 @@ Class('Siesta.Test', {
8454
8604
 
8455
8605
 
8456
8606
  warn : function (message) {
8457
- // TODO add "warning" diagnostic message with red background or somethin
8458
-
8459
- if (typeof console != 'undefined' && console.warn) console.warn(message)
8607
+ this.addResult(new Siesta.Result.Diagnostic({
8608
+ description : message,
8609
+ isWarning : true
8610
+ }))
8460
8611
  }
8461
8612
  }
8462
8613
 
@@ -8471,6 +8622,21 @@ Role('Siesta.Test.Todo', {
8471
8622
 
8472
8623
  methods : {
8473
8624
 
8625
+ getExceptionCatcher : function () {
8626
+ return this.parent.getExceptionCatcher()
8627
+ },
8628
+
8629
+
8630
+ getTestErrorClass : function () {
8631
+ return this.parent.getTestErrorClass()
8632
+ },
8633
+
8634
+
8635
+ getStartTestAnchor : function () {
8636
+ return this.parent.getStartTestAnchor()
8637
+ },
8638
+
8639
+
8474
8640
  addResult : function (result) {
8475
8641
  if (result instanceof Siesta.Result.Assertion) result.isTodo = true
8476
8642
 
@@ -8539,6 +8705,10 @@ Class('Siesta.Test.Action', {
8539
8705
  has : {
8540
8706
  args : null,
8541
8707
 
8708
+ /**
8709
+ * @cfg {String} desc When provided, once step is completed, a passing assertion with this text will be added to a test.
8710
+ * This configuration option can be useful to indicate the progress of "wait" steps
8711
+ */
8542
8712
  desc : null,
8543
8713
  test : { required : true },
8544
8714
  next : { required : true },
@@ -8598,6 +8768,8 @@ Class('Siesta.Test.Action.Done', {
8598
8768
 
8599
8769
  process : function () {
8600
8770
  this.test.done(this.delay)
8771
+
8772
+ this.next()
8601
8773
  }
8602
8774
  }
8603
8775
  });
@@ -8673,14 +8845,14 @@ Class('Siesta.Test.Action.Wait', {
8673
8845
  *
8674
8846
  * A number of milliseconds to wait before continuing.
8675
8847
  */
8676
- delay : 1000,
8848
+ delay : 1000,
8677
8849
 
8678
8850
  /**
8679
8851
  * @cfg {Number} timeout
8680
8852
  *
8681
8853
  * The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
8682
8854
  */
8683
- timeout : null,
8855
+ timeout : null,
8684
8856
 
8685
8857
  /**
8686
8858
  * @cfg {Array} args
@@ -8725,25 +8897,24 @@ Class('Siesta.Test.Action.Wait', {
8725
8897
  this.args = [ waitFor ];
8726
8898
  waitFor = '';
8727
8899
  }
8728
-
8900
+
8901
+ if (waitFor == null) {
8902
+ this.args = [ this.delay ];
8903
+ waitFor = '';
8904
+ }
8905
+
8729
8906
  if (this.test.typeOf(this.args) !== "Array") {
8730
8907
  this.args = [ this.args ];
8731
8908
  }
8732
8909
 
8733
- if (waitFor != null) {
8734
- // also allow full method names
8735
- waitFor = waitFor.replace(/^waitFor/, '')
8736
- var methodName = 'waitFor' + Joose.S.uppercaseFirst(waitFor);
8737
-
8738
- if (!test[methodName]){
8739
- throw 'Could not find a waitFor method named ' + methodName;
8740
- }
8741
- test[methodName].apply(test, this.args.concat(this.next, test, this.timeout || test.waitForTimeout));
8742
- } else {
8743
- var originalSetTimeout = test.originalSetTimeout;
8910
+ // also allow full method names
8911
+ waitFor = waitFor.replace(/^waitFor/, '')
8912
+ var methodName = 'waitFor' + Joose.S.uppercaseFirst(waitFor);
8744
8913
 
8745
- originalSetTimeout(this.next, this.delay);
8914
+ if (!test[methodName]){
8915
+ throw 'Could not find a waitFor method named ' + methodName;
8746
8916
  }
8917
+ test[methodName].apply(test, this.args.concat(this.next, test, this.timeout || test.waitForTimeout));
8747
8918
  }
8748
8919
  }
8749
8920
  });
@@ -8753,6 +8924,115 @@ Joose.A.each(['wait', 'delay'], function(name) {
8753
8924
  });;
8754
8925
  /**
8755
8926
 
8927
+ @class Siesta.Test.Action.Eval
8928
+ @extends Siesta.Test.Action
8929
+
8930
+ This action can be included in the `t.chain` steps only with a plain string. Siesta will examine the passed string,
8931
+ and call an apropriate method of the test class. String should have the following format:
8932
+
8933
+ methodName(params)
8934
+
8935
+ Method name is anything until the first parenthes. Method name may have an optional prefix `t.`.
8936
+ Everything in between of outermost parentheses will be treated as parameters for method call. For example:
8937
+
8938
+ t.chain(
8939
+ // string should look like a usual method call,
8940
+ // but arguments can't reference any variables
8941
+ // strings should be quoted, to include quoting symbol in string use double slash: \\
8942
+ 't.click("combo[type=some\\"Type] => .x-form-trigger")',
8943
+
8944
+ // leading "t." is optional, but quoting is not
8945
+ 'waitForComponent("combo[type=someType]")',
8946
+
8947
+ // JSON objects are ok, but they should be a valid JSON - ie object properties should be quoted
8948
+ 'myClick([ 10, 10 ], { "foo" : "bar" })',
8949
+ )
8950
+
8951
+ * **Note** You can pass the JSON objects as arguments, but they should be serialized as valid JSON - ie object properties should be quoted.
8952
+
8953
+ * **Note** A callback for next step in chain will be always appended to provided parameters. Make sure it is placed in a correct spot!
8954
+ For example if method signature is `t.someMethod(param1, param2, callback)` and you are calling this method as:
8955
+
8956
+ t.chain(
8957
+ `t.someMethod("text")`
8958
+ )
8959
+ it will fail - callback will be provided in place of `param2`. Instead call it as:
8960
+
8961
+ t.chain(
8962
+ `t.someMethod("text", null)`
8963
+ )
8964
+
8965
+ This action may save you few keystrokes, when you need to perform some action with static arguments (known prior the action).
8966
+
8967
+ */
8968
+ Class('Siesta.Test.Action.Eval', {
8969
+
8970
+ isa : Siesta.Test.Action,
8971
+
8972
+ has : {
8973
+ /**
8974
+ * @cfg {Object} options
8975
+ *
8976
+ * Any options that will be used when simulating the event. For information about possible
8977
+ * config options, please see: https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent
8978
+ */
8979
+ actionString : null
8980
+ },
8981
+
8982
+
8983
+ methods : {
8984
+
8985
+ process : function () {
8986
+ var test = this.test
8987
+ var parsed = this.parseActionString(this.actionString)
8988
+
8989
+ if (parsed.error) {
8990
+ test.fail(parsed.error)
8991
+ this.next()
8992
+ return
8993
+ }
8994
+
8995
+ var methodName = parsed.methodName
8996
+
8997
+ if (!methodName || test.typeOf(test[ methodName ]) != 'Function') {
8998
+ test.fail("Invalid method name: " + methodName)
8999
+ this.next()
9000
+ return
9001
+ }
9002
+
9003
+ parsed.params.push(this.next)
9004
+
9005
+ test[ methodName ].apply(test, parsed.params)
9006
+ },
9007
+
9008
+
9009
+ parseActionString : function (actionString) {
9010
+ var match = /^\s*(.+?)\(\s*(.*)\s*\)\s*$/.exec(actionString)
9011
+
9012
+ if (!match) return {
9013
+ error : "Wrong format of the action string: " + actionString
9014
+ }
9015
+
9016
+ var methodName = match[ 1 ].replace(/^t\./, '')
9017
+
9018
+ try {
9019
+ var params = JSON.parse('[' + match[ 2 ] + ']')
9020
+ } catch (e) {
9021
+ return {
9022
+ error : "Can't parse arguments: " + match[ 2 ]
9023
+ }
9024
+ }
9025
+
9026
+ return {
9027
+ methodName : methodName,
9028
+ params : params
9029
+ }
9030
+ }
9031
+ }
9032
+ });
9033
+ ;
9034
+ /**
9035
+
8756
9036
  @class Siesta.Harness
8757
9037
 
8758
9038
  `Siesta.Harness` is an abstract base harness class in Siesta hierarchy. This class provides no UI,
@@ -8836,7 +9116,7 @@ Class('Siesta.Harness', {
8836
9116
 
8837
9117
  has : {
8838
9118
  /**
8839
- * @cfg {String} title The title of the test suite. Can contain HTML.
9119
+ * @cfg {String} title The title of the test suite. Can contain HTML. When provided in the test file descriptor - will change the name of test in the harness UI.
8840
9120
  */
8841
9121
  title : null,
8842
9122
 
@@ -8849,8 +9129,6 @@ Class('Siesta.Harness', {
8849
9129
  testClass : Siesta.Test,
8850
9130
  contentManagerClass : Siesta.Content.Manager,
8851
9131
 
8852
- testsByURL : Joose.I.Object,
8853
-
8854
9132
  // fields of test descriptor:
8855
9133
  // - id - either `url` or wbs + group - computed
8856
9134
  // - url
@@ -8872,7 +9150,12 @@ Class('Siesta.Harness', {
8872
9150
  descriptors : Joose.I.Array,
8873
9151
  descriptorsById : Joose.I.Object,
8874
9152
 
9153
+ launchCounter : 0,
9154
+
9155
+ launches : Joose.I.Object,
9156
+
8875
9157
  scopesByURL : Joose.I.Object,
9158
+ testsByURL : Joose.I.Object,
8876
9159
 
8877
9160
  /**
8878
9161
  * @cfg {Boolean} transparentEx When set to `true` harness will not try to catch any exception, thrown from the test code.
@@ -8935,7 +9218,7 @@ Class('Siesta.Harness', {
8935
9218
  * This option can be also specified in the test file descriptor.
8936
9219
  */
8937
9220
  expectedGlobals : Joose.I.Array,
8938
- // will be populated by `setup`
9221
+ // will be populated by `populateCleanScopeGlobals`
8939
9222
  cleanScopeGlobals : Joose.I.Array,
8940
9223
 
8941
9224
  /**
@@ -9035,6 +9318,17 @@ Class('Siesta.Harness', {
9035
9318
  */
9036
9319
  keepResults : true,
9037
9320
 
9321
+ /**
9322
+ * @cfg {Number} keepNLastResults
9323
+ *
9324
+ * Only meaningful when {@link #keepResults} is set to `false`. Indicates the number of the test results which still should be kept, for user examination.
9325
+ * Results are cleared when their total number exceed this value, based on FIFO order.
9326
+ */
9327
+ keepNLastResults : 2,
9328
+
9329
+ lastResultsURLs : Joose.I.Array,
9330
+ lastResultsByURL : Joose.I.Object,
9331
+
9038
9332
  /**
9039
9333
  * @cfg {Boolean} overrideSetTimeout When set to `false`, the tests will not override "setTimeout" from the context of each test
9040
9334
  * for asynchronous code tracking. User will need to use `beginAsync/endAsync` calls to indicate that test is still running.
@@ -9225,13 +9519,14 @@ Class('Siesta.Harness', {
9225
9519
  },
9226
9520
 
9227
9521
  /**
9228
- * This method will launch a test suite. It accepts a variable number of *test file descriptors*. A test file descritor is one of the following:
9522
+ * This method will launch a test suite. It accepts a variable number of *test file descriptors* or an array of such. A test file descritor is one of the following:
9229
9523
  *
9230
9524
  * - a string, containing a test file url
9231
9525
  * - an object containing the `url` property `{ url : '...', option1 : 'value1', option2 : 'value2' }`. The `url` property should point to the test file.
9232
9526
  * Other properties can contain values of some configuration options of the harness (marked accordingly). In this case, they will **override** the corresponding values,
9233
9527
  * provided to harness or parent descriptor.
9234
- * - an object `{ group : 'groupName', items : [], expanded : true, option1 : 'value1' }` specifying the folder of test files. The `items` property can contain an array of test file descriptors.
9528
+ * - an object `{ group : 'groupName', items : [], expanded : true, option1 : 'value1' }` specifying the folder of test files. The `expanded` property
9529
+ * sets the initial state of the folder - "collapsed/expanded". The `items` property can contain an array of test file descriptors.
9235
9530
  * Other properties will override the applicable harness options **for all child descriptors**.
9236
9531
  *
9237
9532
  * Groups (folder) may contain nested groups. Number of nesting levels is not limited.
@@ -9294,19 +9589,19 @@ Class('Siesta.Harness', {
9294
9589
  *
9295
9590
  * Values from this object takes the highest priority and will override any other configuration.
9296
9591
  *
9297
- * @param {Mixed} descriptor1
9592
+ * @param {Array/Mixed} descriptor1 or an array of descriptors
9298
9593
  * @param {Mixed} descriptor2
9299
9594
  * @param {Mixed} descriptorN
9300
9595
  */
9301
9596
  start : function () {
9302
9597
  // a bit hackish - used by Selenium reporter..
9303
- var me = __ACTIVE_HARNESS__ = this
9598
+ var me = Siesta.my.activeHarness = this
9304
9599
 
9305
9600
  this.mainPreset = new Siesta.Content.Preset({
9306
9601
  preload : this.processPreloadArray(this.preload)
9307
9602
  })
9308
9603
 
9309
- var descriptors = this.descriptors = Joose.A.map(arguments, function (desc, index) {
9604
+ var descriptors = this.descriptors = Joose.A.map(Array.prototype.concat.apply([], arguments), function (desc, index) {
9310
9605
  return me.normalizeDescriptor(desc, me, index)
9311
9606
  })
9312
9607
 
@@ -9334,6 +9629,8 @@ Class('Siesta.Harness', {
9334
9629
  if (desc.preset != me.mainPreset) presets.push(desc.preset)
9335
9630
 
9336
9631
  testScriptsPreset.addResource(desc.url)
9632
+
9633
+ me.deleteTestByURL(desc.url)
9337
9634
  })
9338
9635
 
9339
9636
  // cache either everything (this.cachePreload) or only the test files (to be able to show missing files / show content)
@@ -9359,8 +9656,9 @@ Class('Siesta.Harness', {
9359
9656
 
9360
9657
  // if testConfig contains the "preload" or "alsoPreload" key - then we need to update the preset of the descriptor
9361
9658
  if (testConfig && (testConfig.preload || testConfig.alsoPreload)) desc.preset = me.getDescriptorPreset(desc)
9362
- } else
9363
- desc.isMissing = true
9659
+ } else
9660
+ // allow subclasses to define there own logic when found missing test file
9661
+ me.markMissingFile(desc)
9364
9662
 
9365
9663
  me.normalizeScopeProvider(desc)
9366
9664
  })
@@ -9377,6 +9675,11 @@ Class('Siesta.Harness', {
9377
9675
  },
9378
9676
 
9379
9677
 
9678
+ markMissingFile : function (desc) {
9679
+ desc.isMissing = true
9680
+ },
9681
+
9682
+
9380
9683
  flattenDescriptors : function (descriptors, includeFolders) {
9381
9684
  var flatten = []
9382
9685
  var me = this
@@ -9568,10 +9871,10 @@ Class('Siesta.Harness', {
9568
9871
 
9569
9872
 
9570
9873
  getSeedingCode : function (desc) {
9571
- return 'StartTest = function () { StartTest.args = arguments };' +
9874
+ return 'StartTest = startTest = function () { StartTest.args = arguments };' +
9572
9875
  // for older IE - the try/catch should be from the same context as the exception
9573
9876
  'StartTest.exceptionCatcher = function (func) { var ex; try { func() } catch (e) { ex = e; }; return ex; };' +
9574
- 'StartTest.testError = Error;'
9877
+ 'StartTest.testErrorClass = Error;'
9575
9878
  },
9576
9879
 
9577
9880
 
@@ -9584,12 +9887,54 @@ Class('Siesta.Harness', {
9584
9887
  },
9585
9888
 
9586
9889
 
9890
+ keepTestResult : function (url) {
9891
+ // already keeping
9892
+ if (this.lastResultsByURL[ url ]) {
9893
+ var indexOf = -1
9894
+
9895
+ Joose.A.each(this.lastResultsURLs, function (resultUrl, i) {
9896
+ if (resultUrl == url) { indexOf = i; return false }
9897
+ })
9898
+
9899
+ this.lastResultsURLs.splice(indexOf, 1)
9900
+ this.lastResultsURLs.push(url)
9901
+
9902
+ return
9903
+ }
9904
+
9905
+ this.lastResultsURLs.push(url)
9906
+ this.lastResultsByURL[ url ] = true
9907
+
9908
+ if (this.lastResultsURLs.length > this.keepNLastResults) this.releaseTestResult()
9909
+ },
9910
+
9911
+
9912
+ releaseTestResult : function () {
9913
+ if (this.lastResultsURLs.length <= this.keepNLastResults) return
9914
+
9915
+ var url = this.lastResultsURLs.shift()
9916
+
9917
+ delete this.lastResultsByURL[ url ]
9918
+
9919
+ var test = this.getTestByURL(url)
9920
+
9921
+ if (test && test.isFinished()) this.cleanupScopeForURL(url)
9922
+ },
9923
+
9924
+
9925
+ isKeepingResultForURL : function (url) {
9926
+ return this.lastResultsByURL[ url ]
9927
+ },
9928
+
9929
+
9587
9930
  setupScope : function (desc) {
9588
9931
  var url = desc.url
9589
9932
  var scopeProvideClass = eval(desc.scopeProvider)
9590
9933
 
9591
9934
  this.cleanupScopeForURL(url)
9592
9935
 
9936
+ this.keepTestResult(url)
9937
+
9593
9938
  return this.scopesByURL[ url ] = new scopeProvideClass(this.getScopeProviderConfigFor(desc))
9594
9939
  },
9595
9940
 
@@ -9705,7 +10050,7 @@ Class('Siesta.Harness', {
9705
10050
  var args = startTestAnchor && startTestAnchor.args
9706
10051
 
9707
10052
  // pick either 1st or 2nd argument depending which one is a function
9708
- var runFunc = args && (typeof args[ 0 ] == 'function' && args[ 0 ] || args[ 1 ])
10053
+ var runFunc = args && (typeof args[ 0 ] == 'function' ? args[ 0 ] : args[ 1 ])
9709
10054
 
9710
10055
  me.launchTest({
9711
10056
  testHolder : testHolder,
@@ -9716,6 +10061,8 @@ Class('Siesta.Harness', {
9716
10061
  preloadErrors : preloadErrors,
9717
10062
  onErrorHandler : onErrorHandler,
9718
10063
 
10064
+ startTestAnchor : startTestAnchor,
10065
+
9719
10066
  runFunc : runFunc
9720
10067
  }, callback)
9721
10068
  });
@@ -9736,15 +10083,18 @@ Class('Siesta.Harness', {
9736
10083
  // after the scope setup, the `onerror` handler might be cleared - installing it again
9737
10084
  if (!this.getDescriptorConfig(desc, 'transparentEx')) scopeProvider.addOnErrorHandler(options.onErrorHandler)
9738
10085
 
9739
- var testConfig = me.getNewTestConfiguration(desc, scopeProvider, options.contentManager, options.urlOptions, options.runFunc)
10086
+ var testConfig = me.getNewTestConfiguration(desc, scopeProvider, options.contentManager, options.urlOptions, options.runFunc, options.startTestAnchor)
9740
10087
 
9741
10088
  testConfig.callback = function () {
9742
- if (!me.keepResults) me.cleanupScopeForURL(url)
10089
+ if (!me.keepResults) {
10090
+ if (!me.isKeepingResultForURL(url)) me.cleanupScopeForURL(url)
10091
+ }
9743
10092
 
9744
10093
  callback && callback()
9745
10094
  }
9746
10095
 
9747
- var test = options.testHolder.test = me.testsByURL[ url ] = new testClass(testConfig)
10096
+ var test = options.testHolder.test = new testClass(testConfig)
10097
+ this.saveTestWithURL(url, test)
9748
10098
 
9749
10099
  scopeProvider.scope.setTimeout(function() {
9750
10100
  //console.timeEnd('launch')
@@ -9755,7 +10105,7 @@ Class('Siesta.Harness', {
9755
10105
  },
9756
10106
 
9757
10107
 
9758
- getNewTestConfiguration : function (desc, scopeProvider, contentManager, options, runFunc) {
10108
+ getNewTestConfiguration : function (desc, scopeProvider, contentManager, options, runFunc, startTestAnchor) {
9759
10109
  var scope = scopeProvider.scope
9760
10110
 
9761
10111
  var config = {
@@ -9763,6 +10113,11 @@ Class('Siesta.Harness', {
9763
10113
 
9764
10114
  harness : this,
9765
10115
  run : runFunc,
10116
+
10117
+ startTestAnchor : startTestAnchor,
10118
+
10119
+ exceptionCatcher : startTestAnchor.exceptionCatcher,
10120
+ testErrorClass : startTestAnchor.testErrorClass,
9766
10121
 
9767
10122
  expectedGlobals : this.cleanScopeGlobals.concat(this.getDescriptorConfig(desc, 'expectedGlobals')),
9768
10123
  autoCheckGlobals : this.getDescriptorConfig(desc, 'autoCheckGlobals'),
@@ -9797,18 +10152,34 @@ Class('Siesta.Harness', {
9797
10152
  },
9798
10153
 
9799
10154
 
10155
+ getTestByURL : function (url) {
10156
+ return this.testsByURL[ url ]
10157
+ },
10158
+
10159
+
10160
+ saveTestWithURL : function (url, test) {
10161
+ this.testsByURL[ url ] = test
10162
+ },
10163
+
10164
+
10165
+ deleteTestByURL : function (url) {
10166
+ delete this.testsByURL[ url ]
10167
+ },
10168
+
10169
+
9800
10170
  allPassed : function () {
9801
10171
  var allPassed = true
9802
10172
  var me = this
9803
10173
 
9804
10174
  Joose.A.each(this.flattenDescriptors(this.descriptors), function (descriptor) {
9805
- var test = me.testsByURL[ descriptor.url ]
10175
+ // if at least one test is missing then something is wrong
10176
+ if (descriptor.isMissing) { allPassed = false; return false }
10177
+
10178
+ var test = me.getTestByURL(descriptor.url)
9806
10179
 
9807
10180
  // ignore missing tests (could be skipped by test filtering
9808
10181
  if (!test) return
9809
10182
 
9810
- if (descriptor.isMissing) { allPassed = false; return false }
9811
-
9812
10183
  allPassed = allPassed && test.isPassed()
9813
10184
  })
9814
10185
 
@@ -9825,6 +10196,11 @@ Class('Siesta.Harness', {
9825
10196
  if (!this[ methodName ]) throw "Can't generate report - missing the `" + methodName + "` method"
9826
10197
 
9827
10198
  return this[ methodName ](options)
10199
+ },
10200
+
10201
+
10202
+ typeOf : function (object) {
10203
+ return Object.prototype.toString.call(object).replace(/^\[object /, '').replace(/\]$/, '')
9828
10204
  }
9829
10205
  }
9830
10206
  // eof methods
@@ -9904,15 +10280,26 @@ Role('Siesta.Role.ConsoleReporter', {
9904
10280
 
9905
10281
  does : Siesta.Role.CanStyleOutput,
9906
10282
 
10283
+ has : {
10284
+ // special flag which will be used by automation launchers to prevent the summary message
10285
+ // after every page
10286
+ needSummaryMessage : true
10287
+ },
10288
+
9907
10289
 
9908
10290
  after : {
9909
10291
 
10292
+ markMissingFile : function (desc) {
10293
+ this.warn("Test file [" + desc.url + "] not found.")
10294
+ },
10295
+
10296
+
9910
10297
  onTestSuiteStart : function () {
9911
10298
  },
9912
10299
 
9913
10300
 
9914
10301
  onTestSuiteEnd : function () {
9915
- this.log(this.getSummaryMessage())
10302
+ if (this.needSummaryMessage) this.log(this.getSummaryMessage())
9916
10303
 
9917
10304
  this.exit(this.getExitCode())
9918
10305
  },
@@ -9949,7 +10336,14 @@ Role('Siesta.Role.ConsoleReporter', {
9949
10336
  }
9950
10337
  }
9951
10338
 
9952
- if (result instanceof Siesta.Result.Diagnostic) text = this.styled(text, 'bold')
10339
+ if (result instanceof Siesta.Result.Diagnostic) {
10340
+ text = this.styled(text, 'bold')
10341
+
10342
+ if (result.isWarning) {
10343
+ this.warn(text)
10344
+ return
10345
+ }
10346
+ }
9953
10347
 
9954
10348
  if (needToShow) this.log(text)
9955
10349
  }
@@ -9958,8 +10352,13 @@ Role('Siesta.Role.ConsoleReporter', {
9958
10352
 
9959
10353
  methods : {
9960
10354
 
9961
- getSummaryMessage : function (lineBreaks) {
9962
- var allPassed = this.allPassed()
10355
+ warn : function (text) {
10356
+ this.log(this.styled('[WARN] ', 'red') + text)
10357
+ },
10358
+
10359
+
10360
+ getSummaryMessage : function (allPassed) {
10361
+ allPassed = allPassed != null ? allPassed : this.allPassed()
9963
10362
 
9964
10363
  return allPassed ? this.style().bold(this.style().green('All tests passed')) : this.style().bold(this.style().red('There are failures'))
9965
10364
  },
@@ -10144,13 +10543,14 @@ Class('Siesta.Harness.NodeJS', {
10144
10543
  ;
10145
10544
  ;
10146
10545
  Class('Siesta', {
10147
- /*PKGVERSION*/VERSION : '1.1.0',
10546
+ /*PKGVERSION*/VERSION : '1.1.5',
10148
10547
 
10149
10548
  // "my" should been named "static"
10150
10549
  my : {
10151
10550
 
10152
10551
  has : {
10153
- config : null
10552
+ config : null,
10553
+ activeHarness : null
10154
10554
  },
10155
10555
 
10156
10556
  methods : {