Metadata-Version: 2.1
Name: caqui
Version: 5.0.0rc2
Summary: Run asynchronous commands in WebDrivers
Author-email: Douglas Cardoso <noemail@noemail.com>
License: MIT License
        
        Copyright (c) 2023 Douglas Cardoso
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/douglasdcm/caqui
Project-URL: Bug Tracker, https://github.com/douglasdcm/caqui/issues
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests
Requires-Dist: aiohttp
Requires-Dist: webdriver-manager
Requires-Dist: types-requests
Requires-Dist: beautifulsoup4
Requires-Dist: chrome-devtools-protocol >=0.5.1rc0

# Caqui

[![Python application](https://github.com/douglasdcm/caqui/actions/workflows/python-app.yml/badge.svg)](https://github.com/douglasdcm/caqui/actions/workflows/python-app.yml)
[![PyPI Downloads](https://static.pepy.tech/badge/caqui)](https://pepy.tech/projects/caqui)

**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

```bash
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:](https://caqui.readthedocs.io/en/latest/caqui.html)

Chrome Devtools Protocol example:
```python
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:

```python
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**:

```python
@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%](https://medium.com/@douglas.dcm/speed-up-your-web-crawlers-at-90-148f3ca97b6)

Example:

```python
@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:

```bash
$ ./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](https://pypi.org/project/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](https://github.com/douglasdcm/caqui/blob/main/docs/CODE_OF_CONDUCT.md)

Contribution Guide:
[CONTRIBUTING](https://github.com/douglasdcm/caqui/blob/main/docs/CONTRIBUTING.md)

Contributions, issue reports, and performance feedback are welcome.
