Skip to content

Some playwright methods#303

Merged
digitronik merged 1 commit intoRedHatQE:mainfrom
digitronik:some_playwright_methods
May 3, 2026
Merged

Some playwright methods#303
digitronik merged 1 commit intoRedHatQE:mainfrom
digitronik:some_playwright_methods

Conversation

@digitronik
Copy link
Copy Markdown
Member

@digitronik digitronik commented May 3, 2026

Summary by Sourcery

Introduce higher-level Playwright browser utilities for screenshots, page content, cookies, keyboard input, and network interception.

New Features:

  • Add a unified screenshot API that returns image bytes and optionally saves PNG/JPEG files with configurable options.
  • Expose a page_content property to retrieve the full HTML source of the current page.
  • Add cookie management helpers to clear all cookies, add a cookie, and retrieve cookies from the current context.
  • Add a press_key helper to send keyboard events either to a specific element or the currently focused element.
  • Add expect_request and expect_response context manager helpers to capture matching network requests and responses during interactions.

Enhancements:

  • Deprecate the old save_screenshot method in favor of the new screenshot API and route it through the new helper.

Tests:

  • Add tests covering the new screenshot API, page_content property, cookie lifecycle helpers, press_key behavior, and network interception helpers.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 3, 2026

Reviewer's Guide

Adds higher-level Playwright helpers for screenshots, page content, cookies, keyboard input, and network interception, plus corresponding tests and deprecates the old save_screenshot API in favor of a unified screenshot method.

Sequence diagram for Browser.screenshot wrapper

sequenceDiagram
    actor TestCode
    participant Browser
    participant Page

    TestCode->>Browser: screenshot(path, full_page, type, quality, animations, scale, kwargs)
    Browser->>Browser: logger.debug(...)
    Browser->>Browser: build opts dict
    alt path is not None
        Browser->>Browser: opts["path"] = path
    end
    alt quality is not None
        Browser->>Browser: opts["quality"] = quality
    end
    Browser->>Page: screenshot(**opts)
    Page-->>Browser: image_bytes
    Browser-->>TestCode: image_bytes
Loading

Sequence diagram for Browser.expect_request context usage

sequenceDiagram
    actor TestCode
    participant Browser
    participant Page
    participant ContextManager as ExpectRequestContext

    TestCode->>Browser: expect_request(url_or_predicate, timeout)
    Browser->>Browser: logger.debug(...)
    Browser->>Page: expect_request(url_or_predicate, timeout)
    Page-->>Browser: ExpectRequestContext
    Browser-->>TestCode: ExpectRequestContext

    rect rgb(230,230,230)
        TestCode->>ContextManager: __enter__()
        Note over TestCode,ContextManager: Test action inside with block
        TestCode->>Page: trigger UI action that makes network request
        Page-->>ContextManager: matching Request captured
        TestCode->>ContextManager: __exit__()
    end

    ContextManager-->>TestCode: value (Request)
Loading

Class diagram for new Playwright helper methods on Browser

classDiagram
    class Browser {
        +logger
        +page
        +url
        +screenshot(path, full_page, type, quality, animations, scale, kwargs) bytes
        +save_screenshot(filename) void
        +page_content str
        +clear_cookies() void
        +add_cookie(cookie) void
        +get_cookies(urls) List_Dict
        +press_key(key, locator, args, kwargs) void
        +expect_request(url_or_predicate, timeout) ExpectRequestContext
        +expect_response(url_or_predicate, timeout) ExpectResponseContext
    }

    class Page {
        +context Context
        +keyboard Keyboard
        +screenshot(opts) bytes
        +content() str
        +expect_request(url_or_predicate, timeout) ExpectRequestContext
        +expect_response(url_or_predicate, timeout) ExpectResponseContext
    }

    class Context {
        +browser BrowserEngine
        +clear_cookies() void
        +add_cookies(cookies) void
        +cookies(urls) List_Dict
    }

    class Keyboard {
        +press(key) void
    }

    class ExpectRequestContext {
        +value Request
    }

    class ExpectResponseContext {
        +value Response
    }

    class Request {
        +url str
    }

    class Response {
        +url str
        +status int
        +ok bool
    }

    class BrowserEngine {
        +version str
    }

    Browser *-- Page
    Page *-- Context
    Page *-- Keyboard
    Context o-- BrowserEngine
    ExpectRequestContext o-- Request
    ExpectResponseContext o-- Response
Loading

File-Level Changes

Change Details Files
Introduce a unified screenshot API and deprecate the old save_screenshot method.
  • Add Browser.screenshot method wrapping Playwright page.screenshot with options for path, full_page, type, quality, animations, and scale plus passthrough kwargs.
  • Log screenshot calls and construct the options dict conditionally based on provided arguments.
  • Mark save_screenshot as deprecated, emit a DeprecationWarning, and internally delegate to screenshot(path=...).
  • Add tests to assert screenshot returns PNG bytes and writes to disk when a path is provided, and keep coverage for save_screenshot.
src/widgetastic/browser.py
testing/test_browser.py
Expose page content and cookie lifecycle helpers on the Browser object.
  • Add page_content property that returns page.content() with documentation for debugging use cases.
  • Add clear_cookies helper that logs and clears all cookies in the current context.
  • Add add_cookie to add a single cookie, automatically defaulting url to the current page URL when neither url nor domain is provided, and forwarding to context.add_cookies.
  • Add get_cookies helper that optionally filters by URLs and delegates to context.cookies.
  • Add tests covering page_content, cookie add/get behavior, and clearing cookies back to an empty list.
src/widgetastic/browser.py
testing/test_browser.py
Add a press_key helper to unify element-scoped and focused-element key presses.
  • Implement press_key that, when given a locator, resolves the element and calls el.press(key) with logging, otherwise calls page.keyboard.press(key) on the focused element.
  • Ensure press_key forwards *args and **kwargs to element() to support existing locator resolution patterns.
  • Add tests that verify behavior when pressing keys on a specific input element and when relying on the focused element path.
src/widgetastic/browser.py
testing/test_browser.py
Add network request/response interception convenience context managers.
  • Introduce expect_request wrapper that logs and delegates to page.expect_request with a configurable timeout and a URL/predicate matcher.
  • Introduce expect_response wrapper that logs and delegates to page.expect_response with a configurable timeout and a URL/predicate matcher.
  • Add tests using browser.refresh to ensure expect_request captures navigation requests and expect_response captures successful navigation responses with status 200.
src/widgetastic/browser.py
testing/test_browser.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • Consider renaming the type parameter in screenshot to something like image_type or format to avoid shadowing the built-in type and make its purpose clearer.
  • The clear_cookies method logs at INFO level on every call, which could be noisy in test-heavy or high-traffic usage; consider downgrading this to DEBUG unless cookie clearing is an exceptional operation in your context.
  • In test_screenshot_saves_to_file, using tempfile._get_default_tempdir() relies on a private API; prefer tempfile.gettempdir() or tempfile.NamedTemporaryFile to avoid depending on an internal function.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider renaming the `type` parameter in `screenshot` to something like `image_type` or `format` to avoid shadowing the built-in `type` and make its purpose clearer.
- The `clear_cookies` method logs at INFO level on every call, which could be noisy in test-heavy or high-traffic usage; consider downgrading this to DEBUG unless cookie clearing is an exceptional operation in your context.
- In `test_screenshot_saves_to_file`, using `tempfile._get_default_tempdir()` relies on a private API; prefer `tempfile.gettempdir()` or `tempfile.NamedTemporaryFile` to avoid depending on an internal function.

## Individual Comments

### Comment 1
<location path="src/widgetastic/browser.py" line_range="434-443" />
<code_context>
+    def add_cookie(self, cookie: Dict[str, Any]) -> None:
</code_context>
<issue_to_address>
**issue:** Align the documented requirement for `url`/`domain` with the actual behavior that auto-fills `url`.

The docstring states that either `url` or `domain` is required and that the current page URL is used only if neither is provided, but the implementation always auto-fills `url` from `self.url` when both are missing. Consider either enforcing the documented requirement by raising in that case, or updating the docstring to clarify that `url`/`domain` are optional and default to the current page. The current mismatch makes the method’s contract misleading.
</issue_to_address>

### Comment 2
<location path="src/widgetastic/browser.py" line_range="1321-1330" />
<code_context>
+    def press_key(self, key: str, locator: LocatorAlias = None, *args, **kwargs) -> None:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Clarify or constrain how `*args`/`**kwargs` are meant to be used with `press_key`, since they’re silently ignored when no locator is provided.

Because `locator` defaults to `None`, any `*args`/`**kwargs` are only forwarded to `element()` when a locator is provided. Calls like `press_key("Enter", some_timeout)` would silently ignore `some_timeout`, which is easy to miss and can introduce subtle bugs. Please either adjust the API (e.g. make `locator` keyword-only or accept only `**element_kwargs`) or raise an error when extra arguments are passed without a locator so misuse is surfaced early.

Suggested implementation:

```python
    def press_key(
        self,
        key: str,
        locator: LocatorAlias = None,
        **element_kwargs,
    ) -> None:
        """Press a keyboard key, optionally targeting a specific element.

        When a locator is provided the key press is scoped to that element
        (``Locator.press``). Without a locator the key is sent to whatever
        element currently has focus (``page.keyboard.press``).

        Any additional keyword arguments in ``element_kwargs`` are forwarded
        to :meth:`element` when a ``locator`` is provided (for example, to
        control waiting/timeout behavior). Passing extra keyword arguments
        without a locator has no effect and is considered a misuse; callers
        should always provide ``locator=...`` when supplying ``element_kwargs``.

        Args:
            key: Key name — e.g. ``"Enter"``, ``"Escape"``, ``"Space"``,
                ``"Backspace"``, ``"ArrowDown"``, ``"Control+A"``.
            locator: Optional element locator to press the key on.
            element_kwargs: Additional keyword arguments forwarded to
                :meth:`element` when ``locator`` is provided.

```

To fully enforce the behavior described in the docstring and surface misuse early, you should also:

1. In the body of `press_key`, add a guard at the top of the function:

   ```python
   if locator is None and element_kwargs:
       raise TypeError(
           "press_key() received keyword arguments without a locator; "
           "pass 'locator=...' when providing element lookup arguments."
       )
   ```

2. Update any internal calls to `press_key` that were passing positional arguments after `key` (other than the locator) so they now pass keyword-only arguments via `element_kwargs` (e.g. `press_key("Enter", locator=foo, timeout=5)`).
3. Ensure that wherever `element()` is called inside `press_key`, it is updated to accept `**element_kwargs` instead of the previous `*args, **kwargs` signature (if applicable).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/widgetastic/browser.py
Comment thread src/widgetastic/browser.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 3, 2026

Codecov Report

❌ Patch coverage is 83.33333% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.82%. Comparing base (027c39b) to head (61d5b3d).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/widgetastic/browser.py 83.33% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #303      +/-   ##
==========================================
- Coverage   92.99%   92.82%   -0.18%     
==========================================
  Files          19       19              
  Lines        2698     2744      +46     
==========================================
+ Hits         2509     2547      +38     
- Misses        189      197       +8     
Flag Coverage Δ
unittests 92.82% <83.33%> (-0.18%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@digitronik digitronik force-pushed the some_playwright_methods branch 6 times, most recently from 1076887 to 990fe9a Compare May 3, 2026 11:53
Signed-off-by: Nikhil Dhandre <ndhandre@redhat.com>
@digitronik digitronik force-pushed the some_playwright_methods branch from 990fe9a to 61d5b3d Compare May 3, 2026 12:18
@digitronik digitronik merged commit 9959ddf into RedHatQE:main May 3, 2026
12 of 14 checks passed
Comment thread src/widgetastic/browser.py
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.

2 participants