Skip to main content

Run asynchronous commands in WebDrivers

Project description

Caqui

Python application PyPI Downloads

Caqui is a Python library for browser, mobile, and desktop automation that works with any driver that exposes a WebDriver-style REST API or Chrome Devtools Protocol. It lets you send commands synchronously or asynchronously, and you don’t need to think about which underlying driver you’re using.

Caqui is designed for developers who want a unified automation API that can run:

  • Chrome Devtools Protocol ((Chrome, Opera, Edge))
  • WebDriver (Chrome, Firefox, Opera, Edge)
  • Appium (Android, iOS)
  • Winium / WinAppDriver (Windows desktop applications)
  • Any remote WebDriver-compatible server

Caqui runs seamlessly on a local machine or across remote hosts, and supports both multitasking with asyncio and multiprocessing for high-throughput use cases such as parallel testing, web scraping, or distributed automation.


Supported Web Drivers

WebDriver Version Remote* If remote
Appium 2.0.0+ Y Accepts remote calls by default. Tested with Appium in Docker
Firefox (geckodriver) 113+ Y Requires defining the host IP, e.g. --host 123.45.6.78
Google Chrome 113+ Y Requires allowed IPs, e.g. --allowed-ips=123.45.6.78
Opera 99+ Y Same restrictions as Chrome
WinAppDriver 1.2.1+ Y Requires host IP, e.g. WinApppage.exe 10.0.0.10 4723
Winium Desktop 1.6.0+ Y Accepts remote calls by default

*Remote = can accept REST requests when running as a server.


Installation

pip install caqui

Using Caqui 2.0.0+

From version 2.0.0+, Caqui includes a high-level API that mirrors Selenium’s object model and exposes async methods for browser, mobile, and desktop automation. Full documentation:

Chrome Devtools Protocol example:

import time
from pytest import raises, fixture
from caqui.cdp.by import By
from caqui.cdp.connection import SyncCDPConnection
from caqui.cdp.synchronous.drivers import SyncDriverCDP
from caqui.exceptions import WebDriverError
from tests.constants import OTHER_URL
from caqui.cdp.server import LocalServerCDP, get_ws_url

@fixture(autouse=True, scope="session")
def launch_browser():
    server = LocalServerCDP()
    server.start_chrome()
    yield server
    server.dispose()

@fixture
def setup_sync_cdp_playground():
    with SyncCDPConnection(get_ws_url()) as conn:
        driver = SyncDriverCDP(conn)
        driver.get(PAGE_URL)
        driver.set_window_size(1000, 1000)
        yield driver

class TestSyncCDPElement:
    def test_cdp_is_element_enabled(self, setup_sync_cdp_playground: SyncDriverCDP):
        driver = setup_sync_cdp_playground
        locator_type = By.XPATH
        locator_value = "//input"
        element = driver.find_element(locator_type, locator_value)
        assert element.is_enabled() is True

Web Driver example:

from os import getcwd
from pytest import mark, fixture
from caqui.webdriver.drivers import AsyncDriver
from caqui.webdriver.capabilities import ChromeCapabilitiesBuilder
from caqui.by import By
from caqui.webdriver.server import LocalServer

BASE_DIR = getcwd()
PAGE_URL = f"file:///{BASE_DIR}/html/playground.html"
SERVER_PORT = 9999
SERVER_URL = f"http://localhost:{SERVER_PORT}"


@fixture(autouse=True, scope="session")
def setup_server():
    server = LocalServer(port=SERVER_PORT)
    server.start_chrome()
    yield
    server.dispose(delay=3)


@fixture
def caqui_driver():
    server_url = SERVER_URL
    capabilities = (
        ChromeCapabilitiesBuilder().accept_insecure_certs(True).args(["headless"])
    )
    page = AsyncDriver(server_url, capabilities)
    yield page
    page.quit()


@mark.asyncio
async def test_switch_to_parent_frame_and_click_alert(caqui_driver: AsyncDriver):
    await caqui_driver.get(PAGE_URL)
    element_frame = await caqui_driver.find_element(By.ID, "my-iframe")
    assert await caqui_driver.switch_to.frame(element_frame)

    alert_button_frame = await caqui_driver.find_element(By.ID, "alert-button-iframe")
    await alert_button_frame.click()
    await caqui_driver.switch_to.alert.dismiss()

    await caqui_driver.switch_to.default_content()
    alert_button_parent = await caqui_driver.find_element(By.ID, "alert-button")
    assert await alert_button_parent.get_attribute("any") == "any"
    await alert_button_parent.click()

Running Tests with Multitasking

Caqui supports asyncio out of the box. To run multiple async tests concurrently, use pytest-async-cooperative:

@mark.asyncio_cooperative
async def test_save_screenshot(caqui_driver: AsyncDriver):
    await caqui_driver.get(PAGE_URL)
    assert await caqui_driver.save_screenshot("/tmp/test.png")


@mark.asyncio_cooperative
async def test_click(caqui_driver: AsyncDriver):
    await caqui_driver.get(PAGE_URL)
    element = await caqui_driver.find_element(By.XPATH, "//button")
    await element.click()

Running tests this way significantly reduces execution time, especially when interacting with multiple drivers or sessions.


Running Tests with Multiprocessing

If your workloads benefit from multiple processes, Caqui also works with pytest-xdist. This approach is often faster than cooperative multitasking.

A guide to optimizing performance (including a real benchmark): Speed up your web crawlers at 90%

Example:

@mark.asyncio
async def test_save_screenshot(caqui_driver: AsyncDriver):
    await caqui_driver.get(PAGE_URL)
    assert await caqui_driver.save_screenshot("/tmp/test.png")


@mark.asyncio
async def test_click(caqui_driver: AsyncDriver):
    await caqui_driver.get(PAGE_URL)
    element = await caqui_driver.find_element(By.XPATH, "//button")
    await element.click()

Running a Driver as a Server

If you use external drivers such as Appium, Winium, or a standalone ChromeDriver, run them as servers and point Caqui to their URL.

Example for ChromeDriver on port 9999:

$ ./chromedriver --port=9999
Starting ChromeDriver 94.0.4606.61 (418b78f5838ed0b1c69bb4e51ea0252171854915-refs/branch-heads/4606@{#1204}) on port 9999
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.

WebDriver Manager

Caqui’s LocalServer class uses Webdriver Manager. The tool comes with its own constraints. Check its documentation for details if you need custom driver handling.


Contributing

Before submitting a pull request, review the project guidelines: Code of Conduct: CODE OF CONDUCT

Contribution Guide: CONTRIBUTING

Contributions, issue reports, and performance feedback are welcome.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

caqui-5.0.0rc2.tar.gz (65.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

caqui-5.0.0rc2-py3-none-any.whl (81.2 kB view details)

Uploaded Python 3

File details

Details for the file caqui-5.0.0rc2.tar.gz.

File metadata

  • Download URL: caqui-5.0.0rc2.tar.gz
  • Upload date:
  • Size: 65.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for caqui-5.0.0rc2.tar.gz
Algorithm Hash digest
SHA256 ae19224fc3f538e581a2f8fba21c1433b1163e8e621ca661f09eef394e896efe
MD5 a8d7fecd4750db6a5e8a151ad4408912
BLAKE2b-256 0defaa092a494c621c6b346d364aae7a2641e7ba746fa87f289909b49b25d2f7

See more details on using hashes here.

File details

Details for the file caqui-5.0.0rc2-py3-none-any.whl.

File metadata

  • Download URL: caqui-5.0.0rc2-py3-none-any.whl
  • Upload date:
  • Size: 81.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for caqui-5.0.0rc2-py3-none-any.whl
Algorithm Hash digest
SHA256 9a4cab46bb396359b8e4aeb882bd5bff4c8ca9ca930d2a2c8a05b5ef976462af
MD5 dce3019c6843b81842966d95ea53f04c
BLAKE2b-256 fcb3143b8288ce9073a79ae0f95dd4c06661b7bbcd18b38adcdde9a8bfb525a5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page