Customizing pytest junitxml failure reports

2024/11/15 5:51:24

I am trying to introspect test failures and include additional data into the junit xml test report. Specifically, this is a suite of functional tests on an external product, and I want to include the product's logs into the failure reports.

Using the method found here, I was able to print the logs to stdout before executing the multicall which eventually show in jenkin's fail report. But I'm sure there's a better way to achieve this.

I tried to use the pytest_runtest_logreport hook to append the logs into the 'sections' attribute, which already contains the 'captured stdout' and 'captured stderr' streams. But the newly added sections do not make it to the xml file. I tried the above technique directly into the pytest_runtest_makereport hook as well, with similar results.

The release notes for pytest 2.7 states that using multicall support is being dropped for 2.8 and that @pytest.mark.hookwrapper is the new way to do it, however I can't seem to make that work at all - the "yield" returns None instead of a CallOutcome object (tried it in the makereport hook). And even if it returned something, I'm not sure I could add stuff to it that would show up in the xml report.

Is there any functionality I'm missing that will let me do this in a flexible way? (by flexible I mean: not being bound to stdout or logging calls like the capture-logs plugin)

Answer

EDIT: Since I needed access to the test item's funcargs (and test result) for my reporting, I was able to move the logic to the pytest_runtest_makereport(item, __multicall__) hook. The trick is to execute the multicall, which returns the report object:

@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call, __multicall__):report = __multicall__.execute()# then I was able to manipulate report and get the same results as below

Bruno's answer gave me the motivation I needed to analyze this feature more thoroughly :)

So here's how it works:

def pytest_runtest_logreport(report):if report.failed:report.longrepr.sections.append(("Header", "Message", "-"))report.sections.append(("Captured stdout", "This is added to stdout"))report.sections.append(("Captured stderr", "This is added to stderr"))report.sections.append(("Custom Section", "This can only be seen in the console - the xml won't have it."))

The longrepr attribute is only available in case of failures. It takes a 3-tuple, the last value being a character used to decorate the decorate/surround the header. It will appear in the "failure" section of the report:

----------------------------------- Header ------------------------------------
Message

Custom sections will create additional result sections to be printed out to the console. But they won't make it to junitxml:

------------------------------- Custom Section --------------------------------
This can only be seen in the console - the xml won't have it.

The junitxml report only has 2 sections: out and err. To add custom text to it, you must create sections called "Captured std" and only those will make it to the xml file. Any other name will result in a custom section that will only be seen in the console.

Here's the resulting junitxml using the code above, with some reformatting for the sake of this post:

<?xml version="1.0" encoding="utf-8" ?> 
<testsuite errors="0" failures="1" name="pytest" skips="0" tests="1" time="0.646"><testcase classname="test_reporting" name="test_fail" time="0.000999927520752"><failure message="test failure">@ut def test_fail(): > assert 0, "It failed"E AssertionError: It failed E assert 0 test_reporting.py:346: AssertionError----------------------------------- Header ------------------------------------Message</failure> <system-out>This is added to stdout</system-out> <system-err>This is added to stderr</system-err> </testcase>
</testsuite>
https://en.xdnf.cn/q/71487.html

Related Q&A

python nltk keyword extraction from sentence

"First thing we do, lets kill all the lawyers." - William ShakespeareGiven the quote above, I would like to pull out "kill" and "lawyers" as the two prominent keywords to …

Getting the parameter names of scipy.stats distributions

I am writing a script to find the best-fitting distribution over a dataset using scipy.stats. I first have a list of distribution names, over which I iterate:dists = [alpha, anglit, arcsine, beta, bet…

Does Python 3 gzip closes the fileobj?

The gzip docs for Python 3 states thatCalling a GzipFile object’s close() method does not close fileobj, since you might wish to append more material after the compressed dataDoes this mean that the g…

pip stopped working after upgrading anaconda v4.4 to v5.0

I ran the command conda update anaconda to update anaconda v4.4 to v5.0After anaconda was successfully upgraded to v5.0, I had problems running pip.This is the error output I see after running pip;Trac…

Python Django- How do I read a file from an input file tag?

I dont want the file to be saved on my server, I just want the file to be read and printed out in the next page. Right now I have this.(index.html)<form name="fileUpload" method="post…

ImportError: cannot import name AutoModelWithLMHead from transformers

This is literally all the code that I am trying to run: from transformers import AutoModelWithLMHead, AutoTokenizer import torchtokenizer = AutoTokenizer.from_pretrained("microsoft/DialoGPT-small&…

UnicodeEncodeError: ascii codec cant encode characters in position 0-6: ordinal not in range(128)

Ιve tried all the solution that I could find, but nothing seems to work: teext = str(self.tableWidget.item(row, col).text())Im writing in greek by the way...

selenium PhantomJS send_keys doesnt work

I am using selenium and PhantomJS for testing. I followed Seleniums simple usage, but send_keys doesnt work on PhantomJS, it works on Firefox. Why? I have to use button.click() instead?#!/usr/bin/pyt…

Replace values in column of Pandas DataFrame using a Series lookup table

I want to replace a column of values in a DataFrame with a more accurate/complete set of values generated by a look-up table in the form of a Series that I have prepared.I thought I could do it this wa…

Behavior of round function in Python

Could anyone explain me this pice of code:>>> round(0.45, 1) 0.5 >>> round(1.45, 1) 1.4 >>> round(2.45, 1) 2.5 >>> round(3.45, 1) 3.5 >>> round(4.45, 1) 4.5…