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.0rc5.tar.gz (304.4 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.0rc5-py3-none-any.whl (361.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: caqui-5.0.0rc5.tar.gz
  • Upload date:
  • Size: 304.4 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.0rc5.tar.gz
Algorithm Hash digest
SHA256 582d2b8ad6dcda0dbea7db486054be64f5ad7c58e8707e1c77c22dd693181940
MD5 61c8bdd1817ece96a561eb855507b091
BLAKE2b-256 8c698ee47e0b5e5f94088d026e8b444d7c88a9f59d35e9c90e59dfa03d5a93d9

See more details on using hashes here.

File details

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

File metadata

  • Download URL: caqui-5.0.0rc5-py3-none-any.whl
  • Upload date:
  • Size: 361.1 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.0rc5-py3-none-any.whl
Algorithm Hash digest
SHA256 00a1eb79082eaf4b17880688858c7e92633430abc630ac94bddb197c98ff00cc
MD5 a3dc24467618a9063d0880896a03c487
BLAKE2b-256 a745591fbce33fe50a8b75c1b93dac2a91678ff1f464575a3a362188cf3a9d59

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