Skip to content

Commit

Permalink
New reporter that supports Jasmine 2 API
Browse files Browse the repository at this point in the history
This adds a new reporter that uses the Jasmine 2 JsApiReporter API.

It creates an almost identical JSON response to the
old API but with a few differences.

The main one being that it's unable to create the "messages" array for each spec.

Instead it puts the messages on the "errors" array,
but has more detail about the expectation that failed.

It's a first pass at resolving # 161
  • Loading branch information
nathanstitt committed May 23, 2014
1 parent a911a82 commit 392aa6d
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 1,456 deletions.
219 changes: 63 additions & 156 deletions lib/guard/jasmine/phantomjs/guard-jasmine.coffee
Original file line number Diff line number Diff line change
@@ -1,193 +1,100 @@
# This file is the script that runs within PhantomJS, requests the Jasmine specs
# and waits until they are ready.
phantom.injectJs 'lib/result.js'

# Set default values
options =
url: phantom.args[0] || 'http://127.0.0.1:3000/jasmine'
timeout: parseInt(phantom.args[1] || 10000)
specdoc: phantom.args[2] || 'failure'
focus: /true/i.test phantom.args[3]
console: phantom.args[4] || 'failure'
errors: phantom.args[5] || 'failure'
junit: /true/i.test phantom.args[6]
junit_consolidate: /true/i.test phantom.args[7]
junit_save_path: phantom.args[8] || ''

# Create the web page.
#
page = require('webpage').create()

# Used to collect log messages for later assignment to the spec
#
currentSpecId = -1
logs = {}
errors = {}
resultsKey = "__jr" + Math.ceil(Math.random() * 1000000)
fs = require("fs")

# Catch JavaScript errors
#
page.onError = (msg, trace) ->
if currentSpecId
errors[currentSpecId] ||= []
errors[currentSpecId].push({ msg: msg, trace: trace })

# Capture console.log output to add it to
# the result when specs have finished.
#
page.onConsoleMessage = (msg, line, source) ->
if /^RUNNER_END$/.test(msg)
result = page.evaluate -> window.reporter.runnerResult
console.log JSON.stringify(new Result(result, logs, errors, options).process())
page.evaluate -> window.resultReceived = true

else if /^SPEC_START: (\d+)$/.test(msg)
currentSpecId = Number(RegExp.$1)
# abort the request and return the error
page.onError = (message, trace) ->
reportError "Javascript error encountered on Jasmine test page: #{ message }", trace

else
logs[currentSpecId] ||= []
logs[currentSpecId].push(msg)

# Initialize the page before the JavaScript is run.
#
# Once the page is initialized, setup the script for
# the GuardReporter class
page.onInitialized = ->
overloadPageEvaluate(page)
setupWriteFileFunction(page, resultsKey, fs.separator)

page.injectJs 'lib/console.js'
page.injectJs 'lib/reporter.js'
page.injectJs 'lib/junit_reporter.js'

setupReporters = ->
# Attach the console reporter when the document is ready.
window.onload = ->
window.onload = null
window.resultReceived = false
window.reporter = new ConsoleReporter()
if window.jasmine
jasmine.getEnv().addReporter(new JUnitXmlReporter("%save_path%", "%consolidate%"))
jasmine.getEnv().addReporter(window.reporter)

page.evaluate(setupReporters, {save_path: options.junit_save_path, consolidate: options.junit_consolidate})


getXmlResults = (page, key) ->
getWindowObj = ->
window["%resultsObj%"] || {}
page.evaluate getWindowObj, {resultsObj: key}

replaceFunctionPlaceholders= (fn, replacements) ->
if replacements && typeof replacements == 'object'
fn = fn.toString()
for p of replacements
if replacements.hasOwnProperty(p)
match = new RegExp("%" + p + "%", "g")
loop
fn = fn.replace(match, replacements[p])
break unless fn.indexOf(match) != -1
fn

overloadPageEvaluate = (page) ->
page._evaluate = page.evaluate
page.evaluate = (fn, replacements) ->
page._evaluate(replaceFunctionPlaceholders(fn, replacements))
page

setupWriteFileFunction= (page,key, path_separator) ->
saveData = () ->
window["%resultsObj%"] = {}
window.fs_path_separator = "%fs_path_separator%"
window.__phantom_writeFile = (filename, text) ->
window["%resultsObj%"][filename] = text;

page.evaluate saveData, {resultsObj: key, fs_path_separator: path_separator}

# Open web page and run the Jasmine test runner
#
page.open options.url, (status) ->
# Avoid that a failed iframe load breaks the runner, see https://github.com/netzpirat/guard-jasmine/pull/19
page.onLoadFinished = ->
if status isnt 'success'
console.log JSON.stringify({ error: "Unable to access Jasmine specs at #{ options.url }" })
phantom.exit()
else
waitFor jasmineReady, jasmineAvailable, options.timeout, jasmineMissing
page.injectJs 'guard-reporter.js'
page.evaluate ->
window.onload = ->
window.reporter = new GuardReporter()
window.jasmine.getEnv().addReporter(window.reporter)

# Once the page is finished loading
page.onLoadFinished = (status)->
if status isnt 'success'
reportError "Unable to access Jasmine specs at #{ options.url }, page returned status: #{status}"
else
waitFor reporterReady, jasmineAvailable, options.timeout, reporterMissing

# Test if the jasmine has been loaded
#
jasmineReady = ->
page.evaluate -> window.jasmine
# Open web page, which will kick off the Jasmine test runner
page.open options.url

# Test if Jasmine and guard has been loaded
reporterReady = ->
page.evaluate ->
window.jasmine && window.reporter

# Start specs after they are have been loaded
#
jasmineAvailable = ->
waitFor specsReady, specsDone, options.timeout, specsTimedout
waitFor specsDone, exitSuccessfully, options.timeout, specsTimedout

# Error message for when jasmine never loaded asynchronously
#
jasmineMissing = ->
reporterMissing = ->
text = page.evaluate -> document.getElementsByTagName('body')[0]?.innerText

if text
error = """
The Jasmine reporter is not available!
reportError """
The reporter is not available!
Perhaps the url ( #{ options.url } ) is incorrect?
#{ text }
"""
console.log JSON.stringify({ error: error })
else
console.log JSON.stringify({ error: 'The Jasmine reporter is not available!' })

# Test if the specs have finished.
#
specsReady = ->
page.evaluate -> window.resultReceived
# tests if the resultComplete flag is set on the reporter
specsDone = ->
result = page.evaluate ->
window.reporter.resultComplete

# We should end up here. Logs the results as JSON and exits
exitSuccessfully = ->
results = page.evaluate -> window.reporter.results()
console.log JSON.stringify( results )
phantom.exit()


# Error message for when specs time out
#
specsTimedout = ->
text = page.evaluate -> document.getElementsByTagName('body')[0]?.innerText
if text
error = """
reportError """
Timeout waiting for the Jasmine test results!
#{ text }
"""
console.log JSON.stringify({ error: error })
else
console.log JSON.stringify({ error: 'Timeout for the Jasmine test results!' })

specsDone = ->
if options.junit == true
xml_results = getXmlResults(page, resultsKey)
for filename of xml_results
if xml_results.hasOwnProperty(filename) && (output = xml_results[filename]) && typeof(output) == 'string'
fs.write(filename, output, 'w')

phantom.exit()

# Wait until the test condition is true or a timeout occurs.
#
# @param [Function] test the test that returns true if condition is met
# @param [Function] ready the action when the condition is fulfilled
# @param [Number] timeout the max amount of time to wait in milliseconds
#
waitFor = (test, ready, timeout = 10000, timeoutFunction) ->
start = Date.now()
condition = false
interval = undefined

wait = ->
if (Date.now() - start < timeout) and not condition
condition = test()
else
clearInterval interval

if condition
ready()
else
timeoutFunction()
phantom.exit(1)

interval = setInterval wait, 250
waitFor = (test, ready, timeout = 10000, timeoutFunction)->
condition = false
interval = undefined
start = Date.now(0)
wait = ->
if !condition && (Date.now() - start < timeout)
condition = test()
else
clearInterval interval
if condition
ready()
else
timeoutFunction()
interval = setInterval( wait, 250 )

# Logs the error to the console as JSON and exits with status '1'
reportError = (msg, trace=[])->
if 0 == trace.length
err = new Error();
trace = err.stack
console.log JSON.stringify({ error: msg, trace: trace })
phantom.exit(1)
Loading

3 comments on commit 392aa6d

@dirkhainc3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nathanstitt Does the new jasmine reporter still support dumping an xunit results file which can be consumed by tools like Jenkins? This was a feature that @edspencer implemented in 2013 using @larrymyers junit reporter but seems to have been lost since this commit.

@nathanstitt
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately yes, the unit reporter was removed there. I'm trying to remember the reasoning but I suspect it was because the junit reporter wasn't compatible with Jasmine 2.

here's the old reporter https://github.com/guard/guard-jasmine/blob/840b385df59a5c9624d4ebf65c974f105f6d29c4/lib/guard/jasmine/phantomjs/lib/junit_reporter.js

The current Guard Jasmine reporter: https://github.com/guard/guard-jasmine/blob/master/lib/guard/jasmine/phantomjs/src/guard-jasmine.coffee

If someone was interested in migrating the junit reporter to Jasmine2 I'd be willing to help. I don't know anything about junit though so probably wouldn't be the best to take the lead on it.

@dirkhainc3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest report claims compatibility with Jasmine 2.0
https://github.com/larrymyers/jasmine-reporters
Would you be able to swap in the new reporter (https://github.com/larrymyers/jasmine-reporters/blob/master/src/junit_reporter.js)? The arguments to dump the report are still in the code but not functional at the moment.

Please sign in to comment.