selene

repository·master·Indexed 20 days ago

https://github.com/yashaka/selene

A Python library for writing concise, user-oriented browser UI tests, serving as a Pythonic port of the Java Selenide project. It provides a high-level wrapper around Selenium WebDriver with features including smart waiting, lazy-evaluated elements, a natural-language API, and automatic driver management. It supports web, Appium, and remote drivers like Selenium Grid.

Tokens
21.7K
Snippets
67
Records
84
Agent score
71%

What's inside selene

  1. Overview of Selene

    master

    Selene is a concise, user-oriented library for writing browser UI tests in Python. It is a Pythonic port of the Java Selenide project.

    Key capabilities include:

    • User-oriented API: Expressive, natural-language syntax for writing readable tests.
    • Smart Waiting: Built-in retry mechanisms and implicit waiting to handle Ajax-like dynamic loading.
    • PageObject & Widget Support: Uses lazy-evaluated elements to build reusable PageObjects and component-based Widgets.
    • Extended Matchers: A larger set of expected conditions (matchers) and predefined element commands compared to raw Selenium.
    • Automatic Driver Management: Handles browser driver setup and teardown automatically for local execution.
    • Flexible Configuration: Supports global, instance, element, and action-level customization (e.g., timeouts).
    • Multiplatform: Works with web, Appium (mobile/desktop), and remote drivers like Selenium Grid.
  2. What is Selene?

    master
    Selene is a high-level Python tool for automating user actions in a web browser. It acts as a wrapper around Selenium WebDriver, designed to allow developers to implement business logic in the language of the user. It abstracts away technical complexities such as element waits for dynamic web applications, high-level element actions, and complex locator management.
  3. Understand Selene's 'Lazy Element' pattern

    master

    A fundamental difference in Selene is that browser.element(selector) and browser.all(selector) do not immediately interact with the browser. They return objects that represent a description of an element (or collection of elements).

    The actual search in the DOM is postponed (lazy) until you perform one of the following:

    1. An Action: e.g., .click(), .type(), .press_enter().
    2. A Check: e.g., .should(condition).

    This allows you to define Page Objects containing element locators without worrying if the elements are present at the exact moment the Page Object is instantiated.

  4. Understand the Selene documentation structure

    master

    Selene's documentation follows a specific organizational pattern using the docs/ directory:

    • Root Files: docs/index.md is a snippet of the project's README.md. docs/license.md and docs/changelog.md are snippets of the root LICENSE.md and CHANGELOG.md respectively.
    • Sections: Each major topic has its own directory containing an index.md file:
      • learn-basics/: Tutorials for beginners.
      • learn-advanced/: Deeper technical guides.
      • faq/: Task-oriented *-howto.md files.
      • use-cases/: Usage *-example.md files.
      • contribution/: Guides for contributing to the project.
    • Assets: Images for specific sections are stored in an assets/ subfolder within that section's directory. The top-level docs/assets/ folder is reserved for theme-related assets (like logos and favicons).
  5. Compare Selenium WebDriver vs Selene API patterns

    master

    When refactoring tests to use variables for locators, Selenium WebDriver requires storing tuples of (By.TYPE, 'selector') and unpacking them with the * operator during every call. In contrast, Selene elements are 'lazy', meaning you can assign the result of browser.element() or browser.all() directly to a variable. These variables act as persistent, reusable element handles that do not attempt to find the element until an action or assertion is performed.

    # Selenium WebDriver pattern (requires unpacking)
    query = (By.NAME, 'q')
    driver.find_element(*query).send_keys('text')
    
    # Selene pattern (lazy elements)
    query = browser.element(by.name('q'))
    query.type('text')
  6. How clipboard operations work in Selene

    master

    Selene manages clipboard operations using the pyperclip package. Most clipboard interactions in Selene are performed via the command.paste(text) command, which uses OS-based shortcuts to simulate pasting.

    For operations that do not require OS-level simulation (like simply putting text into the clipboard without interacting with the browser UI), you should use pyperclip directly.

  7. How to use high-level locators for code reuse

    master

    In Selene, browser.element and browser.all are 'high-level locators'. They do not attempt to find the element in the browser immediately when called. Instead, they return a locator object that can be stored in a variable and reused. This allows you to define your selectors at the top of your test file or within Page Objects, improving readability and making maintenance easier when selectors change.

    Note that the actual search in the browser only happens when you perform an action (like .type()) or an assertion (like .should()).

    from selene import browser, by
    
    # Define locators early for reuse
    query_input = browser.element(by.name('q'))
    search_results = browser.all('#rso>div')
    
    def test_search():
        browser.open('https://google.com')
        # The actual search happens here
        query_input.type('python selene').press_enter()
        search_results.should(have.size_greater_than_or_equal(6))
  8. Compare Selene API with Selenium WebDriver

    master

    Selene provides a high-level, user-oriented API that abstracts away the low-level complexities of Selenium WebDriver. While Selenium focuses on being a universal browser driver, Selene is designed specifically for testing, offering 'waiting checks' and more descriptive error messages.

    Key conceptual differences:

    • Lazy Elements: In Selenium, find_element immediately searches for the element. In Selene, browser.element() and browser.all() are 'lazy'; they only describe the element and do not perform the actual search until an action (like .click()) or a check (like .should()) is called.
    • Waiting Checks vs. Explicit Waits: Selenium requires manual WebDriverWait with expected_conditions. Selene uses .should(condition), which automatically handles waiting and provides much more informative error messages when a condition fails.
    • Relative URLs: Selene's browser.open() supports relative URLs if browser.config.base_url is configured, whereas Selenium's driver.get() requires absolute URLs.
    ### API Comparison Summary
    
    | Feature | Selenium WebDriver | Selene |
    | :--- | :--- | :--- |
    | **Core Object** | `driver` | `browser` |
    | **Navigation** | `driver.get(url)` | `browser.open(url)` (supports relative) |
    | **Single Element** | `driver.find_element(By.CSS_SELECTOR, '...')` | `browser.element('...')` (string or `by` object) |
    | **Collection** | `driver.find_elements(By.CSS_SELECTOR, '...')` | `browser.all('...')` |
    | **Waiting/Asserting** | `WebDriverWait(driver, timeout).until(condition)` | `browser.should(condition)` |
    | **Element State** | `element.get_attribute('value') == ''` | `browser.element(...).should(be.blank)` |
  9. Interact with Chrome extension settings via Shadow DOM

    master

    Because Chrome's internal pages use Shadow DOM, standard CSS selectors often fail. You must use browser.execute_script to access elements inside the shadow roots.

    For example, to target a specific extension card in chrome://extensions/, you can use a JavaScript snippet that drills through the extensions-manager shadow root and the #items-list shadow root using the extension's unique ID.

    When an extension undergoes state changes (e.g., being enabled/disabled during setup), you may need to increase the timeout for stability using .with_(timeout=...).

    Example pattern for extension card selection:

    # ublock_id is the unique constant for the extension
    ublock_id = 'cjpalhdlnbpafiamejdnhcphjbkeiagm'
    js = f'''return document.querySelector('body > extensions-manager')
        .shadowRoot.querySelector('#items-list')
        .shadowRoot.querySelector('#{ublock_id}')
        .shadowRoot.querySelector('#card')'''
    
    card = Element(
        Locator('ublock extension card', lambda: browser.execute_script(js)),
        browser.config,
    )
  10. Use assertions and implicit waits with should()

    master

    Selene uses the should() method to perform assertions. Unlike standard Selenium, Selene has built-in 'implicit waits' for both checks and actions.

    • Checks: element.should(condition) waits until the condition is met (default timeout is 4 seconds).
    • Actions: element.type() or element.click() will automatically wait for the element to be available/interactable before performing the action.

    Common condition prefixes:

    • have.*: Used for checking properties (e.g., have.exact_text('...'), have.css_class('...'), have.exact_texts('a', 'b')).
    • be.*: Used for state checks (e.g., be.visible).
    • have.no.*: Used to negate a condition (e.g., have.no.css_class('completed')).
    # Wait for visibility then type
    browser.element('#new-todo').should(be.visible).type('a').press_enter()
    
    # Check exact texts in a collection
    browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c'))
    
    # Check for a specific CSS class
    browser.all('#todo-list>li').by(have.css_class('completed')).should(have.exact_texts('b'))
  11. How lazy and dynamic elements work in Selene

    master

    Selene elements are lazy and dynamic. This means they are not located at the moment the variable is assigned, but rather each time an action (like .click() or .type()) is performed. This ensures that Selene always interacts with the most up-to-date version of the element in the DOM, even if the page has changed since the element was defined.

    You can safely store elements in variables before the page has even loaded.

    from selene import browser, by, be
    
    # Because elements are "lazy", you can store them in a variable
    # even before the actual page is loaded:
    search_box = browser.element(by.name('q'))
    
    browser.open('https://google.com/ncr')
    search_box.should(be.blank).type('Selenium').press_enter()
  12. Validate Markdown syntax with markdownlint

    master

    To ensure documentation follows the project's style and syntax rules, use a Markdown linter. Several methods are available:

    Using VS Code

    Install the markdownlint extension. Warnings appear in the Problems tab (Ctrl+Shift+M) and start with the prefix MD###.

    To suppress specific warnings like MD041 (first line should be H1) or MD046 (non-standard indentation) in your VS Code settings, add the following to your settings.json:

    "markdownlint.config": {
        "MD041": false,
        "MD046": { "style": "fenced"},
        "MD007": { "indent": 4}
    }

    To temporarily disable a specific check for a block of text in a .md file, use comments:

    <!-- markdownlint-disable MD046 -->  
    !!! info
    
        Example text of Info block.
    <!-- markdownlint-enable MD046 -->

    Using CLI tools

    • npm: Use markdownlint-cli2 (requires Node.js).
    • Python: Use pymarkdown.
    • Other: markdownlint-cli.