# Welcome to Pylenium

Web Test Automation for Python

<figure><img src="/files/H86LU3Z52h6RBu0D39br" alt=""><figcaption></figcaption></figure>

### The mission is simple

> Bring the best of Selenium, Cypress and Python into one package.

This means:

* Automatic waiting and synchronization
* Quick setup to start writing tests
* Easy to use and clean syntax for amazing readability and maintainability
* Automatic driver installation so you don't need to manage drivers
* Leverage the awesome Python language
* and more!

#### Test Example

Let's use this simple scenario to show the difference between using `Selenium` and `Pylenium`:

1. **Visit** the QA at the Point website: [https://qap.dev](https://qap.dev/)
2. **Hover** the About link to reveal a menu
3. **Click** the Leadership link in that menu
4. **Assert** Carlos Kidman is on the Leadership page

{% code title="Using Pylenium" %}

```python
def test_carlos_is_on_leadership(py):
    py.visit("https://qap.dev")
    py.get("a[href='/about']").hover()
    py.get("a[href='/leadership'][class^='Header-nav']").click()
    assert py.contains("Carlos Kidman")
```

{% endcode %}

{% code title="The same test using Selenium" %}

```python
# Define your setup and teardown fixture
@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()


def test_carlos_is_on_leadership(driver):
    wait = WebDriverWait(driver, timeout=10)
    driver.get("https://qap.dev")

    # Hover About link
    about_link = driver.find_element(By.CSS_SELECTOR, "a[href='/about']")
    actions = ActionChains(driver)
    actions.move_to_element(about_link).perform()

    # Click Leadership link in About menu
    wait.until(EC.element_visible(By.CSS_SELECTOR, "a[href='/leadership'][class^='Header-nav']")).click()

    # Check if 'Carlos Kidman' is on the page
    assert wait.until(lambda _: driver.find_element(By.XPATH, "//*[contains(text(), 'Carlos Kidman')]"))
```

{% endcode %}

#### Purpose

I teach courses and do trainings for **Selenium,** **Cypress, and Playwright**, but Selenium, out of the box, *feels* clunky. When you start at a new place, you almost always need to "setup" the framework from scratch all over again. Instead of getting right to creating meaningful tests, you end up spending most of your time building a custom framework, maintaining it, and having to teach others to use it.

Also, many people blame Selenium for bad or flaky tests. This usually tells me that they have yet to experience someone that truly knows how to make Selenium amazing! This also tells me that they are not aware of the usual root causes that make Test Automation fail:

* Poor programming skills, test design, and practices
* Flaky applications
* Complex frameworks

What if we tried to get the best from both worlds and combine it with a fantastic language?

**Selenium** has done an amazing job of providing W3C bindings to many languages, making scaling a breeze. W3C is the standard for the web, so leveraging it just makes sense.

**Cypress** has done an amazing job of making the testing experience more enjoyable - especially for beginners. It's easy to start with and the API is readable and flows nicely.

**Pylenium** looks to bring more Cypress-like bindings and techniques to Selenium (like automatic waits) and still leverage Selenium's power along with the ease of use and power of **Python**.

### Quick Start

{% hint style="success" %}
If you are new to Selenium or Python, do the [Getting Started steps 1-4](/getting-started/virtual-environments)
{% endhint %}

You can also watch the Getting Started video with Pylenium's creator, Carlos Kidman!

{% embed url="<https://www.youtube.com/watch?v=li1nc4SUojo>" %}
Getting Started with v1.7.7+
{% endembed %}

{% hint style="success" %}
You don't need to worry about installing any driver binaries like `chromedriver`. **Pylenium** does this all for you automatically :)
{% endhint %}

#### 1. Install **pyleniumio**

{% code title="Terminal $" %}

```python
pip install pyleniumio

---or---

pipenv install pyleniumio

---or---

poetry add pyleniumio
```

{% endcode %}

#### 2. Initialize Pylenium

{% code title="Terminal $ " %}

```
pylenium init
```

{% endcode %}

{% hint style="success" %}
Execute this command at your Project Root
{% endhint %}

This creates three files:

* <mark style="color:yellow;">**`conftest.py`**</mark> - This has the fixtures needed for Pylenium
* <mark style="color:yellow;">**`pylenium.json`**</mark> - This is the [configuration ](https://github.com/ElSnoMan/pyleniumio/blob/master/docs/configuration/pylenium.json.md)file for Pylenium
* <mark style="color:yellow;">**`pytest.ini`**</mark> - This is the configuration file for pytest

By default, Pylenium uses the Chrome browser. You have to install Chrome or update the `pylenium.json` file to use the browser of your choice.

#### 3. Write a test

Create a directory called `tests` and then a test file called `test_google.py`

Define a new test called `test_google_search`

{% code title="test\_google.py" %}

```python
def test_google_search(py)
```

{% endcode %}

{% hint style="info" %}
Pylenium uses <mark style="color:yellow;">**pytest**</mark> as the Test Framework. You only need to pass in `py`to the function!
{% endhint %}

Now we can use <mark style="color:yellow;">**Pylenium Commands**</mark> to interact with the browser.

{% code title="test\_google.py" %}

```python
from pylenium.driver import Pylenium

def test_google_search(py: Pylenium):
    py.visit('https://google.com')
    py.get("[name='q']").type('puppies')
    py.get("[name='btnK']").submit()
    assert py.should().contain_title('puppies')
```

{% endcode %}

{% hint style="info" %}
Some IDEs, like PyCharm, auto-detect pytest fixtures and provide intellisense and autocomplete.
{% endhint %}

#### 4. Run the Test

This will depend on your IDE, but you can always run tests from the CLI:

{% code title="Terminal $ (venv)" %}

```bash
pytest tests/test_google.py
```

{% endcode %}

You're all set! You should see the browser open and complete the commands we had in the test :)


# Getting Started

How to get started with Web Automation and Pylenium

## Project from Scratch

Start here if you are new to Python and/or Web Automation.

1. [Virtual Environments](/getting-started/virtual-environments)
2. [Setup pytest](/getting-started/setup-pytest)
3. [Project Structure with pytest](/getting-started/project-structure-with-pytest)
4. [Writing Tests with Pylenium](/getting-started/writing-tests-with-pylenium)

## Commands

Look at these sections if you are looking for the commands available in the Pylenium API.

* [Driver Commands](/driver-commands)
* [Element Commands](/element-commands)
* [Elements Commands](/elements-commands)

## Guides

If you are looking for different flows and examples using Pylenium, then look at the [Guides](/guides) section. Some guides include:

* [Run Tests in Parallel](/guides/run-tests-in-parallel)
* [Visualize Test Results with Allure](/guides/visualize-test-results-with-allure)
* [How to wait for things using the .should() API](/guides/should-expected-conditions)


# 1. Virtual Environments

This is the first, critical piece to modern software development with Python.

## A Virtual Environment is required

**PyCharm** creates a venv by default when you create a new Project.

{% hint style="success" %}
You can skip this step if you already have a Virtual Environment in your Project
{% endhint %}

## What is a Virtual Environment?&#x20;

Without Virtual Environments (<mark style="color:yellow;">**venv**</mark> or <mark style="color:yellow;">**.venv**</mark>), everything you install would be global to your machine. Every project you have would be sharing the same packages and dependencies which could cause clashes or unwanted side effects.

Luckily, **venvs** are easy to setup. Open a Terminal in the context of your Project Directory.

{% hint style="warning" %}
We assume you already have **python3** installed on your machine
{% endhint %}

{% tabs %}
{% tab title="Mac" %}

```bash
$ python3 --version
# should print 3.x.x

$ python3 -m venv "venv"
```

{% endtab %}

{% tab title="Windows" %}

```bash
$ python --version
# should print 3.x.x
 
$ python -m venv "venv"
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Download Python if you haven't already <https://www.python.org/downloads/>
{% endhint %}

Depending on your IDE, it *should* automatically detect that a Virtual Environment has been created and ask if it should use it. Accept :)

Otherwise, you can manually configure your IDE to use the Virtual Environment.

{% tabs %}
{% tab title="VS Code" %}

```
1. Install the Python extension
2. Open the Command Palette (CMD + SHIFT + P or CTRL + SHIFT + P)
3. Search for "Python: Select Interpreter"
4. Select the venv for your Project
```

{% endtab %}

{% tab title="PyCharm" %}

```
1. Open Preferences (or Settings)
2. Open Project > Project Interpreter
3. Select the venv for your Project in the Project Interpreter dropdown
4. Click APPLY, then OK
```

{% endtab %}
{% endtabs %}

Kill all Terminal sessions, then reopen a Terminal. It should now open and activate the Virtual Environment automatically. This is indicated by the <mark style="color:yellow;">**(venv)**</mark> prefix as seen in the example below:

{% code title="New Terminal" %}

```bash
$ (venv) python --version
# should print 3.x.x for Mac or Windows.
# Mac users don't need to use python3 or pip3 anymore!
```

{% endcode %}

{% hint style="info" %}
Real Python goes more in-depth on their website: [Virtual Environments](https://realpython.com/python-virtual-environments-a-primer/)
{% endhint %}


# 2. Setup pytest

pytest is a modern and powerful Test Framework and we want to get intellisense and autocomplete

## 1. Install pyleniumio

Install **Pylenium** into your [Virtual Environment](/getting-started/virtual-environments) if you haven't already:

{% tabs %}
{% tab title="pip" %}
{% code title="Terminal $ (venv)" %}

```bash
pip install pyleniumio
```

{% endcode %}
{% endtab %}

{% tab title="poetry" %}
{% code title="Terminal" %}

```bash
poetry add pyleniumio
```

{% endcode %}
{% endtab %}

{% tab title="pipenv" %}
{% code title="Terminal " %}

```bash
pipenv install pyleniumio
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="success" %}
`poetry` and `pipenv` auto-generate virtual environments for you!
{% endhint %}

## 2. Initialize Pylenium

{% code title="Terminal $ (venv)" %}

```
pylenium init
```

{% endcode %}

{% hint style="success" %}
Execute this command at your Project Root
{% endhint %}

This creates three files:

* <mark style="color:yellow;">**`conftest.py`**</mark> - This has the fixtures needed for Pylenium
* <mark style="color:yellow;">**`pylenium.json`**</mark> - This is the [configuration ](/configuration/pylenium-json)file for Pylenium
* <mark style="color:yellow;">**`pytest.ini`**</mark> - This is the configuration file for pytest

By default, Pylenium uses the Chrome browser. Chrome must be installed on the machine, but you don't have to worry about installing any of the drivers.

## 3. Select pytest as the Test Framework

To get the most out of your IDE, you need to configure it to use <mark style="color:yellow;">**pytest**</mark> as the Test Framework. This will give you:

* Intellisense
* Autocomplete
* Run/Debug Test functionality with breakpoints
* more depending on IDE

{% tabs %}
{% tab title="VS Code" %}

```
1. Open Command Palette (CMD + SHIFT + P or CTRL + SHIFT + P)
2. Search for "Python: Configure Tests"
3. Select pytest
```

{% endtab %}

{% tab title="PyCharm" %}

```
1. Open Preferences (or Settings)
2. Open Tools > Python Integrated Tools
3. Select pytest in the "Default test runner" dropdown
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Visit the pytest docs for more info on how to use it: <https://docs.pytest.org/>
{% endhint %}


# 3. Project Structure with pytest

pytest uses specific naming conventions and project structure

## Pylenium Files

You should have created these in the [previous step](/getting-started/setup-pytest), but they are required for **Pylenium** to do its magic.

* <mark style="color:yellow;">**`conftest.py`**</mark>
* <mark style="color:yellow;">**`pylenium.json`**</mark>
* <mark style="color:yellow;">**`pytest.ini`**</mark>

{% hint style="success" %}
Make sure these are at the Project Root (aka Workspace)
{% endhint %}

## conftest.py

pytest uses special functions called <mark style="color:purple;">**Fixtures**</mark> to control the <mark style="color:purple;">**Setup**</mark> and <mark style="color:purple;">**Teardown**</mark> of tests and runs.

{% hint style="danger" %}
If you put any other **custom** functions or fixtures in this **conftest.py**, they will be *overwritten* when you upgrade Pylenium. Instead, create your own `conftest.py` file under your `/tests` directory.
{% endhint %}

### Fixture Example

```python
import pytest

@pytest.fixture
def user():
    new_user = user_service.create()
    yield new_user
    user_service.delete(new_user.id)
```

* `@pytest.fixture` - this decorator indicates that this function has a Setup and Teardown&#x20;
* `def user():` - define the function normally. `user` will be the name of the fixture to be used in tests
* Everything *before* the `yield` is executed before each test
* `yield new_user` - returns `new_user` and gives control back to the test. The rest of the function is not executed yet
* Everything *after* the `yield` is executed after each test

### Use the Fixture

{% code title="test\_\*.py file" %}

```python
def test_my_website(py, login, user):
    py.visit('https://qap.dev')
    login.with(user)
    ...
```

{% endcode %}

When this test is executed:

1. test - The test looks at its parameter list and calls the `py` fixture
2. fixture - `user` yields the newly created user
3. test - line 2 is executed by navigating to `https://qap.dev` and then logging in with the new user
4. fixture - test is complete (doesn't matter if it passes or fails) and `user_service.delete_user()` is executed

### Folder Structure

The `conftest.py` file is used to *store* fixtures and make them available to any tests in their **Scope**.

{% hint style="info" %}
**Scope** refers to the file's siblings and descendants.
{% endhint %}

Take a look at the following Project Structure

* Project
  * conftest.py  # 1
  * pylenium.json
  * api\_tests
    * conftest.py  # 2&#x20;
    * test\_rest\_api.py
  * ui\_tests
    * conftest.py  # 3
    * test\_google.py

`test_google.py` would have access to fixtures in `conftest.py #1` and `conftest.py #3`

`test_rest_api.py` would have access to fixtures in `conftest.py #1` and `conftest.py #2`

## Test Naming Conventions

By now it might be obvious that pytest has specific naming conventions by default.

### Folders and Files

* You may want to store your tests in a `/tests` directory (optional)
* You may want to make files easily identified as test files, so use `test_*.py` (optional)

{% hint style="info" %}
These techniques help you and the Test Runner discover/find and execute your tests more easily, but they are not required. Do what works best for you and your team.
{% endhint %}

**pytest** can run tests based off of directories or files, so you can group tests into **Suites** this way.

* Project
  * tests
    * ui
      * test\_login.py
      * test\_checkout.py
    * api
      * test\_payment.py
      * test\_user.py
    * unit

```bash
# run all tests
$ pytest tests

# run tests in ui directory
$ pytest tests/ui

# run only the payment api tests
$ pytest tests/api/test_payment.py
```

### Classes

You *can* group tests into Suites using Classes.

{% hint style="danger" %}
This is not the recommended approach for beginners
{% endhint %}

```python
def TestCheckout:

    def test_with_visa(self, py):
        # test code
    
    def test_with_mastercard(self, py):
        # test code
```

* The class must start with the word `Test`
* Test functions must have `self` as their first parameter (since they are in a class)

{% hint style="info" %}
You can have as many Test Classes and Test Functions as you want in a file
{% endhint %}

### Test Functions

Tests do NOT need to be in a Test Class. They can exist by themselves in a file and makes the tests and overall file look much cleaner.

{% hint style="success" %}
RECOMMEND this approach for working with Pylenium for beginners (and everyone else really 😄)
{% endhint %}

{% code title="test\_checkout.py" %}

```python
def test_with_visa(py):
    # test_code
    
def test_with_mastercard(py):
    # test_code
```

{% endcode %}

* Test names must start with `test_`, but can have anything else after that

{% hint style="danger" %}
Tests should not *share* **data** or **state**.
{% endhint %}

{% hint style="success" %}
Tests should be **modular**, **deterministic,** and **meaningful**
{% endhint %}

Pylenium is architected in a way that makes test design easy and intuitive but also gives you a lot of things for free. **The framework is already designed to be scaled with containerized solutions like Docker and Kubernetes.**


# 4. Writing Tests with Pylenium

Easy as py 🥁

## Create a Test File

A **Test File** is just that - a file with tests in it. Depending on the type of project you're working on, you may want these to live right next to your code files or in a separate <mark style="color:yellow;">**`/tests`**</mark> directory.

{% hint style="info" %}
In these docs, we will assume you are writing your tests in a `/tests` directory of your project.
{% endhint %}

Create a Test File called `test_qap_dev.py`

* Test Files do not need to start with `test_`, but it's recommended
* This is a naming convention used to make it easier to distinguish between code and tests

You should now have a Project Structure that looks like this:

* Project
  * tests
    * test\_qap\_dev.py
  * conftest.py
  * pylenium.json
  * pytest.ini
  * venv

## Write the Test

We are going to make a test that does the following:

1. *Visits* the **QA at the Point** website: <https://qap.dev>
2. *Hovers* the **About** link to reveal a menu
3. *Click* the **Leadership** link in that menu
4. *Assert* **Carlos Kidman** is on the Leadership page

{% code title="test\_qap\_dev.py" %}

```python
def test_carlos_is_on_leadership(py):
    py.
```

{% endcode %}

When you type `py.`, you should see an **auto-complete** or **IntelliSense** menu appear with the list of [Pylenium Commands](/driver-commands/commands) like `.visit()` and `.get()`

{% hint style="warning" %}
&#x20;Your IDE may not do this or you may be missing an Extension or Plugin.

PyCharm has [pytest support](/getting-started/setup-pytest) out-of-the-box.
{% endhint %}

Let's move on with the steps.

* Visit <https://qap.dev>

{% code title="test\_qap\_dev.py" %}

```bash
def test_carlos_is_on_leadership(py):
    py.visit("https://qap.dev")
```

{% endcode %}

* Hover the **About** link to reveal a menu

{% code title="test\_qap\_dev.py" %}

```python
def test_carlos_is_on_leadership(py):
    py.visit("https://qap.dev")
    py.get("a[href='/about']").hover()
```

{% endcode %}

{% hint style="info" %}
When you get an Element from locator methods:

* `.get()  | .getx()`
* `.find() | .findx()`
* `.contains()`

you can perform actions against the element like:

* `.click()`
* `.type()`
* `.hover()`
* `and more!`
  {% endhint %}

{% hint style="success" %}
Make sure to check out the many commands available in Pylenium
{% endhint %}

* Click the **Leadership** link in the menu

{% code title="test\_qap\_dev.py" %}

```python
def test_carlos_is_on_leadership(py):
    py.visit("https://qap.dev")
    py.get("a[href='/about']").hover()
    py.get("a[href='/leadership'][class^='Header-nav']").click()
```

{% endcode %}

* Assert **Carlos Kidman** is on the Leadership page

{% code title="test\_qap\_dev.py" %}

```python
def test_carlos_is_on_leadership(py):
    py.visit("https://qap.dev")
    py.get("a[href='/about']").hover()
    py.get("a[href='/leadership'][class^='Header-nav']").click()
    assert py.contains("Carlos Kidman")
```

{% endcode %}

## Run the Test

If you're using PyCharm or VS Code, there should be a green **Play** button next to the test definition. Click it and select either **Run** to execute normally or **Debug** to use breakpoints in Debug Mode.

Otherwise, use the method your IDE provides. You can always use the CLI as well:

{% code title="Terminal $ (venv)" %}

```bash
pytest tests/test_qap_dev.py
```

{% endcode %}

### Look at the Difference

Here is the same test but written with Selenium out of the box:

```bash
# Define your setup and teardown fixture
@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()


def test_carlos_is_on_leadership_page_with_selenium(driver):
    driver.get("https://qap.dev")
    
    # Hover About link
    about_link = driver.find_element(By.CSS_SELECTOR, "a[href='/about']")
    actions = ActionChains(driver)
    actions.move_to_element(about_link).perform()
    
    # Click Leadership link in About menu
    driver.find_element(By.CSS_SELECTOR, "a[href='/leadership'][class^='Header-nav']").click()
    
    # Check if 'Carlos Kidman' is on the page
    assert driver.find_element(By.XPATH, "//*[contains(text(), 'Carlos Kidman')]")
```

## Another Test Example

Let's write another test that searches for `Pylenium` and makes sure the results page contains that term in the title.

1. Navigate to Google.com
2. Type `Pylenium` into the search field
3. Submit the search
4. Assert the results page contains the title

```python
def test_google_search(py):
    py.visit("https://google.com")
    py.get("[name='q']").type("Pylenium")
    py.get("[name='btnK']").submit()
    assert py.should().contain_title("Pylenium")
```

You've already seen different Element commands like <mark style="color:yellow;">**`.visit()`**</mark>, <mark style="color:yellow;">**`.type()`**</mark> and <mark style="color:yellow;">**`.submit()`**</mark>,  but there is also a *Should* object for:

* [Element](/element-commands/should)
* [Elements](/element-commands/should)
* [Pylenium](/driver-commands/should)

In the example above, <mark style="color:yellow;">**`py.should()`**</mark> uses an Explicit Wait to wait until the "driver" detects that the current page's title contains `"Pylenium"`.&#x20;

* If the title contains `"Pylenium"` within the specified timeout, then it returns `True` and passes the assertion
* If the title does not meet the expectation within the specified timeout, then it returns `False` and fails the assertion

{% hint style="success" %}
You can leverage these *Should* expectations to easily wait for conditions or write assertions!
{% endhint %}


# Guides

Examples, recipes, gists, snippets, and other guides to help you with Pylenium.


# Visualize Test Results with Allure

How to record and visualize your test results with Allure Reports

{% hint style="success" %}
Pylenium uses <mark style="color:yellow;">**`pytest`**</mark> as the Test Framework and uses <mark style="color:yellow;">**`pytest-allure`**</mark> for reporting. However, you can use your preferred reporting strategy since it's easy to extend the framework :thumbsup:
{% endhint %}

Visit Allure's websites for more info and details:

* Official website: <https://qameta.io/allure-report/>
* Official docs: <https://docs.qameta.io/allure-report/>

## Quickstart

Pylenium comes with Allure natively integrated, so all you have to do is add the **--alluredir** argument to the pytest command. Besides that, continue using Pylenium and pytest normally 😄

### 1. Install allure

Visit their [installation docs](https://docs.qameta.io/allure-report/#_installing_a_commandline) and install allure using the appropriate method. For example, on Macs, you can use homebrew:

{% code title="Terminal" %}

```bash
brew install allure
```

{% endcode %}

Pylenium also includes a [CLI command](/cli/allure-cli) to try and do the installation for you:

{% code title="Terminal" %}

```bash
pylenium allure install
```

{% endcode %}

{% hint style="warning" %}
It's recommended to visit Allure's docs and install it from there. For example, the Linux instructions require the use of **`sudo`** and you want to make sure you trust it first before executing 👍🏽
{% endhint %}

### 2. Run Tests

{% code title="Terminal" %}

```bash
pytest --alluredir=allure-report
```

{% endcode %}

⬆️ That creates a folder called **`./allure-report`** and stores the test results there

### 3. Serve Test Results

Then you generate the results into an Allure Report and "serve" it:

{% code title="Terminal" %}

```bash
allure serve allure-report
```

{% endcode %}

{% hint style="warning" %}
It's recommended to use the <mark style="color:yellow;">**`allure`**</mark> command directly instead of through Pylenium:
{% endhint %}

{% code title="Terminal" %}

```bash
pylenium allure serve
```

{% endcode %}

### 4. View the Report

By default, allure will open a new window or browser tab in your default browser and show you something like this:

<figure><img src="/files/Qd5m7GZtQuOSAinqVFZn" alt=""><figcaption><p>Local example</p></figcaption></figure>

Take some time to review all of the data that you get from this report, but some notable mentions:

* pass/fail percentages
* trends in historical test results

{% hint style="success" %}
Yes, the more you run tests and save them to the same folder, you'll get more data in the report!
{% endhint %}

* detailed steps on setup and teardown of each test
* logs automatically included for each test
* on test failure, a screenshot is automatically taken and attached to the test
* test case durations and test run durations
* other visuals in the Graphs tab
* and more!

## Advanced Usage

If you haven't checked out their [official docs](https://docs.qameta.io/allure-report/#_pytest) yet, you should because they go over what you can send, how you send it, and how to use the Allure Report once it's served!

*This guide will cover more common scenarios*

### Attaching screenshots

By default, Pylenium already takes a screenshot and attaches it to allure on any test failure so you can see the last "view" of the page before the test failed. However, you can add more screenshots.

For example, if you want to take a screenshot after every page transition, this is what the scripted version of the test would look like:

{% code title="test\_example.py" %}

```python
import allure
from pylenium.driver import Pylenium


def test_attach_screenshots(py: Pylenium):
    # Go to QAP Home page
    py.visit("https://qap.dev")
    allure.attach(py.webdriver.get_screenshot_as_png(), "home.png", allure.attachment_type.PNG)
    
    # Navigate to Leadership page
    py.get("[href='/about']").hover()
    py.get("[href='/leadership']").click()
    allure.attach(py.webdriver.get_screenshot_as_png(), "leadership.png", allure.attachment_type.PNG)
    
    # Screenshot taken and attached automatically if any part of the test fails
    assert py.contains("@CarlosKidman")
```

{% endcode %}

Run the test

{% code title="Terminal" %}

```bash
pytest -k test_attach_screenshots --alluredir=allure-report
```

{% endcode %}

Serve the report

{% code title="Terminal" %}

```bash
allure serve allure-report
```

{% endcode %}

And observe that we have both screenshots in the Test Body section of our test report:

<figure><img src="/files/T1e1VkGHjD31QIR16Sl0" alt=""><figcaption><p>Both screenshots are attached to the test</p></figcaption></figure>

<mark style="color:yellow;">**allure.attach()**</mark> is all you need, meaning that you can add it to your Page Objects, flow functions, or even as a reusable decorator!

{% hint style="info" %}
allure knows the current test that is running and attaches the screenshot appropriately. You don't have to worry about "assigning" it to the right test.
{% endhint %}

### Tagging Tests

pytest tags (aka *marks*) tests using its <mark style="color:yellow;">**`mark`**</mark> feature and allure simply leverages it. Take the following example:

{% code title="test\_sauce\_demo.py" %}

```python
import pytest
from pylenium.driver import Pylenium


@pytest.fixture(scope="session")
def sauce(pys: Pylenium) -> Pylenium:
    """Login to saucedemo.com as standard user."""
    pys.visit("https://www.saucedemo.com/")
    pys.get("#user-name").type("standard_user")
    pys.get("#password").type("secret_sauce")
    pys.get("#login-button").click()
    yield pys
    pys.get("#react-burger-menu-btn").click()
    pys.get("#logout_sidebar_link").should().be_visible().click()


class TestSauceDemo:
    @pytest.mark.single_item
    def test_add_to_cart_css(self, sauce: Pylenium):
        """Add an item to the cart. The number badge on the cart icon should increment as expected."""
        sauce.get("[id*='add-to-cart']").click()
        assert sauce.get("a.shopping_cart_link").should().have_text("1")

    @pytest.mark.many_items
    def test_add_to_cart_xpath(self, sauce: Pylenium):
        """Add 6 different items to the cart. There should be 6 items in the cart."""
        for button in sauce.findx("//*[contains(@id, 'add-to-cart')]"):
            button.click()
        sauce.getx("//a[@class='shopping_cart_link']").click()
        assert sauce.findx("//*[@class='cart_item']").should().have_length(6)

```

{% endcode %}

We can run all tests in the file

{% code title="Terminal" %}

```bash
pytest tests/test_sauce_demo.py --alluredir=allure-report
```

{% endcode %}

Or we can run a subset of tests given their tag or mark using the <mark style="color:yellow;">**`-m`**</mark> flag

```
pytest -m single_item --alluredir=allure-report
```

Either way, we now see the tag(s) on the test in our report

<figure><img src="/files/IL7z1BBzPDJJXCbvyHQA" alt=""><figcaption><p>You can have as many tags on a test as you'd like</p></figcaption></figure>

{% hint style="info" %}
Also notice how the docstring in the test function has been converted to a Description in our report! 👀
{% endhint %}

### Categorize Tests

In pytest, you can use the [mark feature to tag tests](#tagging-tests). However, in Allure, they have a unique way to categorize tests based on each test's results.

{% embed url="<https://docs.qameta.io/allure-report/#_categories_2>" %}
⬆️ Visit this section of their docs to see how ⬆️
{% endembed %}

### Link Results to a Bug Reporter

To integrate the Allure Report with a bug tracker or test management system, Allure has **`@allure.link`**, **`@allure.issue`** and **`@allure.testcase`** descriptors.

{% embed url="<https://docs.qameta.io/allure-report/#_links_5>" %}
⬆️ Visit this section of their docs to see how ⬆️
{% endembed %}

### Mark Tests with Severity

The **severity mark** is part of Allure, not pytest. For example:

```python
import allure

@allure.severity(allure.severity_level.CRITICAL)
def test_with_critical_severity():
    pass
```

{% hint style="info" %}
This is another way to mark and filter tests!
{% endhint %}

{% embed url="<https://docs.qameta.io/allure-report/#_severity_markers>" %}
⬆️ Visit this section of their docs to see how ⬆️
{% endembed %}


# Logging

How to use Pylenium's built-in logger

Pylenium includes two custom <mark style="color:yellow;">**Log Levels**</mark> and a global <mark style="color:yellow;">**Logger**</mark> instance that you *can* use.

## Log Levels

| Name     | Level | Note    |
| -------- | ----- | ------- |
| CRITICAL | 50    |         |
| ERROR    | 40    |         |
| WARNING  | 30    |         |
| USER     | 25    | Custom  |
| INFO     | 20    | Default |
| COMMAND  | 15    | Custom  |
| DEBUG    | 10    |         |

If you are familiar with logging, then the above table is straightforward. If not, then all you really need to know about these levels is that you can set the <mark style="color:yellow;">**Log Level**</mark> when executing tests, and any logs at the specified level or higher will be captured.

For example, if you wanted to set the <mark style="color:yellow;">**Log Level**</mark> to see only logs at **`INFO`** and higher, you would do this:

```bash
pytest --pylog_level=INFO
```

{% hint style="info" %}
The above command would ignore logs *below* the `INFO` level. In other words, ignore the **`COMMAND`** and **`DEBUG`** logs.
{% endhint %}

### &#x20;COMMAND Level

The **`COMMAND`** Log Level is used by **Pylenium** for logging its commands in a cleaner and easier to parse format. You shouldn't use this level unless you *really want to*. Take a look at our <mark style="color:purple;">**`visit()`**</mark> command to see it in action:

```python
def visit(self, url: str) -> "Pylenium":
    log.command("py.visit() - Visit URL: `%s`", url)
    self.webdriver.get(url)
    return self
```

{% hint style="success" %}
Notice how the string uses the **`%s`** format and NOT the f-string format.

***This is intentional!***
{% endhint %}

### USER Level

The **`USER`** Log Level is meant for you! This is a convenient way for logging things if you don't want everything from the **`INFO`** level.

{% hint style="info" %}
I highly recommend creating your own loggers, but sometimes something simple like this is all you need 😄
{% endhint %}

To take advantage of this level, use <mark style="color:purple;">**`log.this()`**</mark>:

```python
# You can import this in any file
from pylenium.log import logger as log

# Log this
def add_to_cart(item: str, quantity: int):
    log.this("Adding %s %s to my cart", quantity, item)
    ...

# Then call the function
add_to_cart("Charizard", 3)
>>> USER Adding 3 Charizard to my cart
```

You can also directly use <mark style="color:purple;">**`py.log`**</mark>:

```python
# Log this
def add_to_cart(py: Pylenium, item: str, quantity: int):
    py.log.this("Adding %s %s to my cart", quantity, item)
    ...

# Then call the function
add_to_cart(py, "Charizard", 3)
>>> USER Adding 3 Charizard to my cart
```


# Run Tests in Containers

How to run tests in containers like Docker

## Configure the Test Run

Regardless of the scaling option you go with (Selenoid, Zalenium, Docker vs Kubernetes, etc.), you will need to connect your tests to a **Remote URL.**

You can do this two ways:

* Update <mark style="color:yellow;">**remote\_url**</mark> in **`pylenium.json`**
* Pass in the argument when running the tests in the CLI

### Run Tests in CLI

{% hint style="info" %}
This is the most common option since it is what you will use in your pipelines and CI
{% endhint %}

{% code title="Terminal" %}

```bash
pytest tests/ui -n 2 --remote_url="http://localhost:4444/wd/hub"
```

{% endcode %}

### Update pylenium.json

{% code title="pylenium.json" %}

```bash
"remote_url": "http://localhost:4444/wd/hub"
```

{% endcode %}

You can have multiple `pylenium.json` files, so you might have:

* **`dev-pylenium.json`** for local development
* **`ci-pylenium.json`** for CI Pipelines

Then pick which config file to use in the CLI. For example, in a CI Pipeline:

{% code title="Terminal" %}

```bash
pytest pylenium_json=ci-pylenium.json
```

{% endcode %}

### Config Layers

* Layer 1 - `pylenium.json` is deserialized into **PyleniumConfig**
* Layer 2 - If there are any CLI args, they will override their respective values in **PyleniumConfig**

## Docker Example

With **Docker** installed, you can easily spin up a **Selenium Grid** with the `docker-compose` command.

### docker-compose.yml

You will need a `docker-compose.yml` file and then open a Terminal in the same directory as this file.

{% code title="docker-compose.yml" %}

```yaml
version: "3"
services:

  selenium-hub:
    image: selenium/hub
    ports:
      - "4444:4444"
    environment:
        GRID_MAX_SESSION: 16
        GRID_BROWSER_TIMEOUT: 300
        GRID_TIMEOUT: 300

  chrome:
    image: selenium/node-chrome
    depends_on:
      - selenium-hub
    environment:
      HUB_PORT_4444_TCP_ADDR: selenium-hub
      HUB_PORT_4444_TCP_PORT: 4444
      NODE_MAX_SESSION: 2
      NODE_MAX_INSTANCES: 2

  firefox:
    image: selenium/node-firefox
    depends_on:
      - selenium-hub
    environment:
      HUB_PORT_4444_TCP_ADDR: selenium-hub
      HUB_PORT_4444_TCP_PORT: 4444
      NODE_MAX_SESSION: 4
      NODE_MAX_INSTANCES: 4
```

{% endcode %}

This configuration will spin up a **Hub** node (load balancer), a **Chrome** node with 2 available drivers and a **Firefox** node with 4 available drivers.

### Spin up the Grid

With a single command you will have all of this created for you:

{% code title="Terminal $" %}

```bash
docker-compose up -d
```

{% endcode %}

{% hint style="info" %}
Once complete, you can visually see this Grid by going to <http://localhost:4444/grid/console>
{% endhint %}

Now **Configure the Test Run** (steps at top of this doc) to target the Hub which will balance the tests across its Nodes.

### Scale Nodes

With the YAML file example above, it will create 1 chrome Node with 2 available drivers by default. You can easily scale this to the number you need.

{% code title="Terminal $" %}

```bash
docker-compose up -d --scale chrome=5
```

{% endcode %}

This will spin up the Grid with 5 chrome Nodes!

### Tear Down the Grid

When you're done using the Grid, a single command will tear it completely down.

{% code title="Terminal $" %}

```bash
docker-compose down
```

{% endcode %}


# Run Tests in Parallel

## Simple CLI

Pylenium comes with <mark style="color:yellow;">**pytest**</mark> and the <mark style="color:yellow;">**pytest-xdist**</mark> plugin to run tests concurrently. All you need to do is use the `-n [NUMBER]` option when running the tests in the CLI.

{% code title="Terminal" %}

```bash
# run two tests in parallel
pytest tests -n 2
```

{% endcode %}

{% hint style="success" %}
&#x20;Pylenium is already designed to scale in parallel with or without containers
{% endhint %}

## Configure the IDE

Most IDEs will allow you to configure your Test File or Test Run with additional arguments.

For example, in PyCharm, you can:

* Open **Run** in the Top Menu
* Select **Edit Configurations**
* Then add `-n 2` to the **Additional Arguments** field

{% hint style="info" %}
That allows you to Run and Debug tests while still having 2 run at a time
{% endhint %}


# Should / Expected Conditions

Pylenium provides fluent syntax to check for expected conditions.

Pylenium's [*<mark style="color:orange;">**Driver**</mark>*](/driver-commands/should), [*<mark style="color:orange;">**Element**</mark>*](/element-commands/should), and [*<mark style="color:orange;">**Elements**</mark>*](/elements-commands/elements.should) objects each have a **`Should`** API that allows you to write fluent-like code to wait and check for any expected conditions.

For example, to check that an element is visible on the webpage, you would do this:

{% code title="Pylenium" %}

```python
def test_element_is_visible(py):
    py.visit("https://qap.dev")
    assert py.get("a[href='/about']").should().be_visible()
```

{% endcode %}

With Selenium, you normally use the **`ExpectedConditions`** class:

{% code title="Selenium" %}

```python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait


def test_element_is_visible():
    driver = webdriver.Chrome()
    wait = WebDriverWait(driver, timeout=10)
    
    driver.get("https://qap.dev")
    element = wait.until(EC.visibility_of_element_located(By.CSS_SELECTOR, "a[href='/about']"))
    assert element.is_displayed()
```

{% endcode %}

### Similar Pages

* [*<mark style="color:orange;">**Driver.should()**</mark>*](/driver-commands/should)
* [*<mark style="color:orange;">**Element.should()**</mark>*](/element-commands/should)
* [*<mark style="color:orange;">**Elements.should()**</mark>*](/elements-commands/elements.should)


# Script with Standalone Pylenium

How to use Pylenium in a regular script instead of in a test

## Setup

Pylenium needs two things in order to be instantiated:

* <mark style="color:orange;">**PyleniumConfig**</mark>
* <mark style="color:orange;">**Pylenium**</mark>

Create a `main.py` file and add the necessary imports:

{% code title="main.py" %}

```python
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig
```

{% endcode %}

### Create an instance of PyleniumConfig

Start by creating an instance of PyleniumConfig. Leaving it blank will create a config with default values. **NOTE**: This *<mark style="color:red;">does not</mark>* use <mark style="color:yellow;">**`pylenium.json`**</mark>

{% code title="Default config" %}

```python
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

config = PyleniumConfig()
```

{% endcode %}

To use <mark style="color:yellow;">**`pylenium.json`**</mark>, you'd have to read the file first:

{% code title="Use pylenium.json" %}

```python
import json
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

with open("pylenium.json") as file:
    pylenium_json = json.load(file)

config = PyleniumConfig(**pylenium_json)
```

{% endcode %}

You can set config values directly in the script - mixing and matching as needed

```python
import json
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

with open("pylenium.json") as file:
    pylenium_json = json.load(file)

config = PyleniumConfig(**pylenium_json)
config.browser = "firefox"
```

### Create an instance of Pylenium

Once the config is ready, instantiate Pylenium with it:

```python
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

config = PyleniumConfig()
py = Pylenium(config)
```

## Write your Script

You now have access to Pylenium's many commands to script what you need:

{% code title="Google Search" %}

```python
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

config = PyleniumConfig()
py = Pylenium(config)

py.visit("https://google.com")
py.get("[name='q']").type("pylenium.io\n")
py.should().contain_title("pylenium.io")
py.quit()
```

{% endcode %}

## Run your Script

Use python to execute `main.py`

```bash
python main.py
```


# CLI

Pylenium comes with a CLI


# Pylenium CLI

The CLI comes with commands to initialize and create Pylenium files, and more.

## pylenium init

Initializes Pylenium into the current directory. This creates Pylenium's required files:

* **`conftest.py`**
* **`pylenium.json`**
* **`pytest.ini`**

{% code title="Terminal $" %}

```bash
pylenium init
```

{% endcode %}

{% hint style="success" %}
Make sure to run this command at the <mark style="color:yellow;">**Project Root**</mark> (aka Workspace)
{% endhint %}

{% hint style="info" %}
By default, this will not overwrite Pylenium files if they already exist.
{% endhint %}

## Overwrite conftest.py file

You can overwrite an existing <mark style="color:yellow;">**conftest.py**</mark> file with the latest version by using the **`-c`** flag.

{% code title="Terminal $" %}

```bash
pylenium init -c
```

{% endcode %}

## Overwrite pylenium.json file

You can overwrite an existing <mark style="color:yellow;">**pylenium.json**</mark> file with the latest defaults by using the **`-p`** flag.

{% code title="Terminal $" %}

```bash
pylenium init -p
```

{% endcode %}

## Overwrite pytest.ini file

You can overwrite an existing <mark style="color:yellow;">**pytest.ini**</mark> file with the latest defaults by using the **`-i`** flag.

{% code title="Terminal $" %}

```bash
pylenium init -i
```

{% endcode %}

## Overwrite multiple files at once

You can overwrite two or more files by combining flags.

{% code title="Terminal" %}

```bash
pylenium init -cpi
```

{% endcode %}


# Allure CLI

Pylenium has commands for working with allure reporting

These are "convenience" commands if you are new to allure, but you can use these Pylenium commands or use the <mark style="color:yellow;">**`allure`**</mark>  CLI directly *(recommended)*

## allure install

Install the <mark style="color:yellow;">**`allure`**</mark> CLI on the current machine.

{% code title="Terminal" %}

```bash
pylenium allure install
```

{% endcode %}

Pylenium detects your operating system and *tries* to install allure with the appropriate installation commands. However, it's recommended that you use their official installation docs instead.

{% hint style="warning" %}
<https://docs.qameta.io/allure-report/#_installing_a_commandline>
{% endhint %}

## allure check

Check that there is valid allure CLI installed on the current machine.

{% code title="Terminal" %}

```bash
pylenium allure check
```

{% endcode %}

* If successful, a message is displayed with the current version of allure
* Otherwise, allure is not installed or not added to the PATH correctly

{% hint style="info" %}
This is equivalent to the following allure command ⬇️
{% endhint %}

{% code title="Terminal" %}

```bash
allure --version
```

{% endcode %}

## allure serve

Starts the allure server, generates the report, and serves it as a new browser tab.

{% code title="Terminal" %}

```bash
pylenium allure serve --folder [FOLDER]
```

{% endcode %}

{% hint style="info" %}
This is equivalent to the following allure command ⬇️
{% endhint %}

{% code title="Terminal" %}

```bash
allure serve [FOLDER]
```

{% endcode %}

### Example

Run your tests with pytest and specify that the output be saved to the **`allure-report`** folder

{% code title="Terminal" %}

```bash
pytest --alluredir=allure-report
```

{% endcode %}

With the test run finished we can serve the report and see the results

{% code title="Terminal" %}

```bash
pylenium allure serve --folder allure-report
```

{% endcode %}


# Configuration


# pylenium.json

The configuration file for Pylenium

## Configure with a JSON File

If you don't want to use Pylenium's defaults but you don't want to configure it via the CLI, you can create a <mark style="color:orange;">**pylenium.json**</mark> file at the <mark style="color:yellow;">**Project Root**</mark> (same directory as our <mark style="color:orange;">**conftest.py**</mark> file) and do it with a JSON instead.

{% hint style="info" %} <mark style="color:orange;">**pylenium.json**</mark> is already created when using the <mark style="color:purple;">**`pylenium init`**</mark> command
{% endhint %}

Here are all of the current settings (and their defaults) you can configure right now:

```javascript
{
  "driver": {
    "browser": "chrome",
    "remote_url": "",
    "wait_time": 10,
    "page_load_wait_time": 0,
    "options": [],
    "capabilities": {},
    "experimental_options": null,
    "extension_paths": [],
    "webdriver_kwargs": {},
    "local_path": ""
  },
  "logging": {
    "screenshots_on": true
  },
  "viewport": {
    "maximize": true,
    "width": 1440,
    "height": 900,
    "orientation": "portrait"
  },

  "custom": {}
}

```

## Change a single value

{% hint style="info" %}
You only need to change the values you care about.
{% endhint %}

If I only wanted to change the browser to be `"firefox"`, then only include that:

```bash
{
  "driver": {
    "browser": "firefox"
  }
}
```

## Adding custom values

{% hint style="info" %}
You can add any objects within the <mark style="color:yellow;">**custom**</mark> object to be used by <mark style="color:orange;">**py.config**</mark>
{% endhint %}

Adding your own key/value pairs is easy:

```bash
{
  "custom": {
    "env_url": "https://staging.our-app.com"
  }
}
```

Now you can use it like any other dictionary in Python:

```python
py.config.custom.get("env_url")

---or---

py.config.custom["env_url"]
```

### Complex custom objects

More complex or nested objects are easy to add as well:

```bash
{
  "custom": {
    "environment": {
      "url": "https://staging.our-app.com",
      "username": "foo",
      "password": "bar",
      "clusters": [ "cl01", "cl03", "cl05" ]
    }
  }
}
```

It's still just a Python dictionary, so you can easily access them:

```python
# Get the entire environment object
py.config.custom.get("environment")

# Get only the url
py.config.custom["environment"]["url"]

# Get the first item in the list of clusters
py.config.custom["environment"]["clusters"][0]
```

## Multiple Versions

You can have multiple <mark style="color:yellow;">**`pylenium.json`**</mark> files and pick which one to use when executing tests.

For example, you can have multiple at your Project Root:

```
📂 Project
    📃 conftest.py
    📃 pylenium.json
    📃 local.pylenium.json
    ...
```

or store them in another folder:

```
📂 Project
    📃 conftest.py
    📃 pylenium.json
    📂 config
	📃 local.pylenium.json
	📃 dev.pylenium.json
	📃 stage.config.json
```

{% hint style="success" %}
Keep the original `pylenium.json` at the Project Root so the default behavior continues to work 😉
{% endhint %}

Then, use the <mark style="color:yellow;">**`--pylenium_json`**</mark> argument to pick which to use:

```bash
pytest --pylenium_json="local.pylenium.json"

pytest --pylenium_json="config/dev.pylenium.json"
```

{% hint style="info" %}
You can name your custom Pylenium config files whatever you like, but they MUST be `.json` and have the same shape (aka schema) as the default `pylenium.json`
{% endhint %}


# Driver

Configure the driver via the pylenium.json or the CLI.

## The Driver Settings

Supported Drivers:

* **Chrome**
* **Edge**
* **Safari**
* **Firefox**
* **Internet Explorer**

&#x20;Let's take a look at the Driver Settings in <mark style="color:orange;">**pylenium.json**</mark>

{% code title="pylenium.json" %}

```javascript
"driver": {
    "browser": "chrome",
    "remote_url": "",
    "wait_time": 10,
    "page_load_wait_time": 0,
    "options": [],
    "capabilities": {},
    "experimental_options": null,
    "extension_paths": [],
    "webdriver_kwargs": {},
    "local_path": ""
}
```

{% endcode %}

Let's break each one of these down so you know what they are for and how you can configure them.

### browser

{% hint style="info" %}
Default is <mark style="color:yellow;">**`chrome`**</mark>
{% endhint %}

This is the browser name - <mark style="color:purple;">`"chrome"`</mark> or <mark style="color:purple;">`"firefox"`</mark> or <mark style="color:purple;">`"ie"`</mark> or <mark style="color:purple;">`"safari"`</mark> or <mark style="color:purple;">`"edge"`</mark>

{% code title="pylenium.json" %}

```javascript
"driver": {
    "browser": "firefox"
}
```

{% endcode %}

{% code title="Terminal" %}

```bash
pytest tests --browser=firefox
```

{% endcode %}

### remote\_url

{% hint style="info" %}
Default is empty o&#x72;**`""`**
{% endhint %}

This is used to connect to things like <mark style="color:yellow;">**Selenium Grid**</mark>**.**

{% hint style="success" %}
Check out [Run Tests in Containers](/guides/run-tests-in-containers) for an example of how to do this locally with <mark style="color:yellow;">**Docker**</mark>
{% endhint %}

{% code title="pylenium.json" %}

```javascript
"driver": {
    "remote_url": "http://localhost:4444/wd/hub"
}
```

{% endcode %}

{% code title="Terminal" %}

```bash
pytest tests --remote_url="http://localhost:4444/wd/hub"
```

{% endcode %}

### wait\_time

{% hint style="info" %}
Default is <mark style="color:yellow;">**`10`**</mark>
{% endhint %}

The global number of seconds for actions to wait for.

{% code title="pylenium.json" %}

```javascript
"driver": {
    "wait_time": 7
}
```

{% endcode %}

{% code title="Terminal" %}

```bash
You cannot set this from the command line
```

{% endcode %}

### page\_load\_wait\_time

{% hint style="info" %}
Default is <mark style="color:yellow;">**0**</mark>
{% endhint %}

The amount of time to wait for the page to load before raising an error.

```bash
# set it globally in CLI
--page_load_wait_time 10
```

```javascript
// set it globally in pylenium.json
{
    "driver": {
        "page_load_wait_time": 10
    }
}
```

```python
# override the global page_load_wait_time just for the current test
py.set_page_load_timeout(10)
```

### options

{% hint style="info" %}
Default is empty or **`[]`**
{% endhint %}

A list of browser options to include when instantiating Pylenium.

{% code title="pylenium.json" %}

```javascript
"driver": {
    "options": ["headless", "incognito"]
}
```

{% endcode %}

{% code title="Terminal" %}

```bash
pytest tests --options="headless, incognito"
```

{% endcode %}

### experimental\_options

{% hint style="info" %}
Default is <mark style="color:yellow;">**`null`**</mark> or <mark style="color:yellow;">**`None`**</mark>
{% endhint %}

A list of experimental options to include in the driver. These can only be added using <mark style="color:orange;">**pylenium.json**</mark>

```javascript
{
    "experimental_options": [
        {"useAutomationExtension": false},
        {"otherName": "value"}
    ]
}
```

### capabilities

{% hint style="info" %}
Default is empty or `{}`
{% endhint %}

A dictionary of the desired capabilities to include when instantiating Pylenium.

{% code title="pylenium.json" %}

```python
{
    "driver": {
        "capabilities": {
            "enableVNC": true,
            "enableVideo": false,
            "name": "value"
        }
    }
}
```

{% endcode %}

{% code title="Terminal" %}

```python
pytest tests --caps = '{"name": "value", "boolean": true}'
```

{% endcode %}

### extension\_paths

The list of extensions to be included when instantiating Pylenium.

{% hint style="info" %}
Default is empty or `[]`
{% endhint %}

```javascript
{
    "driver": {
        "extension_paths": ["path_to_crx.crx", "other-path.crx"]
    }
}
```


# Viewport

Configure the viewport, or browser window dimensions, for all tests.

## The Viewport Settings

Let's take a look at the default viewport settings inside of <mark style="color:orange;">**pylenium.json**</mark>

```javascript
"viewport": {
    "maximize": true,
    "width": 1440,
    "height": 900,
    "orientation": "portrait"
}
```

By default, Pylenium will open each browser window in "maximized mode", meaning that the browser window will take up the entire screen that it's running on.

{% hint style="info" %}
With <mark style="color:purple;">`"maximize": true`</mark>, Pylenium ignores the <mark style="color:yellow;">`width`</mark>, <mark style="color:yellow;">`height`</mark>, and <mark style="color:yellow;">`orientation`</mark> values
{% endhint %}

### maximize

* <mark style="color:purple;">`true`</mark> (default) - The browser window will take up the entire screen
* <mark style="color:purple;">`false`</mark> - Use the <mark style="color:yellow;">`width`</mark>, <mark style="color:yellow;">`height`</mark>, and <mark style="color:yellow;">`orientation`</mark> values to set the brower window dimensions

### width & height

These are useful if you want all tests to use the same dimensions instead of dynamically changing to the current screen. For example, running tests locally will probably be different than when running them in your Continuous Integration pipeline.

{% hint style="success" %}
Another useful scenario is for testing your website on different mobile device sizes!
{% endhint %}

{% hint style="info" %}
Make sure <mark style="color:purple;">maximize</mark> is set to <mark style="color:yellow;">`false`</mark>
{% endhint %}

### orientation

* <mark style="color:purple;">`"portrait"`</mark> (default)
* <mark style="color:purple;">`"landscape"`</mark>


# Fixtures


# api

A library for working with HTTP Clients and APIs.

## What is requests?

<mark style="color:orange;">**Requests**</mark> is an elegant and simple HTTP library for Python, built for human beings.

{% embed url="<https://requests.readthedocs.io/en/latest/>" %}
requests official documentation
{% endembed %}

## Two Ways to Use it

* `api fixture` - A fixture of **requests** for any tests
* `import requests` - Simply use the import statement to bring it into any file!

## Syntax

```python
def test_(api)

---or--- # just import it

# recommended
import requests
```

## Usage

{% code title="api fixture" %}

```python
def test_api_fixture(api):
    response = api.get(f"{BASE_URL}/api/cards")
```

{% endcode %}

{% code title="Recommended" %}

```python
import requests

response = requests.get(f"{BASE_URL}/api/cards")
```

{% endcode %}

## CRUD

Requests provides everything you need out of the box, but these are probably the actions you want :)

### GET

```python
requests.get()
```

### POST

```python
requests.post()
```

### DELETE

```python
requests.delete()
```

### PATCH

```python
requests.patch()
```

### PUT

```python
requests.put()
```


# axe

Accessibility (A11y) Testing with aXe

## Usage

The <mark style="color:yellow;">**`axe`**</mark> fixture is the recommended way to run A11y audits since it's so easy and straightforward.

```python
def test_axe_fixture(py, axe):
    py.visit("https://qap.dev")
    # save the axe report as a file
    report = axe.run(name="a11y_audit.json")
    # and/or use the report directly in the test(s)
    assert len(report.violations) == 0
```

{% hint style="success" %}
&#x20;You can generate the report as a JSON and/or use the <mark style="color:orange;">**AxeReport**</mark> object directly.
{% endhint %}

{% hint style="warning" %}
Running an audit will generate a JSON report *only* if a `name` is given.
{% endhint %}

{% code title="function signature" %}

```python
def run(name: str = None, context: Dict = None, options: Dict = None) -> AxeReport
```

{% endcode %}

## Arguments

* <mark style="color:yellow;">**`name: str`**</mark> The file path (including name and `.json` extension) of the report to save as a JSON
* <mark style="color:yellow;">**`context: Dict`**</mark> The dictionary of page part(s), by CSS Selectors, to include or exclude in the audit
* <mark style="color:yellow;">**`options: Dict`**</mark> The dictionary of aXe options to include in the audit

{% hint style="info" %}
Visit the official aXe documentation for more information about the `context` and `options` arguments.

<https://github.com/dequelabs/axe-core/blob/master/doc/API.md#parameters-axerun>
{% endhint %}

## Yields

* <mark style="color:orange;">**AxeReport**</mark> - object that represents the audit report in code

If you include the <mark style="color:yellow;">**`name`**</mark> argument, then that report is also created at the specified file path.

{% hint style="danger" %}
If any of the directories in the path do not exist, then a <mark style="color:red;">`FileNotFound`</mark> error is raised.
{% endhint %}


# fake

A basic instance of Faker to generate test data.

## What is Faker?

Put simply, Faker is a library that generates fake data for you.

{% hint style="info" %}
Check out their docs when you have some time: [https://faker.readthedocs.io](https://faker.readthedocs.io/en/stable/index.html)
{% endhint %}

## Three Ways to Use it

* <mark style="color:purple;">`py.fake`</mark> - A basic Faker instance for UI tests
* `fake fixture` - A fixture of Faker for any tests
* `Create your own` - Some users may need advanced functionality like **Locales** and **Providers**

{% hint style="info" %}
This doc will go over the first two. Check out their docs for more advanced usage.
{% endhint %}

## Syntax

```python
# Faker instance for UI tests
py.fake

---or---

# fake fixture to be used in any tests
def test_(fake)
```

## Usage

{% code title="py.fake" %}

```python
def test_new_user_flow(py):
    py.visit("https://some-page.com")
    py.get("#email").type(py.fake.email())
    py.get("#password").type(py.fake.password())
    py.contains("Login").click()
    assert py.contains("Success!")
```

{% endcode %}

{% code title="fake fixture" %}

```python
def test_fake_cc_expire(fake):
    fake.credit_card_expire(start="now", end="+10y", date_format="%m/%y")
    # '07/27'
    

def test_fake_address(fake):
    fake.address()
    # '00232 Isabel Creek\nReynoldsport, CA 05875'
```

{% endcode %}

## What can I fake?

To see a full list of all the default providers that come out of the box, go to the link below:

{% embed url="<https://faker.readthedocs.io/en/stable/providers.html>" %}

## FAQs

When I type <mark style="color:purple;">`py.fake.`</mark>, I'm not seeing <mark style="color:purple;">`address()`</mark> or anything else in your examples. What gives?

* Because of the way Faker works with their Providers, you don't get IntelliSense. This is good and bad. Bad because you don't see all the options that are available, but good because you can create your own, custom Providers to generate almost everything you'd need for your applications and systems. Just type <mark style="color:purple;">`py.fake.address()`</mark> and it will work!

Which of the three approaches should I use?

* It's entirely up to you and your needs and style. This is completely valid:

```python
def test_a_page(py, fake):
    py.visit("https://page.").get("#email").type(fake.email())
```

* If you need more advanced power, you can always create your own instance of Faker:

```python
from faker import Faker

fake = Faker()
```


# py

The main Pylenium fixture

## Usage

The <mark style="color:yellow;">**`py`**</mark> fixture is the recommended way to use Pylenium because it gives each test its own instance of Pylenium which makes it easy to scale and parallelize.

```python
from pylenium.driver import Pylenium

def test_element_should_be_visible(py: Pylenium):
    py.visit("https://demoqa.com/buttons")
    assert py.contains("Click Me").should().be_visible()
```

{% hint style="success" %}
Import <mark style="color:yellow;">**`Pylenium`**</mark> and add the ***type hint*** to your test (as shown above) so you get intellisense and autocomplete when writing tests :muscle:
{% endhint %}

## Arguments

* <mark style="color:yellow;">**`none`**</mark>

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - an instance of Pylenium driver that interacts with the web

## py\_config

When using <mark style="color:yellow;">**`py`**</mark>, an instance of <mark style="color:orange;">**PyleniumConfig**</mark> is also created and can be managed per test. You can access <mark style="color:yellow;">**`py_config`**</mark> as a fixture or directly from <mark style="color:yellow;">**`py.config`**</mark>

### Access by Fixture

```python
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

def test_element_should_be_visible(py: Pylenium, py_config: PyleniumConfig):
    # Only this test will be against firefox
    py_config.driver.browser = "firefox"
    py.visit("https://demoqa.com/buttons")
    assert py.contains("Click Me").should().be_visible()
```

### Access Directly (recommended)

Recommended because it's fewer lines of code and you already have access via <mark style="color:orange;">**`Pylenium`**</mark>

```python
from pylenium.driver import Pylenium

def test_element_should_be_visible(py: Pylenium):
    # Only this test will be against firefox
    py.config.driver.browser = "firefox"
    py.visit("https://demoqa.com/buttons")
    assert py.contains("Click Me").should().be_visible()
```


# pyc

An instance of Pylenium for a Test Class

## Usage

The <mark style="color:yellow;">**`pyc`**</mark> fixture is used if you want a single instance of Pylenium that is *<mark style="color:red;">**shared across tests**</mark>* within a Test Class.

```python
from pylenium.driver import Pylenium

class TestSauceDemo:
    def test_land_on_products_page_after_login(self, pyc: Pylenium):
        pyc.visit("https://www.saucedemo.com/")
        pyc.get("#user-name").type("standard_user")
        pyc.get("#password").type("secret_sauce")
        pyc.get("#login-button").click()
        assert pyc.contains("Products").should().be_visible()
        
    def test_add_item_to_cart_increments_counter_by_1(self, pyc: Pylenium):
        pyc.get("[id*='add-to-cart']").click()
        assert pyc.get("a.shopping_cart_link").should().have_text("1")
```

{% hint style="success" %}
Import <mark style="color:yellow;">**`Pylenium`**</mark> and add the ***type hint*** to your tests (as shown above) so you get intellisense and autocomplete when writing tests :muscle:
{% endhint %}

## Arguments

* <mark style="color:yellow;">**`none`**</mark>

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - an instance of Pylenium driver that interacts with the web

## pyc\_config

When using <mark style="color:yellow;">**`pyc`**</mark>, an instance of <mark style="color:orange;">**PyleniumConfig**</mark> is also created and can be managed per test class. You can access <mark style="color:yellow;">**`pyc_config`**</mark> as a fixture or directly from <mark style="color:yellow;">**`pyc.config`**</mark>

### Access by Fixture

```python
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

class TestSauceDemo:
    def test_land_on_products_page_after_login(self, pyc: Pylenium, pyc_config: PyleniumConfig):
        pyc_config.custom["user"] = "standard_user" # Set a value in one test...
        
        pyc.visit("https://www.saucedemo.com/")
        pyc.get("#user-name").type("standard_user")
        pyc.get("#password").type("secret_sauce")
        pyc.get("#login-button").click()
        assert pyc.contains("Products").should().be_visible()
        
    def test_add_item_to_cart_increments_counter_by_1(self, pyc: Pylenium, pyc_config: PyleniumConfig):
        print(pyc_config.custom.get("user")) # And use it in another test
        pyc.get("[id*='add-to-cart']").click()
        assert pyc.get("a.shopping_cart_link").should().have_text("1")
```

### Access Directly (recommended)

Recommended because it's fewer lines of code and you already have access via <mark style="color:orange;">**`Pylenium`**</mark>

```python
from pylenium.driver import Pylenium

class TestSauceDemo:
    def test_land_on_products_page_after_login(self, pyc: Pylenium):
        pyc.config.custom["user"] = "standard_user" # Set a value in one test...
        
        pyc.visit("https://www.saucedemo.com/")
        pyc.get("#user-name").type("standard_user")
        pyc.get("#password").type("secret_sauce")
        pyc.get("#login-button").click()
        assert pyc.contains("Products").should().be_visible()
        
    def test_add_item_to_cart_increments_counter_by_1(self, pyc: Pylenium):
        print(pyc.config.custom.get("user")) # And use it in another test
        pyc.get("[id*='add-to-cart']").click()
        assert pyc.get("a.shopping_cart_link").should().have_text("1")
```


# pys

A single instance of Pylenium for an entire Test Session

## Usage

The <mark style="color:yellow;">**`pys`**</mark> fixture is used if you want a single instance of Pylenium that is *<mark style="color:red;">**shared across all tests**</mark>* within a Test Session.

{% hint style="danger" %}
Using [**py**](/fixtures/axe-1) is the recommended way of working with Pylenium. Sharing data and states across tests is a *<mark style="color:red;">**bad practice**</mark>*. For example, *you cannot run tests in parallel in this mode*.
{% endhint %}

```python
from pylenium.driver import Pylenium

class TestSauceDemo:
    def test_land_on_products_page_after_login(self, pys: Pylenium):
        pys.visit("https://www.saucedemo.com/")
        pys.get("#user-name").type("standard_user")
        pys.get("#password").type("secret_sauce")
        pys.get("#login-button").click()
        assert pys.contains("Products").should().be_visible()
        
    def test_add_item_to_cart_increments_counter_by_1(self, pys: Pylenium):
        pys.get("[id*='add-to-cart']").click()
        assert pys.get("a.shopping_cart_link").should().have_text("1")
```

{% hint style="success" %}
Import <mark style="color:yellow;">**`Pylenium`**</mark> and add the ***type hint*** to your tests (as shown above) so you get intellisense and autocomplete when writing tests :muscle:
{% endhint %}

## Arguments

* <mark style="color:yellow;">**`none`**</mark>

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - an instance of Pylenium driver that interacts with the web

## pys\_config

When using <mark style="color:yellow;">**`pys`**</mark>, an instance of <mark style="color:orange;">**PyleniumConfig**</mark> is also created and can be managed for the test session. You can access <mark style="color:yellow;">**`pys_config`**</mark> as a fixture or directly from <mark style="color:yellow;">**`pys.config`**</mark>

### Access by Fixture

```python
from pylenium.driver import Pylenium
from pylenium.config import PyleniumConfig

class TestSauceDemo:
    def test_land_on_products_page_after_login(self, pys: Pylenium, pys_config: PyleniumConfig):
        pys_config.custom["user"] = "standard_user" # Set a value in one test...
        
        pys.visit("https://www.saucedemo.com/")
        pys.get("#user-name").type("standard_user")
        pys.get("#password").type("secret_sauce")
        pys.get("#login-button").click()
        assert pys.contains("Products").should().be_visible()
        
    def test_add_item_to_cart_increments_counter_by_1(self, pys: Pylenium, pys_config: PyleniumConfig):
        print(pys_config.custom.get("user")) # And use it in another test
        pys.get("[id*='add-to-cart']").click()
        assert pys.get("a.shopping_cart_link").should().have_text("1")
```

### Access Directly (recommended)

Recommended because it's fewer lines of code and you already have access via <mark style="color:orange;">**`Pylenium`**</mark>

```python
from pylenium.driver import Pylenium

class TestSauceDemo:
    def test_land_on_products_page_after_login(self, pys: Pylenium):
        pys.config.custom["user"] = "standard_user" # Set a value in one test...
        
        pys.visit("https://www.saucedemo.com/")
        pys.get("#user-name").type("standard_user")
        pys.get("#password").type("secret_sauce")
        pys.get("#login-button").click()
        assert pys.contains("Products").should().be_visible()
        
    def test_add_item_to_cart_increments_counter_by_1(self, pys: Pylenium):
        print(pys.config.custom.get("user")) # And use it in another test
        pys.get("[id*='add-to-cart']").click()
        assert pys.get("a.shopping_cart_link").should().have_text("1")
```


# Driver Commands


# Overview

Pylenium offers many commands and features out of the box.

## py

This is the main object in Pylenium. This is essentially the **Bot or Browser** you're controlling in your tests. Navigate to websites, take screenshots, find elements to click on or enter text, and much more!

{% code title="example" %}

```python
from pylenium.driver import Pylenium


def test_visit(py: Pylenium):
    py.visit("https://qap.dev")
```

{% endcode %}

## Element and Elements

These commands allow you to interact and perform actions against an [Element](/element-commands) or [Elements](/elements-commands).

{% code title="Chain commands" %}

```python
py.get("ul").find("li").first().click()
```

{% endcode %}

{% code title="or use variables" %}

```bash
# Click the first element with id=button
element = py.get("#button")
element.click()
```

{% endcode %}

{% code title="Mix and match variables and chains" %}

```python
# Print the href value of all links on the page
elements = py.find("a")
for el in elements:
    print(el.get_attribute("href"))
```

{% endcode %}

{% code title="Use what is best for you :)" %}

```python
# Check all checkboxes
py.find("input.checkbox").check()
```

{% endcode %}


# Navigation

Commands to navigate the driver to different web sites and pages.

* [go](/driver-commands/navigation/go)        Navigate the browser forward or back in the browser history
* [quit](/driver-commands/navigation/quit)      Closes the browser and all associated windows and tabs
* [visit](/driver-commands/navigation/visit)      Navigates the browser to the given URL and opens the website or page&#x20;


# go

Navigate forward or back in the browser's history.

## Syntax

```python
py.go(direction: str) -> Pylenium
py.go(direction: str, number: int) -> Pylenium
```

## Usage

* Go forward one page

```python
py.go("forward")
```

* Go back two pages

```python
py.go("back", 2)
```

## Arguments

* <mark style="color:purple;">`direction (str)`</mark> - forward or back
* <mark style="color:purple;">`number=1 (int)`</mark> - go back or forward N pages in history

{% hint style="warning" %}
**`number`** must be a positive integer.
{% endhint %}

## Yields

* <mark style="color:orange;">**Pylenium**</mark>**&#x20;-** The current instance of Pylenium so you can chain commands.


# quit

The command to quit the driver and close all associated windows.

## Syntax

```python
py.quit() -> None
```

## Usage

{% code title="correct usage" %}

```python
py.quit()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'quit' terminates the current browser session
py.quit().get()
```

{% endcode %}

## Arguments

* None

## Yields

* None


# reload

The command to reload or "refresh" the current page

## Syntax

```python
py.reload() -> Pylenium
```

## Usage

```python
py.reload()
```

## Arguments

* None

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of **Pylenium** so you can *chain* another command

## Examples

```python
# Reload the page and click on the About link
py.reload().contains("About").click()
```

&#x20;


# visit

The command to navigate to URLs.

## Syntax

```python
py.visit(url: str) -> Pylenium
```

## Usage

```bash
py.visit("https://qap.dev")
```

## Arguments

* <mark style="color:purple;">`url (str)`</mark> - the URL to visit

{% hint style="info" %}
Make sure to include the protocol **http** or **https**
{% endhint %}

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of **Pylenium** so you can *chain* another command

## Examples

```bash
# Navigate to a URL
py.visit("https://qap.dev")
```

```bash
# Navigate to a URL and click on About link
py.visit("https://qap.dev").contains("About").click()
```


# Find Elements

How to find one or more elements in Pylenium

Pylenium provides 5 main ways to find elements:

* [contains](/driver-commands/find-elements/contains)   get a <mark style="color:yellow;">single element</mark> by **TEXT**
* [find](/driver-commands/find-elements/find)           find a <mark style="color:yellow;">list of elements</mark> by **CSS**
* [findx](/driver-commands/find-elements/find_xpath)         find a <mark style="color:yellow;">list of elements</mark> by **XPATH**
* [get](/driver-commands/find-elements/get)            get a <mark style="color:yellow;">single element</mark> by **CSS**
* [getx](/driver-commands/find-elements/get_xpath)          get a <mark style="color:yellow;">single element</mark> by **XPATH**


# contains

The command to get a single Element containing the given text.

## Syntax

```python
py.contains(text: str) -> Element
py.contains(text: str, timeout: int) -> Element

---or---

Element.contains(text: str) -> Element
Element.contains(timeout: int) -> Element
```

## Usage

{% code title="correct usage" %}

```python
# Yield Element in .nav containing "About"
py.get(".nav").contains("About")

---or---

# Yield first Element in document containing 'Hello'
py.contains("Hello")

---or--- # store in a variable

element = py.contains("About")

---or--- # chain an Element command

py.contains("About").click()

---or--- # control the timeout in any of the above usages

py.contains("Deck Builder", timeout=5).click()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'title' does not yield Element
py.title().contains("QAP")

---or---

# Errors, 'get_cookies' does not yield Element
py.get_cookies().contains("Cooke Monster")
```

{% endcode %}

## Arguments

* `text (str)` - The text to look for
* `timeout=None (int)` - The number of seconds for this command to succeed.
  * <mark style="color:purple;">`timeout=None`</mark> will use the default <mark style="color:orange;">**wait\_time**</mark> in [pylenium.json](/configuration/pylenium-json)
  * <mark style="color:purple;">`timeout=0`</mark> will poll the DOM immediately with no wait
  * Any value greater than zero will override the default **wait\_time**

{% hint style="info" %}
It does not need to be an *exact* match
{% endhint %}

## Yields

* <mark style="color:orange;">**Element**</mark> - The first element that is found, even if multiple elements match the query


# find

The command to get a list of Elements that match the CSS selector.

## Syntax

```python
py.find(css: str) -> Elements
py.find(css: str, timeout: int) -> Elements

---or---

Element.find(css: str) -> Elements
Element.find(css: str, timeout: int) -> Elements
```

## Usage

{% code title="correct usage" %}

```python
# Yield Elements in .nav with tag name of a
py.get(".nav").find("a")

---or---

# Yield all Elements in the DOM with id of 'button'
py.find("#button")

---or--- # store in a variable

elements = py.find("li")

---or--- # chain an Elements command

element = py.find("ul > li").first()

---or--- # control the timeout in any of the above usages

py.find("li", timeout=5).last()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'title' does not yield Element
py.title.find("QAP")

---or---

# Errors, 'get_cookie' does not yield Element
py.get_cookie().find("Cooke Monster")
```

{% endcode %}

## Arguments

* `css (str)` - The CSS selector to use
* `timeout=None (int)` - The number of seconds for this command to succeed.
  * <mark style="color:purple;">`timeout=None`</mark> will use the default <mark style="color:orange;">**wait\_time**</mark> in [pylenium.json](/configuration/pylenium-json)
  * <mark style="color:purple;">`timeout=0`</mark> will poll the DOM immediately with no wait
  * Greater than zero will *override* the default <mark style="color:orange;">**wait\_time**</mark>

## Yields

* <mark style="color:orange;">**Elements**</mark> - A list of elements that match the query.

## Examples

```python
# If you expect the elements not to be present
assert py.find("ul > li").should().be_empty()

# Otherwise, just use the default
elements = py.find("ul > li")
```


# findx

The command to get a list of Elements that match the XPath selector.

## Syntax

```python
py.findx(xpath: str) -> Elements
py.findx(xpath: str, timeout: int) -> Elements

---or---

Element.findx(xpath: str) -> Elements
Element.findx(xpath: str, timeout: int) -> Elements
```

## Usage

{% code title="correct usage" %}

```python
# Yield all Elements in .nav with tag name of a
py.get(".nav").findx("//a")

---or---

# Yield all Elements in document with id of 'button'
py.findx("//*[@id='button']")

---or--- # store in a variable

elements = py.findx("//*[@id='button']")

---or--- # chain an Element(s) command

# if one element is found, still returns a list of 1: [Element]
py.findx("//*[@id='button']").first().click()

---or--- # control the timeout in any of the above usages

py.findx("//a[@href='/about']", timeout=5).length()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'title' does not yield Element
py.title().findx("//a")

---or---

# Errors, 'get_cookie' does not yield Element
py.get_cookie().findx("//[text()='foo' and @class='bar']")
```

{% endcode %}

## Arguments

* `xpath (str)` - The XPATH selector to use
* `timeout=None (int)` - The number of seconds for this command to succeed.
  * <mark style="color:purple;">`timeout=None`</mark> will use the default <mark style="color:orange;">**wait\_time**</mark> in [pylenium.json](/configuration/pylenium-json)
  * <mark style="color:purple;">`timeout=0`</mark> will poll the DOM immediately with no wait
  * Greater than zero will *override* the default <mark style="color:orange;">**wait\_time**</mark>

## Yields

* <mark style="color:orange;">**Elements**</mark>**&#x20;-** The list of elements found.
  * If none are found, returns an empty list
  * If one or more are found, return the list normally

## Examples

```python
# There should be 3 `a` elements
py.findx("//a").should().have_length(3)
```


# get

The command to get a single Element that matches the CSS selector.

## Syntax

```python
py.get(css: str) -> Element
py.get(css: str, timeout: int) -> Element

---or---

Element.get(css: str) -> Element
Element.get(css: str, timeout: int) -> Element
```

## Usage

{% code title="correct usage" %}

```python
# Yield Element in .nav with tag name of a
py.get(".nav").get("a")

---or---

# Yield first Element in the DOM with id of 'button'
py.get("#button")

---or--- # store in a variable

element = py.get("#login")

---or--- # chain an Element command

py.get("#save-button").click()

---or--- # control the timeout in any of the above usages

py.get("a[href='/about']", timeout=5).click()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'title' does not yield Element
py.title.get("QAP")

---or---

# Errors, 'get_cookie' does not yield Element
py.get_cookie().get("Cooke Monster")
```

{% endcode %}

## Arguments

* `css (str)` - The CSS selector to use
* `timeout=None (int)` - The number of seconds for this command to succeed.
  * <mark style="color:purple;">`timeout=None`</mark> will use the default <mark style="color:orange;">**wait\_time**</mark> in [pylenium.json](/configuration/pylenium-json)
  * <mark style="color:purple;">`timeout=0`</mark> will poll the DOM immediately with no wait
  * Greater than zero will *override* the default <mark style="color:orange;">**wait\_time**</mark>

## Yields

* <mark style="color:orange;">**Element**</mark> - The first element that is found, even if multiple elements match the query.


# getx

The command to get a single Element using an XPath selector.

## Syntax

```python
py.getx(xpath: str) -> Element
py.getx(xpath: str, timeout: int) -> Element

---or---

Element.getx(xpath: str) -> Element
Element.getx(xpath: str, timeout: int) -> Element
```

## Usage

{% code title="correct usage" %}

```python
# Yield the first Element in .nav with tag name of a
py.get(".nav").getx("//a")

---or---

# Yield the first Element in document with id of 'button'
py.getx("//*[@id='button']")

---or--- # store in a variable

element = py.getx("//*[@id='button']")

---or--- # chain an Element(s) command

# chain an action
py.getx("//*[@id='button']").click()

---or--- # control the timeout in any of the above usages

py.getx("//a[@href='/about']", timeout=5).click()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'title' does not yield Element
py.title.getx("//a")

---or---

# Errors, 'get_cookie' does not yield Element
py.get_cookie().getx("//[text()='foo' and @class='bar']")
```

{% endcode %}

## Arguments

* `xpath (str)` - The XPATH selector to use
* `timeout=None (int)` - The number of seconds for this command to succeed.
  * <mark style="color:purple;">`timeout=None`</mark> will use the default <mark style="color:orange;">**wait\_time**</mark> in [pylenium.json](/configuration/pylenium-json)
  * <mark style="color:purple;">`timeout=0`</mark> will poll the DOM immediately with no wait
  * Greater than zero will override the default <mark style="color:orange;">**wait\_time**</mark>

## Yields

* <mark style="color:orange;">**Element**</mark> - The first element found, even if multiple elements match the query.

## Examples

```python
# The button should be displayed
py.getx("//*[@id='button']").should().be_visible()
```


# Driver.should()

A collection of expected conditions against the current browser.

## Expectations

* <mark style="color:purple;">`.contain_title(title: str)`</mark> - The substring for the title to contain
* <mark style="color:purple;">`.contain_url(url: str)`</mark> - The substring for the URL to contain
* <mark style="color:purple;">`.have_title(title: str)`</mark> - The case-sensitive title to match
* <mark style="color:purple;">`.have_url(url: str)`</mark> - The case-sensitive url to match
* <mark style="color:purple;">`.not_find(css: str)`</mark> - The CSS selector
* <mark style="color:purple;">`.not_findx(xpath: str)`</mark> - The XPATH selector
* <mark style="color:purple;">`.not_contain(text: str)`</mark> - The text to contain

## Syntax

```python
# Use the default wait_time
py.should().<expectation>

---or---

# Customize the wait_time for this expectation
py.should(timeout: int).<expectation>

---or---

# Ignore exceptions that you expect to "get in the way"
py.should(ignored_exceptions: list).<expectation>

---or---

# Customize both fully
py.should(timeout: int, ignored_exceptions: list).<expectation>
```

## Examples

{% code title=".contain\_title()" %}

```python
def test_title_contains(py):
    py.visit("https://qap.dev")
    assert py.should().contain_title("QA")
```

{% endcode %}

{% code title=".contain\_url()" %}

```python
def test_url_contains(py):
    py.visit("https://qap.dev")
    assert py.should().contain_url("www.qap.dev")
```

{% endcode %}

{% code title=".have\_title()" %}

```python
def test_title_matches(py):
    py.visit("https://qap.dev")
    assert py.should().have_title("QA at the Point")
```

{% endcode %}

{% code title=".have\_url()" %}

```python
def test_url_matches(py):
    py.visit("https://qap.dev")
    assert py.should().have_url("https://www.qap.dev/")
```

{% endcode %}

{% code title=".not\_find()" %}

```python
def test_page_does_not_have_element(py):
    py.visit("https://qap.dev")
    assert py.should().not_find("#zaboomafoo")
```

{% endcode %}

{% code title=".not\_findx()" %}

```python
def test_page_does_not_have_element(py):
    py.visit("https://qap.dev")
    assert py.should().not_findx("//*[@id='zaboomafoo']")
```

{% endcode %}

{% code title=".not\_contain()" %}

```python
def test_text_should_not_be_present_on_page(py):
    py.visit("https://qap.dev")
    assert py.should().not_contain("zaboomafoo")
```

{% endcode %}

{% code title="Customize timeout" %}

```python
def test_title_matches_within_5_seconds(py):
    py.visit("https://qap.dev")
    # Override global timeout for only this action
    assert py.should(timeout=5).have_title("QA at the Point")
```

{% endcode %}

{% code title="Ignore exceptions" %}

```python
def test_title_matches(py):
    py.visit("https://qap.dev")
    # These exceptions will not stop the "wait until"
    exceptions = [WebDriverException, NoSuchElementException]
    assert py.should(ignored_exceptions=exceptions).have_title("QA at the Point")
```

{% endcode %}

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - If the assertion passes, then the current instance of Pylenium is returned, else an **AssertionError** is raised if the condition is not met within the specified timeout.
* **bool** - for the Find Element expectations.


# Browser

Other commands that deal with the browser windows, tabs, URL, title, and more!


# execute\_script

The command to execute javascript into the browser.

## Syntax

```python
py.execute_script(javascript: str) -> Any
py.execute_script(javascript: str, *args) -> Any
```

## Usage

{% code title="correct usage" %}

```python
# Yields the value of document.title
py.execute_script("return document.title;")

---or---

# Yields the .innerText of the element with the id of 'foo'
py.execute_script("return document.getElementById(arguments[0]).innerText", "foo")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'execute_script' yields a WebElement, not a Pylenium Element
py.execute_script("return document.getElementById(arguments[0])").get()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`javascript (str)`</mark> - The javascript to execute
* <mark style="color:purple;">`*args (Any)`</mark> - A comma-separated list of arguments to pass into the javascript string

{% hint style="info" %}
You can access the **\*args** in the javascript by using `arguments[0]`, `arguments[1]`, etc.
{% endhint %}

## Yields

* <mark style="color:orange;">**Any**</mark> - This will return whatever is in the `return statement` of your javascript.

{% hint style="info" %}
If you do not include a **return**, then `.execute_script()` will return **None**
{% endhint %}

## Examples

```python
# You can pass in complex objects
ul_element = py.get("ul")
py.execute_script("return arguments[0].children;", ul_element.webelement)
# We use the .webelement property to send Selenium's WebElement
# that is understood by the browser
```

```python
# You can create complex javascript strings
get_siblings_script = '''
    elem = document.getElementById(arguments[0]);
    var siblings = [];
    var sibling = elem.parentNode.firstChild;

    while (sibling) {
        if (sibling.nodeType === 1 && sibling !== elem) {
            siblings.push(sibling);
        }
        sibling = sibling.nextSibling
    }
    return siblings;
    '''
siblings = self.py.execute_script(get_siblings_script, "foo")
```


# execute\_async\_script

The command to execute async javascript into the browser.

Similar to the [execute\_script](/driver-commands/browser/execute_script) command, you can pass in any <mark style="color:purple;">javascript string</mark> and <mark style="color:purple;">\*args</mark>. The main difference is that you can execute *<mark style="color:yellow;">**asynchronous**</mark>* javascript. For example, using **callbacks**.

```python
script = "var callback = arguments[arguments.length - 1]; " \
         "window.setTimeout(function(){ callback('timeout') }, 3000);"
driver.execute_async_script(script)
```

## Syntax

```python
py.execute_async_script(javascript: str) -> Any
py.execute_async_script(javascript: str, *args) -> Any
```

## Usage

{% code title="correct usage" %}

```python
# Yields the value of document.title
py.execute_async_script("return document.title;")

---or---

# Yields the .innerText of the element with the id of 'foo'
py.execute_async_script("return document.getElementById(arguments[0]).innerText", "foo")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'execute_script' yields a WebElement, not a Pylenium Element
py.execute_async_script("return document.getElementById(arguments[0])").get()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`javascript (str)`</mark> - The async javascript to execute
* <mark style="color:purple;">`*args (Any)`</mark> - A comma-separated list of arguments to pass into the javascript string

{% hint style="info" %}
You can access the **\*args** in the javascript by using `arguments[0]`, `arguments[1]`, etc.
{% endhint %}

## Yields

* <mark style="color:orange;">**Any**</mark> - This will return whatever is in the `return statement` of your javascript.

{% hint style="info" %}
If you do not include a **return**, then `.execute_async_script()` will return **None**
{% endhint %}

## Examples

```python
# You can pass in complex objects
ul_element = py.get("ul")
py.execute_script("return arguments[0].children;", ul_element.webelement)
# We use the .webelement property to send Selenium's WebElement
# that is understood by the browser
```

```python
# You can create complex javascript strings
get_siblings_script = '''
    elem = document.getElementById(arguments[0]);
    var siblings = [];
    var sibling = elem.parentNode.firstChild;

    while (sibling) {
        if (sibling.nodeType === 1 && sibling !== elem) {
            siblings.push(sibling);
        }
        sibling = sibling.nextSibling
    }
    return siblings;
    '''
siblings = self.py.execute_async_script(get_siblings_script, "foo")
```


# maximize\_window

The command the maximize the current window.

## Syntax

```python
py.maximize_window() -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
# By default, Pylenium will maximize the window for you, but just in case...
py.maximize_window().visit("https://qap.dev")
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands


# screenshot

The command to take a screenshot of the current window.

## Syntax

```python
py.screenshot(filename: str) -> None
```

## Usage

{% code title="correct usage" %}

```python
# saves the screenshot to the current working directory
py.screenshot("ss.png")

---or---

# saves the screenshot using the filepath
py.screenshot("../images/ss.png")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, include the file extension like '.png'
py.screenshot("ss")

---or---

# Errors, .screenshot() yields None
py.screenshot("ss.png").get("a")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`filename (str)`</mark> - The filename including the **path** to the directory you want to save it in

{% hint style="info" %}
Make sure to include the file extension like **.png**
{% endhint %}

## Yields

* None


# scroll\_to

The command to scroll to the given location.

## Syntax

```python
py.scroll_to(x: int, y: int) -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
# scroll down 500px
py.scroll_to(0, 500)
```

{% endcode %}

## Arguments

* <mark style="color:purple;">**`x (int)`**</mark>: The number of pixels to scroll horizontally
* <mark style="color:purple;">**`y (int)`**</mark>: The number of pixels to scroll vertically

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - so you can chain another command


# title

The command to get the current page's title.

## Syntax

```python
py.title() -> str
```

## Usage

{% code title="correct usage" %}

```python
py.title()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
py.title
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**str**</mark>  - The <mark style="color:purple;">`document.title`</mark> property of the current page

## Examples

```python
assert py.title() == "QA at the Point"
```


# url

The command to get the current page's URL.

## Syntax

```python
py.url() -> str
```

## Usage

{% code title="correct usage" %}

```
py.url()
```

{% endcode %}

{% code title="incorrect usage" %}

```
py.url
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**str**</mark> - The current page's URL

## Examples

```python
assert py.url().endswith("/checkout")
```


# window\_handles

This property gets a list of all the window handles in the current browser session.

## Syntax

```python
py.window_handles -> List[str]
```

## Usage

{% code title="correct usage" %}

```python
# this property is mainly used to switch to windows or tabs

# assert that there are two windows - the main website and a new tab
windows = py.window_handles
assert len(windows) == 2

# then switch to the new tab
py.switch_to.window(name_or_handle=windows[1])
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**List\[str]**</mark> - A list of all the window handles in the current browser session.


# window\_size

This property get the size of the current window.

## Syntax

```python
py.window_size -> Dict[str, int]
```

## Usage

{% code title="correct usage" %}

```python
size = py.window_size

# print the width
print(size["width"])

# print the height
print(size["height"]
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Dict\[str, int]**</mark> - The current window's size as a dictionary


# viewport

The command to control the size and orientation of the current browser window.

## Syntax

```python
py.viewport(width: int, height: int) -> Pylenium
py.viewport(width: int, height: int, orientation: str) -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
py.viewport(1280, 800) # macbook-13 size
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`width (int)`</mark> - The width in pixels
* <mark style="color:purple;">`height (int)`</mark> - The height in pixels
* <mark style="color:purple;">`orientation="portrait" (str)`</mark> - Pass <mark style="color:purple;">`"landscape"`</mark> to reverse the width and height

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can change commands

## Examples

```python
py.viewport(1280, 800) # macbook-13 size
py.viewport(1440, 900) # macbook-15 size
py.viewport(375, 667, orientation="landscape")  # iPhone X size
```


# Cookies

Driver commands to work with browser cookies.


# delete\_all\_cookies

The command to delete all cookies in the current browser session.

## Syntax

```python
py.delete_all_cookies() -> None
```

## Usage

{% code title="correct usage" %}

```python
py.delete_all_cookies()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'delete_all_cookies' yields None
py.delete_all_cookies().get_cookie()
```

{% endcode %}

## Arguments

* None

## Yields

* None


# delete\_cookie

The command to delete a cookie with the given name.

## Syntax

```python
py.delete_cookie(name: str) -> None
```

## Usage

{% code title="correct usage" %}

```python
py.delete_cookie("foo")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'delete_cookie' yields None
py.delete_cookie("foo").get_cookie()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`name (str)`</mark> - The name of the cookie

## Yields

* None


# get\_all\_cookies

The command to get all cookies in the current browser session.

## Syntax

```python
py.get_all_cookies() -> List[Dict]
```

## Usage

{% code title="correct usage" %}

```python
py.get_all_cookies()

---or--- # store in a variable

cookies = py.get_all_cookies()
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:purple;">**`List[Dict]`**</mark> A list of cookie objects. Each cookie object has the following properties:
  * `name`
  * `value`
  * `path`
  * `domain`
  * `httpOnly`
  * `secure`
  * `expiry`

## Examples

```python
py.set_cookie({"name": "foo", "value": "bar"})

cookie = py.get_all_cookies()[0]

print(cookie["name"])      # "foo"
print(cookie.get("value")) # "bar"
```

```python
py.set_cookie({"name": "foo", "value": "bar"})
py.set_cookie({"name": "yes", "value", "please"})

for cookie in py.get_all_cookies():
    print(cookie["name"])
    print(cookie.get("value"))
```


# get\_cookie

The command to get the cookie with the given name.

## Syntax

```python
py.get_cookie(name: str) -> dict
```

## Usage

{% code title="correct usage" %}

```python
py.get_cookie("foo")

---or--- # "key" into the dictionary

val = py.get_cookie("foo")["value"]

---or--- # use the .get() function in dict

val = py.get_cookie("foo").get("value")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`name (str)`</mark> - The name of the cookie

## Yields

* <mark style="color:purple;">**`Dict`**</mark>**&#x20;-** The cookie as a dictionary. Cookie objects have the following properties:
  * `name`
  * `value`
  * `path`
  * `domain`
  * `httpOnly`
  * `secure`
  * `expiry`

{% hint style="warning" %}
Returns **None** if the cookie does not exist
{% endhint %}


# set\_cookie

The command to set a cookie into the current browser session.

## Syntax

```python
py.set_cookie(cookie: dict) -> None
```

## Usage

{% code title="correct usage" %}

```python
py.set_cookie({"name" : "foo", "value" : "bar"})
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'set_cookie' accepts a single argument that is a Dict
py.set_cookie("foo", "bar")

---or---

# Errors, 'set_cookie' yields None
py.set_cookie({"name" : "foo", "value" : "bar"}).get_cookie()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`cookie (Dict)`</mark> - A dictionary with required keys: `"name"` and `"value"`

{% hint style="info" %}
Optional keys: `"path"`, `"domain"`, `"secure"`, `"expiry"`
{% endhint %}

## Yields

* None


# Switch To

Commands to switch the driver's context between tabs, windows, and iframes.

* [default\_content](/driver-commands/switch-to/switch_to.default_content) - TBD
* [frame](/driver-commands/switch-to/switch_to.frame) - Switch to different frames like *iframes*
* [parent\_frame](/driver-commands/switch-to/switch_to.parent_frame) - TBD
* [window](/driver-commands/switch-to/switch_to.window) - TBD


# default\_content

The command to switch the driver's context to the default (or starting) content.

## Syntax

```python
py.switch_to.default_content() -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
py.switch_to.default_content()

---or--- # chain a Pylenium command in the new context

py.switch_to.default_content().get(".link")
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands

{% hint style="info" %}
If the driver is already in the default context, nothing changes
{% endhint %}


# frame

The command to switch the driver's context to the frame given its name or id.

## Syntax

```python
py.switch_to.frame(name_or_id: str, timeout: int = 0) -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
# switch to an iframe with name of 'main-content'
py.switch_to.frame("main-content")

---or--- # chain a Pylenium command

py.switch_to.frame("main-content").contains("Add New").click()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`name_or_id (str)`</mark> - The **name** or **id** attribute value of the `<frame>` element
* <mark style="color:purple;">`timeout=0 (int)`</mark> - The number of seconds to wait for the frame to be switched to

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands

## Examples

```html
<div>
    <frame id='foo'>
        <a href='/different-page' id='bar'>Link in iframe</a>
    </frame>
</div>
```

If we wanted to click the link above, we would need to:

1. Switch the driver's context to the iframe
2. Then perform the click

This is a piece of cake with Pylenium:

```python
py.switch_to.frame("foo").get("#bar").click()
```


# frame\_by\_element

The command to switch the driver's context to the given element.

## Syntax

```python
py.switch_to.frame_by_element(element: Element, timeout: int = 0) -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
iframe = py.get("iframe")
py.switch_to.frame_by_element(iframe)

---or--- # chain a Pylenium command

iframe = py.get("iframe")
py.switch_to.frame_by_element(iframe).contains("Add New").click()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`element (Element)`</mark> - The <mark style="color:orange;">**Element**</mark> to switch to
* <mark style="color:purple;">`timeout=0 (int)`</mark> - The number of seconds to wait for the frame to be switched to

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands

## Examples

```html
<div>
    <frame id='foo'>
        <a href='/different-page' id='bar'>Link in iframe</a>
    </frame>
</div>
```

If we wanted to click the link above, we would need to:

1. Switch the driver's context to the iframe
2. Then perform the click

This is a piece of cake with Pylenium:

```python
iframe = py.get("#foo")
py.switch_to.frame_by_element(iframe).get("#bar").click()
```


# parent\_frame

The command to switch the driver's context to the parent frame of the current frame.

## Syntax

```python
py.switch_to.parent_frame() -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
# Switch to a frame with name of 'iframe'
py.switch_to.frame("iframe")

# Switch back to the main website
py.switch_to.parent_frame()
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands

## Examples

```html
<div>
    <frame id='foo'>
        <button>Button in iframe</button>
    </frame>
    <button id='bar'>Button in main html (aka default content)</button>
</div>
```

```python
# Switch to the iframe to click the 'Button in iframe'
py.switch_to.frame("foo").contains("Button in iframe").click()

# Switch back to the main html to click the 'bar' button
py.switch_to.parent_frame().get("#bar").click()
```


# window

The command to switch the driver's context to the specified Window or Browser Tab.

## Syntax

```python
py.switch_to.window(name_or_handle: str) -> Pylenium
py.switch_to.window(index: int) -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
# Switch to a Window by handle
windows = py.window_handles
py.switch_to.window(name_or_handle=windows[1])
```

{% endcode %}

{% code title="correct usage" %}

```python
# switch to a newly opened Browser Tab by index
py.switch_to.window(index=1)
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`name_or_handle="" (str)`</mark> - The **name** or **window handle** of the Window to switch to
* <mark style="color:purple;">`index=0 (int)`</mark> - The index position of the Window Handle

{% hint style="info" %}
**index=0** will switch to the default content
{% endhint %}

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands


# new\_window

The command to open a new browser window and switch to it.

## Syntax

```python
py.switch_to.new_window() -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
# Open a new window and hold it in a variable
window = py.switch_to.new_window()

---or---

# Open a new window and chain a command
py.switch_to.new_window().visit("https://qap.dev")
```

{% endcode %}

## Arguments

* None

{% hint style="info" %}
**index=0** will switch to the default content
{% endhint %}

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands in the new window


# new\_tab

The command to open a new browser tab and switch to it.

## Syntax

```python
py.switch_to.new_tab() -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
# Open a new window and hold it in a variable
tab = py.switch_to.new_tab()

---or---

# Open a new window and chain a command
py.switch_to.new_tab().visit("https://qap.dev")
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands in the new tab


# Web Performance

Pylenium provides different APIs to capture web performance metrics.


# Performance API

Pylenium's custom performance API to capture different metrics.

## Syntax

```python
py.performance -> Performance
py.performance.get() -> WebPerformance
```

## Usage

The <mark style="color:orange;">**Performance**</mark> class is where everything lives. It provides access to the following methods:

### WebPerformance

The main method used to generate a <mark style="color:orange;">**WebPerformance**</mark> object from the current web page. This is really all you need from this API and will be discussed in more detail [further below in Metrics](#metrics).

{% hint style="danger" %}
Calling this method too soon may yield NoneTypes because the browser hasn't generated them yet.
{% endhint %}

```python
py.performance.get() -> WebPerformance
```

### Time Origin

Return the <mark style="color:yellow;">**timeOrigin**</mark> precision value. This is the high-resolution timestamp of the start time of the performance measurement.

```python
py.performance.get_time_origin() -> float
```

### Navigation Timing

Return the <mark style="color:yellow;">**PerformanceNavigationTiming**</mark> W3 object as a Python object called <mark style="color:orange;">**NavigationTiming**</mark>.

```python
py.performance.get_navigation_timing() -> NavigationTiming
```

### Paint Timing

Return the <mark style="color:yellow;">**PerformancePaintTiming**</mark> object as a Python object called <mark style="color:orange;">**PaintTiming**</mark>.

```python
py.performance.get_paint_timing() -> PaintTiming
```

### Resources

Return a list of <mark style="color:yellow;">**PerformanceResourceTiming**</mark> objects as Python objects called <mark style="color:orange;">**\[ResourceTiming]**</mark>.

```python
py.performance.get_resources() -> List[ResourceTiming]
```

## Metrics

All of the timing objects include A LOT of data points, but many of them may not be useful.

{% hint style="info" %}
If you want to see ALL the data points, take a look at [the file in the GitHub Repo](https://github.com/ElSnoMan/pyleniumio/blob/main/pylenium/performance.py)
{% endhint %}

This is why the <mark style="color:orange;">**WebPerformance**</mark> object exists! It contains calculations for metrics that have been very useful when we talk about and measure web performance. After navigating to a website and capturing these metrics with <mark style="color:purple;">`py.performance.get()`</mark>, we can do whatever we want with them!

With a few lines of code, you have access to many valuable metrics:

```python
# perf will be used in the examples below
py.visit("https://your-website.com")
perf = py.performance.get()
```

### Page Load Time

The time it takes for the page to load as experienced by the user.

```python
perf.page_load_time() -> float
```

### Time to First Byte

The time it takes before the first byte of response is received from the server.

```python
perf.time_to_first_byte() -> float
```

### Time to First Contentful Paint

The time it takes for the majority of content to be fully rendered and consumable by the user.

```python
perf.time_to_first_contentful_paint() -> float
```

### Time to Interactive (TTI)

The time it takes for the layout to be stabilized and the page is responsive.

```python
perf.time_to_interactive() -> float
```

### Number of Requests

The number of requests sent from start of navigation until end of page load.

```python
perf.number_of_requests() -> int
```

### Time to DOM Content Loaded

The time it takes for the DOM content to load.

```python
perf.time_to_dom_content_loaded() -> float
```

### Page Weight

The amount of bytes transferred for the page to be loaded.

```python
perf.page_weight() -> float
```

### Connection Time

The time taken to connect to the server.

```python
perf.connection_time() -> float
```

### Request Time

The time taken to send a request to the server and receive the response.

```python
perf.request_time() -> float
```

### Fetch Time

The time to complete the document fetch (including accessing any caches, etc.).

```python
perf.fetch_time() -> float
```

## Examples

Store the entire <mark style="color:orange;">**WebPerformance**</mark> object in a variable, then convert it to a <mark style="color:yellow;">**Dictionary**</mark> to log it.

```python
perf = py.performance.get()
py.log.info(perf.dict())
```

Store a single data point in a variable and test against it.

```python
tti = py.performance.get().time_to_interactive()
assert tti < BASELINE, f"TTI should be less than our baseline."
```


# CDP Performance

Chrome DevTools Protocol (CDP) Performance API to capture metrics.

**Selenium 4** uses the <mark style="color:yellow;">**Chrome DevTools Protocol (CDP)**</mark> which has a <mark style="color:orange;">`"Performance.getMetrics"`</mark> command! Pylenium provides a simple wrapper to capture these metrics.

## Syntax

```python
py.cdp.get_performance_metrics() -> Dict
```

## Usage

The <mark style="color:yellow;">**Dictionary**</mark> of performance metrics returned includes metrics like:

* ScriptDuration
* ThreadTime
* ProcessTime
* DomContentLoaded

{% code title="correct usage" %}

```python
metrics = py.cdp.get_performance_metrics()
```

{% endcode %}

{% code title="dictionary" %}

```python
{'metrics':
  [
    {'name': 'Timestamp', 'value': 425608.80694},
    {'name': 'AudioHandlers', 'value': 0},
    {'name': 'ThreadTime', 'value': 0.002074},
    ...
  ]
}
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:yellow;">**Dict**</mark>

## Examples

```python
def test_capture_performance_metrics(py: Pylenium):
    py.visit("https://qap.dev")
    metrics = py.cdp.get_performance_metrics()["metrics"]
    assert metrics
    assert metrics[0]["name"] == "Timestamp"
    assert metrics[0]["value"] > 0
```


# fake

A basic instance of Faker to generate test data.

{% hint style="success" %}
Read the [Official Faker Docs](https://faker.readthedocs.io/en/stable/providers.html) for more info! There's a lot you can do with it.
{% endhint %}

## Syntax

```
py.fake.<object>()
```

{% hint style="info" %}
This is a **command** and a **fixture**. More details in his doc: [**Fixtures > fake**](/fixtures/fake)
{% endhint %}

## Examples

```python
# Generate fake names
py.fake.name()
py.fake.first_name()
py.fake.last_name()

# Generate a bunch of other things!
py.fake.email()
py.fake.address()
py.fake.ssn()
```


# wait

The command to execute a method or function as a condition to wait for.

{% hint style="success" %}
Pylenium provides a <mark style="color:orange;">**Should API**</mark> for [Driver](/driver-commands/should), [Element](/element-commands/should), and [Elements](/elements-commands/elements.should) objects. This is the **recommended** way to wait for things in Pylenium.

However, you can use Selenium's ExpectedConditions class or lambdas as shown on the rest of this page.
{% endhint %}

There are two types of Wait objects:

* **WebDriverWait (default)**
  * Directly from Selenium
  * Returns <mark style="color:orange;">**`WebElement`**</mark> and <mark style="color:orange;">**`List[WebElement]`**</mark>
* **PyleniumWait**
  * Returns <mark style="color:orange;">**`Element`**</mark> and <mark style="color:orange;">**`Elements`**</mark>
  * Has a built-in <mark style="color:purple;">`.sleep()`</mark> method

<mark style="color:purple;">`wait.until(condition)`</mark> is the most common use of Wait and allows you to wait until the condition returns a *non-False* value.

However, both Waits require the condition to use a WebDriver. In the example below, we can pass in a **lambda** (aka anonymous function) where `x` *is* the **WebDriver**.

```python
# .is_displayed() returns a bool, so the return value is True
py.wait().until(lambda x: x.find_element(By.ID, "foo").is_displayed())
```

```python
# the WebElement is returned once the element is found in the DOM
py.wait().until(lambda x: x.find_element(By.ID, "foo"))
```

```python
# because use_py=True, this will now return Element instead
# also, this will wait up to 5 seconds instead of the default in pylenium.json
py.wait(5, use_py=True).until(lambda x: x.find_element(By.ID, "foo"))
```

## Syntax

```python
# all 3 parameters are Optional with defaults
py.wait(timeout=0, use_pylenium=False, ignored_exceptions: list = None)
```

## Usage

The usages are almost identical between the Wait objects, but you need to identify why you need to use a Wait in the first place. Pylenium does a lot of waiting for you automatically, but not for everything.

{% hint style="info" %}
Remember, the biggest difference is what is returned: **`WebElement`** vs **`Element`**
{% endhint %}

{% hint style="success" %}
Good framework and test design includes waiting for the right things. This is called **Synchronization**
{% endhint %}

### WebDriverWait

This is the default Wait object. This will return <mark style="color:orange;">**WebElement**</mark>, so you won't have Pylenium's Element commands like <mark style="color:purple;">`.hover()`</mark> - that is what <mark style="color:orange;">**PyleniumWait**</mark> is for.

* Using the defaults

{% code title="defaults" %}

```python
# uses WebDriverWait and returns WebElement once '#save' is found
py.wait().until(lambda x: x.find_element(By.ID, "save")).click()
```

{% endcode %}

* Using custom <mark style="color:orange;">**`timeout`**</mark>

{% code title="Custom timeout" %}

```python
# uses WebDriverWait but overrides the default wait_time used in pylenium.json
py.wait(5).until(lambda x: x.find_element(By.ID, "login-button").is_enabled())
```

{% endcode %}

* Using <mark style="color:orange;">**`ignored_exceptions`**</mark>

By default, the only exception that is ignored is the <mark style="color:purple;">`NoSuchElementException`</mark>. You can change this by adding a list of Exceptions that you want your condition to ignore.

{% code title="ignored\_exceptions" %}

```python
# ignore exceptions every time the condition is executed
# also, this will return True because
    # x.title == 'QA at the Point'
# is a boolean expression
exceptions = [NoSuchElementException, WebDriverException]
py.wait(ignored_exceptions=exceptions).until(lambda x: x.title == "Pylenium.io")
```

{% endcode %}

* Combine arguments

```python
exceptions = [NoSuchElementException, WebDriverException]
py.wait(7, ignored_exceptions=exceptions).until(lambda x: x.execute_script("js'")
```

### PyleniumWait

If you want to return Pylenium objects like <mark style="color:orange;">**`Element`**</mark> and <mark style="color:orange;">**`Elements`**</mark>, then set <mark style="color:purple;">`use_py=True`</mark>`.` Otherwise, it works the same way as WebDriverWait.

{% code title="PyleniumWait with default timeout" %}

```python
py.wait(use_py=True).until(lambda x: x.find_element(By.ID, "menu")).hover()
```

{% endcode %}

{% code title="PyleniumWait with custom timeout" %}

```python
py.wait(5, use_py=True).until(lambda x: x.find_element(By.ID, "menu")).hover()
```

{% endcode %}

* <mark style="color:orange;">**PyleniumWait**</mark> also includes a <mark style="color:purple;">**`.sleep()`**</mark> command

```python
# time.sleep() for 3 seconds
py.wait(use_py=True).sleep(3)
```

### Expected Conditions

Expected Conditions are a list of pre-built conditions that you can use in your Waits and can be used in either WebDriverWait or PyleniumWait to replace the lambda functions in the examples above.

```python
from selenium.webdriver.support import expected_conditions as EC

py.wait().until(EC.title_is("Pylenium.io"))
```

## Yields

* <mark style="color:orange;">**Any**</mark> - Whatever the non-False value of the condition is

## Raises

* <mark style="color:orange;">**`TimeoutException`**</mark> if the condition is not met within the timeout time
* Depending on the condition, it would raise other Exceptions. If you know which ones are expected, you can include them in the <mark style="color:purple;">**`ignored_exceptions`**</mark> as an argument.


# webdriver

The property to get the current instance of Selenium's WebDriver.

## Syntax

```
py.webdriver -> WebDriver
```

## Usage

{% code title="correct usage" %}

```python
py.webdriver
```

{% endcode %}

{% code title="incorrect usage" %}

```python
py.webdriver()
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**WebDriver**</mark>  - The current instance of Selenium WebDriver that Pylenium is wrapping

## Examples

Most scenarios won't need this, but it's provided just in case. The biggest reasons to use <mark style="color:purple;">`py.webdriver`</mark>

* access functionality that may not exist in Pylenium
* functionality that requires you pass in a WebDriver

```python
# get WebDriver's current Capabilities
caps = py.webdriver.capabilities
```

```python
# function requires a WebDriver
actions = ActionChains(py.webdriver)
```


# Element Commands


# Find Elements

Commands to find elements within the context of another element.

The <mark style="color:orange;">**Element**</mark> class provides 5 main ways to find elements:

* [contains](/driver-commands/find-elements/contains)   get a <mark style="color:yellow;">single element</mark> by **TEXT**
* [find](/driver-commands/find-elements/find)           find a <mark style="color:yellow;">list of elements</mark> by **CSS**
* [findx](/driver-commands/find-elements/find_xpath)         find a <mark style="color:yellow;">list of elements</mark> by **XPATH**
* [get](/driver-commands/find-elements/get)            get a <mark style="color:yellow;">single element</mark> by **CSS**
* [getx](/driver-commands/find-elements/get_xpath)          get a <mark style="color:yellow;">single element</mark> by **XPATH**

These are very similar to how <mark style="color:orange;">**py**</mark> finds elements. For example, the following code snippet will   search the ***entire*** DOM (aka webpage) for the first Element with <mark style="color:purple;">`id=name`</mark> and type "Carlos Kidman" into it.

```python
py.get("#name").type("Carlos Kidman")
```

Now take a look at the next code snippet:

```python
py.get(".form").get("#city").type("Salt Lake City")
```

This starts by searching the entire DOM for an element with <mark style="color:purple;">`class=form`</mark>. Then, ***within*** the form element, search for the first element with <mark style="color:purple;">`id=city`</mark> and type "Salt Lake City" into it.

Also, you can hold <mark style="color:orange;">**Element**</mark> and <mark style="color:orange;">**Elements**</mark> in variables instead of chaining them like the snippet above. The ability to set this context with a smaller scope can be powerful!

```python
form = py.get(".form")
form.get("#name").type("Carlos Kidman")
form.get("#city").type("Salt Lake City")
form.get("#search").submit()
```


# Element.should()

A collection of expected conditions against an Element.

## Expectations

### Positive Conditions

* <mark style="color:purple;">`.be_checked()`</mark>
* <mark style="color:purple;">`.be_clickable()`</mark>
* <mark style="color:purple;">`.be_disabled()`</mark>
* <mark style="color:purple;">`.be_enabled()`</mark>
* <mark style="color:purple;">`.be_focused()`</mark>
* <mark style="color:purple;">`.be_hidden()`</mark>
* <mark style="color:purple;">`.be_selected()`</mark>
* <mark style="color:purple;">`.be_visible()`</mark>
* <mark style="color:purple;">`.contain_text(text: str, case_sensitive=True)`</mark>
* <mark style="color:purple;">`.disappear()`</mark>
* <mark style="color:purple;">`.have_attr(attr: str, value: Optional[str])`</mark>
* <mark style="color:purple;">`.have_class(class_name: str)`</mark>
* <mark style="color:purple;">`.have_prop(prop: str, value: str)`</mark>
* <mark style="color:purple;">`.have_text(text: str, case_sensitive=True)`</mark>
* <mark style="color:purple;">`.have_value(value: any)`</mark>

### Negative Conditions

* <mark style="color:purple;">`.not_be_focused()`</mark>
* <mark style="color:purple;">`.not_have_attr(attr: str, value: Optional[str])`</mark>
* <mark style="color:purple;">`.not_have_text(text: str, case_sensitive=True)`</mark>
* <mark style="color:purple;">`.not_have_value(value: any)`</mark>

## Syntax

```python
# Use the default wait_time
Element.should().<expectation>

---or---

# Customize the wait_time for this expectation
Element.should(timeout: int).<expectation>

---or---

# Ignore exceptions that you expect to "get in the way"
Element.should(ignored_exceptions: list).<expectation>

---or---

# Customize both fully
Element.should(timeout: int, ignored_exceptions: list).<expectation>
```

## Examples

{% code title="Is element displayed?" %}

```python
def test_element_visible(py):
    py.visit("https://qap.dev")
    assert py.get("a[href='/about']").should().be_visible()
```

{% endcode %}

{% code title="Does it have text?" %}

```python
def test_element_has_correct_text(py):
    py.visit("https://qap.dev")
    assert py.get("a[href='/about']").should().have_text("About")
```

{% endcode %}

## Yields

* <mark style="color:orange;">**Element**</mark> - If the assertion passes, then the current Element is returned, else an **AssertionError** is raised if the condition is not met within the specified timeout.


# Actions

The commands that perform actions against the elements.


# check

The command to select a checkbox or radio buttons.

## Syntax

```python
Element.check() -> Element
Element.check(allow_selected=False) -> Element
```

## Usage

{% code title="correct usage" %}

```python
# check a radio button
py.get("[type='radio']").check()

---or---

# check a box
py.get("[type='checkbox']").check()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'get' yields an Element that is not a checkbox or radio button
py.get("a").check()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`allow_selected=False (bool)`</mark> - If **True,** do not raise an error if the box or radio button to check is *already* selected.

{% hint style="info" %}
Default is **False** because why would you want to select a box that's already selected?
{% endhint %}

## Yields

* <mark style="color:orange;">**Element**</mark> - The current instance so you can chain commands

## Raises

* <mark style="color:yellow;">**ValueError**</mark> if the element is selected already. Set `allow_selected` to **True** to ignore this.
* <mark style="color:yellow;">**ValueError**</mark> if the element is not a checkbox or radio button

## Examples

Given this HTML:

```html
<form id="checkboxes">
    <input type="checkbox">
    checkbox 1
    <br>
    <input type="checkbox" checked="">
    checkbox 2
  </form>
```

We can *check* the first checkbox:

```python
def test_check_the_box(py: Pylenium):
    py.visit("https://the-internet.herokuapp.com/checkboxes")
    checkbox = py.get("input").check()
    assert checkbox.should().be_checked()
```


# clear

The command to clear the input of the current element.

## Syntax

```python
Element.clear() -> Element
```

## Usage

{% code title="correct usage" %}

```python
py.get("input").clear()
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Element**</mark> - The current Element that was cleared so you can chain commands.


# click

The command to click the element.

## Syntax

```python
Element.click() -> Pylenium
Element.click(force=False) -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
py.get("a").click()

---or--- # chain a Pylenium command

py.get("a").click().wait.until(lambda _: py.title == "New Page")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'click' yields Pylenium, not Element
py.get("a").click().text()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`force=False (bool)`</mark> - If **True**, a JavascriptExecutor command is sent instead of Selenium's native `.click()`

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands.

## Examples

Given this HTML:

```html
<div class="example">
  <button onclick="addElement()">Add Element</button>
  <hr>
  <div id="elements">
    <button class="added-manually" onclick="deleteElement()">Delete</button></div>
</div>
```

We can click to add another element and click to delete them:

```python
URL = "https://the-internet.herokuapp.com/add_remove_elements/"
ADD_BUTTON = "[onclick='addElement()']"
DELETE_BUTTON = "[onclick='deleteElement()']"

def test_click_to_add_and_delete(py: Pylenium):
    py.visit(URL)
    py.get(ADD_BUTTON).click()
    py.get(DELETE_BUTTON).click()
    assert py.should().not_find(DELETE_BUTTON)
```

{% hint style="info" %}
Give it a try yourself! <https://the-internet.herokuapp.com/add_remove_elements/>
{% endhint %}


# deselect

The command to deselect an \<option> within a multi \<select> element.

## Syntax

```python
Element.deselect(value)
```

## Usage

{% code title="correct usage" %}

```python
py.get('select').deselect('option-2')

---or--- # chain a Pylenium command

py.get('select').deselect('locked').get('#start-edit').click()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, can only perform 'deselect' on <select> elements
py.get('ul > li').deselect('option-2')
```

{% endcode %}

## Arguments

* `value (str)` - The text or value of the option to deselect.

## Yields

* **(Pylenium)** The current instance of Pylenium so you can chain commands.


# double\_click

The command to double click the element.

## Syntax

```python
Element.double_click()
```

## Usage

{% code title="correct usage" %}

```python
py.get('a').double_click()

---or--- # chain a Pylenium command

py.get('a').double_click().switch_to.window(index=1)
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'double_click' yields Pylenium, not Element
py.get('a').double_click().text
```

{% endcode %}

## Arguments

* None

## Yields

* **(Pylenium)** The current instance of Pylenium so you can chain commands


# drag\_to

The command to drag the current element to another element given its CSS selector.

## Syntax

```python
Element.drag_to(css)
```

## Usage

{% code title="correct usage" %}

```python
py.get('#drag-this').drag_to('#drop-here')

---or---

from_element = py.get('#drag-this')
from_element.drag_to('#drop-here')
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'drag_to' takes a CSS selector string

to_element = py.get('#drop-here')
py.get('#drag-this').drag_to(to_element)

# Use the .drag_to_element() command instead
```

{% endcode %}

## Arguments

* `css(str)` - The CSS selector of the element to drag to.

## Yields

* **(Element)** The current element that was dragged.


# drag\_to\_element

The command to drag the current element to the given element.

## Syntax

```python
Element.drag_to_element(to_element)
```

## Usage

{% code title="correct usage" %}

```python
element = py.get('#drop-here')
py.get('#drag-this').drag_to_element(element)
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'drag_to_element' takes an Element
py.get('#drag-this').drag_to_element('#drop-here')

# Use the .drag_to() command instead

---or---

# Errors, `drag_to_element` and `drag_to` are not part of the Pylenium object
element = py.get('#drop-here')
py.drag_to_element(element)
```

{% endcode %}

## Arguments

* `to_element(Element)` - The destination element to drag to.

## Yields

* **(Element)** The current element that was dragged.


# focus

The command to switch focus to the element.

## Syntax

```python
Element.focus() -> Element
```

## Usage

{% code title="correct usage" %}

```python
py.get(".menu").focus()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, Pylenium doesn't have a focus() command
py.focus()
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Element**</mark> - The element that has been focused


# hover

The command to hover the element.

## Syntax

```python
Element.hover() -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
py.get(".menu").hover()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'hover' yields Pylenium, not Element
py.get(".menu").hover().click()
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands

## Examples

```python
def test_hover_shows_user_info(py: Pylenium):
    py.visit("https://the-internet.herokuapp.com/hovers")
    py.get("[alt='User Avatar']").hover()
    assert py.contains("name: user1").should().be_visible()
```

{% hint style="info" %}
Give it a try yourself! <https://the-internet.herokuapp.com/hovers>
{% endhint %}


# right\_click

The command to right-click the element.

## Syntax

```
Element.right_click()
```

## Usage

{% code title="correct usage" %}

```python
py.get('#context-menu').right_click()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# 'py' does not have this command
py.right_click()
```

{% endcode %}

## Arguments

* None

## Yields

* **(Pylenium)** so you can chain another command


# scroll\_into\_view

The command to scroll this element into the viewport

## Syntax

```
Element.scroll_into_view()
```

## Usage

{% code title="correct usage" %}

```python
py.get('#footer-link').scroll_into_view()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# 'py' does not have this command
py.scroll_into_view()
```

{% endcode %}

## Arguments

* None

## Yields

* **(Element)** so you can chain another command


# select\_by\_index

The command to select an \<option> by its index within a \<select> dropdown element.

## Syntax

```python
Element.select_by_index(index: int) -> Element
```

## Usage

{% code title="correct usage" %}

```python
py.get("#dropdown").select_by_index(2)

---or--- # chain an Element command

py.get("#dropdown").select_by_index(0).get_attribute("value")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, can only perform this command on a <select> dropdown element
py.get("ul > li").select_by_index(1)
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`index (int)`</mark> - The **index** or "position" of the option to select.

## Yields

* <mark style="color:orange;">**Element**</mark> - The current instance of Element so you can chain commands.

## Examples

Given this HTML

```html
<select id="dropdown">
    <option value="" disabled="disabled" selected="selected">Please select an option</option>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
</select>
```

We can select any of the options

```python
dropdown = py.get("dropdown")

# Select the first option that is "disabled"
dropdown.select_by_index(0)

# Select Option 1
dropdown.select_by_index(1)

# Select Option 2
dropdown.select_by_index(2)
```

{% hint style="info" %}
Give this a try yourself! <https://the-internet.herokuapp.com/dropdown>
{% endhint %}

## See also

* [select\_*by\_*&#x74;ext](/element-commands/actions/select_many)
* [select\_*by\_*&#x76;alue](/element-commands/actions/select_many-1)
* [click()](/element-commands/actions/click) - If the dropdown is NOT a \<select> element, <mark style="color:purple;">`.click()`</mark> will work


# select\_by\_text

The command to select an \<option> by its text within a \<select> dropdown element.

## Syntax

```python
Element.select_by_text(text: str) -> Element
```

## Usage

{% code title="correct usage" %}

```python
py.get("#dropdown").select_by_text("Option 1")

---or--- # chain an Element command

py.get("#dropdown").select_by_text("Option 2").get_attribute("value")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, can only perform this command on a <select> dropdown element
py.get("ul > li").select_by_text("Option 3")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`text (str)`</mark> - The **text** of the option to select.

## Yields

* <mark style="color:orange;">**Element**</mark> - The current instance of Element so you can chain commands.

## Examples

Given this HTML

```html
<select id="dropdown">
    <option value="" disabled="disabled" selected="selected">Please select an option</option>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
</select>
```

We can select any of the options

```python
dropdown = py.get("dropdown")

# Select the first option that is "disabled"
dropdown.select_by_text("Please select an option")

# Select Option 1
dropdown.select_by_text("Option 1")

# Select Option 2
dropdown.select_by_text("Option 2")
```

{% hint style="info" %}
Give this a try yourself! <https://the-internet.herokuapp.com/dropdown>
{% endhint %}

## See also

* [select\_*by\_*&#x69;ndex](/element-commands/actions/select)
* [select\_*by\_*&#x76;alue](/element-commands/actions/select_many-1)
* [click()](/element-commands/actions/click) - If the dropdown is NOT a \<select> element, <mark style="color:purple;">`.click()`</mark> will work


# select\_by\_value

The command to select an \<option> by its value within a \<select> dropdown element.

## Syntax

```python
Element.select_by_value(value: Any) -> Element
```

## Usage

{% code title="correct usage" %}

```python
py.get("#dropdown").select_by_value(2)

---or--- # chain an Element command

py.get("#dropdown").select_by_value("1").get_attribute("value")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, can only perform this command on a <select> dropdown element
py.get("ul > li").select_by_value("2")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`value (Any)`</mark> - The **value** of the option to select. Usually a `str`, but can be other types.

## Yields

* <mark style="color:orange;">**Element**</mark> - The current instance of Element so you can chain commands.

## Examples

Given this HTML

```html
<select id="dropdown">
    <option value="" disabled="disabled" selected="selected">Please select an option</option>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
</select>
```

We can select any of the options

```python
dropdown = py.get("dropdown")

# Select the first option that is "disabled"
dropdown.select_by_value("")

# Select Option 1
dropdown.select_by_value("1")

# Select Option 2
dropdown.select_by_value("2")
```

{% hint style="info" %}
Give this a try yourself! <https://the-internet.herokuapp.com/dropdown>
{% endhint %}

## See also

* [select\_*by\_*&#x69;ndex](/element-commands/actions/select)&#x20;
* [select\_*by\_*&#x74;ext](/element-commands/actions/select_many)
* [click()](/element-commands/actions/click) - If the dropdown is NOT a \<select> element, <mark style="color:purple;">`.click()`</mark> will work


# submit

The command to submit a form or input element.

## Syntax

```python
Element.submit() -> Pylenium
```

## Usage

{% code title="correct usage" %}

```python
py.get("form").submit()

---or---

py.get("input[type='submit']").submit()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'submit' may have no effect on certain elements
py.get("a").submit()
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**Pylenium**</mark> - The current instance of Pylenium so you can chain commands.

## Examples

Given this HTML:

```html
<form name="login" id="login" action="/authenticate" method="post">
    <div class="row">
      <div class="large-6 small-12 columns">
        <label for="username">Username</label>
        <input type="text" name="username" id="username">
      </div>
    </div>
    <div class="row">
      <div class="large-6 small-12 columns">
        <label for="password">Password</label>
        <input type="password" name="password" id="password">
      </div>
    </div>
      <button class="radius" type="submit"><i class="fa fa-2x fa-sign-in"> Login</i></button>
</form>
```

We could type credentials into the fields and submit the form to login:

```python
def test_login(py: Pylenium):
    py.visit("https://the-internet.herokuapp.com/login")
    py.get("#username").type("tomsmith")
    py.get("#password").type("SuperSecretPassword!")
    py.get("button[type='submit']").submit()
    assert py.contains("You logged into a secure area!").should().be_visible()
```


# type

The command to type keys into a field, input or text box.

{% hint style="info" %}
Replaces **`send_keys`** from Selenium
{% endhint %}

## Syntax

```python
Element.type(*args) -> Element
```

## Usage

{% code title="correct usage" %}

```python
py.get("#username").type("my-username")

---or--- # combine with other keys and strings

# import the Keys from selenium
py.get("#search").type("puppies", Keys.ENTER)

---or--- # chain an Element command

py.get("#email").type("foo@example.com").get_attribute("value")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'type' may have no effect on other types of elements
py.get("a").type("foo")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`*args (Any)`</mark> - A comma-separated list of arguments to type

{% hint style="success" %}
It's best to use **strings** and the **Keys** from Selenium
{% endhint %}

## Yields

* <mark style="color:orange;">**Element**</mark> - The current Element so you can chain commands

## Examples

Given this HTML:

```html
<form name="login" id="login" action="/authenticate" method="post">
    <div class="row">
      <div class="large-6 small-12 columns">
        <label for="username">Username</label>
        <input type="text" name="username" id="username">
      </div>
    </div>
    <div class="row">
      <div class="large-6 small-12 columns">
        <label for="password">Password</label>
        <input type="password" name="password" id="password">
      </div>
    </div>
      <button class="radius" type="submit"><i class="fa fa-2x fa-sign-in"> Login</i></button>
</form>
```

We could type credentials into the fields and submit the form to login:

```python
def test_login(py: Pylenium):
    py.visit("https://the-internet.herokuapp.com/login")
    py.get("#username").type("tomsmith")
    py.get("#password").type("SuperSecretPassword!")
    py.get("button[type='submit']").submit()
    assert py.contains("You logged into a secure area!").should().be_visible()
```


# uncheck

The command to deselect checkboxes and radio buttons.

## Syntax

```python
Element.uncheck() -> Element
Element.uncheck(allow_selected=False) -> Element
```

## Usage

{% code title="correct usage" %}

```python
# uncheck a radio button
py.get("[type='radio']").uncheck()
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'get' yields an Element that is not a checkbox or radio button
py.get("a").uncheck()
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`allow_deselected=False (bool)`</mark> - If **True,** do not raise an error if the box or radio button to uncheck is *already* deselected.

{% hint style="info" %}
Default is **False** because why would you want to deselect a box that's not selected?
{% endhint %}

## Yields

* <mark style="color:orange;">**Element**</mark> - The current Element so you can chain commands

## Raises

* <mark style="color:yellow;">**ValueError**</mark> if the element is not selected already. Set `allow_deselected` to **True** to ignore this.
* <mark style="color:yellow;">**ValueError**</mark> if the element is not a checkbox or radio button

## Examples

Given this HTML:

```html
<form id="checkboxes">
    <input type="checkbox">
    checkbox 1
    <br>
    <input type="checkbox" checked="">
    checkbox 2
  </form>
```

We can *uncheck* the second checkbox:

```python
def test_uncheck(py: Pylenium):
    py.visit("https://the-internet.herokuapp.com/checkboxes")
    checkboxes = py.find("input")
    second_box = checkboxes[1].uncheck()
    assert second_box.is_checked() is False
```


# upload

The command to upload a file to the element.

## Syntax

```python
Element.upload(filepath: str) -> Element
```

## Usage

{% code title="correct usage" %}

```python
py.get("#file-upload").upload("path/to/file.png")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, must be an element that can accept an upload
py.get("a").upload("path/to/file.png")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">**filepath (str)**</mark> - The absolute path to the file including the name and extension

{% hint style="success" %}
You can use Path objects to make this easier and work for any OS
{% endhint %}

## Yields

* <mark style="color:orange;">**Element**</mark> - The element you attempted to upload to

## Examples

Before the `upload()` command, you would do this:

```python
# Selenium .send_keys()
driver.find_element(By.ID("select-file")).send_keys("path/to/file.png")

# Pylenium .type()
py.get("#select-file").type("path/to/file.png")
```

That was not as clear or intuitive :cry:, but now it's much cleaner!

```python
py.get("#select-file").upload("path/to/file.png")
py.get("#upload-button").click()
```

{% hint style="info" %}
Give it a try! <https://the-internet.herokuapp.com/upload>
{% endhint %}


# Element Data

The commands to get details or data about the current element.


# css\_value

Get the CSS Value of the element given the property name.

## Syntax

```python
Element.css_value(property_name: str) -> Any
```

## Usage

{% code title="correct usage" %}

```python
py.get("a").css_value("background-color")

---or--- # chain a Pylenium command

py.find("a").first().get("span").css_value("color")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'Pylenium' yields Pylenium, not Element
py.css_value("color")

---or---

# Errors, 1 is not a valid property name
py.get("#button").css_value(1)
```

{% endcode %}

## Arguments

* <mark style="color:purple;">**`property_name (str)`**</mark> - The name of the CSS Property

## Yields

* <mark style="color:orange;">**Any**</mark> - Typically strings, but this depends on the CSS Property


# get\_attribute

The command to get the attribute's value with the given name.

## Syntax

```python
Element.get_attribute(attribute: str) -> bool | str | None
```

## Usage

{% code title="correct usage" %}

```python
py.get("a").get_attribute("href")

---or--- # store in a variable

href = py.get("a").get_attribute("href")
assert href.startswith("https://")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`attribute (str)`</mark> = The name of the attribute to find in the Element

## Yields

* If the value is <mark style="color:purple;">`"true"`</mark> or <mark style="color:purple;">`"false"`</mark>, then this returns a bool of <mark style="color:yellow;">**True**</mark> or <mark style="color:yellow;">**False**</mark>
* If the name does not exist, return <mark style="color:yellow;">**None**</mark>
* All other values are returned as <mark style="color:yellow;">**strings**</mark>


# get\_property

The command to get the specified property's value of the element.

## Syntax

```python
Element.get_property(prop: str) -> Any
```

## Usage

{% code title="correct usage" %}

```python
py.get(".nav-link").get_property("innerHTML")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'py' cannot call this directly
py.get_property("className")
```

{% endcode %}

## Arguments

* <mark style="color:purple;">`property (str)`</mark> - The name of the property.

## Yields

* The value returned by the property, but this is usually a <mark style="color:yellow;">**string**</mark>.


# tag\_name

The command that gets the current Element's tag name.

## Syntax

```python
Element.tag_name() -> str
```

## Usage

{% code title="correct usage" %}

```python
assert py.get(".btn").tag_name() == "button"
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'tag_name' is not a property
py.get("a").tag_name
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**str**</mark> - The tag name of the current Element


# text

The command to get the text of the current Element.

## Syntax

```python
Element.text() -> str
```

## Usage

{% code title="correct usage" %}

```python
assert py.get(".nav.link").text() == "About"

---or---

assert py.get(".nav.link").should().have_text("About")
```

{% endcode %}

{% code title="incorrect usage" %}

```python
# Errors, 'text' is not a property
py.get("a").text
```

{% endcode %}

## Arguments

* None

## Yields

* <mark style="color:orange;">**str**</mark> - The text of the current Element




---

[Next Page](/llms-full.txt/1)

