Simple Automation using Python - Atomac in Mac OS X

The Product Automation in MAC OS X is quite easy if we have enough knowledge in the following languages and tools

  1.  Bash Scripts
  2.  Apple Scripts
  3.  Python especially ATOMac
  4. Knowledge in Accessibility.
For more understanding of the LDTP:
LDTP - Tutorial is written by  Nagappan Alagappan. He has given the clear idea and architecture of the LDTP and its working.

I have used the contents from the tutorial for knowledge sharing. Following are the few information and contents from  the LDTP - Tutorial.

Few interesting key points,
    • Linux Desktop Testing Project (LDTP) is aimed at producing high quality test automation framework and cutting-edge tools that can be used to test GNU/Linux Desktop and improve it. 
    • It uses the Accessibility libraries to poke through the application's user interface. 
    • This idea has been extended to Microsoft Windows as Cobra, Mac OS X as ATOMac
    • LDTP is now known to work on Windows / Mac /Linux / Palm Source / Solaris / NetBSD / FreeBSD.
    • LDTP can test any .NET / GNOME / KDE (QT >= 4.8) application which are accessibility enabled, Mozilla, Open Office/Libre Office, any Java application (should have a UI based on swing

    Cross Platform GUI Test Automation tool
     Linux version is LDTP, Windows version is Cobra and Mac version is PyATOM + ATOMac.


    About testing

    Why testing ?
    Testing is a process to identify defects in a (software) system. For more information - 

    Why automation ?
    Testing an application multiple times with same steps, automated process can do a better job.

    Complexity of GUI testing ?
    • Identification of object in a window (push button, menu item)
    • Should be context sensitive (Window specific operation)
    • Handling of unexpected pop-up windows
    • Keeping the test script in sync with the UI changes
    • Failures has to be verified on each operation
    • Rendering of images / text in the display area
    What type of testing can be done using LDTP ?
    LDTP can be used to test the functionality of any accessibility enabled application.

    Advantage of accessibility based testing
    • Accessibility libraries provide applications property, state, its child items etc.
    • No need to work in toolkit (GTK, AWT, QT) level

    LDTP Features
    • LDTP concepts are derived from Software Automation Framework Support
    • LDTP supports verification of actions performed (guiexist, verifystate, etc) - API Reference
    • Writing test scripts are very easy, the script writer need not know about the object hierarchy
    • CPU / Memory performance monitoring of application-under-test can be measured - Class pstats
    LDTP

    ##################             AUTOMATION IN MAC                 #######################

    Now its time to learn the automation in MAC OS X. Before that make sure you the following app and setup to test your

    Prerequisite
    1. Install Xcode with UNIX development option
    2. sudo easy_install atomac [or] sudo pip atomac
    sudo easy_install atomac [or] sudo pip atomac

         3. Install Pycharm IDE for running python 


    Enabling Accessibility feature and adding the app in it. 

    To reach Accessibility, 
    choose
     System Preferences --> Security & Privacy --> Accessibility



    We should add Terminal.app or PyCharm CE.app to Accessibility to get UI controls in the Mac Product. 

    We can add app directly by dragging the app to Accessibility 
                              or
     by adding the entries directly to the TCC.db where Accessibility permissions for the app will be stored locally in machine.

    sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','/usr/sbin/jamfAgent',1,1,1,NULL)"

    For more learnings regarding accessibility,  Jacob Salmela has written a good python tool to interact with TCC.db
    +

    __author__ = 'vijay'
    
    import os
    import atomac
    from atomac.AXKeyCodeConstants import *
    import time
    import commands
    from atomac import AXKeyCodeConstants
    
    #  Open the Notes. Store the new content. Close it. Reopen and validate the stored content in the Note.app
    
    global log_dir
    global log_file
    global log_fp
    
    notes = 'com.apple.Notes'   # CFBundleIdentifier
    
    
    def logger(msg=''):
        '''
        TO Log the activites of the program
        '''
        try:
            global log_fp
            global log_dir
            global log_file
            if os.path.isfile(log_file):
                log_fp.write("\n")
                print msg
                log_fp.write(msg)
            else:
                print "[+] Looks like the Log Directory or Log File is not found"
                print "[+] Creating log file and directory for the first time"
                os.system("mkdir -p "+log_dir)
                os.system("touch "+log_file)
                log_fp = open(log_file, 'a')
                log_fp.write("\n")
                print msg
                log_fp.write(msg)
        except Exception as er:
            print "[-] Exception occurred while calling the logger "
            print "[-] Error is "+str(er)
    
    
    def open_app(cbundleID):   # cbundleID = 'com.apple.Notes'
        '''
        To Open the Notes App
        '''
        try:
            logger("[+] Opening the Notes.app")
            atomac.launchAppByBundleId(cbundleID)
            time.sleep(3)
            notes_id = atomac.getAppRefByBundleId(cbundleID)
            notes_id.activate()
        except Exception as er:
            logger("[-] Exception while opening the notes app")
            logger("[-] Error @open_app is "+str(er))
    
    
    def close_app(cbundleID):   # cbundleID = 'com.apple.Notes'
        '''
        To Close the Notes App 
        '''
        try:
            logger("[+] Closing the Notes.app")
            atomac.terminateAppByBundleId(cbundleID)
    
            logger("Force Killing  the process with signal")
            os.system("ps -eaf|grep -i 'Notes'|grep -v grep|awk '{print $2}'|xargs kill ")
            time.sleep(3)
        except Exception as er:
            logger("[-] Exception while closing the notes app")
            logger("[-] Error @close_app is "+str(er))
    
    
    def click_left(note):
        '''
        mouse left click action
        '''
        position = note.AXPosition
        size = note.AXSize
        clickpoint = ((position[0] + size[0] / 2), (position[1] + size[1] / 2))
        try:
            note.clickMouseButtonLeft(clickpoint)
        except Exception as er:
            logger("[-] Error @add_notes")
            logger('[-] Error is '+str(er))
    
    def click_right(note):
        '''
        mouse right click action
        '''
        position = note.AXPosition
        size = note.AXSize
        clickpoint = ((position[0] + size[0] / 2), (position[1] + size[1] / 2))
        try:
            note.clickMouseButtonRight(clickpoint)
        except Exception as er:
            logger("[-] Error @add_notes")
            logger('[-] Error is '+str(er))
    
    def add_notes():
        '''
        Click Add Notes button
        '''
        try:
            notes_id = atomac.getAppRefByBundleId(notes)
            note = notes_id.windows()[0].findAllR(AXRole='AXButton', AXRoleDescription='button', AXDescription='add')[0]
            click_left(note)
        except Exception as er:
            logger("[-] Exception while adding the notes")
            logger("[-] Error @add_notes is "+str(er))
    
    
    def edit_notes(msg_note=''):
        '''
        Enter the input given to the notes
        '''
        try:
            notes_id = atomac.getAppRefByBundleId(notes)
            notes_id.activate()
            note = notes_id.windows()[0].findAllR(AXRole='AXWebArea', AXRoleDescription='HTML content', AXDescription='')[0]
            click_left(note)
            note.sendKeys(msg_note)
            time.sleep(1)
            note.sendKeys('\n Vijay Anand Pandian  ')
            time.sleep(2)
            note.sendKeyWithModifiers(";", [SHIFT])
            note.sendKeyWithModifiers("0", [SHIFT])
            time.sleep(3)
        except Exception as er:
            logger("[-] Exception while editing the notes")
            logger("[-] Error @edit_notes is "+str(er))
    
    
    def validate_notes(msg_validate=''):
        '''
        simple validation of UI function 
        '''
        try:
            notes_id = atomac.getAppRefByBundleId(notes)
            notes_id.activate()
            check = True
            if len(msg_validate) > 1:
                for i in range(len(notes_id.windows()[0].staticTextsR())):
                    try:
                        text = str(notes_id.windows()[0].staticTextsR()[i].AXValue.replace(u"\xa0", u""))
                        if msg_validate == text:
                            logger("Validation Succesfull")
                            print "Match is Found: "+text
                            check = False
                            break
                    except:
                        pass
                if check:
                    logger("Validation Failed")
                    print "Match not Found"
            else:
                logger("Enter the correct value to validate")
        except Exception as er:
            logger("[-] Exception while validating the notes")
            logger("[-] Error @validate_notes is "+str(er))
    
    # 
    # def delete_notes(note_delete=''):
    #     try:
    #         notes_id = atomac.getAppRefByBundleId(notes)
    #         notes_id.activate()
    #         if len(note_delete) > 1:
    #             #click_right(notes_id.windows()[0].findAllR(AXRole='AXStaticText', AXRoleDescription='text', AXValue=note_delete+'\xa0')[0])
    #             time.sleep(1)
    #             click_left(notes_id.windows()[0].findAllR(AXRole='AXStaticText', AXRoleDescription='text', AXValue=note_delete+'\xa0')[0])
    #             time.sleep(1)
    #             notes_id.windows()[0].findAllR(AXRole='AXStaticText', AXRoleDescription='text', AXValue=note_delete+'\xa0')[0].sendKeyWithModifiers('q',[DELETE])
    #             time.sleep(1)
    #             # click_left(notes_id.windows()[0].findAllR(AXRole='AXMenuItem', AXRoleDescription='menu item', AXTitle='Delete')[0])
    #             # time.sleep(1)
    #             # click_left(notes_id.windows()[0].findAllR(AXRole='AXButton', AXRoleDescription='button', AXTitle='Delete Note')[0])
    #             # time.sleep(1)
    #         else:
    #             logger("Enter the correct value to validate")
    #     except Exception as er:
    #         logger("[-] Exception while deleting the notes")
    #         logger("[-] Error @deleting_notes is "+str(er))
    
    
    def main():
        global log_dir
        global log_file
        global log_fp
        status, username = commands.getstatusoutput("users")
        log_dir = '/Users/'+username+'/log/notes_test/'
        log_file = '/Users/'+username+'/log/notes_test/notes_test.log'
        try:
            log_fp = open(log_file, 'a')
        except:
            pass
    
    
    if __name__ == "__main__":
        main()
        open_app(notes)
        time.sleep(3)
        add_notes()
        time.sleep(1)
        msg_note="""FirstNote \n \n Hi This is My First Tutorial on ATOMac \n \n Glad You found this \n \n \n """
        edit_notes(msg_note)
        add_notes()
        time.sleep(1)
        msg_note="""SecondNote \n \n Hi This is My First Tutorial on ATOMac \n
                    \n Glad You found this second time \n \n \n """
        edit_notes(msg_note)
        validate_notes('FirstNote')
        time.sleep(2)
        validate_notes('SecondNote')
        time.sleep(2)
        # delete_notes('FirstNote')
        # time.sleep(5)
        # delete_notes('SecondNote')
        # time.sleep(5)
        close_app(notes)
        logger("[*] Successfully opened and closed the Notes.app")
    

    Comments

    1. Thanks for guide and mentioning tccutil.py, that is one awesome tool!

      ReplyDelete
    2. You are Welcome :). If you need a framework for automation. Here is my open source projecct for UI automation esp for Accessibility enabled software on Mac OS X. https://github.com/vijayanandrp/Automation---Mac-OS-X---Applications

      ReplyDelete
    3. Hi
      Can you tell me how to get all the buttons in the screen

      ReplyDelete

    Post a Comment

    Popular posts from this blog

    Python Speech recognition for Mac OS X

    Baby Step Giant Step Algorithm Python Code