天天看點

【Python+selenium】自動發郵件功能

自動發郵件功能也是自動化策劃四項目的重要需求之一。例如,我們想在自動化腳本運作完成之後,郵箱就可以收到最新的測試報告結果。假設生成的測試報告與多人相關,每個人都去測試伺服器檢視就會比較麻煩,如果把這種自動的且不及時的檢視變成被動且及時的查收,就友善多了。

SMTP(Simple Mail Transfer Protocol)是簡單郵件傳輸協定,它是一組用于由于由源位址到目的位址傳送郵件的規則,由它來控制信件的中轉方式。

Python的smtplib子產品提供了一種很友善的途徑用來發送電子郵件。它對SMTP協定進行了簡單的封裝。我們可以使用SMTP對象的sendmail方法發送郵件,通過help()檢視SMTP所提供的方法如下。

【Python+selenium】自動發郵件功能

輸出結果:

Help on class SMTP in module smtplib:

class SMTP(builtins.object)

 |  This class manages a connection to an SMTP or ESMTP server.

 |  SMTP Objects:

 |      SMTP objects have the following attributes:

 |          helo_resp

 |              This is the message given by the server in response to the

 |              most recent HELO command.

 |  

 |          ehlo_resp

 |              This is the message given by the server in response to the

 |              most recent EHLO command. This is usually multiline.

 |  

 |          does_esmtp

 |              This is a True value _after you do an EHLO command_, if the

 |              server supports ESMTP.

 |  

 |          esmtp_features

 |              This is a dictionary, which, if the server supports ESMTP,

 |              will _after you do an EHLO command_, contain the names of the

 |              SMTP service extensions this server supports, and their

 |              parameters (if any).

 |  

 |              Note, all extension names are mapped to lower case in the

 |              dictionary.

 |  

 |      See each method's docstrings for details.  In general, there is a

 |      method of the same name to perform each SMTP command.  There is also a

 |      method called 'sendmail' that will do an entire mail transaction.

 |  

 |  Methods defined here:

 |  

 |  __enter__(self)

 |  

 |  __exit__(self, *args)

 |  

 |  __init__(self, host='', port=0, local_hostname=None, timeout=<object object at 0x000000000037F0C0>, source_address=None)

 |      Initialize a new instance.

 |      

 |      If specified, `host' is the name of the remote host to which to

 |      connect.  If specified, `port' specifies the port to which to connect.

 |      By default, smtplib.SMTP_PORT is used.  If a host is specified the

 |      connect method is called, and if it returns anything other than a

 |      success code an SMTPConnectError is raised.  If specified,

 |      `local_hostname` is used as the FQDN of the local host in the HELO/EHLO

 |      command.  Otherwise, the local hostname is found using

 |      socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host,

 |      port) for the socket to bind to as its source address before

 |      connecting. If the host is '' and port is 0, the OS default behavior

 |      will be used.

 |  

 |  auth(self, mechanism, authobject, *, initial_response_ok=True)

 |      Authentication command - requires response processing.

 |      

 |      'mechanism' specifies which authentication mechanism is to

 |      be used - the valid values are those listed in the 'auth'

 |      element of 'esmtp_features'.

 |      

 |      'authobject' must be a callable object taking a single argument:

 |      

 |              data = authobject(challenge)

 |      

 |      It will be called to process the server's challenge response; the

 |      challenge argument it is passed will be a bytes.  It should return

 |      bytes data that will be base64 encoded and sent to the server.

 |      

 |      Keyword arguments:

 |          - initial_response_ok: Allow sending the RFC 4954 initial-response

 |            to the AUTH command, if the authentication methods supports it.

 |  

 |  auth_cram_md5(self, challenge=None)

 |      Authobject to use with CRAM-MD5 authentication. Requires self.user

 |      and self.password to be set.

 |  

 |  auth_login(self, challenge=None)

 |      Authobject to use with LOGIN authentication. Requires self.user and

 |      self.password to be set.

 |  

 |  auth_plain(self, challenge=None)

 |      Authobject to use with PLAIN authentication. Requires self.user and

 |      self.password to be set.

 |  

 |  close(self)

 |      Close the connection to the SMTP server.

 |  

 |  connect(self, host='localhost', port=0, source_address=None)

 |      Connect to a host on a given port.

 |      

 |      If the hostname ends with a colon (`:') followed by a number, and

 |      there is no port specified, that suffix will be stripped off and the

 |      number interpreted as the port number to use.

 |      

 |      Note: This method is automatically invoked by __init__, if a host is

 |      specified during instantiation.

 |  

 |  data(self, msg)

 |      SMTP 'DATA' command -- sends message data to server.

 |      

 |      Automatically quotes lines beginning with a period per rfc821.

 |      Raises SMTPDataError if there is an unexpected reply to the

 |      DATA command; the return value from this method is the final

 |      response code received when the all data is sent.  If msg

 |      is a string, lone '\r' and '\n' characters are converted to

 |      '\r\n' characters.  If msg is bytes, it is transmitted as is.

 |  

 |  docmd(self, cmd, args='')

 |      Send a command, and return its response code.

 |  

 |  ehlo(self, name='')

 |      SMTP 'ehlo' command.

 |      Hostname to send for this command defaults to the FQDN of the local

 |      host.

 |  

 |  ehlo_or_helo_if_needed(self)

 |      Call self.ehlo() and/or self.helo() if needed.

 |      

 |      If there has been no previous EHLO or HELO command this session, this

 |      method tries ESMTP EHLO first.

 |      

 |      This method may raise the following exceptions:

 |      

 |       SMTPHeloError            The server didn't reply properly to

 |                                the helo greeting.

 |  

 |  expn(self, address)

 |      SMTP 'expn' command -- expands a mailing list.

 |  

 |  getreply(self)

 |      Get a reply from the server.

 |      

 |      Returns a tuple consisting of:

 |      

 |        - server response code (e.g. '250', or such, if all goes well)

 |          Note: returns -1 if it can't read response code.

 |      

 |        - server response string corresponding to response code (multiline

 |          responses are converted to a single, multiline string).

 |      

 |      Raises SMTPServerDisconnected if end-of-file is reached.

 |  

 |  has_extn(self, opt)

 |      Does the server support a given SMTP service extension?

 |  

 |  helo(self, name='')

 |      SMTP 'helo' command.

 |      Hostname to send for this command defaults to the FQDN of the local

 |      host.

 |  

 |  help(self, args='')

 |      SMTP 'help' command.

 |      Returns help text from server.

 |  

 |  login(self, user, password, *, initial_response_ok=True)

 |      Log in on an SMTP server that requires authentication.

 |      

 |      The arguments are:

 |          - user:         The user name to authenticate with.

 |          - password:     The password for the authentication.

 |      

 |      Keyword arguments:

 |          - initial_response_ok: Allow sending the RFC 4954 initial-response

 |            to the AUTH command, if the authentication methods supports it.

 |      

 |      If there has been no previous EHLO or HELO command this session, this

 |      method tries ESMTP EHLO first.

 |      

 |      This method will return normally if the authentication was successful.

 |      

 |      This method may raise the following exceptions:

 |      

 |       SMTPHeloError            The server didn't reply properly to

 |                                the helo greeting.

 |       SMTPAuthenticationError  The server didn't accept the username/

 |                                password combination.

 |       SMTPNotSupportedError    The AUTH command is not supported by the

 |                                server.

 |       SMTPException            No suitable authentication method was

 |                                found.

 |  

 |  mail(self, sender, options=[])

 |      SMTP 'mail' command -- begins mail xfer session.

 |      

 |      This method may raise the following exceptions:

 |      

 |       SMTPNotSupportedError  The options parameter includes 'SMTPUTF8'

 |                              but the SMTPUTF8 extension is not supported by

 |                              the server.

 |  

 |  noop(self)

 |      SMTP 'noop' command -- doesn't do anything :>

 |  

 |  putcmd(self, cmd, args='')

 |      Send a command to the server.

 |  

 |  quit(self)

 |      Terminate the SMTP session.

 |  

 |  rcpt(self, recip, options=[])

 |      SMTP 'rcpt' command -- indicates 1 recipient for this mail.

 |  

 |  rset(self)

 |      SMTP 'rset' command -- resets session.

 |  

 |  send(self, s)

 |      Send `s' to the server.

 |  

 |  send_message(self, msg, from_addr=None, to_addrs=None, mail_options=[], rcpt_options={})

 |      Converts message to a bytestring and passes it to sendmail.

 |      

 |      The arguments are as for sendmail, except that msg is an

 |      email.message.Message object.  If from_addr is None or to_addrs is

 |      None, these arguments are taken from the headers of the Message as

 |      described in RFC 2822 (a ValueError is raised if there is more than

 |      one set of 'Resent-' headers).  Regardless of the values of from_addr and

 |      to_addr, any Bcc field (or Resent-Bcc field, when the Message is a

 |      resent) of the Message object won't be transmitted.  The Message

 |      object is then serialized using email.generator.BytesGenerator and

 |      sendmail is called to transmit the message.  If the sender or any of

 |      the recipient addresses contain non-ASCII and the server advertises the

 |      SMTPUTF8 capability, the policy is cloned with utf8 set to True for the

 |      serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send.

 |      If the server does not support SMTPUTF8, an SMPTNotSupported error is

 |      raised.  Otherwise the generator is called without modifying the

 |      policy.

 |  

 |  sendmail(self, from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])

 |      This command performs an entire mail transaction.

 |      

 |      The arguments are:

 |          - from_addr    : The address sending this mail.

 |          - to_addrs     : A list of addresses to send this mail to.  A bare

 |                           string will be treated as a list with 1 address.

 |          - msg          : The message to send.

 |          - mail_options : List of ESMTP options (such as 8bitmime) for the

 |                           mail command.

 |          - rcpt_options : List of ESMTP options (such as DSN commands) for

 |                           all the rcpt commands.

 |      

 |      msg may be a string containing characters in the ASCII range, or a byte

 |      string.  A string is encoded to bytes using the ascii codec, and lone

 |      \r and \n characters are converted to \r\n characters.

 |      

 |      If there has been no previous EHLO or HELO command this session, this

 |      method tries ESMTP EHLO first.  If the server does ESMTP, message size

 |      and each of the specified options will be passed to it.  If EHLO

 |      fails, HELO will be tried and ESMTP options suppressed.

 |      

 |      This method will return normally if the mail is accepted for at least

 |      one recipient.  It returns a dictionary, with one entry for each

 |      recipient that was refused.  Each entry contains a tuple of the SMTP

 |      error code and the accompanying error message sent by the server.

 |      

 |      This method may raise the following exceptions:

 |      

 |       SMTPHeloError          The server didn't reply properly to

 |                              the helo greeting.

 |       SMTPRecipientsRefused  The server rejected ALL recipients

 |                              (no mail was sent).

 |       SMTPSenderRefused      The server didn't accept the from_addr.

 |       SMTPDataError          The server replied with an unexpected

 |                              error code (other than a refusal of

 |                              a recipient).

 |       SMTPNotSupportedError  The mail_options parameter includes 'SMTPUTF8'

 |                              but the SMTPUTF8 extension is not supported by

 |                              the server.

 |      

 |      Note: the connection will be open even after an exception is raised.

 |      

 |      Example:

 |      

 |       >>> import smtplib

 |       >>> s=smtplib.SMTP("localhost")

 |       >>> tolist=["[email protected]","[email protected]","[email protected]","[email protected]"]

 |       >>> msg = '''\

 |       ... From: [email protected]

 |       ... Subject: testin'...

 |       ...

 |       ... This is a test '''

 |       >>> s.sendmail("[email protected]",tolist,msg)

 |       { "[email protected]" : ( 550 ,"User unknown" ) }

 |       >>> s.quit()

 |      

 |      In the above example, the message was accepted for delivery to three

 |      of the four addresses, and one was rejected, with the error code

 |      550.  If all addresses are accepted, then the method will return an

 |      empty dictionary.

 |  

 |  set_debuglevel(self, debuglevel)

 |      Set the debug output level.

 |      

 |      A non-false value results in debug messages for connection and for all

 |      messages sent to and received from the server.

 |  

 |  starttls(self, keyfile=None, certfile=None, context=None)

 |      Puts the connection to the SMTP server into TLS mode.

 |      

 |      If there has been no previous EHLO or HELO command this session, this

 |      method tries ESMTP EHLO first.

 |      

 |      If the server supports TLS, this will encrypt the rest of the SMTP

 |      session. If you provide the keyfile and certfile parameters,

 |      the identity of the SMTP server and client can be checked. This,

 |      however, depends on whether the socket module really checks the

 |      certificates.

 |      

 |      This method may raise the following exceptions:

 |      

 |       SMTPHeloError            The server didn't reply properly to

 |                                the helo greeting.

 |  

 |  verify(self, address)

 |      SMTP 'verify' command -- checks for address validity.

 |  

 |  vrfy = verify(self, address)

 |  

 |  ----------------------------------------------------------------------

 |  Data descriptors defined here:

 |  

 |  __dict__

 |      dictionary for instance variables (if defined)

 |  

 |  __weakref__

 |      list of weak references to the object (if defined)

 |  

 |  ----------------------------------------------------------------------

 |  Data and other attributes defined here:

 |  

 |  debuglevel = 0

 |  

 |  default_port = 25

 |  

 |  does_esmtp = 0

 |  

 |  ehlo_msg = 'ehlo'

 |  

 |  ehlo_resp = None

 |  

 |  file = None

 |  

 |  helo_resp = None

None

Process finished with exit code 0

==========================================================================================

導入SMTP對象,通過help()檢視對象的注釋,從中找到sendmail()方法的使用說明。

connect(host,port)方法參數說明如下。

  • host:指定連接配接的郵箱伺服器。
  • port:指定連接配接伺服器的端口号。

login(user,password)方法參數說明如下。

  • user:登入郵箱使用者用。
  • password:登入郵箱密碼。

sendmail(from_addr,to_addrs,msg,..)方法參數說明如下。

  • from_addr:郵件發送者位址。
  • to_addrs:字元串清單,郵件發送位址。
  • msg:發送消息。

quit()方法:用于結束SMTP會話。

一般我們發郵件時有兩種方式。方式一:自己郵箱的Web頁面(如:mail.126.com),輸入自己郵箱的使用者名和密碼登入,打開發郵件頁面,填寫對方的郵箱位址及郵件标題與正文,完成後單擊發送。方式二:下載下傳安裝郵箱用戶端(如Outlook、Foxmail等),填寫郵箱帳号、密碼及郵箱伺服器(如:smtp.126.com),一般的郵箱用戶端會預設記下這些資訊,是以,這個過程隻需填寫一次,後面發郵件的過程與方法一相同。

而我們通過Python的SMTP對象發郵件則更像方式二,因為需要填寫郵箱伺服器。

當然,在具體發郵件時會涉及諸多需求,例如,郵件的正文的格式、是否帶圖檔、郵件是否需要添加附件(及多附近)、郵件是否需要同時向多人發送等。

一、發送HTML格式的郵件

send_mail.py

import smtplib
from email.mime.text import MIMEText
from email.header import Header

#發送郵箱伺服器
smtpserver='smtp.163.com'
#發送郵箱使用者/密碼
user='****@163.com'
password='*********'
#發送郵箱
sender='*****@163.com'
#接收郵箱
receiver='*****@163.com'
#發送郵件主題
subject='Python email test'
#編寫HTML類型的郵件正文
msg=MIMEText('<html><h1>你好!</h1></html>','html','utf-8')
msg['Subject']=Header(subject,'utf-8')

#連接配接發送郵件
smtp=smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(user,password)
smtp.sendmail(sender,receiver,msg.as_string())
smtp.quit()
           

本例中,除SMTP子產品外,我們還用到了email子產品,它主要用來定義郵件的标題和正文;Header()方法用來定義郵件标題;MIMEText()用于定義郵件正文,參數為html格式的文本。登入[email protected]郵箱,檢視郵箱内容如圖所示。

【Python+selenium】自動發郵件功能

二、發送帶附件的郵件

在發送檔案時,有時需要發送附件。下面的執行個體實作了帶附件的郵件發送。

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

#發送郵箱伺服器
smtpserver='smtp.163.com'
#發送郵箱
sender='******@163.com'
#接收郵箱
receiver='*********@163.com'
#發送郵箱使用者/密碼
user='********@163.com'
password='*******'
#發送郵件主題
subject='Python send email test'
#發送的附件
sendfile=open('D:\\下載下傳包\\build.xml','rb').read()
att=MIMEText(sendfile,'base64','utf-8')
att["Content-Type"]='application/octet-stream'
att["Content-Disposition"]='attachment;filename="build.xml"'
msgRoot=MIMEMultipart('related')
msgRoot['Subject']=subject
msgRoot.attach(att)

#連接配接發送郵件
smtp=smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(user,password)
smtp.sendmail(sender,receiver,msgRoot.as_string())
smtp.quit()      

相比上一個執行個體,通過MIMEMultipart()子產品構造的帶附件的郵件如下圖。

【Python+selenium】自動發郵件功能

三、查找最新的測試報告

現在已經知道如何通過Python編寫發郵件程式,但要想和自動化測試項目結合還需要解決一個問題,因為測試報告的名稱是根據目前時間生成的,是以如何找到最新生成的測試報告是實作發郵件功能的關鍵。

import os
#定義檔案目錄
result_dir='C:\\Users\\Administrator\\PycharmProjects\\test2\\HTML測試報告'

lists=os.listdir(result_dir)
#重新按時間對應目錄下的檔案進行排序
lists.sort(key=lambda fn:os.path.getmtime(result_dir+"\\"+fn))

print(("最新的檔案為:"+lists[-1]))
file=os.path.join(result_dir,lists[-1])
print(file)      

首先定義測試報告的目錄result_dir,os.listdir()可以擷取目錄下的所有檔案及檔案夾。利用sort()方法對目錄下的檔案夾按時間重新排序。list[-1]取到的就是最新生成的檔案或檔案夾。程式運作結果如下。

【Python+selenium】自動發郵件功能

四、整合自動發郵件功能

解決了前面的問題後,現在就可以将自動發郵件功能內建到自動化測試項目中了。下面打開runtest.py檔案重新進行編輯。

from HTMLTestRunner import HTMLTestRunner
from email.mime.text import MIMEText
from email.header import Header
import smtplib
import unittest
import time
import os
#===================定義發送郵件==================
def send_mail(file_new):
    f=open(file_new,'rb')
    mail_body=f.read()
    f.close()

    msg=MIMEText(mail_body,'html','utf-8')
    msg['Subject']=Header("自動化測試報告","utf-8")
    smtp=smtplib.SMTP()

    smtp.connect("smtp.163.com")
    smtp.login("******@163.com","*******")   #密碼為授權碼
    smtp.sendmail("******@163.com","******@163.com",msg.as_string())
    smtp.quit()
    print('email has send out!')
#===============查找測試報告目錄,找到最新生成的測試報告檔案============================
def new_report(testreport):
    lists=os.listdir(testreport)
    lists.sort(key=lambda fn:os.path.getmtime(testreport+"\\"+fn))
    file_new = os.path.join(testreport, lists[-1])
    print(file_new)
    return file_new
if __name__=='__main__':
    test_dir='C:\\Users\\Administrator\\PycharmProjects\\test2\\HTML測試報告\\test_case'
    test_report='C:\\Users\\Administrator\\PycharmProjects\\test2\\HTML測試報告'

    discover=unittest.defaultTestLoader.discover(test_dir,pattern='test_*.py')

    now=time.strftime("%Y-%m-%d %H_%M_%S")
    filename=test_report+"\\"+now+'result.html'
    fp=open(filename,'wb')
    runner = HTMLTestRunner(stream=fp,
                            title='百度搜尋測試報告',
                            description='用例執行情況:')
    runner.run(discover)  # 運作測試用例
    fp.close()  # 關閉報告檔案

    new_report=new_report(test_report)
    send_mail(new_report)  #發送測試報告      

整個程式的執行過程可以分為三個步驟:

①通過unittest架構discover()找到比對測試用例,由HTMLTestRunner的run()方法執行測試用例并生成最新的測試報告。

②調用new_report()函數找到測試報告目錄(report)下最新生成的測試報告,傳回測試報告的路徑。

③将得到的最新測試報告的完整路徑傳給send_mail()函數,實作發郵件功能。

整個腳本執行完成後,打開接收郵箱,即可看到最新測試執行的測試報告。

【Python+selenium】自動發郵件功能

繼續閱讀