I am trying to generate a self contained html report using pytest-html and selenium. I have been trying to imbedded screenshots into the report but they are not being displayed.
My conftest.py looks like this
#pytest.fixture()
def chrome_driver_init(request, path_to_chrome):
driver = webdriver.Chrome(options=opts, executable_path=path_to_chrome)
request.cls.driver = driver
page_object_init(request, driver)
driver.get(URL)
driver.maximize_window()
yield driver
driver.quit()
# Hook that takes a screenshot of the web browser for failed tests and adds it to the HTML report
#pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
report = outcome.get_result()
extra = getattr(report, "extra", [])
if report.when == "call":
feature_request = item.funcargs['request']
driver = feature_request.getfixturevalue('chrome_driver_init')
nodeid = item.nodeid
xfail = hasattr(report, "wasxfail")
if (report.skipped and xfail) or (report.failed and not xfail):
file_name = f'{nodeid}_{datetime.today().strftime("%Y-%m-%d_%H_%M")}.png'.replace("/", "_").replace("::", "_").replace(".py", "")
driver.save_screenshot("./reports/screenshots/"+file_name)
extra.append(pytest_html.extras.image("/screenshots/"+file_name))
report.extra = extra
I am convinced the problem is with the path to the image, and I have tried so many str combinations, os.path and pathlib but nothing has worked. The screenshot is being saved in the expected location and I can open it like any other image. Its just not displaying on the report.
<div class="image"><img src="data:image/png;base64,screenshots\scr_tests_test_example_TestExample_test_fail_example_2022-01-18_16_26.png"/></div>
EDIT: For addional clairification. I have tried to use absolute path in the extra.append but it kept giving me a Cant Resolve File error in the HTML file. My absoulte path was(with some personal details redacted) C:\Users\c.Me\OneDrive - Me\Documents\GitHub\project\build\reports\screenshots\filename.png I have tried it with both '/' and '\'
Also my File structure
project
├───build
│ ├───reports
│ ├───screenshots
│ ├───filename.png
| ├───report.html
| ├───run.py # I am running the test suite from here
├───scr
| ├───settings.py
│ ├───tests
│ ├───confest.py
run.py
if __name__ == "__main__":
os.system(f"pytest --no-header -v ../scr/tests/ --html=./reports/Test_Report_{today}.html --self-contained-html")
For Prophet, may be bless me this day
To get the Cannot Resolve Directory error my code is the following
file_name = f'{nodeid}_{datetime.today().strftime("%Y-%m-%d_%H_%M")}.png'.replace("/", "_").replace("::", "_").replace(".py", "")
img_path = os.path.join(REPORT_PATH, 'screenshots', file_name)
driver.save_screenshot(img_path)
extra.append(pytest_html.extras.image(img_path))
The variable REPORT_PATH is imported from the settings.py (see directory tree above) and is created by
PROJ_PATH = Path(__file__).parent.parent
REPORT_PATH = PROJ_PATH.joinpath("build\reports")
also fun fact if I do img_path.replace("\\", "/") the error changes to Cannot Resolve File
I have learned so much in this painful journey. Mostly I have learned I am an idiot. The problem was that I wanted to make a self contained HTML. Pytest-html does not work as expected with adding images to a self contained report. Before you can you have to convert the image into its text base64 version first. So the answers to all my owes was a single line of code.
#pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
report = outcome.get_result()
extra = getattr(report, "extra", [])
if report.when == "call":
feature_request = item.funcargs['request']
driver = feature_request.getfixturevalue('chrome_driver_init')
nodeid = item.nodeid
xfail = hasattr(report, "wasxfail")
if (report.skipped and xfail) or (report.failed and not xfail):
file_name = f'{nodeid}_{datetime.today().strftime("%Y-%m-%d_%H_%M")}.png'.replace("/", "_").replace("::", "_").replace(".py", "")
img_path = os.path.join(REPORT_PATH, "screenshots", file_name)
driver.save_screenshot(img_path)
screenshot = driver.get_screenshot_as_base64() # the hero
extra.append(pytest_html.extras.image(screenshot, ''))
report.extra = extra
Thank you Prophet for guiding on this pilgrimage. Now I must rest.
I'm not completely sure how it works with PyTest, however we have similar issue with Java Extent Manager.
There you have to pass the absolute path of the image file, not the relative path.
As I can see here the current working directory can be achieved as following:
import pathlib
pathlib.Path().resolve()
So, if I understand that correctly you should change your code from
extra.append(pytest_html.extras.image("/screenshots/"+file_name))
to
working_root = pathlib.Path().resolve()
extra.append(pytest_html.extras.image(working_root + "/screenshots/"+file_name))
UPD
I think you are missing a reports subfolder here.
Instead of
working_root = pathlib.Path().resolve()
extra.append(pytest_html.extras.image(working_root + "/screenshots/"+file_name))
Try using
working_root = pathlib.Path().resolve()
extra.append(pytest_html.extras.image(working_root + "/reports/screenshots/"+file_name))
Related
I'm trying to customize report.html of pytest using pytest-html plugin.
I searched up many sites(including pytest-html documentation) and found that the code below is commonly used.(The code is in conftest.py)
(https://pytest-html.readthedocs.io/en/latest/user_guide.html#extra-content)
#pytest.hookimpl(hookwrapper = True)
def pytest_runtest_makereport(item, call):
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
report = outcome.get_result()
extra = getattr(report, "extra", [])
if report.outcome == "call":
#always add url to report
xfail = hasattr(report, "wasxfail")
if (report.skipped and xfail) or (report.failed and not xfail):
extra.append(pytest_html.extras.url("http://www.google.com/"))
extra.append(pytest_html.extras.text('Hi', name = 'TEXT'))
# only add additional html on failure
# extra.append(pytest_html.extras.html("<div>Additional HTML</div>"))
report.extra = extra
However, I have no idea of each lines.
No one explained what the line does actually.
Why does the script allocates yield keyword to outcome with out any variable(e.g. yield 1), and what does yield.get_result() actually do?
Also, I have no idea of xfail("wasxfail").
I found that #pytest.xfail makes the test function fail in the pytest run, but I think it has nothing to do with the above code.
Why don't we use 'fail' not 'xfail'?
Anyway, what I need is
First, the meaning of each line and what it does.
Second, I wanna set different message in the report.html depending on the pass/fail.
I tried python report.outcome == 'failed', report.outcome == 'passed' to divide conditions, but it didn't work.
Third, when adding the text not url, it becomes tag and helps redirecting the page containing the text.
However, if I click the page in the html, it opens about:blank page not the desired one.
Using right click and open in new tab redirects to the desired one.
Any help is welcomed. Thanks.
+ I have more questions, I tried
if report.passed:
extra.append(pytest_html.extras.url("https://www.google.com/")
report.extra = extra
It attaches 3 same links in the report.html(Results table) How can I handle it?
+ I could log a message when test is failed like msg = 'hi', pytest.fail(msg) However, I cannot get a clue to do it when the test is passed.
Trying to answer as many lines as possible.
Pytest uses generators to iterate over the report steps.
The function pytest_runtest_makereport iterates over every result.when (not .outcome, this is a bug in the documentation) which according to pytest are as follows: 'collect', 'setup', 'call', and 'teardown'.
The get_result is how pytest implements its hooks.
The confusion about failed and xfail (expected to fail) is how you define a test failure: It is an error if it was skipped but was expected to fail or if it failed but was not expected to fail.
The thing with the about:blank could also be a bug.
What you want to use your if statements on is not the call info but the report:
if report.failed:
do_stuff()
if report.passed:
do_stuff_different()
One way to get more info about code and its context would be to debug it using breakpoint().
So the snippet you are looking for is:
#pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
report = outcome.get_result()
extra = getattr(report, "extra", [])
if report.when == "call":
xfail = hasattr(report, "wasxfail")
if (report.skipped and xfail) or (report.failed and not xfail):
extra.append(pytest_html.extras.url("http://www.google.com/"))
if report.passed:
extra.append(pytest_html.extras.url("http://www.stackoverflow.com/"))
report.extra = extra
I have automation testing, using pytest, and is added to the container in docker and running from there. And when I run and it fails the screenshots are not added to my report.
In the contest test I use this code to generate reports and add screenshots if one test is fail:
def pytest_runtest_makereport(item, call):
timestamp = datetime.now().strftime('%H-%M-%S')
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
extra = getattr(report, 'extra', [])
if report.when == 'call':
feature_request = item.funcargs['request']
xfail = hasattr(report, "wasxfail")
if (report.skipped and xfail) or (report.failed and not xfail):
feature_request = item.funcargs["request"]
driver = feature_request.getfixturevalue("setup")
timestamp = datetime.now().strftime('%H-%M-%S')
img_name = "name" + timestamp + ".png"
img_path = os.path.join("- .:/oalaCuBunatati", img_name)
driver.save_screenshot(img_path)
if (report.skipped and xfail) or (report.failed and not xfail):
# only add additional html on failure
extra.append(pytest_html.extras.image(img_path))
extra.append(pytest_html.extras.html('<div>Additional HTML</div>'))
report.extra = extra
I was checking my folder from where the container is built and I observe that screenshots are not added if I add the path to the specific folder this issue goes to thinking if the docker is not saved my screenshots to the local folder from where is built and maybe is saving in a volume.
And after some research, I found that docker can create volumes for data that is input or needed, and in the upper code I use the specific volume as a path for the save screenshots which is:- .:/oalaCuBunatati but every time when the test case fails the image is still not added:
I tried to mount that volume by adding the code:
ismount= os.path.ismount(path)
img_name = "name" + timestamp + ".png"
img_path = os.path.join(ismount, img_name)
But then I got a conflict for the specific method which is not allowed to boolean only str or text.
The next question is:
Is any possible to use or add screenshots from a docker container run in my report?
From testing the path like "/oalaCuBunatati/scrennshot" I successfully create the screenshots but not in added to my report, the image is not displayed but if I use it from my local and not in docker, the screenshots are added.
I m new in python and also in stackoverflow even if I sometimes I read it for various issue...
These days I m approaching to python3 cause I would like to improve a simple procedure: everyday I need to download data from an ubuntu system to win 10 and create the relative path but this already happens and works fine.
I just want to add a function in this program (made from other people) and create a folder inside the folder created and named with the date of the day (so everyday this folder has a different name!).
import paramiko
import stat
import os
import pandas as pd
#from tqdm.notebook import tqdm
from tqdm import tqdm
import logging
from logging_config import getJupyterHandler
logging.basicConfig(level=logging.INFO, handlers=[getJupyterHandler()])
logging.getLogger('paramiko.transport').setLevel(logging.WARNING)
logging.getLogger('paramiko.transport.sftp').setLevel(logging.WARNING)
import datetime
import utils
root = '/...path'
date = None
overwrite_existing = False
if date is None:
date = datetime.date.today().strftime("%Y%m%d")
logging.info("Checking data for today")
else:
logging.warning("Manual date is set")
sftp = utils.getPandoraSFTP(ip='...')
dates = sftp.listdir(root)
if date not in dates:
logging.error("No folder found")
else:
logging.info("Folder found")
files, numberOfFiles = utils.getFileListToCopy(sftp, f"{root}/{date}", f"C:\\data\\to_upload\\
{date}", f"C:\\data\\to_brighter\\{date}", True)
logging.info("Download tags")
tags = {k:v for k,v in files.items() if 'tag/' in k}
if len(tags)>0:
for remote, local in tags.items():
if os.path.exists(local) == False or overwrite_existing:
sftp.get(remote, local)
logging.info("Create summary table")
folder = os.path.dirname(list(tags.values())[0])
df = pd.DataFrame(columns=['id', 'Time', 'type' ,'Function', 'Leveling', 'Weather',
'Illumination', 'visibility', 'Road Type', 'sky', 'other', 'Issue description', 'driver',
'tester'])
for file in os.listdir(folder):
if file.lower().endswith('csv'):
try:
df = df.append(pd.read_csv(f"{folder}\\{file}", header=None,
names=df.columns))
except Exception as e:
logging.error(f"Unable to process tag: {file} due to {e}")
df['Location'] = ''
filename = folder.split('\\')[-2]
summary_path = f'...'
df[['Time', 'Function', 'Road Type', 'Illumination', 'Weather', 'Location', 'Issue
description']].to_excel(summary_path, index=False, header=False)
logging.info("Table created on ")
else:
logging.warning(f"No tags found")
pbar = tqdm(files.items())
for remote, local in pbar:
pbar.set_postfix_str(os.path.basename(remote))
if os.path.exists(local) == False or overwrite_existing:
sftp.get(remote, local)
#making new folder
folder_n = "name of folder..."
os.chdir ("C:\\data\\to_upload\\{date}") #choose directory
os.mkdir (folder_n)
How you can see in these lasts strings (#making new folder) I simply added a mkdir function for create the folder inside the folder {date}. Of course the error says that it does not find that path!
Could someone help me and suggest a way on how identify that folder?
Thanks
You wrote
os.chdir("C:\\data\\to_upload\\{date}")
but you wanted
os.chdir(f"C:\\data\\to_upload\\{date}")
That is, you want an f-string to interpolate the date variable
so its value will become part of the string.
You'd be better off phrasing it as a raw r-string
os.chdir(rf"C:\data\to_upload\{date}")
rather than doubling the \ backwhacks.
Better still, just use regular / slashes:
os.chdir(f"C:/data/to_upload/{date}")
thank you very much, it works!!
So I missed that "f"! I was sure paths must be wrote just inside quotation but I will study in deep this aspect.
I am writing tests in pytest bdd with selenium. I am using pytest-html to generate report. For debug purpose or just to have a proper logging, I want selenium screenshots and rest of the logs in html report. But I am unable to have selenium screenshot in passed report.
Here are the things I am trying.
There is a pytest-html hook wrapper in conftest.py
conftest.py
#pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
print("printing report")
extra = getattr(report, 'extra', [])
if report.when == 'call':
mylogs = ""
with open('/tmp/test.log', 'r') as logfile:
for line in logfile:
mylogs = mylogs + line + "<br>"
extra.append(pytest_html.extras.html('<html><body>{}</body></html>'.format(mylogs)))
report.extra = extra
This code is adding logs in my report.html
Similarly, I will be adding few selenium screenshots in my test code.
I want to know if we can generate a report containing all selenium screenshots.
Following is my test file
test_file.py
def test_case():
logger.info("I will now open browser")
driver = webdriver.Chrome()
driver.get('http://www.google.com')
driver.save_screenshot('googlehome.png')
time.sleep(3)
driver.quit()
I want googlehome.png and all other png file to be part of html report. I will be great if the we can generate a robot framework like html report.
Is there any way in pytest we can do that?
Following is the command I use to generate report
py.test -s --html=report.html --self-contained-html -v
You have to pass webdriver from test into pytest reporting system.
In my case I use webdriver as fixtuer. That have a lot of other advantages - for example you can test for any set of browsers and control that from one place.
#pytest.fixture(scope='session', params=['chrome'], ids=lambda x: 'Browser: {}'.format(x))
def web_driver(request):
browsers = {'chrome': webdriver.Chrome}
return browsers[]()
def test_case(web_driver):
logger.info("I will now open browser")
web_driver.get('http://www.google.com')
#pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
if rep.when == 'call' and not rep.failed:
try:
if 'web_driver' in item.fixturenames:
web_driver = item.funcargs['web_driver']
else:
return # This test does not use web_driver and we do need screenshot for it
# web_driver.save_screenshot and other magic to add screenshot to your report
except Exception as e:
print('Exception while screen-shot creation: {}'.format(e))
Here is how I solved mine:
Okay so here is how you access webdriver from the report generation hook:
#pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
timestamp = datetime.now().strftime('%H-%M-%S')
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
extra = getattr(report, 'extra', [])
if report.when == 'call':
feature_request = item.funcargs['request']
driver = feature_request.getfuncargvalue('browser')
driver.save_screenshot('D:/report/scr'+timestamp+'.png')
extra.append(pytest_html.extras.image('D:/report/scr'+timestamp+'.png'))
# always add url to report
extra.append(pytest_html.extras.url('http://www.example.com/'))
xfail = hasattr(report, 'wasxfail')
if (report.skipped and xfail) or (report.failed and not xfail):
# only add additional html on failure
extra.append(pytest_html.extras.image('D:/report/scr.png'))
extra.append(pytest_html.extras.html('<div>Additional HTML</div>'))
report.extra = extra
I have around 100 journal articles. Each journal article is represented as an HTML page. A journal's HTML page, its css file and the figures that are part of the journal are located within its folder. In other words, each html file refers to it's own css file and corresponding image files. And I have created the following folder structure ( | represents a folder, -- represents a file):
-- app.py
|views
-- Disp_Res.tpl
-- UI.tpl
|static
|articles
|PMC45677
--PMC45677.html
--jats-preview.css
--fig1_45677.jpg
--fig2_45677.jpg
|PMC23456
--PMC23456.html
--jats-preview.css
--fig1_23456.jpg
--fig2_23456.jpg
I have the following code within my app.py
#app.get('/articles/<pmc:re:PMC[0-9]*>')
def html_article(pmc):
global pmcno
pmcno = pmc;
pmc_article = pmcno + ".html"; print pmc_article
rootdir = os.path.join('static/articles', pmcno) #'static/articles/{pmcno}'
print "html_article", rootdir # This statement displays the right dir
return static_file(pmc_article, root=rootdir)
#app.get('/<filename:re:.*\.css>')
def stylesheets(filename):
rootdir = os.path.join('static/articles', pmcno)
print "stylesheets", rootdir # This statement does NOT display
return static_file(filename, root=rootdir)
#app.get('/<filename:re:.*\.(jpg|png|gif|ico)>')
def images(filename):
rootdir = os.path.join('static/articles', pmcno)
print "images", rootdir # This statement does NOT display
return static_file(filename, root=rootdir)
And needless to say this doesn't work. When I run app.py, it just gives me the error:
"Sorry, the requested URL 'http://localhost:8080/articles/PMC45677' caused an error:
File does not exist."
Any idea what I am doing wrong? Or do you have a better idea of achieving what I am aiming at? Please do let me know. Any help will be highly appreciated!
Thanks in advance.
Maybe because Bottle doesn't know what /articles/ is ?
Try to add this in top of your code :
#app.route('/articles/<filepath:path>')
def file_stac(filepath):
return static_file(filepath,root="./articles")