Email your pytest results as html report

We have noticed that sometimes we want to see test results in our email inbox even though good reporting tools are being used. We at Qxf2 Services, use pytest as our test runner. A simple utility email_pytest_reports.py has been added to our framework which can send pytest results as simple HTML reports. This blog is on how we built an email utility to send pytest results to any email address.


Steps to build and run the utility

Step 1: Created a email_conf to get the email details
You can add the email credentials, sender and target details in email_conf.py

Step 2: Install pytest-html plugin:

 pip install pytest-html

Step 3: Added the required functions to send email in email_pytest_report util file
We need to write functions to create the html report, make it as an attachment and finally send the email with the attachment.

a. Creating html report is accomplished by get_test_report_data function

def get_test_report_data(self,html_body_flag= True,report_file_path= 'default'):
        "get test report data from pytest_report.html or pytest_report.txt or from user provided file"
        if html_body_flag == True and report_file_path == 'default':
            #To generate pytest_report.html file use following command e.g. py.test --html = log/pytest_report.html
            test_report_file = os.path.abspath(os.path.join(os.path.dirname(__file__),'..','log','pytest_report.html'))#Change report file name & address here
        elif html_body_flag == False and report_file_path == 'default':
            #To generate pytest_report.log file add ">pytest_report.log" at end of py.test command e.g. py.test -k example_form -r F -v > log/pytest_report.log
            test_report_file = os.path.abspath(os.path.join(os.path.dirname(__file__),'..','log','pytest_report.log'))#Change report file name & address here
        else:
            test_report_file = report_file_path
        #check file exist or not
        if not os.path.exists(test_report_file):
            raise Exception("File '%s' does not exist. Please provide valid file"%test_report_file)
 
        with open(test_report_file, "r") as in_file:
            testdata = ""
            for line in in_file:
                testdata = testdata + '\n' + line
 
        return testdata

b. Get and attach the report file

def get_attachment(self,attachment_file_path = 'default'):
        "Get attachment and attach it to mail"
        if attachment_file_path == 'default':
            #To generate pytest_report.html file use following command e.g. py.test --html = log/pytest_report.html
            attachment_report_file = os.path.abspath(os.path.join(os.path.dirname(__file__),'..','log','pytest_report.html'))#Change report file name & address here
        else:
            attachment_report_file = attachment_file_path
        #check file exist or not
        if not os.path.exists(attachment_report_file):
            raise Exception("File '%s' does not exist. Please provide valid file"%attachment_report_file)
 
        # Guess encoding type
        ctype, encoding = mimetypes.guess_type(attachment_report_file)
        if ctype is None or encoding is not None:
            ctype = 'application/octet-stream'  # Use a binary type as guess couldn't made
 
        maintype, subtype = ctype.split('/', 1)
        if maintype == 'text':
            fp = open(attachment_report_file)
            attachment = MIMEText(fp.read(), subtype)
            fp.close()
        elif maintype == 'image':
            fp = open(attachment_report_file, 'rb')
            attachment = MIMEImage(fp.read(), subtype)
            fp.close()
        elif maintype == 'audio':
            fp = open(attachment_report_file, 'rb')
            attachment = MIMEAudio(fp.read(), subtype)
            fp.close()
        else:
            fp = open(attachment_report_file, 'rb')
            attachment = MIMEBase(maintype, subtype)
            attachment.set_payload(fp.read())
            fp.close()
            # Encode the payload using Base64
            encoders.encode_base64(attachment)
        # Set the filename parameter
        attachment.add_header('Content-Disposition',
                   'attachment',
                   filename=os.path.basename(attachment_report_file))
 
        return attachment

c. Finally write the send email function like below
To run the utility using python command and to see the full file, please check the file in our framework

def send_test_report_email(self,html_body_flag = True,attachment_flag = False,report_file_path = 'default'):
        "send test report email"
        #1. Get html formatted email body data from report_file_path file (log/pytest_report.html) and do not add it as an attachment
        if html_body_flag == True and attachment_flag == False:
            testdata = self.get_test_report_data(html_body_flag,report_file_path) #get html formatted test report data from log/pytest_report.html
            message = MIMEText(testdata,"html") # Add html formatted test data to email
 
        #2. Get text formatted email body data from report_file_path file (log/pytest_report.log) and do not add it as an attachment
        elif html_body_flag == False and attachment_flag == False:
            testdata = self.get_test_report_data(html_body_flag,report_file_path) #get html test report data from log/pytest_report.log
            message  = MIMEText(testdata) # Add text formatted test data to email
 
        #3. Add html formatted email body message along with an attachment file
        elif html_body_flag == True and attachment_flag == True:
            message = MIMEMultipart()
            #add html formatted body message to email
            html_body = MIMEText('''Hello,Please check the attachment to see test built report.<strong>Note: For best UI experience, download the attachment and open using Chrome browser.</strong>''',"html") # Add/Update email body message here as per your requirement
            message.attach(html_body)
            #add attachment to email
            attachment = self.get_attachment(report_file_path)
            message.attach(attachment)
 
        #4. Add text formatted email body message along with an attachment file
        else:
            message = MIMEMultipart()
            #add test formatted body message to email
            plain_text_body = MIMEText('''Hello,\n\tPlease check attachment to see test built report.
                                       \n\nNote: For best UI experience, download the attachment and open  using Chrome browser.''')# Add/Update email body message here as per your requirement
            message.attach(plain_text_body)
            #add attachment to email
            attachment = self.get_attachment(report_file_path)
            message.attach(attachment)
 
        message['From'] = self.sender
        message['To'] = ', '.join(self.targets)
        message['Subject'] = 'Script generated test report' # Update email subject here
 
        #Send Email
        server = smtplib.SMTP_SSL(self.smtp_ssl_host, self.smtp_ssl_port)
        server.login(self.username, self.password)
        server.sendmail(self.sender, self.targets, message.as_string())
        server.quit()

d. Conftest.py changes

In pytest, there is a provision to run scripts/code after the execution of all tests. pytest_sessionfinish plugin hook executes after the whole test run finishes. So we used pytest_sessionfinish plugin hook auto-email the report every time you run the test. To achieve this we added a method pytest_terminal_summary in conftest. This method will be run only after tests are exited which means after the execution of self.driver.quit(), this method will be called.

#Contents of conftest.py
from utils.email_pytest_report import Email_Pytest_Report
 
#Test arguments
@pytest.fixture
def email_pytest_report(request):
"pytest fixture for device flag"
return request.config.getoption("--email_pytest_report")
 
#Command line options:
parser.addoption("--email_pytest_report",
dest="email_pytest_report",
help="Email pytest report: Y or N",
default="N")
 
def pytest_terminal_summary(terminalreporter, exitstatus):
    "add additional section in terminal summary reporting."
    if terminalreporter.config.getoption("--email_pytest_report").lower() == 'y':
        #Initialize the Email_Pytest_Report object
        email_obj = Email_Pytest_Report()
        # Send html formatted email body message with pytest report as an attachment
        email_obj.send_test_report_email(html_body_flag=True,attachment_flag=True,report_file_path= 'default')

Step 4: Running the utility
You can run the test using below command

pytest --email_pytest_report Y --html=log/pytest_report.html --self-contained-html --capture=sys

After running the command you will see console output as shown below and an email with an HTML report attached.

I hope this blog has helped you get started with emailing your pytest reports.


I started my career as a Dotnet Developer. I had the opportunity of writing automation scripts for unit testing in Testcomplete using C#. In a couple of years, I was given the opportunity to set up QA function for our in-house products and that’s when I became full-time Tester. I have an overall 12 years of experience. I have experience in Functional – Integration and Regression Testing, Database, Automation testing and exposure to Application Security and API testing. I have excellent experience in test processes, methodologies and delivery for products in Information security and Pharma domain. On the Personal side, I love playing with kids, watch Football, read novels by Indian Authors and cooking.

18 Comments

  1. Akhil Gupta said:

    Hey Rohini,

    Hope you doing good !!

    I have done the same steps but not able to run the command, it shows below error :error: unrecognized arguments: –Email_Pytest_Report

    run below command :

    pytest –Email_Pytest_Report Y –html=pytest_report.html –self-contained-html

    March 24, 2020
    Reply
    • Akhil, have you tried with two hyphens -- and the correct capitalization for the argument email_pytest_report. So your option should look like this: --email_pytest_report.

      March 24, 2020
      Reply
  2. Akhil Gupta said:

    This is not woking at my end, getting below error :

    pytest: error: unrecognized arguments: –Email_Pytest_Report

    Please have look and get back to me asap. Irequest.

    March 24, 2020
    Reply
    • Akhil, have you tried with two hyphens -- and the correct capitalization for the argument email_pytest_report. So your option should look like this: --email_pytest_report.

      March 24, 2020
      Reply
  3. Akhil Gupta said:

    pytest: error: unrecognized arguments: –email_pytest_report
    inifile: /Users/akhgupta0/Documents/TAMM-Framework-GIT/Tamm-Automation-GC/ui-automation-gca/ui_tests/pytest.ini
    rootdir: /Users/akhgupta0/Documents/TAMM-Framework-GIT/Tamm-Automation-GC/ui-automation-gca/ui_tests

    It is throw above error. I am not able to call the above method directly. Please guide me for the missing steps.

    March 25, 2020
    Reply
    • Akkul D N said:

      Hi Akhil,
      could you please verify if the fixture called email_pytest_report exists in your conftest.py file? Also run pytest -h and see if “email_pytest_report” exists in the list

      March 26, 2020
      Reply
  4. Akhil Gupta said:

    Hey,

    I am not sure how to implement this.
    Please do let me know what kind of changes are require in conftest.py file.
    Please do let me know your changes…

    March 26, 2020
    Reply
  5. Akhil Gupta said:

    Hey Team,

    thanks for the reply.
    I am very close to this solution. I have done below steps :

    1) create the email_conf.py and reside it util folder/
    2) added the email_pytest_report.py file into framework and added all required method.
    3) Now in the file path “email_pytest_report.py” , i am running this command. it state below error.
    unrecognized arguments: –email_pytest_report

    I beleive this error come when we are not able to pass file name in the command. Also, please do let me know what changes in conftest.py file. So, I can incorporate the same.

    March 29, 2020
    Reply
  6. Akhil Gupta said:

    Problem statement is , we can not pass the file name in the command.

    If we call this method fixture. So, user can not send the html report because it can not exist. It exist only when the execution is get completed.

    March 29, 2020
    Reply
    • Rohan Joshi Rohan Joshi said:

      @Akhil Do you have these two methods in conftest.py?
      @pytest.fixture
      def email_pytest_report(request):
      “pytest fixture for device flag”
      return request.config.getoption(“–email_pytest_report”)

      def pytest_terminal_summary(terminalreporter, exitstatus):
      “add additional section in terminal summary reporting.”
      if terminalreporter.config.getoption(“–email_pytest_report”).lower() == ‘y’:
      #Initialize the Email_Pytest_Report object
      email_obj = Email_Pytest_Report()
      # Send html formatted email body message with pytest report as an attachment
      email_obj.send_test_report_email(html_body_flag=True,attachment_flag=True,report_file_path= ‘default’)

      And have you added below option in the method pytest_addoption method
      parser.addoption(“–email_pytest_report”,
      dest=”email_pytest_report”,
      help=”Email pytest report: Y or N”,
      default=”N”)

      March 30, 2020
      Reply
  7. Akhil Gupta said:

    Hey ,

    Thanks for the Prompt responce, code is compiled and run successfully, But it is not sending email and not calling the pytest_terminal_summary fucntion. Even i am not sure if it call how it will send to the report which generate after the execution of the test. Do we have any method which send the report from cache.
    Please do confirm.

    April 2, 2020
    Reply
    • Rohan Joshi Rohan Joshi said:

      Hey Akhil ,

      Good to hear that code is working now . About the emails not getting sent issue, can you check if you have configured email_conf (Refer step 1 of blog)

      April 3, 2020
      Reply
  8. Akhil Gupta said:

    Hey ,

    I crosesed check all things are in place, run throuh debugger but my below function is never executed . Please do let me if i need to include any special to get them execute. Also , request please share the working copy of code. So, it run without flawless.

    @pytest.fixture
    def pytest_terminal_summary(terminalreporter, exitstatus):

    April 3, 2020
    Reply
  9. Akhil Gupta said:

    Any update on my comment. Also,please allow to connect you through any channel. so, i can resolve this issue without any hassle.

    April 5, 2020
    Reply
  10. Akhil Gupta said:

    Any update on my comment. Also,please allow to connect you through any channel. so, i can resolve this issue without any hassle.

    Please do let me know on this.

    April 5, 2020
    Reply
    • Akhil, We do not connect via non-public channels since we don’t know under what NDAs and legal constraints you operate under. In general, we won’t take private messages about anything technical. We’ll help you on public channels like this but just so you know, we work full-time and attend to comments on the blog when we free up. So the replies and approvals won’t be regular.

      April 7, 2020
      Reply
  11. Akhil Gupta said:

    Hey

    I beleive there is no point to send an email if HTML file generated the after theexcution and take place and given location in command line.
    Please do update me if this is post havingh different understanding.

    April 5, 2020
    Reply
    • Rohan Dudam Rohan Dudam said:

      Hey Akhil,
      To send generated HTML file via email refer our util here You can directly run util just you need to modify import statement to point email config file.
      In case, if you want auto email report after every run, you need to add method “pytest_terminal_summary” under conftest.py file and inside that method, you need call method to the sent email. Look at here, how we added method “pytest_terminal_summary” under contest here Akhil, ignore the flags we are checking here, look at Line 258 and 260, how we calling send email method. You can also add the flags the way we added for more control.
      Note: Method “pytest_terminal_summary” executed only after tests get existed, I mean after the execution of self.driver.quit()

      April 7, 2020
      Reply

Leave a Reply

Your email address will not be published.