Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Vendored third-party bundles used by rich-text editor test fixtures
test/data/app/js/
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const compat = new FlatCompat({

export default [
{
ignores: ['test/data/output', 'lib/css2xpath/*'],
ignores: ['test/data/output', 'test/data/app/js/*', 'lib/css2xpath/*'],
},
{
languageOptions: {
Expand Down
35 changes: 28 additions & 7 deletions lib/plugin/junitReporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,26 @@ export default function (config = {}) {
config = Object.assign({}, defaultConfig, config)

let written = false
const hookFailures = []

event.dispatcher.on(event.hook.failed, hook => {
if (!hook || !['BeforeSuite', 'AfterSuite'].includes(hook.hookName)) return
const err = hook.err || hook.error
if (!err) return
const runnable = hook.ctx && hook.ctx.test
const suite = runnable && runnable.parent
hookFailures.push({
title: hook.title || `${hook.hookName} hook failed`,
state: 'failed',
err,
parent: suite,
file: (runnable && runnable.file) || (suite && suite.file),
tags: (suite && suite.tags) || [],
meta: {},
steps: [],
duration: (runnable && runnable.duration) || 0,
})
})

const writeReport = result => {
if (written) return
Expand All @@ -76,25 +96,26 @@ export default function (config = {}) {
mkdirp.sync(dir)
const file = path.join(dir, config.outputName)

fs.writeFileSync(file, buildXml(result, config))
fs.writeFileSync(file, buildXml(result, config, hookFailures))
output.plugin('junitReporter', `JUnit report saved to ${file}`)
}

event.dispatcher.on(event.all.result, writeReport)
event.dispatcher.on(event.workers.result, writeReport)
}

function buildXml(result, config) {
function buildXml(result, config, hookFailures = []) {
const doc = new DOMImplementation().createDocument(null, null, null)
const suites = groupBySuite(result.tests)
const allTests = result.tests.concat(hookFailures)
const suites = groupBySuite(allTests)

const root = doc.createElement('testsuites')
setAttr(root, 'name', config.testGroupName)
setAttr(root, 'tests', result.tests.length)
setAttr(root, 'failures', countState(result.tests, 'failed'))
setAttr(root, 'skipped', countSkipped(result.tests))
setAttr(root, 'tests', allTests.length)
setAttr(root, 'failures', countState(allTests, 'failed'))
setAttr(root, 'skipped', countSkipped(allTests))
setAttr(root, 'errors', 0)
setAttr(root, 'time', toSeconds(sumDuration(result.tests)))
setAttr(root, 'time', toSeconds(sumDuration(allTests)))
setAttr(root, 'timestamp', toIso(result.stats && result.stats.start))
doc.appendChild(root)

Expand Down
17 changes: 17 additions & 0 deletions test/data/app/js/codemirror6.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/data/app/view/form/richtext/codemirror6.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<button type="submit" id="submit">Submit</button>
</form>
<script type="module">
import { EditorView, minimalSetup } from 'https://esm.sh/codemirror@6.0.1'
import { EditorView, minimalSetup } from '/js/codemirror6.js'
const initial = <?php echo json_encode($initial, JSON_UNESCAPED_UNICODE); ?>;
const view = new EditorView({
doc: initial,
Expand Down
55 changes: 55 additions & 0 deletions test/unit/junitReporter_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import xml2js from 'xml2js'
import junitReporter from '../../lib/plugin/junitReporter.js'
import event from '../../lib/event.js'
import store from '../../lib/store.js'
import { BeforeSuiteHook, BeforeHook } from '../../lib/mocha/hooks.js'
import Step from '../../lib/step/base.js'
import MetaStep from '../../lib/step/meta.js'

Expand Down Expand Up @@ -93,11 +94,13 @@ describe('JUnit Reporter Plugin', () => {
store.outputDir = tmpDir
event.dispatcher.removeAllListeners(event.all.result)
event.dispatcher.removeAllListeners(event.workers.result)
event.dispatcher.removeAllListeners(event.hook.failed)
})

afterEach(() => {
event.dispatcher.removeAllListeners(event.all.result)
event.dispatcher.removeAllListeners(event.workers.result)
event.dispatcher.removeAllListeners(event.hook.failed)
store._outputDir = prevOutputDir
fs.rmSync(tmpDir, { recursive: true, force: true })
})
Expand Down Expand Up @@ -198,4 +201,56 @@ describe('JUnit Reporter Plugin', () => {
const secondMtime = fs.statSync(path.join(tmpDir, 'report.xml')).mtimeMs
expect(secondMtime).to.equal(firstMtime)
})

it('adds suite hook failures as failed testcases', async () => {
junitReporter({})

const suite = { title: 'Suite hooks @smoke', file: '/tests/hooks_test.js', tags: ['@smoke'], startedAt: Date.now() }
const beforeSuiteRunnable = { title: '"before all" hook: BeforeSuite for "test something"', parent: suite, file: suite.file, duration: 42 }
event.dispatcher.emit(event.hook.failed, new BeforeSuiteHook({ ctx: { test: beforeSuiteRunnable } }, new Error('BeforeSuite failed')))

const beforeRunnable = { title: '"before each" hook: Before for "test something"', parent: suite, file: suite.file, duration: 7 }
event.dispatcher.emit(event.hook.failed, new BeforeHook({ ctx: { test: beforeRunnable } }, new Error('Before failed')))

event.dispatcher.emit(event.all.result, {
tests: [],
stats: { start: new Date(), passes: 0, failures: 0, pending: 0, tests: 0 },
})

const parsed = await parseReport(tmpDir)
expect(parsed.testsuites.$.tests).to.equal('1')
expect(parsed.testsuites.$.failures).to.equal('1')

const suiteEl = parsed.testsuites.testsuite[0]
expect(suiteEl.$.name).to.equal('Suite hooks @smoke')
expect(suiteEl.testcase).to.have.length(1)
expect(suiteEl.testcase[0].$.name).to.contain('"before all" hook: BeforeSuite')
expect(suiteEl.testcase[0].failure[0].$.message).to.contain('BeforeSuite failed')

const tagsProp = suiteEl.testcase[0].properties[0].property.find(p => p.$.name === 'tags')
expect(tagsProp.$.value).to.equal('@smoke')
})

it('adds suite hook failures forwarded from workers as failed testcases', async () => {
junitReporter({})

event.dispatcher.emit(event.hook.failed, {
hookName: 'AfterSuite',
title: '"after all" hook: AfterSuite for "test something"',
error: { message: 'AfterSuite failed', stack: 'Error: AfterSuite failed\n at suite' },
})

event.dispatcher.emit(event.workers.result, {
tests: [],
stats: { start: new Date(), passes: 0, failures: 0, pending: 0, tests: 0 },
})

const parsed = await parseReport(tmpDir)
expect(parsed.testsuites.$.tests).to.equal('1')
expect(parsed.testsuites.$.failures).to.equal('1')

const suiteEl = parsed.testsuites.testsuite[0]
expect(suiteEl.testcase[0].$.name).to.contain('"after all" hook: AfterSuite')
expect(suiteEl.testcase[0].failure[0].$.message).to.contain('AfterSuite failed')
})
})