Skip to content

feat: implement run_in_parallel#408

Open
tiagogcampos wants to merge 8 commits into
autoscrape-labs:mainfrom
tiagogcampos:tgc/run_in_parallel
Open

feat: implement run_in_parallel#408
tiagogcampos wants to merge 8 commits into
autoscrape-labs:mainfrom
tiagogcampos:tgc/run_in_parallel

Conversation

@tiagogcampos

@tiagogcampos tiagogcampos commented May 2, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Description

Related Issue(s)

Closes #224

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes, no API changes)
  • Performance improvement
  • Tests (adding missing tests or correcting existing tests)
  • Build or CI/CD related changes

How Has This Been Tested?

Executed full test suite of tests

Testing Checklist

  • Unit tests added/updated
  • Integration tests added/updated
  • All existing tests pass

Screenshots

Implementation Details

API Changes

Additional Info

Checklist before requesting a review

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have run poetry run task lint and fixed any issues
  • I have run poetry run task test and all tests pass
  • My commits follow the conventional commits style

@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces a new Browser.run_in_parallel() method for concurrent coroutine execution with optional concurrency limiting via max_parallel_tasks configuration, includes supporting interface and options changes, comprehensive test coverage, and updates documentation across multiple languages to use the new method in place of asyncio.gather().

Changes

Run-In-Parallel Feature

Layer / File(s) Summary
Interface & Configuration
pydoll/browser/interfaces.py, pydoll/browser/options.py
Options interface gains abstract max_parallel_tasks property; ChromiumOptions adds _max_parallel_tasks field with validated getter/setter supporting int | None values (must be positive or None).
Core Implementation
pydoll/browser/chromium/base.py
Browser.run_in_parallel() method added with @overload type signatures. Executes coroutines concurrently; when max_parallel_tasks is set and less than coroutine count, uses asyncio.Semaphore to enforce concurrency limit. On asyncio.CancelledError, closes any coroutines not yet started before re-raising.
Type Fixes
pydoll/elements/web_element.py
Adds cast import and wraps CDP command objects with cast(Command, ...) at 16 call sites for typing correctness (box model, HTML retrieval, shadow root, screenshot, scrolling, mouse/keyboard dispatch, script execution, etc.).
Unit Tests
tests/test_browser/test_browser_base.py, tests/test_browser/test_browser_options.py
Four new tests for run_in_parallel: result ordering, exception propagation, max_parallel_tasks enforcement, and cancellation cleanup. Four new tests for max_parallel_tasks option: default, setting, reset, and validation. Interface stub updated to include new property.
Integration Tests
tests/test_core_integration.py
New TestBrowserRunInParallelIntegration class with test_run_in_parallel_across_tabs: opens two tabs, navigates to different pages, extracts title and heading text concurrently, and verifies result tuples.
Documentation
README.md, README_zh.md, docs/en/features/.../*, docs/pt/features/.../*, docs/zh/features/.../*
Replaces asyncio.gather(...) with browser.run_in_parallel(...) in all existing concurrency examples across English, Portuguese, and Chinese documentation. Adds new sections in browser-management/tabs.md and configuration/browser-options.md documenting run_in_parallel() behavior (order preservation, exception propagation, concurrency throttling) with code examples showing max_parallel_tasks configuration.

Sequence Diagram

sequenceDiagram
    participant Client as Client Code
    participant BrowserAPI as Browser.run_in_parallel()
    participant Semaphore as Semaphore (if limit set)
    participant Tasks as Coroutines
    participant Results as Result Collector

    Client->>BrowserAPI: run_in_parallel(*coroutines)
    alt max_parallel_tasks is set and < count
        BrowserAPI->>Semaphore: Create semaphore(limit)
        loop For each coroutine
            BrowserAPI->>Semaphore: acquire()
            Semaphore-->>BrowserAPI: permit
            BrowserAPI->>Tasks: Start coroutine
            Tasks-->>BrowserAPI: Running
        end
        loop As tasks complete
            Tasks-->>BrowserAPI: Result or Exception
            BrowserAPI->>Semaphore: release()
            BrowserAPI->>Results: Store in order
        end
    else No limit or limit >= count
        BrowserAPI->>Tasks: Start all coroutines concurrently
        Tasks-->>BrowserAPI: Results (may complete out of order)
        BrowserAPI->>Results: Collect in submission order
    end
    Results-->>Client: list[T] (order preserved)
    alt If any task raised
        Results-->>Client: Exception propagated
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Suggested reviewers

  • thalissonvs

Poem

🐰 A rabbit hops through async land so bright,
With run_in_parallel now taking flight!
No more gather calls, we've got a better way,
Concurrent tasks bloom, order's here to stay.
Semaphores dance and limits hold their sway—
Hop hop 🎉 The browser bounds are here to play!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description lacks critical detail about the new feature being added. Key sections like Description, Implementation Details, and API Changes are entirely empty. Fill in the Description section with a clear explanation of what run_in_parallel does and why it's needed. Complete Implementation Details and API Changes sections to clarify the new method signature and behavior.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title 'feat: implement run_in_parallel' directly and clearly describes the main feature being introduced across the entire changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/en/features/browser-management/tabs.md`:
- Around line 699-703: The example calls to scrape_page use a single URL
argument but the function signature requires (tab, url); update the example to
pass the tab reference along with each URL (e.g., scrape_page(tab,
'https://example.com/a')) or refactor the snippet to iterate over a list of
(tab, url) pairs or map URLs onto an existing tab variable before calling
scrape_page; ensure you update every call in the block (scrape_page) so the
first parameter is the tab object expected by the scrape_page(tab, url)
implementation.

In `@docs/pt/features/browser-management/tabs.md`:
- Around line 699-703: O exemplo chama scrape_page(...) com apenas o URL, mas a
função definida espera a assinatura scrape_page(tab, url); corrija chamadores
nas linhas do exemplo para fornecer ambos os argumentos (o objeto tab usado no
bloco de criação/limite de paralelismo e o URL) ou, alternativamente, altere a
definição de scrape_page para aceitar somente (url) e atualizar suas referências
internas; referencie a função scrape_page e ajuste as invocações no bloco que
monta as chamadas paralelas para corresponder à assinatura usada.

In `@docs/zh/features/browser-management/tabs.md`:
- Around line 690-704: The example calls scrape_page(...) but doesn't define it;
add a minimal async scrape_page function (e.g., async def scrape_page(url): ...
) above the snippet and use it in the run_in_parallel call, or clearly mark
scrape_page as a placeholder; update the docs around ChromiumOptions, Chrome,
and browser.run_in_parallel to show the minimal async implementation that
returns a value (or an explicit TODO comment) so the example is runnable without
errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4fdf476-af45-47d1-bee1-b0cb5d977f18

📥 Commits

Reviewing files that changed from the base of the PR and between fd27c92 and 06fdad6.

📒 Files selected for processing (21)
  • README.md
  • README_zh.md
  • docs/en/features/browser-management/contexts.md
  • docs/en/features/browser-management/tabs.md
  • docs/en/features/configuration/browser-options.md
  • docs/en/features/core-concepts.md
  • docs/pt/features/browser-management/contexts.md
  • docs/pt/features/browser-management/tabs.md
  • docs/pt/features/configuration/browser-options.md
  • docs/pt/features/core-concepts.md
  • docs/zh/features/browser-management/contexts.md
  • docs/zh/features/browser-management/tabs.md
  • docs/zh/features/configuration/browser-options.md
  • docs/zh/features/core-concepts.md
  • pydoll/browser/chromium/base.py
  • pydoll/browser/interfaces.py
  • pydoll/browser/options.py
  • pydoll/elements/web_element.py
  • tests/test_browser/test_browser_base.py
  • tests/test_browser/test_browser_options.py
  • tests/test_core_integration.py

Comment on lines +699 to +703
scrape_page('https://example.com/a'),
scrape_page('https://example.com/b'),
scrape_page('https://example.com/c'),
scrape_page('https://example.com/d'),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix scrape_page call signature in throttling example.

Line 699Line 703 call scrape_page() with one argument, but earlier (Line 238) it requires (tab, url). This example won’t run as written.

Suggested doc fix
 async with Chrome(options=options) as browser:
-    await browser.start()
+    initial_tab = await browser.start()
+    tabs = [initial_tab] + [await browser.new_tab() for _ in range(3)]
     results = await browser.run_in_parallel(
-        scrape_page('https://example.com/a'),
-        scrape_page('https://example.com/b'),
-        scrape_page('https://example.com/c'),
-        scrape_page('https://example.com/d'),
+        *[
+            scrape_page(tab, url)
+            for tab, url in zip(
+                tabs,
+                [
+                    'https://example.com/a',
+                    'https://example.com/b',
+                    'https://example.com/c',
+                    'https://example.com/d',
+                ],
+            )
+        ]
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/en/features/browser-management/tabs.md` around lines 699 - 703, The
example calls to scrape_page use a single URL argument but the function
signature requires (tab, url); update the example to pass the tab reference
along with each URL (e.g., scrape_page(tab, 'https://example.com/a')) or
refactor the snippet to iterate over a list of (tab, url) pairs or map URLs onto
an existing tab variable before calling scrape_page; ensure you update every
call in the block (scrape_page) so the first parameter is the tab object
expected by the scrape_page(tab, url) implementation.

Comment on lines +699 to +703
scrape_page('https://example.com/a'),
scrape_page('https://example.com/b'),
scrape_page('https://example.com/c'),
scrape_page('https://example.com/d'),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Corrija a assinatura de scrape_page no exemplo com limite de paralelismo.

Nas Line 699Line 703, scrape_page() é chamado com 1 argumento, mas a função definida antes recebe (tab, url). O snippet falha ao executar.

Sugestão de ajuste
 async with Chrome(options=options) as browser:
-    await browser.start()
+    initial_tab = await browser.start()
+    tabs = [initial_tab] + [await browser.new_tab() for _ in range(3)]
     results = await browser.run_in_parallel(
-        scrape_page('https://example.com/a'),
-        scrape_page('https://example.com/b'),
-        scrape_page('https://example.com/c'),
-        scrape_page('https://example.com/d'),
+        *[
+            scrape_page(tab, url)
+            for tab, url in zip(
+                tabs,
+                [
+                    'https://example.com/a',
+                    'https://example.com/b',
+                    'https://example.com/c',
+                    'https://example.com/d',
+                ],
+            )
+        ]
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/pt/features/browser-management/tabs.md` around lines 699 - 703, O
exemplo chama scrape_page(...) com apenas o URL, mas a função definida espera a
assinatura scrape_page(tab, url); corrija chamadores nas linhas do exemplo para
fornecer ambos os argumentos (o objeto tab usado no bloco de criação/limite de
paralelismo e o URL) ou, alternativamente, altere a definição de scrape_page
para aceitar somente (url) e atualizar suas referências internas; referencie a
função scrape_page e ajuste as invocações no bloco que monta as chamadas
paralelas para corresponder à assinatura usada.

Comment on lines +690 to +704
```python
from pydoll.browser.options import ChromiumOptions

options = ChromiumOptions()
options.max_parallel_tasks = 3 # 每次最多运行 3 个协程

async with Chrome(options=options) as browser:
await browser.start()
results = await browser.run_in_parallel(
scrape_page('https://example.com/a'),
scrape_page('https://example.com/b'),
scrape_page('https://example.com/c'),
scrape_page('https://example.com/d'),
)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

示例缺少可运行的 scrape_page 定义。

Line 699-703 调用了 scrape_page(url),但该代码块内没有对应定义,读者直接复制会报错。建议补一个最小实现(或显式注明占位函数)。

Suggested doc patch
 from pydoll.browser.options import ChromiumOptions
 
+async def scrape_page(url: str):
+    # 示例占位:按需替换为你的抓取逻辑
+    await asyncio.sleep(1)
+    return url
+
 options = ChromiumOptions()
 options.max_parallel_tasks = 3  # 每次最多运行 3 个协程
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/zh/features/browser-management/tabs.md` around lines 690 - 704, The
example calls scrape_page(...) but doesn't define it; add a minimal async
scrape_page function (e.g., async def scrape_page(url): ... ) above the snippet
and use it in the run_in_parallel call, or clearly mark scrape_page as a
placeholder; update the docs around ChromiumOptions, Chrome, and
browser.run_in_parallel to show the minimal async implementation that returns a
value (or an explicit TODO comment) so the example is runnable without errors.

@codecov

codecov Bot commented May 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.56098% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pydoll/browser/interfaces.py 75.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@tiagogcampos tiagogcampos changed the title Tgc/run in parallel feat: implement run_in_parallel May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add run_in_parallel method to Browser for coroutine execution

1 participant