Here is what the full code for conftest.py would look like for this situation:
conftest.py
import pytest
import smtplib
import ssl
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
failed: bool = False
def send_email():
"""
function to send an email with attachment after all test are run and a failure was detected
"""
subject: str = "subject"
body: str = "body"
sender_email: str = "email"
receiver_email: str = "email"
password: str = "password" # Recommended for mass emails# This should obviously be an input or some other stronger protection object other than a string
smtp: str = "smtp"
filename: str = "result.html"
port: int = 587
# Attachments need to be multipart
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email
# Add body to email
message.attach(MIMEText(body, "plain"))
# Open html file in binary mode
# This will only work if HTML file is located within the same dir as the script
# If it is not, then you will need to modify this to the right path
with open(filename, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP(smtp, port) as server:
server.starttls(context=context)
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, text)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
global failed
report = yield
result = report.get_result()
if result.when == 'call' and result.outcome == "failed":
failed = True
print("FAILED")
def pytest_sessionfinish(session, exitstatus):
if failed is True:
send_email()
There are two key functions within this file.
- pytest_runtest_makereport
- pytest_session
pytest_runtest_makereport will run after every test and simply check if the test failed or not. If any test fails, then the failed
flag will be set to True.
Then at the end of the whole script, pytest_session will run and check if the flag was changed to True. If it was, then an email will be sent with the result.html file attached.
I would like to highlight that part of this question was previously answered before, see the link below:
How to send pytest coverage report via email?
Given the linked question/answer did not contain any information on how to only run if a test failed. I decided it was worth answering here.