Poco UI Automation Framework

repository·master·Indexed 23 days ago

https://github.com/airtestproject/poco

A cross-engine UI automation framework for Unity3D, Cocos2dx, Android, and iOS native apps. It provides engine-independent APIs for UI interaction and includes tools like the Standalone Hierarchy Viewer for UI inspection and the Test Result Player for replaying pocounit test cases. The framework supports various drivers including AndroidUiautomationPoco, OSXPoco, UnityPoco, and StdPoco, and features a SimpleRPC system for remote procedure calls and distributed services.

Tokens
26.1K
Snippets
72
Records
117
Agent score
84%

What's inside Poco

  1. Implement a Player abstraction for game behaviors

    master

    The Player class in lib/player.py is used to abstract and isolate game-related behaviors (character actions, etc.) from specific libraries like hunter, poco, or airtest. This isolation allows for easier framework upgrades.

    Key components:

    • PROCESS: A variable representing the Hunter project code (e.g., 'g62').
    • NeteasePoco: Used via the poco property to interact with the game UI.
    • Singleton: The Player class is implemented as a Singleton to ensure a single instance manages the connection.

    Example implementation structure:

    # coding=utf-8
    import sys
    import re
    from airtest_hunter import AirtestHunter, open_platform, wait_for_hunter_connected
    from poco.drivers.netease.internal import NeteasePoco as Poco
    
    PROCESS = 'g62'  # hunter project code
    
    class Player(object):
        __metaclass__ = Singleton
    
        def __init__(self, hunter=None):
            self._hunter = hunter or get_hunter_instance()
            self._poco_instance = None
    
        @property
        def poco(self):
            if not self._poco_instance:
                self._poco_instance = Poco(PROCESS, self._hunter)
            return self._poco_instance
    
        @property
        def hunter(self):
            return self._hunter
    
        def refresh(self):
            wait_for_hunter_connected(PROCESS, timeout=16)
            self._hunter = get_hunter_instance()
            self._poco_instance = Poco(PROCESS, self._hunter)
    
        def server_call(self, cmd):
            self.hunter.script(cmd, lang='text')
    class Player(object):
        __metaclass__ = Singleton
    
        def __init__(self, hunter=None):
            self._hunter = hunter or get_hunter_instance()
            self._poco_instance = None
    
        @property
        def poco(self):
            if not self._poco_instance:
                self._poco_instance = Poco(PROCESS, self._hunter)
            return self._poco_instance
    
        @property
        def hunter(self):
            return self._hunter
    
        def refresh(self):
            wait_for_hunter_connected(PROCESS, timeout=16)
            self._hunter = get_hunter_instance()
            self._poco_instance = Poco(PROCESS, self._hunter)
    
        def server_call(self, cmd):
            self.hunter.script(cmd, lang='text')
  2. Note that focus() is an immutable method

    master

    The .focus() method is immutable. Calling .focus() on a UI element returns a new object representing the focused position but does not modify the original UI element object. Subsequent calls to methods on the original object will still target its original center/position.

    # coding=utf-8
    from poco.drivers.unity3d import UnityPoco
    
    poco = UnityPoco()
    
    # focus is immutable
    fish = poco('fish').child(type='Image')
    fish_right_edge = fish.focus([1, 0.5])
    fish.long_click()  # still click the center
    time.sleep(0.2)
    fish_right_edge.long_click()  # will click the right edge
    time.sleep(0.2)
  3. Understand Poco core concepts

    master

    To use Poco effectively, understand these fundamental abstractions:

    • Target device: The physical or virtual device (usually a mobile phone) where the application or game is running.
    • UI proxy: Objects within the Poco framework that represent zero, one, or multiple in-game UI elements.
    • Node/UI element: The actual instances of UI elements within the app or game hierarchy.
    • query expression: An internal, serializable data structure used by Poco to interact with the target device and select specific UI elements. This is primarily used when customizing the Selector class.
  4. Understand Poco's coordinate system and local positioning

    master

    In Poco, coordinates are normalized from 0 to 1, representing the percentage of the UI element's size and position.

    To interact with specific parts of a UI element (like an edge or a corner) or to interact with areas near a UI element without selecting a new one, you can use local positioning via the .focus() method. This allows you to apply an offset relative to the selected element.

  5. Perform advanced UI selections using attribute, hierarchy, and positional relationships

    master

    When UI elements lack static names (common in programmatically generated lists), Poco allows you to select elements using multiple criteria. You can chain or combine the following selection methods:

    1. Attribute Selection: Select elements based on existing attributes.
    2. Hierarchy Relationship: Use methods like .child() or .offspring() to navigate the UI tree.
    3. Positional Relationship: Access elements by their index in a collection (e.g., items[0]).

    These methods can be chained together to create complex selectors for specific UI nodes.

    # coding=utf-8
    
    from poco.drivers.unity3d import UnityPoco
    
    poco = UnityPoco()
    
    # Chaining hierarchy methods: find 'main_node', then its 'list_item' children, 
    # then the 'name' offspring of those children
    items = poco('main_node').child('list_item').offspring('name')
    
    # Accessing by position
    first_one = items[0]
    print(first_one.get_text())
    first_one.click()
  6. Understand Poco coordinate systems

    master

    Poco uses two primary coordinate systems for locating UI elements:

    Normalized Coordinate System

    • Origin (0, 0): Top-left corner of the device display.
    • Scale: The height and width of the screen are both defined as 1 unit.
    • Benefit: Coordinates are resolution-independent. An element at (0.5, 0.5) is always at the center of the screen, regardless of device resolution, making it ideal for cross-device testing.

    Local Coordinate System

    • Origin (0, 0): Top-left corner of a specific UI element's bounding box.
    • Scale: The height and width of the reference UI element are both defined as 1 unit.
    • Usage: Expresses coordinates as signed distances relative to the element. For example, (0.5, 0.5) is the center of the element, while values $< 0$ or $> 1$ refer to positions outside the element's bounds.
  7. Integrate PocoSDK with Cocos-Creator

    master

    Supports Cocos Creator 2.2.1 or higher.

    1. Clone the poco-sdk repository.
    2. Copy the cocos-creator/Poco folder to your project's JavaScript folder.
    3. Enable WebSocketServer:
      • Locate your engine's ccConfig.h (e.g., .../CocosCreator_2.2.1/resources/cocos2d-x/cocos/base/ccConfig.h).
      • Change #define USE_WEBSOCKET_SERVER 0 to #define USE_WEBSOCKET_SERVER 1.
    4. Initialize: In your game's first initialized script (e.g., inside onLoad), require the Poco module and assign it to the window object to prevent it from being destroyed.

    Note: Currently only supports Android and Windows platforms. Poco is only available after packaging and is not available in preview mode.

    Example Usage:

    cc.Class({
        extends: cc.Component,
    
        onLoad: function () {
            var poco = require("./Poco") // use your own relative path
            window.poco = new poco(); // do not destroy
            cc.log(window.poco);
        },
    });
    cc.Class({
        extends: cc.Component,
    
        .....
    
        //remember to put code in onLoad function
        onLoad: function () {
           .....
    
                var poco = require("./Poco") // use your own relative path
                window.poco = new poco(); // not destroy
                cc.log(window.poco);
    
            },
    
           .....
        });
  8. Organize an automated testing project using the standard template

    master

    Automated testing should be treated as an engineering project rather than discrete scripts. Use the following directory structure to ensure long-term maintainability and IDE support (like PyCharm autocomplete):

    ─ my_testflow/
        ├─ testflow/            <------- Rename this to a valid identifier (e.g., g18)
        |   ├─ __init__.py
        |   ├─ lib/
        |   |   ├─ __init__.py
        |   |   ├─ case.py
        |   |   └─ player.py
        |   └─ scripts/
        |       ├─ __init__.py
        |       ├─ test1.py
        |       └─ folder/
        |           ├─ __init__.py
        |           └─ test2.py
        ├─ res/
        ├─ pocounit-results/
        ├─ setup.py
        ├─ requirements.txt
        └─ .gitignore

    Directory Roles:

    • testflow/lib: Contains common code modules and libraries.
    • testflow/scripts: Contains all test case scripts.
    • res: Stores resource files.
    • pocounit-results: Stores execution results.
    ─ my_testflow/
        ├─ testflow/            <------- 此文件夹可自定义名称
        |   ├─ __init__.py
        |   ├─ lib/
        |   |   ├─ __init__.py
        |   |   ├─ case.py
        |   |   └─ player.py
        |   └─ scripts/
        |       ├─ __init__.py
        |       ├─ test1.py
        |       └─ folder/
        |           ├─ __init__.py
        |           └─ test2.py
        ├─ res/
        ├─ pocounit-results/
        ├─ setup.py
        ├─ requirements.txt
        └─ .gitignore