aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorVojta Jina2011-05-19 17:33:25 +0200
committerIgor Minar2011-05-19 09:43:56 -0700
commit1abdc097b235366759a889bdcc68359653a9b8a3 (patch)
treeed53346c171de6c60748e65c7f7f065cc8837103 /src
parent9f56af9c15e1096033c91c2619f7f7f0115d0032 (diff)
downloadangular.js-1abdc097b235366759a889bdcc68359653a9b8a3.tar.bz2
JSTD adapter for running e2e tests
Couple of changes into angular.scenario runner: - add autotest config (runs tests when document ready) - update ObjectModel (forwards events) - use only one ObjectModel instance for all outputters - expose error msg and line number in ObjectModel.Spec and ObjectModel.Step - fix generating spec.ids - fix 'html' output so that it does not mutate ObjectModel Couple of changes into docs / generator: - rename copy -> copyTpl - move docs/static into docs/examples (to avoid conflict with jstd proxy) Running all docs e2e tests: ======================================================== 1/ compile angular-scenario, jstd-scenario-adapter >> rake compile 2/ build docs >> rake docs 3/ start jstd server >> ./server-scenario.sh 4/ capture some browser 5/ run node server to serve static content >> node ../lib/nodeserver/server.js 6/ run tests >> ./test-scenario.sh
Diffstat (limited to 'src')
-rw-r--r--src/jstd-scenario-adapter/Adapter.js175
-rw-r--r--src/jstd-scenario-adapter/angular.prefix24
-rw-r--r--src/jstd-scenario-adapter/angular.suffix2
-rw-r--r--src/scenario/Describe.js4
-rw-r--r--src/scenario/ObjectModel.js132
-rw-r--r--src/scenario/Runner.js5
-rw-r--r--src/scenario/Scenario.js23
-rw-r--r--src/scenario/angular-bootstrap.js8
-rw-r--r--src/scenario/angular.suffix9
-rw-r--r--src/scenario/output/Html.js48
-rw-r--r--src/scenario/output/Json.js6
-rw-r--r--src/scenario/output/Object.js4
-rw-r--r--src/scenario/output/Xml.js5
13 files changed, 376 insertions, 69 deletions
diff --git a/src/jstd-scenario-adapter/Adapter.js b/src/jstd-scenario-adapter/Adapter.js
new file mode 100644
index 00000000..fd9674e1
--- /dev/null
+++ b/src/jstd-scenario-adapter/Adapter.js
@@ -0,0 +1,175 @@
+/**
+ * JSTestDriver adapter for angular scenario tests
+ *
+ * Example of jsTestDriver.conf for running scenario tests with JSTD:
+ <pre>
+ server: http://localhost:9877
+
+ load:
+ - lib/angular-scenario.js
+ - lib/jstd-scenario-adapter-config.js
+ - lib/jstd-scenario-adapter.js
+ # your test files go here #
+
+ proxy:
+ - {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
+ </pre>
+ *
+ * For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
+ * Note the order of files - it's important !
+ *
+ * Example of jstd-scenario-adapter-config.js
+ <pre>
+ var jstdScenarioAdapter = {
+ relativeUrlPrefix: '/your-prefix/'
+ };
+ </pre>
+ *
+ * Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
+ * You have to configure this to work together with JSTD proxy.
+ *
+ * Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
+ * Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
+ * That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
+ */
+
+/**
+ * Custom type of test case
+ *
+ * @const
+ * @see jstestdriver.TestCaseInfo
+ */
+var SCENARIO_TYPE = 'scenario';
+
+/**
+ * Plugin for JSTestDriver
+ * Connection point between scenario's jstd output and jstestdriver.
+ *
+ * @see jstestdriver.PluginRegistrar
+ */
+function JstdPlugin() {
+ var nop = function() {};
+
+ this.reportResult = nop;
+ this.reportEnd = nop;
+ this.runScenario = nop;
+
+ this.name = 'Angular Scenario Adapter';
+
+ /**
+ * Called for each JSTD TestCase
+ *
+ * Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
+ * Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
+ *
+ * @param {jstestdriver.TestRunConfiguration} configuration
+ * @param {Function} onTestDone
+ * @param {Function} onAllTestsComplete
+ * @returns {boolean} True if this type of test is handled by this plugin, false otherwise
+ */
+ this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
+ if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
+
+ this.reportResult = onTestDone;
+ this.reportEnd = onAllTestsComplete;
+ this.runScenario();
+
+ return true;
+ };
+
+ this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
+ testRunsConfiguration.push(
+ new jstestdriver.TestRunConfiguration(
+ new jstestdriver.TestCaseInfo(
+ 'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
+
+ return true;
+ };
+}
+
+/**
+ * Singleton instance of the plugin
+ * Accessed using closure by:
+ * - jstd output (reports to this plugin)
+ * - initScenarioAdapter (register the plugin to jstd)
+ */
+var plugin = new JstdPlugin();
+
+/**
+ * Initialise scenario jstd-adapter
+ * (only if jstestdriver is defined)
+ *
+ * @param {Object} jstestdriver Undefined when run from browser (without jstd)
+ * @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
+ * @param {Object=} config Configuration object, supported properties:
+ * - relativeUrlPrefix: prefix for all relative links when navigateTo()
+ */
+function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
+ if (jstestdriver) {
+ // create and register ScenarioPlugin
+ jstestdriver.pluginRegistrar.register(plugin);
+ plugin.runScenario = initScenarioAndRun;
+
+ /**
+ * HACK (angular.scenario.Application.navigateTo)
+ *
+ * We need to navigate to relative urls when running from browser (without JSTD),
+ * because we want to allow running scenario tests without creating its own virtual host.
+ * For example: http://angular.local/build/docs/docs-scenario.html
+ *
+ * On the other hand, when running with JSTD, we need to navigate to absolute urls,
+ * because of JSTD proxy. (proxy, because of same domain policy)
+ *
+ * So this hack is applied only if running with JSTD and change all relative urls to absolute.
+ */
+ var appProto = angular.scenario.Application.prototype,
+ navigateTo = appProto.navigateTo,
+ relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
+
+ appProto.navigateTo = function(url, loadFn, errorFn) {
+ if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
+ url != 'about:blank' && !url.match(/^https?/)) {
+ url = relativeUrlPrefix + url;
+ }
+
+ return navigateTo.call(this, url, loadFn, errorFn);
+ };
+ }
+}
+
+/**
+ * Builds proper TestResult object from given model spec
+ *
+ * TODO(vojta) report error details
+ *
+ * @param {angular.scenario.ObjectModel.Spec} spec
+ * @returns {jstestdriver.TestResult}
+ */
+function createTestResultFromSpec(spec) {
+ var map = {
+ success: 'PASSED',
+ error: 'ERROR',
+ failure: 'FAILED'
+ };
+
+ return new jstestdriver.TestResult(
+ spec.fullDefinitionName,
+ spec.name,
+ jstestdriver.TestResult.RESULT[map[spec.status]],
+ spec.error || '',
+ spec.line || '',
+ spec.duration);
+}
+
+/**
+ * Generates JSTD output (jstestdriver.TestResult)
+ */
+angular.scenario.output('jstd', function(context, runner, model) {
+ model.on('SpecEnd', function(spec) {
+ plugin.reportResult(createTestResultFromSpec(spec));
+ });
+
+ model.on('RunnerEnd', function() {
+ plugin.reportEnd();
+ });
+});
diff --git a/src/jstd-scenario-adapter/angular.prefix b/src/jstd-scenario-adapter/angular.prefix
new file mode 100644
index 00000000..ab8a7152
--- /dev/null
+++ b/src/jstd-scenario-adapter/angular.prefix
@@ -0,0 +1,24 @@
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+(function(window) {
diff --git a/src/jstd-scenario-adapter/angular.suffix b/src/jstd-scenario-adapter/angular.suffix
new file mode 100644
index 00000000..6134fb01
--- /dev/null
+++ b/src/jstd-scenario-adapter/angular.suffix
@@ -0,0 +1,2 @@
+initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
+})(window);
diff --git a/src/scenario/Describe.js b/src/scenario/Describe.js
index 50cfded6..c6484f2f 100644
--- a/src/scenario/Describe.js
+++ b/src/scenario/Describe.js
@@ -37,6 +37,9 @@ angular.scenario.Describe = function(descName, parent) {
// Shared Unique ID generator for every describe block
angular.scenario.Describe.id = 0;
+// Shared Unique ID generator for every it (spec)
+angular.scenario.Describe.specId = 0;
+
/**
* Defines a block to execute before each it or nested describe.
*
@@ -93,6 +96,7 @@ angular.scenario.Describe.prototype.xdescribe = angular.noop;
*/
angular.scenario.Describe.prototype.it = function(name, body) {
this.its.push({
+ id: angular.scenario.Describe.specId++,
definition: this,
only: this.only,
name: name,
diff --git a/src/scenario/ObjectModel.js b/src/scenario/ObjectModel.js
index 263aa5f9..bff14461 100644
--- a/src/scenario/ObjectModel.js
+++ b/src/scenario/ObjectModel.js
@@ -4,21 +4,26 @@
* @param {Object} runner The scenario Runner instance to connect to.
*
* TODO(esprehn): Every output type creates one of these, but we probably
- * want one glonal shared instance. Need to handle events better too
+ * want one global shared instance. Need to handle events better too
* so the HTML output doesn't need to do spec model.getSpec(spec.id)
* silliness.
+ *
+ * TODO(vojta) refactor on, emit methods (from all objects) - use inheritance
*/
angular.scenario.ObjectModel = function(runner) {
var self = this;
this.specMap = {};
+ this.listeners = [];
this.value = {
name: '',
children: {}
};
runner.on('SpecBegin', function(spec) {
- var block = self.value;
+ var block = self.value,
+ definitions = [];
+
angular.forEach(self.getDefinitionPath(spec), function(def) {
if (!block.children[def.name]) {
block.children[def.name] = {
@@ -29,49 +34,78 @@ angular.scenario.ObjectModel = function(runner) {
};
}
block = block.children[def.name];
+ definitions.push(def.name);
});
- self.specMap[spec.id] = block.specs[spec.name] =
- new angular.scenario.ObjectModel.Spec(spec.id, spec.name);
+
+ var it = self.specMap[spec.id] =
+ block.specs[spec.name] =
+ new angular.scenario.ObjectModel.Spec(spec.id, spec.name, definitions);
+
+ // forward the event
+ self.emit('SpecBegin', it);
});
runner.on('SpecError', function(spec, error) {
var it = self.getSpec(spec.id);
it.status = 'error';
it.error = error;
+
+ // forward the event
+ self.emit('SpecError', it, error);
});
runner.on('SpecEnd', function(spec) {
var it = self.getSpec(spec.id);
complete(it);
+
+ // forward the event
+ self.emit('SpecEnd', it);
});
runner.on('StepBegin', function(spec, step) {
var it = self.getSpec(spec.id);
- it.steps.push(new angular.scenario.ObjectModel.Step(step.name));
+ var step = new angular.scenario.ObjectModel.Step(step.name);
+ it.steps.push(step);
+
+ // forward the event
+ self.emit('StepBegin', it, step);
});
runner.on('StepEnd', function(spec, step) {
var it = self.getSpec(spec.id);
- if (it.getLastStep().name !== step.name)
- throw 'Events fired in the wrong order. Step names don\' match.';
- complete(it.getLastStep());
+ var step = it.getLastStep();
+ if (step.name !== step.name)
+ throw 'Events fired in the wrong order. Step names don\'t match.';
+ complete(step);
+
+ // forward the event
+ self.emit('StepEnd', it, step);
});
runner.on('StepFailure', function(spec, step, error) {
- var it = self.getSpec(spec.id);
- var item = it.getLastStep();
- item.error = error;
- if (!it.status) {
- it.status = item.status = 'failure';
- }
+ var it = self.getSpec(spec.id),
+ modelStep = it.getLastStep();
+
+ modelStep.setErrorStatus('failure', error, step.line());
+ it.setStatusFromStep(modelStep);
+
+ // forward the event
+ self.emit('StepFailure', it, modelStep, error);
});
runner.on('StepError', function(spec, step, error) {
- var it = self.getSpec(spec.id);
- var item = it.getLastStep();
- it.status = 'error';
- item.status = 'error';
- item.error = error;
+ var it = self.getSpec(spec.id),
+ modelStep = it.getLastStep();
+
+ modelStep.setErrorStatus('error', error, step.line());
+ it.setStatusFromStep(modelStep);
+
+ // forward the event
+ self.emit('StepError', it, modelStep, error);
+ });
+
+ runner.on('RunnerEnd', function() {
+ self.emit('RunnerEnd');
});
function complete(item) {
@@ -82,6 +116,36 @@ angular.scenario.ObjectModel = function(runner) {
};
/**
+ * Adds a listener for an event.
+ *
+ * @param {string} eventName Name of the event to add a handler for
+ * @param {Function} listener Function that will be called when event is fired
+ */
+angular.scenario.ObjectModel.prototype.on = function(eventName, listener) {
+ eventName = eventName.toLowerCase();
+ this.listeners[eventName] = this.listeners[eventName] || [];
+ this.listeners[eventName].push(listener);
+};
+
+/**
+ * Emits an event which notifies listeners and passes extra
+ * arguments.
+ *
+ * @param {string} eventName Name of the event to fire.
+ */
+angular.scenario.ObjectModel.prototype.emit = function(eventName) {
+ var self = this,
+ args = Array.prototype.slice.call(arguments, 1),
+ eventName = eventName.toLowerCase();
+
+ if (this.listeners[eventName]) {
+ angular.forEach(this.listeners[eventName], function(listener) {
+ listener.apply(self, args);
+ });
+ }
+};
+
+/**
* Computes the path of definition describe blocks that wrap around
* this spec.
*
@@ -113,12 +177,14 @@ angular.scenario.ObjectModel.prototype.getSpec = function(id) {
*
* @param {string} id Id of the spec
* @param {string} name Name of the spec
+ * @param {Array<string>=} definitionNames List of all describe block names that wrap this spec
*/
-angular.scenario.ObjectModel.Spec = function(id, name) {
+angular.scenario.ObjectModel.Spec = function(id, name, definitionNames) {
this.id = id;
this.name = name;
this.startTime = new Date().getTime();
this.steps = [];
+ this.fullDefinitionName = (definitionNames || []).join(' ');
};
/**
@@ -143,6 +209,19 @@ angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
};
/**
+ * Set status of the Spec from given Step
+ *
+ * @param {angular.scenario.ObjectModel.Step} step
+ */
+angular.scenario.ObjectModel.Spec.prototype.setStatusFromStep = function(step) {
+ if (!this.status || step.status == 'error') {
+ this.status = step.status;
+ this.error = step.error;
+ this.line = step.line;
+ }
+};
+
+/**
* A single step inside a Spec.
*
* @param {string} step Name of the step
@@ -151,3 +230,16 @@ angular.scenario.ObjectModel.Step = function(name) {
this.name = name;
this.startTime = new Date().getTime();
};
+
+/**
+ * Helper method for setting all error status related properties
+ *
+ * @param {string} status
+ * @param {string} error
+ * @param {string} line
+ */
+angular.scenario.ObjectModel.Step.prototype.setErrorStatus = function(status, error, line) {
+ this.status = status;
+ this.error = error;
+ this.line = line;
+};
diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js
index 583c8beb..51a81d6a 100644
--- a/src/scenario/Runner.js
+++ b/src/scenario/Runner.js
@@ -1,5 +1,8 @@
/**
- * Runner for scenarios.
+ * Runner for scenarios
+ *
+ * Has to be initialized before any test is loaded,
+ * because it publishes the API into window (global space).
*/
angular.scenario.Runner = function($window) {
this.listeners = [];
diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js
index 87dad5b5..6879d787 100644
--- a/src/scenario/Scenario.js
+++ b/src/scenario/Scenario.js
@@ -87,17 +87,20 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
};
/**
- * Initialization function for the scenario runner.
+ * Initialize the scenario runner and run !
*
- * @param {angular.scenario.Runner} $scenario The runner to setup
- * @param {Object} config Config options
+ * Access global window and document object
+ * Access $runner through closure
+ *
+ * @param {Object=} config Config options
*/
-function angularScenarioInit($scenario, config) {
+angular.scenario.setUpAndRun = function (config) {
var href = window.location.href;
var body = _jQuery(document.body);
var output = [];
+ var objModel = new angular.scenario.ObjectModel($runner);
- if (config.scenario_output) {
+ if (config && config.scenario_output) {
output = config.scenario_output.split(',');
}
@@ -105,7 +108,7 @@ function angularScenarioInit($scenario, config) {
if (!output.length || indexOf(output,name) != -1) {
var context = body.append('<div></div>').find('div:last');
context.attr('id', name);
- fn.call({}, context, $scenario);
+ fn.call({}, context, $runner, objModel);
}
});
@@ -121,12 +124,12 @@ function angularScenarioInit($scenario, config) {
var appFrame = body.append('<div id="application"></div>').find('#application');
var application = new angular.scenario.Application(appFrame);
- $scenario.on('RunnerEnd', function() {
+ $runner.on('RunnerEnd', function() {
appFrame.css('display', 'none');
appFrame.find('iframe').attr('src', 'about:blank');
});
- $scenario.on('RunnerError', function(error) {
+ $runner.on('RunnerError', function(error) {
if (window.console) {
console.log(formatException(error));
} else {
@@ -135,8 +138,8 @@ function angularScenarioInit($scenario, config) {
}
});
- $scenario.run(application);
-}
+ $runner.run(application);
+};
/**
* Iterates through list with iterator function that must call the
diff --git a/src/scenario/angular-bootstrap.js b/src/scenario/angular-bootstrap.js
index 68dc393e..264ce718 100644
--- a/src/scenario/angular-bootstrap.js
+++ b/src/scenario/angular-bootstrap.js
@@ -23,7 +23,8 @@
try {
if (previousOnLoad) previousOnLoad();
} catch(e) {}
- angularScenarioInit($scenario, angularJsConfig(document));
+ var config = angularJsConfig(document);
+ if (config.autotest) angular.scenario.setUpAndRun(config);
};
addCSS("../../css/angular-scenario.css");
@@ -52,8 +53,7 @@
// Create the runner (which also sets up the global API)
document.write(
'<script type="text/javascript">' +
- 'var $scenario = new angular.scenario.Runner(window, angular.scenario.SpecRunner);' +
- '</script>'
- );
+ ' var $runner = new angular.scenario.Runner(window);' +
+ '</script>');
})(window.onload);
diff --git a/src/scenario/angular.suffix b/src/scenario/angular.suffix
index 3ab796d2..014c2cc9 100644
--- a/src/scenario/angular.suffix
+++ b/src/scenario/angular.suffix
@@ -1,7 +1,10 @@
- var $scenario = new angular.scenario.Runner(window);
+var $runner = new angular.scenario.Runner(window),
+ config = angularJsConfig(document);
+if (config.autotest) {
jqLiteWrap(document).ready(function() {
- angularScenarioInit($scenario, angularJsConfig(document));
+ angular.scenario.setUpAndRun(config);
});
-
+}
})(window, document);
+
diff --git a/src/scenario/output/Html.js b/src/scenario/output/Html.js
index 6e2e20f3..ccf7db6f 100644
--- a/src/scenario/output/Html.js
+++ b/src/scenario/output/Html.js
@@ -4,8 +4,9 @@
* TODO(esprehn): This should be refactored now that ObjectModel exists
* to use angular bindings for the UI.
*/
-angular.scenario.output('html', function(context, runner) {
- var model = new angular.scenario.ObjectModel(runner);
+angular.scenario.output('html', function(context, runner, model) {
+ var specUiMap = {},
+ lastStepUiMap = {};
context.append(
'<div id="header">' +
@@ -22,7 +23,7 @@ angular.scenario.output('html', function(context, runner) {
);
runner.on('InteractiveWait', function(spec, step) {
- var ui = model.getSpec(spec.id).getLastStep().ui;
+ var ui = lastStepUiMap[spec.id];
ui.find('.test-title').
html('waiting for you to <a href="javascript:resume()">resume</a>.');
});
@@ -58,59 +59,62 @@ angular.scenario.output('html', function(context, runner) {
name.removeClass('closed').addClass('open');
}
});
- model.getSpec(spec.id).ui = ui;
+
+ specUiMap[spec.id] = ui;
});
runner.on('SpecError', function(spec, error) {
- var ui = model.getSpec(spec.id).ui;
+ var ui = specUiMap[spec.id];
ui.append('<pre></pre>');
ui.find('> pre').text(formatException(error));
});
runner.on('SpecEnd', function(spec) {
+ var ui = specUiMap[spec.id];
spec = model.getSpec(spec.id);
- spec.ui.removeClass('status-pending');
- spec.ui.addClass('status-' + spec.status);
- spec.ui.find("> .test-info .timer-result").text(spec.duration + "ms");
+ ui.removeClass('status-pending');
+ ui.addClass('status-' + spec.status);
+ ui.find("> .test-info .timer-result").text(spec.duration + "ms");
if (spec.status === 'success') {
- spec.ui.find('> .test-info .test-name').addClass('closed');
- spec.ui.find('> .scrollpane .test-actions').hide();
+ ui.find('> .test-info .test-name').addClass('closed');
+ ui.find('> .scrollpane .test-actions').hide();
}
updateTotals(spec.status);
});
runner.on('StepBegin', function(spec, step) {
+ var ui = specUiMap[spec.id];
spec = model.getSpec(spec.id);
step = spec.getLastStep();
- spec.ui.find('> .scrollpane .test-actions').
- append('<li class="status-pending"></li>');
- step.ui = spec.ui.find('> .scrollpane .test-actions li:last');
- step.ui.append(
+ ui.find('> .scrollpane .test-actions').append('<li class="status-pending"></li>');
+ var stepUi = lastStepUiMap[spec.id] = ui.find('> .scrollpane .test-actions li:last');
+ stepUi.append(
'<div class="timer-result"></div>' +
'<div class="test-title"></div>'
);
- step.ui.find('> .test-title').text(step.name);
- var scrollpane = step.ui.parents('.scrollpane');
+ stepUi.find('> .test-title').text(step.name);
+ var scrollpane = stepUi.parents('.scrollpane');
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
});
runner.on('StepFailure', function(spec, step, error) {
- var ui = model.getSpec(spec.id).getLastStep().ui;
+ var ui = lastStepUiMap[spec.id];
addError(ui, step.line, error);
});
runner.on('StepError', function(spec, step, error) {
- var ui = model.getSpec(spec.id).getLastStep().ui;
+ var ui = lastStepUiMap[spec.id];
addError(ui, step.line, error);
});
runner.on('StepEnd', function(spec, step) {
+ var stepUi = lastStepUiMap[spec.id];
spec = model.getSpec(spec.id);
step = spec.getLastStep();
- step.ui.find('.timer-result').text(step.duration + 'ms');
- step.ui.removeClass('status-pending');
- step.ui.addClass('status-' + step.status);
- var scrollpane = spec.ui.find('> .scrollpane');
+ stepUi.find('.timer-result').text(step.duration + 'ms');
+ stepUi.removeClass('status-pending');
+ stepUi.addClass('status-' + step.status);
+ var scrollpane = specUiMap[spec.id].find('> .scrollpane');
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
});
diff --git a/src/scenario/output/Json.js b/src/scenario/output/Json.js
index 2c852496..1e3bef6b 100644
--- a/src/scenario/output/Json.js
+++ b/src/scenario/output/Json.js
@@ -1,10 +1,8 @@
/**
* Generates JSON output into a context.
*/
-angular.scenario.output('json', function(context, runner) {
- var model = new angular.scenario.ObjectModel(runner);
-
- runner.on('RunnerEnd', function() {
+angular.scenario.output('json', function(context, runner, model) {
+ model.on('RunnerEnd', function() {
context.text(angular.toJson(model.value));
});
});
diff --git a/src/scenario/output/Object.js b/src/scenario/output/Object.js
index 3257cfd7..1f29af9c 100644
--- a/src/scenario/output/Object.js
+++ b/src/scenario/output/Object.js
@@ -1,6 +1,6 @@
/**
* Creates a global value $result with the result of the runner.
*/
-angular.scenario.output('object', function(context, runner) {
- runner.$window.$result = new angular.scenario.ObjectModel(runner).value;
+angular.scenario.output('object', function(context, runner, model) {
+ runner.$window.$result = model.value;
});
diff --git a/src/scenario/output/Xml.js b/src/scenario/output/Xml.js
index e8c7f0e3..1a4c362c 100644
--- a/src/scenario/output/Xml.js
+++ b/src/scenario/output/Xml.js
@@ -1,10 +1,9 @@
/**
* Generates XML output into a context.
*/
-angular.scenario.output('xml', function(context, runner) {
- var model = new angular.scenario.ObjectModel(runner);
+angular.scenario.output('xml', function(context, runner, model) {
var $ = function(args) {return new context.init(args);};
- runner.on('RunnerEnd', function() {
+ model.on('RunnerEnd', function() {
var scenario = $('<scenario></scenario>');
context.append(scenario);
serializeXml(scenario, model.value);