Post

Listing Pilot: Mobile Automation Suite for C2C Marketplaces

A platform-agnostic, config-driven mobile automation suite built with Python and Appium, designed to manage over 1,000 active listings across Turkish C2C marketplaces using coordinate-based pairing and overlap detection algorithms.

Listing Pilot: Mobile Automation Suite for C2C Marketplaces

Overview

For individual merchants and collectibles micro-ventures, Turkey’s top C2C marketplaces (such as Dolap and Gardrops) are highly lucrative. However, their mobile-first designs require sellers to spend hours performing repetitive manual listing updates, pricing adjustments, and inventory synchronization on physical mobile devices.

To eliminate this overhead, I engineered Listing Pilot, a platform-agnostic, config-driven mobile automation suite. Built in Python using Appium, it automates the management of ~1,000 active listings, translating hours of manual weekly operations into automated, reliable background scripts.


Results at a Glance

MetricManual ProcessListing Pilot (Automated)Optimization
Workflow Duration4–5 hours / week~15 minutes (Background)95% Time Saved
Listing CapacityLimit of ~200 items1,000+ active listings5x Scale Increase
Sync FrequencyOnce daily (Manual)Multi-interval schedulesRetraining-Free sync
AlertingManual checkImmediate Telegram notificationProactive error response

Phase 1: Architecture — Platform-Agnostic Engine

The primary engineering challenge in mobile app automation is fragility. App updates change UI IDs, screen ratios vary across devices, and running the same script on two different models can cause it to fail.

To overcome this, Listing Pilot separates the automation script logic from specific app selectors and hardware specifications.

flowchart TD
    Config[JSON Configuration Profile] --> Loader[Selector & Capability Loader]
    Loader --> Engine[Appium Automation Core]
    Engine --> AppiumServer[Local Appium Server]
    AppiumServer --> Device[Android/iOS Emulator or Physical Phone]
    Engine --> ErrorLogger[Multi-ID Selector Fallback]
    Engine --> Telegram[Telegram Bot Client]

JSON Profile Injection

All locators (XPaths, IDs, coordinates) are defined in external profiles. If a marketplace changes its layout, we simply update the JSON schema without modifying the underlying Python code.

1
2
3
4
5
6
7
8
9
{
  "platform": "Android",
  "app_package": "com.c2c.marketplace",
  "locators": {
    "listing_card": "//android.widget.FrameLayout[@resource-id='com.c2c:id/card']",
    "edit_btn": "com.c2c:id/btn_edit",
    "submit_btn": "com.c2c:id/btn_save"
  }
}

Phase 2: Advanced Interaction Algorithms

Mobile layouts often lack clean semantic structure. Elements like item titles, prices, and edit buttons are frequently sibling nodes in the view tree, making it difficult to link them using standard relative selectors.

Listing Pilot uses spatial algorithms to navigate these complex layouts.

1. Spatial Title-to-Price Pairing

To edit an item, the script must match its title text to the corresponding edit button on the same card. Listing Pilot fetches the bounding boxes (coordinates) of all visible titles and edit buttons, then pairs them using a distance-matching algorithm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def pair_elements_by_coordinates(titles, edit_buttons):
    pairs = {}
    for title in titles:
        t_box = title.rect  # Contains x, y, width, height
        t_center_y = t_box['y'] + (t_box['height'] / 2)
        
        # Find the edit button closest in vertical alignment (closest Y center)
        closest_btn = min(
            edit_buttons,
            key=lambda btn: abs((btn.rect['y'] + btn.rect['height']/2) - t_center_y)
        )
        
        # Verify the button is on the same row (horizontal boundary)
        if abs((closest_btn.rect['y'] + closest_btn.rect['height']/2) - t_center_y) < 100:
            pairs[title.text] = closest_btn
    return pairs

2. Sticky-Element Overlap Detection

Marketing banners, support widgets, or cookies warnings often load asynchronously and cover key buttons. If Appium attempts to click an element obscured by an overlay, it will fail or click the wrong button.

Listing Pilot inspects element bounding boxes prior to execution:

\[\text{Overlap} = (X_{1,\text{min}} < X_{2,\text{max}}) \land (X_{1,\text{max}} > X_{2,\text{min}}) \land (Y_{1,\text{min}} < Y_{2,\text{max}}) \land (Y_{1,\text{max}} > Y_{2,\text{min}})\]

If an overlap is detected, the script automatically triggers a dismissal function for the sticky overlay before proceeding.


Phase 3: Fault-Tolerant Operations

Mobile environments are unstable. Network hiccups, sudden popups, or application crashes can disrupt automation runs. Listing Pilot implements two main recovery strategies:

  1. Multi-ID Fallback Selector Strategy: When searching for a UI element, the framework checks a prioritized list of selectors (e.g., resource-id first, then content-desc, and finally a calculated coordinate-relative click if the node isn’t found).
  2. Dynamic Sleep Auto-tuning: The runner tracks average response latency. If API requests take longer to load due to network throttling, it automatically scales up the wait time between navigation steps (from a default 1.5s up to 5.0s) to prevent timeouts.

Phase 4: Real-time Telemetry & Telegram Alerts

Since Listing Pilot runs as a background process on a local server, keeping track of its status is crucial. The suite integrates directly with the Telegram Bot API to send updates to the operator.

The system reports:

  • Session Startup: Details on the device ID and the target marketplace.
  • Progress Milestones: Sends a message every 100 listings processed.
  • Screenshots on Failure: If a step fails, Listing Pilot captures a screenshot, highlights the area where the button was expected using OpenCV, and sends it directly to Telegram for quick troubleshooting.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def send_telegram_alert(message, image_path=None):
    bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
    chat_id = os.getenv("TELEGRAM_CHAT_ID")
    
    if image_path:
        with open(image_path, 'rb') as photo:
            requests.post(
                f"https://api.telegram.org/bot{bot_token}/sendPhoto",
                data={"chat_id": chat_id, "caption": message},
                files={"photo": photo}
            )
    else:
        requests.post(
            f"https://api.telegram.org/bot{bot_token}/sendMessage",
            json={"chat_id": chat_id, "text": message}
        )

Listing Pilot has transformed C2C merchant operations from a manual chore into a highly scalable, automated business workflow.

This post is licensed under CC BY 4.0 by the author.