Showing preview only (373K chars total). Download the full file or copy to clipboard to get everything.
Repository: ElSnoMan/pyleniumio
Branch: main
Commit: b82f9301f95e
Files: 139
Total size: 340.2 KB
Directory structure:
gitextract_fn7bqonu/
├── .flake8
├── .gitbook.yaml
├── .github/
│ └── workflows/
│ ├── publish-pylenium.yml
│ └── test-pylenium.yml
├── .gitignore
├── .gitpod.Dockerfile
├── .gitpod.yml
├── .vscode/
│ └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── conftest.py
├── docker-compose.yml
├── docs/
│ ├── README.md
│ ├── SUMMARY.md
│ ├── changelog.md
│ ├── cli/
│ │ ├── pylenium-cli.md
│ │ └── report-portal.md
│ ├── configuration/
│ │ ├── driver.md
│ │ ├── logging.md
│ │ ├── pylenium.json.md
│ │ └── report-portal.md
│ ├── contribute/
│ │ └── clone-and-setup-the-project.md
│ ├── element-commands/
│ │ ├── check.md
│ │ ├── children.md
│ │ ├── click.md
│ │ ├── css_value.md
│ │ ├── deselect.md
│ │ ├── double_click.md
│ │ ├── drag_to.md
│ │ ├── drag_to_element.md
│ │ ├── first.md
│ │ ├── get_attribute.md
│ │ ├── get_property.md
│ │ ├── hover.md
│ │ ├── is_checked.md
│ │ ├── is_displayed.md
│ │ ├── is_empty.md
│ │ ├── is_enabled.md
│ │ ├── is_selected.md
│ │ ├── last.md
│ │ ├── length.md
│ │ ├── open_shadow_dom.md
│ │ ├── parent.md
│ │ ├── right_click.md
│ │ ├── screenshot.md
│ │ ├── scroll_into_view.md
│ │ ├── select.md
│ │ ├── select_many.md
│ │ ├── should.md
│ │ ├── siblings.md
│ │ ├── submit.md
│ │ ├── tag_name.md
│ │ ├── text.md
│ │ ├── type.md
│ │ ├── uncheck.md
│ │ └── webelement.md
│ ├── examples/
│ │ └── test_sample.py
│ ├── fixtures/
│ │ ├── axe.md
│ │ ├── fake.md
│ │ └── requests.md
│ ├── getting-started/
│ │ ├── project-structure-with-pytest.md
│ │ ├── setup-pytest.md
│ │ ├── virtual-environments.md
│ │ └── writing-tests-with-pylenium.md
│ ├── guides/
│ │ ├── run-tests-in-containers.md
│ │ └── run-tests-in-parallel.md
│ ├── misc/
│ │ └── install-chromedriver.md
│ └── pylenium-commands/
│ ├── commands.md
│ ├── contains.md
│ ├── delete_all_cookies.md
│ ├── delete_cookie.md
│ ├── execute_script.md
│ ├── fake.md
│ ├── find.md
│ ├── find_xpath.md
│ ├── get.md
│ ├── get_cookie.md
│ ├── get_cookies.md
│ ├── get_xpath.md
│ ├── go.md
│ ├── maximize_window.md
│ ├── quit.md
│ ├── request.md
│ ├── screenshot.md
│ ├── scroll_to.md
│ ├── set_cookie.md
│ ├── should.md
│ ├── switch_to.default_content.md
│ ├── switch_to.frame.md
│ ├── switch_to.parent_frame.md
│ ├── switch_to.window.md
│ ├── title.md
│ ├── url.md
│ ├── viewport.md
│ ├── visit.md
│ ├── wait.md
│ ├── webdriver.md
│ ├── window_handles.md
│ ├── window_size.md
│ └── xpath.md
├── poetry.toml
├── pylenium/
│ ├── __init__.py
│ ├── a11y.py
│ ├── cdp.py
│ ├── config.py
│ ├── driver.py
│ ├── element.py
│ ├── jquery.py
│ ├── log.py
│ ├── performance.py
│ ├── scripts/
│ │ ├── __init__.py
│ │ ├── allure_reporting.py
│ │ ├── cli.py
│ │ ├── cli_utils.py
│ │ ├── conftest.py
│ │ ├── drag_and_drop.js
│ │ ├── load_jquery.js
│ │ ├── pylenium.json
│ │ └── pytest.ini
│ ├── switch_to.py
│ ├── utils.py
│ ├── wait.py
│ └── webdriver_factory.py
├── pylenium.json
├── pyproject.toml
├── pytest.ini
└── tests/
├── __init__.py
├── performance/
│ ├── test_cdp_performance.py
│ └── test_performance.py
├── test_flows.py
├── ui/
│ ├── Get CRX.crx
│ ├── test_element.py
│ ├── test_element_actions.py
│ └── test_pydriver.py
└── unit/
├── test_config.py
├── test_faker.py
└── test_requests.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .flake8
================================================
[flake8]
extend-ignore = E203, C901
exclude =
.git,
__pycache__,
docs,
old,
dist
max-line-length = 160
================================================
FILE: .gitbook.yaml
================================================
root: ./docs
structure:
readme: README.md
summary: SUMMARY.md
================================================
FILE: .github/workflows/publish-pylenium.yml
================================================
name: Upload Pylenium Package to pypi
on:
release:
types: [created]
workflow_dispatch:
inputs:
version: # keeping this for eventaul autobump feature
description: "The version to publish to pypi"
required: false
default: "develop"
jobs:
pypi-publish:
name: upload release to PyPI
runs-on: ubuntu-latest
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: Build distributions
run: |
pip install poetry
poetry build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
================================================
FILE: .github/workflows/test-pylenium.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Build and Test Pylenium with Selenoid
on:
push:
branches: [main]
paths-ignore:
- "docs/**"
pull_request:
branches: [main]
paths-ignore:
- "docs/**"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
pip install poetry
# install dependencies from pyproject.toml
poetry install
- name: Lint with flake8
run: |
# Using .flake8 file
poetry run poe lint
- name: Run Unit Tests
run: |
poetry run pytest tests/unit
- name: Start Selenoid Server
# https://github.com/marketplace/actions/start-selenoid-server?version=v2
uses: Xotabu4/selenoid-github-action@v2
with:
selenoid-start-arguments: |
--args "-timeout 250s"
- name: Run UI Tests
run: |
poetry run pytest tests/ui --remote_url "http://localhost:4444/wd/hub"
================================================
FILE: .gitignore
================================================
# General
.idea
test_results
allure-report
test_env
tests/data
data
# LambdaTest
.hyperexecute
logs
.updatedhyperexecute.yaml
hyperexecute-code*
hyperexecuteupdate
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
Pipfile.lock
.pypyrc
a11y.json
================================================
FILE: .gitpod.Dockerfile
================================================
FROM gitpod/workspace-full-vnc
USER root
# RUN apt-get update -qqy && apt-get install -y wget curl gnupg2
# So we can install browsers and browser drivers later
RUN wget https://packages.microsoft.com/config/ubuntu/21.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb && rm packages-microsoft-prod.deb
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list
RUN mkdir -p /home/gitpod/selenium /var/run/supervisor /var/log/supervisor && \
chmod -R 777 /var/run/supervisor /var/log/supervisor
ENV DEBIAN_FRONTEND=noninteractive
# Browsers
RUN apt-get update -qqy && \
apt-get -qy install google-chrome-stable firefox && \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
# Browser Drivers
RUN CHROME_MAJOR_VERSION=$(google-chrome --version | sed -E "s/.* ([0-9]+)(\.[0-9]+){3}.*/\1/") \
&& CHROME_DRIVER_VERSION=$(wget --no-verbose -O - "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${CHROME_MAJOR_VERSION}") \
&& echo "Using ChromeDriver version: "$CHROME_DRIVER_VERSION \
&& wget --no-verbose -O /tmp/chromedriver_linux64.zip https://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip \
&& rm -rf /home/gitpod/selenium/chromedriver \
&& unzip /tmp/chromedriver_linux64.zip -d /home/gitpod/selenium \
&& rm /tmp/chromedriver_linux64.zip \
&& mv /home/gitpod/selenium/chromedriver /home/gitpod/selenium/chromedriver-$CHROME_DRIVER_VERSION \
&& chmod 755 /home/gitpod/selenium/chromedriver-$CHROME_DRIVER_VERSION \
&& sudo ln -fs /home/gitpod/selenium/chromedriver-$CHROME_DRIVER_VERSION /usr/bin/chromedriver
RUN GK_VERSION="0.31.0" \
&& echo "Using GeckoDriver version: "$GK_VERSION \
&& wget --no-verbose -O /tmp/geckodriver.tar.gz https://github.com/mozilla/geckodriver/releases/download/v$GK_VERSION/geckodriver-v$GK_VERSION-linux64.tar.gz \
&& rm -rf /home/gitpod/selenium/geckodriver \
&& tar -C /home/gitpod/selenium -zxf /tmp/geckodriver.tar.gz \
&& rm /tmp/geckodriver.tar.gz \
&& mv /home/gitpod/selenium/geckodriver /home/gitpod/selenium/geckodriver-$GK_VERSION \
&& chmod 755 /home/gitpod/selenium/geckodriver-$GK_VERSION \
&& ln -fs /home/gitpod/selenium/geckodriver-$GK_VERSION /usr/bin/geckodriver
# To run browser tests
ENV DISPLAY :99.0
ENV DISPLAY_NUM 99
ENV SCREEN_WIDTH 1360
ENV SCREEN_HEIGHT 1020
ENV SCREEN_DEPTH 24
ENV SCREEN_DPI 96
ENV VNC_PORT 5900
ENV NO_VNC_PORT 7900
================================================
FILE: .gitpod.yml
================================================
vscode:
extensions:
- ms-python.python
- bungcip.better-toml
- PKief.material-icon-theme
image:
file: .gitpod.Dockerfile
tasks:
- name: "Setup"
command: |
git config --global pull.rebase false
pip install poetry
poetry install
ports:
- port: 5900
onOpen: ignore
- port: 6080
onOpen: open-browser
- port: 10000
onOpen: ignore
================================================
FILE: .vscode/settings.json
================================================
{
"python.testing.pytestArgs": ["tests"],
"python.defaultInterpreterPath": ".venv/bin/python",
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/*.crswap": true,
"**/*.pytest_cache": true,
"**/*__pycache__": true
},
"python.linting.enabled": true,
"flake8.args": ["--rcfile=.flake8"],
"python.formatting.provider": "none",
"workbench.iconTheme": "material-icon-theme",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
}
}
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [atom@github.com](mailto:atom@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [contributor-covenant.org/version/2/0/code_of_conduct/][version]
[homepage]: https://contributor-covenant.org
[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Pylenium
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to Pylenium on GitHub. These are mostly guidelines, not rules.
Use your best judgment, and feel free to propose changes to this document in a pull request.
#### Table of Contents
* [Code of Conduct](./CODE_OF_CONDUCT.md)
* [What should I know before getting started?](#what-should-i-know-before-getting-started)
* [How can I Contribute?](#how-can-i-contribute)
* [Setup the Project](#setup-the-project-to-start)
* [Report a Bug](#report-a-bug)
* [Before submitting a Bug](#before-submitting-a-bug-report)
* [How do I submit a (good) Bug](#how-do-i-submit-a-good-bug-report)
* [Suggesting Enhancements](#suggesting-enhancements)
* [Before submitting an Enhancement](#before-submitting-an-enhancement-suggestion)
* [How do I submit a (good) Enhancement?](#how-do-i-submit-a-good-enhancement-suggestion)
* [Your first Code Contribution](#your-first-code-contribution)
* [Pull Requests (PR)](#pull-requests)
* [PR Templates](#pr-templates)
* [Requirements for contributing a Bugfix](#requirements-for-contributing-a-bug-fix)
* [Bug Template](#bug-template)
* [Requirements for contributing Documentation](#requirements-for-contributing-documentation)
* [Documentation Template](#documentation-template)
* [Requirements for contributing adding, changing, or removing a Feature](#requirements-for-adding-changing-or-removing-a-feature)
* [Enhancement Template](#enhancement-template)
* [Style Guides](#style-guides)
* [Git Commit Messages](#git-commit-messages)
* [Python Styleguide](#python-styleguide)
## Code of Conduct
This project and everyone participating in it is governed by the [Pylenium Code of Conduct](CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior to Carlos Kidman (@ElSnoMan).
## What should I know before getting started?
Pylenium is a wrapper of Selenium, not Cypress.
However, like Cypress, we want Pylenium to be an all-in-one solution that comes with everything you need and expect from a robust Test Automation Solution.
Most features should be "plug and play" for the user to make it easy for them, but we also need to keep it extendable
so they can customize or swap components, like a reporting tool, if they want to. However, some components cannot be changed.
For example:
* Selenium (for obvious reasons)
* pytest as the Testing Framework
## How can I Contribute?
### Setup the Project to Start
This [Guide in our Documentation][clone and setup]
gives you the steps to clone and setup the project.
From there, open the [Issues Tab][Issues Tab] and check out the [Pull Requests Guide](#pull-requests)!
### Report a Bug
This section guides you through submitting a bug report for Pylenium. Following these guidelines helps maintainers
and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report).
Following this and providing the information it asks for helps us resolve issues faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before Submitting A Bug Report
* **Perform a cursory search** to see if the problem has already been reported.
If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Bug Report?
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/).
Create an issue on this repository and explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started Pylenium or ran a test(s), e.g. which command exactly you used in the terminal. When listing steps, **don't just say what you did, but explain how you did it**. For example, running tests via the IDE's UI behaves differently than the Terminal.
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you're reporting that Pylenium crashed, quits, or doesn't run tests**, include a stack trace and the Project Structure in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist.
Provide more context by answering these questions:
* **Can you reproduce the problem in the Terminal?** Sometimes Test issues are IDE-specific and not necessarily Selenium or Pytest.
* **Did the problem start happening recently** (e.g. after updating to a new version of Pylenium) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of Pylenium?** What's the most recent version in which the problem doesn't happen?
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
Include details about your configuration and environment:
* **Which version of Pylenium are you using?** You can get the exact version by running `pylenium --version` in your terminal.
* **What's the name and version of the OS you're using**?
* **Are you running Tests in locally vs the Cloud?** If so, which tools or software are you using and which operating systems and versions are used for the host and the guest?
* **Have you checked the Browser, Drivers, or Selenium about WebDriver issues?** Remember, Pylenium is a wrapper of Selenium, so something like Drag and Drop not working may be a WebDriver or Browser issue.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Pylenium, including completely new features and minor improvements to existing functionality or documentation.
Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one.
When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion)
and the steps that you imagine you would take if the feature you're requesting existed.
#### Before Submitting An Enhancement Suggestion
* **Check the [Pylenium Docs](https://elsnoman.gitbook.io/pylenium/)** for tips and commands — you might discover that the enhancement is already available. Most importantly, check if you're using the latest version of Pylenium.
* **Perform a cursory search in the Issues Tab** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on this repository and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Pylenium which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **Explain why this enhancement would be useful** to most Pylenium users and isn't something that can or should be implemented on your own or on another package, like a pytest plugin.
* **List some other automation tools or applications where this enhancement exists.**
* **Specify which version of Pylenium you're using.** You can get the exact version by running `pylenium --version` in your terminal.
* **Specify the name and version of the OS you're using.**
### Your First Code Contribution
Unsure where to begin contributing to Pylenium? You can start by looking through `beginner` and `help-wanted` issues:
* Beginner issues - Labeled with `beginner`, these issues should only require a few lines of code, and a test or two.
* Help wanted issues - Labeled with `help wanted`, these issues should be a bit more involved than `beginner` issues.
## Pull Requests
The process described here has several goals:
- Maintain Pylenium's quality or improve it
- Fix problems that are important to users
- Engage the community in working toward the best possible Pylenium
- Enable a sustainable system for Pylenium's maintainers to review contributions
Please follow these steps to have your contribution considered by the maintainers:
1. Follow all instructions in [the template](#pr-templates)
2. Follow the [style guides](#style-guides)
3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing <details><summary>What if the status checks are failing?</summary>If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.</details>
While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
## PR Templates
### Requirements for Contributing a Bug Fix
* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.
* The pull request must only fix an existing bug. To contribute other changes, you must use a different template.
* The pull request must update the test suite to demonstrate the changed functionality.
* After you create the pull request, all status checks must be pass before a maintainer reviews your contribution.
#### Bug Template
1. Identify the Bug
> Link to the issue describing the bug that you're fixing.
If there is not yet an issue for your bug, please open a new issue and then link to that issue in your pull request.
Note: In some cases, one person's "bug" is another person's "feature." If the pull request does not address an existing issue with the "bug" label, the maintainers have the final say on whether the current behavior is a bug.
2. Description of the Change
> We must be able to understand the design of your change from this description.
If we can't get a good idea of what the code will be doing from the description here, the pull request may be closed at the maintainers' discretion.
Keep in mind that the maintainer reviewing this PR may not be familiar with or have worked with the code here recently, so please walk us through the concepts.
3. Alternate Designs
> Explain what other alternates were considered and why the proposed version was selected
4. Possible Drawbacks
> What are the possible side-effects or negative impacts of the code change?
5. Verification Process
> What process did you follow to verify that the change has not introduced any regressions?
Describe the actions you performed (including buttons you clicked, text you typed, commands you ran, etc.),
and describe the results you observed.
6. Release Notes
> Please describe the changes in a single line that explains this improvement in
terms that a user can understand. This text will be used in Pylenium's release notes.
If this change is not user-facing or notable enough to be included in release notes you may use the strings "Not applicable" or "N/A" here.
Examples:
- The GitHub package now allows you to add co-authors to commits.
- Fixed an issue where multiple cursors did not work in a file with a single line.
- Increased the performance of searching and replacing across a whole project.
### Requirements for Contributing Documentation
* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.
* The pull request must only contribute documentation (for example, markdown files or API docs). To contribute other changes, you must use a different template.
#### Documentation Template
1. Description of the Change
> We must be able to understand the purpose of your change from this description.
If we can't get a good idea of the benefits of the change from the description here, the pull request may be closed at the maintainers' discretion.
2. Release Notes
> Please describe the changes in a single line that explains this improvement in
terms that a user can understand. This text will be used in Pylenium's release notes.
If this change is not user-facing or notable enough to be included in release notes
you may use the strings "Not applicable" or "N/A" here.
Examples:
- The GitHub package now allows you to add co-authors to commits.
- Fixed an issue where multiple cursors did not work in a file with a single line.
- Increased the performance of searching and replacing across a whole project.
### Requirements for Adding, Changing, or Removing a Feature
* Fill out the template below. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion.
* The pull request must contribute a change that has been endorsed by the maintainer team. See details in the template below.
* The pull request must update the test suite to exercise the updated functionality.
* After you create the pull request, all status checks must be pass before a maintainer reviews your contribution.
#### Enhancement Template
1. Issue or RFC Endorsed by Pylenium's Maintainers
> Link to the issue or RFC that your change relates to.
To contribute an enhancement that isn't endorsed, please follow our guide for Suggesting Enhancements.
To contribute other changes, you must use a different template.
2. Description of the Change
> We must be able to understand the design of your change from this description.
If we can't get a good idea of what the code will be doing from the description here, the pull request may be closed at the maintainers' discretion.
Keep in mind that the maintainer reviewing this PR may not be familiar with or have worked with the code here recently, so please walk us through the concepts.
3. Alternate Designs
> Explain what other alternates were considered and why the proposed version was selected.
4. Possible Drawbacks
> What are the possible side-effects or negative impacts of the code change?
5. Verification Process
> What process did you follow to verify that your change has the desired effects?
- How did you verify that all new functionality works as expected?
- How did you verify that all changed functionality works as expected?
- How did you verify that the change has not introduced any regressions?
Describe the actions you performed (including buttons you clicked, text you typed, commands you ran, etc.), and describe the results you observed.
6. Release Notes
> Please describe the changes in a single line that explains this improvement in
terms that a user can understand. This text will be used in Atom's release notes.
If this change is not user-facing or notable enough to be included in release notes
you may use the strings "Not applicable" or "N/A" here.
Examples:
- The GitHub package now allows you to add co-authors to commits.
- Fixed an issue where multiple cursors did not work in a file with a single line.
- Increased the performance of searching and replacing across a whole project.
### Style Guides
#### Git Commit Messages
* Use the present tense ("Add feature" not "Added feature")
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
* Limit the first line to 72 characters or less
* Reference issues and pull requests liberally after the first line
Consider starting the commit message with an applicable emoji:
* 🎨 :art: when improving the format/structure of the code
* 🐎 :racehorse: when improving performance
* 🚱 :non-potable_water: when plugging memory leaks
* 📝 :memo: when writing docs
* 🐧 :penguin: when fixing something on Linux
* 🍎 :apple: when fixing something on macOS
* 🏁 :checkered_flag: when fixing something on Windows
* 🐛 :bug: when fixing a bug
* 🔥 :fire: when removing code or files
* 💚 :green_heart: when fixing the CI build
* ✅ :white_check_mark: when adding tests
* 🔒 :lock: when dealing with security
* ⬆ :arrow_up: when upgrading dependencies
* ⬇ :arrow_down: when downgrading dependencies
* 👕 :shirt: when removing linter warnings
#### Python Styleguide
`pep8` is the styleguide and `flake8` is the linter in CI.
* Indents are 4 spaces (turn your tabs to spaces!)
* Type Hint as much as possible, but it should make sense
* Docstrings are _valuable_ because Pylenium is a library. Help the user stay in code rather than looking up docs all the time
* Docstrings follow the Google format
```python
def first() -> Element:
""" Get the first element from this list. """
return _elements[0]
# --- over ---
def first():
return _elements[0]
```
[clone and setup]: https://elsnoman.gitbook.io/pylenium/contribute/clone-and-setup-the-project
[Issues Tab]: https://github.com/ElSnoMan/pyleniumio/issues
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Carlos Kidman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Pylenium: Easy Python Web Test Automation
- [The Mission](#the-mission-is-simple)
- [Test Example](#test-example)
- [Purpose](#purpose)
- [Quickstart](#quick-start)
- [1. Install](#1-install-pyleniumio)
- [2. Initialize](#2-initialize-pylenium)
- [3. Write a Test](#3-write-a-test)
- [4. Run the Test](#4-run-the-test)
- [Contribute](#contribute)
## 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
Although Pylenium is a thin wrapper of Selenium, 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
```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')
```
```python
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
# 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):
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.visibility_of_element_located((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')]"))
```
### Purpose
I teach courses and do trainings for both **Selenium** and **Cypress**, 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 an amazing language?
**Selenium** has done an amazing job of providing W3C bindings to many languages and makes scaling a breeze.
**Cypress** has done an amazing job of making the testing experience more enjoyable - especially for beginners.
**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
The [Official Pylenium Docs](https://elsnoman.gitbook.io/pylenium) are the best place to start, but you can quickly get going with the following steps:
### 1. Install **pyleniumio**
```python
pip install pyleniumio
---or---
pipenv install pyleniumio
---or---
poetry add pyleniumio
```
### 2. Initialize Pylenium
```bash
# execute at your Project Root
pylenium init
```
This creates three files:
* `conftest.py` - This has the fixtures needed for Pylenium
* `pylenium.json` - This is the config file for Pylenium
* `pytest.ini` - This is the config 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`
```python
def test_google_search(py)
```
Pylenium uses **pytest** as the Test Framework. You only need to pass in `py`to the function!
Now we can use **Pylenium Commands** to interact with the browser.
```python
def test_google_search(py):
py.visit('https://google.com')
py.get("[name='q']").type('puppies')
py.get("[name='btnK']").submit()
assert py.should().contain_title('puppies')
```
### 4. Run the Test
This will depend on your IDE, but you can always run tests from the CLI:
```bash
python -m pytest tests/test_google.py
```
You're all set! You should see the browser open and complete the commands we had in the test :\)
## Contribute
Pylenium uses [Gitpod](https://gitpod.io/) to make it easy to work on it from the _desktop or browser_ without having to worry about the setup like having the correct Python version installed after cloning the repo.
> 💡 With a single click, you can open the repo in your browser, make your changes, then submit a pull request!
0. If you're new to Gitpod, check out their [Getting Started](https://www.gitpod.io/docs/introduction/getting-started) docs to see how to use it
1. Visit [Pylenium's repo](https://github.com/ElSnoMan/pyleniumio) and click the `Gitpod` button to open the repo in a VS Code browser window
2. Wait for Gitpod to set up the project. You'll see things get setup from the various `.gitpod*` files at the Project Root
3. Once complete, create a new branch and start making your changes
4. When ready, submit a Pull Request!
5. Reviewers will see your CI pipeline and even be able to open your Gitpod instance if needed - making collaboration much easier
6. Gitpod instances are ephemeral, so you can create, share, and delete them as needed
> 🧪 By default, UI tests executed in Gitpod are headless. If you'd like to see UI tests run, open port `6080` from the bottom right corner of VS Code.
For more details and other ways to contribute to Pylenium, visit the [CONTRIBUTING.md](/CONTRIBUTING.md) doc 👀
================================================
FILE: conftest.py
================================================
"""
The `conftest.py` and `pylenium.json` files generated by Pylenium should stay at your Workspace Root (aka Project Root).
conftest.py
Although this file is editable, you should only change its contents if you know what you are doing.
Instead, you can create your own conftest.py file in the folder where you store your tests.
pylenium.json
You can change the values, but DO NOT touch the keys or you will break the schema.
py
The main fixture you need from this is `py`. This is the instance of Pylenium for each test.
Just pass py into your test and you're ready to go!
Examples:
def test_go_to_google(py):
py.visit('https://google.com')
assert 'Google' in py.title()
"""
import copy
import json
import logging
import shutil
from pathlib import Path
import allure
import pytest
import requests
from faker import Faker
from pylenium.a11y import PyleniumAxe
from pylenium.config import PyleniumConfig, TestCase
from pylenium.driver import Pylenium
@pytest.fixture(scope="function")
def fake() -> Faker:
"""A basic instance of Faker to make test data."""
return Faker()
@pytest.fixture(scope="function")
def api():
"""A basic instance of Requests to make HTTP API calls."""
return requests
@pytest.fixture(scope="session", autouse=True)
def project_root() -> Path:
"""The Project (or Workspace) root as a filepath.
* This conftest.py file should be in the Project Root if not already.
"""
return Path(__file__).absolute().parent
@pytest.fixture(scope="session", autouse=True)
def test_results_dir(project_root: Path, request) -> Path:
"""Creates the `/test_results` directory to store the results of the Test Run.
Returns:
The `/test_results` directory as a filepath (str).
"""
session = request.node
test_results_dir = project_root.joinpath("test_results")
if test_results_dir.exists():
# delete /test_results from previous Test Run
shutil.rmtree(test_results_dir, ignore_errors=True)
try:
# race condition can occur between checking file existence and
# creating the file when using pytest with multiple workers
test_results_dir.mkdir(parents=True, exist_ok=True)
except FileExistsError:
pass
for test in session.items:
try:
# make the test_result directory for each test
test_results_dir.joinpath(test.name).mkdir(parents=True, exist_ok=True)
except FileExistsError:
pass
return test_results_dir
@pytest.fixture(scope="session")
def _load_pylenium_json(project_root, request) -> PyleniumConfig:
"""Load the default pylenium.json file or the given pylenium.json config file (if specified).
* Pylenium looks for these files from the Project Root!
I may have multiple pylenium.json files with different presets. For example:
- stage-pylenium.json
- dev-testing.json
- firefox-pylenium.json
Examples
--------
$ pytest
>>> Loads the default file: PROJECT_ROOT/pylenium.json
$ pytest pylenium_json=dev-pylenium.json
>>> Loads the config file: PROJECT_ROOT/dev-pylenium.json
$ pytest pylenium_json="configs/stage-pylenium.json"
>>> Loads the config file: PROJECT_ROOT/configs/stage-pylenium.json
"""
custom_config_filepath = request.config.getoption("pylenium_json")
config_filepath = project_root.joinpath(custom_config_filepath or "pylenium.json")
try:
with config_filepath.open() as file:
_json = json.load(file)
config = PyleniumConfig(**_json)
except FileNotFoundError:
logging.warning(f"The config_filepath was not found, so PyleniumConfig will load with default values. File not found: {config_filepath.absolute()}")
config = PyleniumConfig()
return config
@pytest.fixture(scope="session")
def _override_pylenium_config_values(_load_pylenium_json: PyleniumConfig, request) -> PyleniumConfig:
"""Override any PyleniumConfig values after loading the initial pylenium.json config file.
After a pylenium.json config file is loaded and converted to a PyleniumConfig object,
then any CLI arguments override their respective key/values.
"""
config = _load_pylenium_json
# Driver Settings
cli_remote_url = request.config.getoption("--remote_url")
if cli_remote_url:
config.driver.remote_url = cli_remote_url
cli_browser_options = request.config.getoption("--options")
if cli_browser_options:
config.driver.options = [option.strip() for option in cli_browser_options.split(",")]
cli_browser = request.config.getoption("--browser")
if cli_browser:
config.driver.browser = cli_browser
cli_local_path = request.config.getoption("--local_path")
if cli_local_path:
config.driver.local_path = cli_local_path
cli_capabilities = request.config.getoption("--caps")
if cli_capabilities:
# --caps must be in '{"name": "value", "boolean": true}' format
# with double quotes around each key. booleans are lowercase.
config.driver.capabilities = json.loads(cli_capabilities)
cli_page_wait_time = request.config.getoption("--page_load_wait_time")
if cli_page_wait_time and cli_page_wait_time.isdigit():
config.driver.page_load_wait_time = int(cli_page_wait_time)
# Logging Settings
cli_screenshots_on = request.config.getoption("--screenshots_on")
if cli_screenshots_on:
shots_on = cli_screenshots_on.lower() == "true"
config.logging.screenshots_on = shots_on
cli_extensions = request.config.getoption("--extensions")
if cli_extensions:
config.driver.extension_paths = [ext.strip() for ext in cli_extensions.split(",")]
cli_log_level = request.config.getoption("--pylog_level")
if cli_log_level:
level = cli_log_level.upper()
config.logging.pylog_level = level if level in ["DEBUG", "COMMAND", "INFO", "USER", "WARNING", "ERROR", "CRITICAL"] else "INFO"
return config
@pytest.fixture(scope="function")
def py_config(_override_pylenium_config_values) -> PyleniumConfig:
"""Get a fresh copy of the PyleniumConfig for each test
See _load_pylenium_json and _override_pylenium_config_values for how the initial configuration is read.
"""
return copy.deepcopy(_override_pylenium_config_values)
@pytest.fixture(scope="class")
def pyc_config(_override_pylenium_config_values) -> PyleniumConfig:
"""Get a fresh copy of the PyleniumConfig for each test class"""
return copy.deepcopy(_override_pylenium_config_values)
@pytest.fixture(scope="session")
def pys_config(_override_pylenium_config_values) -> PyleniumConfig:
"""Get a fresh copy of the PyleniumConfig for each test session"""
return copy.deepcopy(_override_pylenium_config_values)
@pytest.fixture(scope="function")
def test_case(test_results_dir: Path, request) -> TestCase:
"""Manages data pertaining to the currently running Test Function or Case.
* Creates the test-specific logger.
Args:
test_results_dir: The ./test_results directory this Test Run (aka Session) is writing to
Returns:
An instance of TestCase.
"""
test_name = request.node.name
test_result_path = test_results_dir.joinpath(test_name)
return TestCase(name=test_name, file_path=test_result_path)
@pytest.fixture(scope="function")
def py(test_case: TestCase, py_config: PyleniumConfig, request):
"""Initialize a Pylenium driver for each test.
Pass in this `py` fixture into the test function.
Examples:
def test_go_to_google(py):
py.visit('https://google.com')
assert 'Google' in py.title()
"""
py = Pylenium(py_config)
yield py
try:
if request.node.report.failed:
# if the test failed, execute code in this block
if py_config.logging.screenshots_on:
screenshot = py.screenshot(str(test_case.file_path.joinpath("test_failed.png")))
allure.attach(screenshot, "test_failed.png", allure.attachment_type.PNG)
elif request.node.report.passed:
# if the test passed, execute code in this block
pass
else:
# if the test has another result (ie skipped, inconclusive), execute code in this block
pass
except Exception:
logging.error("Failed to take screenshot on test failure.")
py.quit()
@pytest.fixture(scope="class")
def pyc(pyc_config: PyleniumConfig, request):
"""Initialize a Pylenium driver for an entire test class."""
py = Pylenium(pyc_config)
yield py
try:
if request.node.report.failed:
# if the test failed, execute code in this block
if pyc_config.logging.screenshots_on:
allure.attach(py.webdriver.get_screenshot_as_png(), "test_failed.png", allure.attachment_type.PNG)
elif request.node.report.passed:
# if the test passed, execute code in this block
pass
else:
# if the test has another result (ie skipped, inconclusive), execute code in this block
pass
except Exception:
logging.error("Failed to take screenshot on test failure.")
py.quit()
@pytest.fixture(scope="session")
def pys(pys_config: PyleniumConfig, request):
"""Initialize a Pylenium driver for an entire test session."""
py = Pylenium(pys_config)
yield py
try:
if request.node.report.failed:
# if the test failed, execute code in this block
if pys_config.logging.screenshots_on:
allure.attach(py.webdriver.get_screenshot_as_png(), "test_failed.png", allure.attachment_type.PNG)
elif request.node.report.passed:
# if the test passed, execute code in this block
pass
else:
# if the test has another result (ie skipped, inconclusive), execute code in this block
pass
except Exception:
logging.error("Failed to take screenshot on test failure.")
py.quit()
@pytest.fixture(scope="function")
def axe(py) -> PyleniumAxe:
"""The aXe A11y audit tool as a fixture."""
return PyleniumAxe(py.webdriver)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""Yield each test's outcome so we can handle it in other fixtures."""
outcome = yield
report = outcome.get_result()
if report.when == "call":
setattr(item, "report", report)
return report
def pytest_addoption(parser):
parser.addoption("--browser", action="store", default="", help="The lowercase browser name: chrome | firefox")
parser.addoption("--local_path", action="store", default="", help="The filepath to the local driver")
parser.addoption("--remote_url", action="store", default="", help="Grid URL to connect tests to.")
parser.addoption("--screenshots_on", action="store", default="", help="Should screenshots be saved? true | false")
parser.addoption(
"--pylenium_json",
action="store",
default="",
help="The filepath of the pylenium.json file to use (ie dev-pylenium.json)",
)
parser.addoption(
"--pylog_level", action="store", default="INFO", help="Set the logging level: 'DEBUG' | 'COMMAND' | 'INFO' | 'USER' | 'WARNING' | 'ERROR' | 'CRITICAL'"
)
parser.addoption(
"--options",
action="store",
default="",
help='Comma-separated list of Browser Options. Ex. "headless, incognito"',
)
parser.addoption(
"--caps",
action="store",
default="",
help='List of key-value pairs. Ex. \'{"name": "value", "boolean": true}\'',
)
parser.addoption(
"--page_load_wait_time",
action="store",
default="",
help="The amount of time to wait for a page load before raising an error. Default is 0.",
)
parser.addoption("--extensions", action="store", default="", help='Comma-separated list of extension paths. Ex. "*.crx, *.crx"')
================================================
FILE: docker-compose.yml
================================================
# To execute this docker-compose yml file use `docker-compose -f docker-compose-v3.yml up`
# Add the `-d` flag at the end for detached execution
# To stop the execution, hit Ctrl+C, and then `docker-compose -f docker-compose-v3.yml down`
version: "3"
services:
chrome:
image: selenium/node-chrome:4.1.3-20220327
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
edge:
image: selenium/node-edge:4.1.3-20220327
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
firefox:
image: selenium/node-firefox:4.1.3-20220327
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
selenium-hub:
image: selenium/hub:4.1.3-20220327
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
================================================
FILE: docs/README.md
================================================
---
description: Web Test Automation made easy
---
# Welcome to the Pylenium.io Docs
[](https://github.com/ElSnoMan/pyleniumio/tree/0bd684d227127daf2eccd2f284b849d4a91e3cb5/docs/code_of_conduct.md)
## Welcome to the Pylenium.io Docs
### 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
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
# 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):
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.visibility_of_element_located((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 both **Selenium** and **Cypress**, 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 an amazing language?
**Selenium** has done an amazing job of providing W3C bindings to many languages and makes scaling a breeze.
**Cypress** has done an amazing job of making the testing experience more enjoyable - especially for beginners.
**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.md)
{% endhint %}
You can also watch the Getting Started video with Pylenium's creator, Carlos Kidman!
{% embed url="https://www.youtube.com/watch?v=li1nc4SUojo" caption="Getting Started with v1.7.7+" %}
{% 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
```
{% endcode %}
#### 2. Initialize Pylenium
{% code title="Terminal $ " %}
```text
pylenium init
```
{% endcode %}
{% hint style="success" %}
Execute this command at your Project Root
{% endhint %}
This creates three files:
* `conftest.py` - This has the fixtures needed for Pylenium.
* `pylenium.json` - This is the [configuration ](https://github.com/ElSnoMan/pyleniumio/blob/master/docs/configuration/pylenium.json.md)file for Pylenium.
* `pytest.ini` - This is the configuration file for pytest and is used to connect to [ReportPortal](https://github.com/ElSnoMan/pyleniumio/blob/master/docs/configuration/report-portal.md)
By default, Pylenium uses 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 **pytest** as the Test Framework. You only need to pass in `py`to the function!
{% endhint %}
Now we can use **Pylenium Commands** to interact with the browser.
{% code title="test\_google.py" %}
```python
def test_google_search(py):
py.visit('https://google.com')
py.get("[name='q']").type('puppies')
py.get("[name='btnK']").submit()
assert py.should().contain_title('puppies')
```
{% endcode %}
#### 4. Run the Test
This will depend on your IDE, but you can always run tests from the CLI:
{% code title="Terminal $ \(venv\)" %}
```bash
python -m 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 :\)
================================================
FILE: docs/SUMMARY.md
================================================
# Table of contents
* [Welcome to the Pylenium.io Docs](README.md)
* [Changelog](changelog.md)
## Getting Started
* [1. Virtual Environments](getting-started/virtual-environments.md)
* [2. Setup pytest](getting-started/setup-pytest.md)
* [3. Project Structure with pytest](getting-started/project-structure-with-pytest.md)
* [4. Writing Tests with Pylenium](getting-started/writing-tests-with-pylenium.md)
## Guides
* [Run Tests in Parallel](guides/run-tests-in-parallel.md)
* [Run Tests in Containers](guides/run-tests-in-containers.md)
## CLI
* [Pylenium CLI](cli/pylenium-cli.md)
* [Report Portal](cli/report-portal.md)
## Configuration
* [pylenium.json](configuration/pylenium.json.md)
* [Driver](configuration/driver.md)
* [Logging](configuration/logging.md)
* [Report Portal](configuration/report-portal.md)
## CONTRIBUTE
* [Clone and Setup the Project](contribute/clone-and-setup-the-project.md)
## Fixtures
* [axe](fixtures/axe.md)
* [fake](fixtures/fake.md)
* [api \(aka requests\)](fixtures/requests.md)
## Pylenium Commands
* [Commands](pylenium-commands/commands.md)
* [contains](pylenium-commands/contains.md)
* [delete\_all\_cookies](pylenium-commands/delete_all_cookies.md)
* [delete\_cookie](pylenium-commands/delete_cookie.md)
* [execute\_script](pylenium-commands/execute_script.md)
* [fake](pylenium-commands/fake.md)
* [find](pylenium-commands/find.md)
* [findx](pylenium-commands/find_xpath.md)
* [get](pylenium-commands/get.md)
* [get\_cookies](pylenium-commands/get_cookies.md)
* [get\_cookie](pylenium-commands/get_cookie.md)
* [getx](pylenium-commands/get_xpath.md)
* [go](pylenium-commands/go.md)
* [maximize\_window](pylenium-commands/maximize_window.md)
* [quit](pylenium-commands/quit.md)
* [request](pylenium-commands/request.md)
* [screenshot](pylenium-commands/screenshot.md)
* [scroll\_to](pylenium-commands/scroll_to.md)
* [set\_cookie](pylenium-commands/set_cookie.md)
* [should](pylenium-commands/should.md)
* [switch\_to.default\_content](pylenium-commands/switch_to.default_content.md)
* [switch\_to.frame](pylenium-commands/switch_to.frame.md)
* [switch\_to.parent\_frame](pylenium-commands/switch_to.parent_frame.md)
* [switch\_to.window](pylenium-commands/switch_to.window.md)
* [title](pylenium-commands/title.md)
* [url](pylenium-commands/url.md)
* [wait](pylenium-commands/wait.md)
* [webdriver](pylenium-commands/webdriver.md)
* [window\_handles](pylenium-commands/window_handles.md)
* [window\_size](pylenium-commands/window_size.md)
* [viewport](pylenium-commands/viewport.md)
* [visit](pylenium-commands/visit.md)
* [\(deprecated\) xpath](pylenium-commands/xpath.md)
## Element Commands
* [check](element-commands/check.md)
* [children](element-commands/children.md)
* [click](element-commands/click.md)
* [css\_value](element-commands/css_value.md)
* [deselect](element-commands/deselect.md)
* [double\_click](element-commands/double_click.md)
* [drag\_to](element-commands/drag_to.md)
* [drag\_to\_element](element-commands/drag_to_element.md)
* [first](element-commands/first.md)
* [get\_attribute](element-commands/get_attribute.md)
* [get\_property](element-commands/get_property.md)
* [hover](element-commands/hover.md)
* [is\_checked](element-commands/is_checked.md)
* [is\_displayed](element-commands/is_displayed.md)
* [is\_enabled](element-commands/is_enabled.md)
* [is\_empty](element-commands/is_empty.md)
* [is\_selected](element-commands/is_selected.md)
* [last](element-commands/last.md)
* [length](element-commands/length.md)
* [open\_shadow\_dom](element-commands/open_shadow_dom.md)
* [parent](element-commands/parent.md)
* [right\_click](element-commands/right_click.md)
* [screenshot](element-commands/screenshot.md)
* [scroll\_into\_view](element-commands/scroll_into_view.md)
* [select](element-commands/select.md)
* [select\_many](element-commands/select_many.md)
* [should](element-commands/should.md)
* [siblings](element-commands/siblings.md)
* [submit](element-commands/submit.md)
* [tag\_name](element-commands/tag_name.md)
* [text](element-commands/text.md)
* [type](element-commands/type.md)
* [uncheck](element-commands/uncheck.md)
* [webelement](element-commands/webelement.md)
## Misc
* [Install chromedriver](misc/install-chromedriver.md)
================================================
FILE: docs/changelog.md
================================================
---
description: Summary of notable changes and fixes.
---
# Changelog
## 1.12.3 - 2021-14-04
### Overview
`.select()` and `.select_many()` worked pretty well as expected. However, they wouldn't fail as expected! Because we were trying to combine all the "select strategies" in a single function, it made it harder to test and debug _and_ hid some exceptions... like when an `<option>` didn't exist... whoops!
Now, each strategy has been pulled out into its own method and positive and negative tests have been added to make sure it works 😉
* `.select_by_index(index: int)`
* `.select_by_text(text: str)`
* `.select_by_value(value)`
### Changes
* New `.select_by_*()` methods exist on the `Element` object. Just remember that the dropdowns have to be a `<select>` element!
* `.select()` and `.select_many()` still exist, but show a `DEPRECATED` warning. We will be removing them in a future release
* Our docs have a new home! We are still using GitBook, but we now have the official `docs.pylenium.io` domain! Check it out 😄
## 1.12.2 - 2021-28-01
### Overview
Microsoft's Edge Browser can be used on Macs now, but we hadn't tested it locally on a Mac before. One of our amazing users did and they found a bug! This should now be fixed 😄
### Fixes
Removed `options` from `webdriver_factory.build_edge()` since it isn't needed and was causing the `MicrosoftEdgeDriver` to raise an error.
### Contributors
For anyone looking to contribute, we have changed using `pipenv` as our package manager to `poetry`.
* You can find out more about `poetry` by visiting their website: [https://python-poetry.org](https://python-poetry.org)
* You can see how to setup your machine for Python Development with `poetry` with my [Video on YouTube](https://youtu.be/547Jr26duHQ)
## 1.11.0 - 2020-10-30
### Overview
Pylenium can now do Accessibility \(a11y\) Testing and Audits using aXe! Easily generate JSON reports to share and export or write assertions against it directly in your tests.
### Added
#### aXe integration
There are two ways to start using aXe in your tests:
* `PyleniumAxe` class from `pylenium.a11y` module
* `axe` fixture \(recommended\)
```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
```
In the above example, we are using Pylenium to navigate to the website and then `axe` to run the audit, generate the report, and check that we have zero violations!
#### iframes
The main change here is the ability to drag and drop within iframes. Pylenium uses jQuery to perform this action, but we need to inject jQuery if the page doesn't already have it. However, in V1 of our jQuery implementation, it would only inject into the main document and not within each iframe. This is now fixed!
* Pylenium's jQuery V2 now comes in its own module and injects into all iframes of the page
* `py.switch_to` now comes with a `py.switch_to.frame_by_element()` which is useful when the iframe does not have an `id` or `name` attribute
```python
iframe = py.get('iframe')
py.switch_to.frame_by_element(iframe)
```
## 1.10.0 - 2020-10-7
### Overview
Pylenium can now gather Web Performance metrics right from the browser and into your hands! Through Pylenium's Performance API, you can leverage the different Performance and Timing objects that each browser tracks and generates. Also, we have created some custom data points, like `Time to Interactive`, `Page Load Time`, and `Time to First Contentful Paint`!
### Added
* Performance API
```python
# Access the Performance API right from py
perf = py.performance.get()
```
* WindowPerformance Object
The main abstraction that holds all of these metrics and data points.
```python
# get the TTI
tti = py.performance.get().time_to_interactive()
```
* Stopwatch Decorator
The `performance.py` module includes a Stopwatch Decorator that you add to any function. This will log how long it takes for that function to complete!
```python
# 1. How long does it take to add an item to the cart?
@stopwatch
def add_item_to_cart(py):
py.get('#add-item').click()
py.get('#added-notification').should().be_visible()
# 2. How long does it take to edit an item's available stock via the API and see it change in the UI?
@stopwatch
def update_available_stock(py, item, quantity):
payload = {'item': item, 'qty': quantity}
api.items.update(payload)
py.get(f'#available-stock-{item}').should().have_text(quantity)
```
* CONTRIBUTING.md
* CODE\_OF\_CONDUCT.md
#### Linked Issues
[Gather Web Performance data and log how long actions take](https://app.gitkraken.com/glo/view/card/c08c640de7754a2f9cd68034ffbd93a4)
## 1.9.7 - 2020-09-28
### Added
* Github Actions CI to Pylenium repo
* Pylenium CLI: `pylenium version` is now `pylenium --version`
If you want to import the `Pylenium` class into a Module, you used to do this:
```python
from pylenium import Pylenium
```
This has now been changed \(for CI reasons\) to:
```python
from pylenium.driver import Pylenium
```
So make sure you update this import statement if you were using the previous version!
### Fixes
* pytest version 6.1.0 \(released in September 2020\) causes issues within Pylenium's conftest.py. Changed Pipfile to use previous version until a solution is found
* `is_checked()` now works for Radio Buttons and Checkboxes
## 1.9.0 - 2020-06-24
> Changes were made to the `conftest.py` file, so make sure to run `pylenium init -c` after upgrading to `1.9.0` to overwrite it with the latest. Not doing this will likely result in `ModuleNotFoundErrors`
### Report Portal \(RP\)
RP is now natively supported by Pylenium! If you are not already familiar with Report Portal, I highly suggest you check it out. It gives you robust reporting and categorizing of your test runs and results and is backed with machine learning! [https://reportportal.io](https://reportportal.io)
We had very basic logging and reporting, but we wanted to provide a better and more robust reporting solution. After a lot of research, we landed on RP. They are not only free and Open Source, but they also have a great community, Slack group, and YouTube channel with different demos and presentations to help you take your reporting to the next level. This level of modern support was crucial in our decision and we hope you enjoy it!
### **Added**
* `pylenium init` now also creates a default `pytest.ini` file at your Project Root. This contains values to easily connect with RP.
* `pylenium portal` [CLI Commands](cli/report-portal.md) to quickly setup your RP instance
```bash
# 1. Download the docker-compose file used to spin up RP
pylenium portal download
# 2. Configure your machine and this docker-compose.yml based on your OS and needs
# by going to https://reportportal.io/docs/Deploy-with-Docker
```
```bash
# 3. Spin up the RP instance
pylenium portal up
```
That's it! You'll get helpful hints as you execute each command so you know where to go and how to login. Happy reporting!
### **Fixes**
* `get_xpath` and `find_xpath` functions were not behaving as expected. This has been fixed, but we have also renamed them to
* `getx()`
* `findx()`
* `AttributeError` was raised if there were more than one `pytest_runtest_makereport` fixtures in the project.
* Logging now uses the built-in `logging` python package, but screenshots are still saved to the `test_results` directory.
## 1.8.2 - 2020-05-21
### Changed
* **`Element.should().not_exist()` --> `Element.should().disappear()`**
If the intent is to check that the element is not on the page, then use:
```python
py.should().not_find()
```
If the intent is to wait until an existing element does not exist anymore or "disappear", then you used to have to do
```python
py.get().should().not_exist()
```
However, this is clearly confusing because the way `should not exist` reads would suggest that both options are the same. This has now been changed to more clearly reflect that intent.
```python
py.get().should().disappear()
```
* **`Element.should().have_attr()` doesn't require the `value` argument**
There is a scenario when all you want to check on an element is that an attribute exists or not.
Example:
```python
py.wait().until(lambda _: toggle.get_attribute('aria-checked'))
```
Unfortunately, `ElementShould.have_attr()` requires an attribute name AND a value. Trying to use it in this scenario is difficult to use or straight up doesn't work.
```python
# doesn't work
py.get(TOGGLE).should().have_attr('aria-checked', True)
# doesn't work either
py.get(TOGGLE).should().have_attr('aria-checked', '')
# this is just confusing...
py.get(TOGGLE).should().not_have_attr('aria-checked', None)
# this may work if it sees the custom `aria-checked` attribute as "checked"
py.get(TOGGLE).should().be_checked()
```
### **Solution**
Make the existing expectations not require the `value` argument.
* `should().have_attr(name, value=None)`
* `should().not_have_attr(name, value=None)`
### Fixed
* `drag_and_drop.js` was not included in the pylenium installation. Now it is!
* Some typos
## 1.8.0 - 2020-05-11
### Added
This is a bigger change that sets us up for things we want to do with better reporting and BDD functionality. There may be some breaking changes depending how you wrote your tests.
For example, the property of `Element.text` is now a function `Element.text()` and `.find()` no longer has an `at_least_one` parameter.
Make sure you run your tests after upgrading to catch errors like `str is not invocable`. They should be easy to fix.
### **PyleniumShould**
The use case of checking that an element is NOT on the page or DOM was much more common than anticipated. I have changed how the `.find()` and `.find_xpath()` functions behave to help with this, but there are now three easy to use "should" commands as well.
* **`.not_find()`**
* **`.not_find_xpath()`**
* **`.not_contain()`**
```python
# example usage
py.should().not_find('#hidden-element')
```
### **Driver**
Having these as properties was actually messing people up as they used Pylenium. Because almost all of the commands are functions, it was common that someone would try `py.url()` or `py.title()` only to see the test fail saying that `str is not invocable`. Changing these to functions feels more natural.
* `.url` property changed to `.url()` function
* `.title` property changed to `.title()` function
### **XPaths**
Removed the `.xpath()` function from _Pylenium_ and _Element_ and replaced with `get` and `find` options. The `.xpath()` function _could_ return an empty list, a single element, or a list of 2 or more elements. The flexibility was pretty "clever", but it was not intuitive to work with. Separating it into two distinct functions to match the CSS versions of `get()` and `.find()` made more sense.
* `.get_xpath()`
* `find_xpath()`
```python
# single element with xpath
py.get_xpath('//input[@name="q"]')
# list of elements with xpath
py.find_xpath('//li')
```
### **Find Elements**
The `.find()` and `.find_xpath()` functions on the _Pylenium_ and _Element_ objects will now return an empty list if none are found rather than throwing an exception. Dealing with an empty list is easier and cleaner than having to handle an exception.
However, this is not the case If the timeout is set to `0` \(zero\). The next section goes into more detail.
### **Immediate Poll with timeout=0**
There are times when you don't want to use an awesome wait and a timeout of 1 second isn't good enough. For all of the _Find Element_ commands, you can now set the timeout to `0` \(zero\) to poll the DOM immediately as if you were using Selenium with no wait.
> This will still return an `Element` or `Elements` object if found, but no _wait_ is used.
Let's take a look at the `.get()` signature:
```python
def get(self, css: str, timeout: int = None) -> Element
```
* If `timeout=None` \(default\), the function will use the default `wait_time` in `pylenium.json` which is 10 seconds.
```python
# use `wait_time` in pylenium.json
py.get('#button').click()
```
* If `timeout > 0`, override the default wait\_time.
```python
# shorten it to 3 seconds instead
py.get('#button', timeout=3).click()
---or---
# give it even more time
py.get('#button', timeout=30).click()
```
* If timeout=0, poll the DOM immediately without any waiting.
```python
# no waiting, immediately poll the DOM
py.get('#button', timeout=0).click()
```
### **Element and Elements**
Changed some properties to functions for the same reasons as the props in Driver.
* `Elements.length` property changed to `Elements.length()` function
* `Element.tag_name` property changed to `Element.tag_name()` function
* `Element.text` property changed to `Element.text()` function
### **ElementsShould**
_Pylenium_ and _Element_ have their own Should classes for expectations. Most of our assertions and checks are done against them, but there were enough use cases against the length of the Elements that I wanted to include them to make it easier. Now when you have a list of elements \(Elements\), you can use `.should()`:
* `be_empty()`
* `not_be_empty()`
* `have_length()`
* `be_greater_than()`
* `be_less_than()`
## 1.7.7 - 2020-05-08
### Added
Pylenium CLI
### Details
After a fresh install of pyleniumio, you now need to initialize pylenium using the Pylenium CLI:
```bash
$ pylenium init
```
You can also see the available options using the `--help` argument.
```bash
$ pylenium init --help
```
This will create the `conftest.py` file needed by Pylenium as well as the default `pylenium.json` config file.
{% hint style="info" %}
Run this command at the Project Root so Pylenium is globally accessible by all files/tests in your project.
{% endhint %}
### Purpose
Originally, Pylenium would copy a conftest.py file and overwrite any existing conftest.py files the user had at the Project Root. This was a necessary side effect with how `setup.py` was working. With `pylenium init`, you now have the option to create or overwrite these files rather than needing to start from scratch.
`pylenium init` also creates a default `pylenium.json` so the user knows what config values they can change globally. This makes for a much easier experience for users.
This also removes the requirement of the user being in the context of a virtual environment. Although this is still 100% recommended, `pylenium init` can be executed in or out of the venv.
## 1.6.2 - 2020-05-07
### Added
* `options.add_extension()`
* `Element.open_shadow_dom()`
### Details
**Add Extension**
You can now easily add extensions to your browser sessions by either using the `--extensions` CLI argument and passing in a list of file paths, or you can also do this in the `pylenium.json`
```javascript
{
"driver": {
"extension_paths": ["path.crx", "other-path.crx"]
}
}
```
**Shadow DOM**
Shadow DOMs are a bit tricky, but you can now find elements within them by using the `Element.open_shadow_dom()` command. Check out this example using `chrome://extensions`:
```python
def test_loading_extension_to_browser(py):
py.visit('chrome://extensions/')
shadow1 = py.get('extensions-manager').open_shadow_dom()
shadow2 = shadow1.get('extensions-item-list').open_shadow_dom()
extension_shadow_dom = shadow2.find('extensions-item')[1].open_shadow_dom()
assert extension_shadow_dom.get('#name-and-version').should().contain_text('Get CRX')
```
## 1.6.1 - 2020-05-01
### Added
* `drag_to( css )`
* `drag_to_element( to_element )`
### Details
`Element.drag_to( css )` will drag the current element to the element with the given CSS.
```python
py.get('#draggable-box').drag_to('#drop-here')
```
`Element.drag_to_element( to_element )` will drag the current element to the given element.
```python
to_element = py.get('#drop-here')
py.get('#draggable-box').drag_to(to_element)
```
```python
# one more example
from_element = py.get('#draggable-box')
to_element = py.get('#drop-here')
from_element.drag_to_element(to_element)
```
## 1.6.0 - 2020-04-28
### Added
* Page Load Wait Time
* Test Case Name into Capabilities for frameworks like Selenoid
* Add Experimental Options via `pylenium.json`
### Details
**Page Load Wait Time**
By default, the Page Load timeout is `0` just like Selenium. However, there were cases where users wanted to control this globally or as needed per test case. You can now do this a few different ways:
```bash
# set it globally in CLI
--page_load_wait_time 10
```
```javascript
// set it globally in pylenium.json
{
"page_load_wait_time": 10
}
```
```python
# override the global page_load_wait_time just for the current test
py.set_page_load_timeout(10)
```
**Test Case Name into Capabilities**
This was primarily for other frameworks like Selenoid and Zalenium that used this name to label the tests in their runners. For example, in Selenoid, you can filter tests by name. Before this change, the tests were given an unhelpful, generic name instead of the proper test name. That's fixed now :\)
**Add Experimental Options**
For users that want to use some of the experimental options for browsers, you can now do this within `pylenium.json`. This is a list of dictionaries \(key-value pairs\) that you want to include globally.
```javascript
{
"experimental_options": [
{"useAutomationExtension": false},
{"otherName": "value"}
]
}
```
## 1.5.4 - 2020-04-27
### Added
* `WebDriverFactory().build_capabilities()`
* capabilities is a single dictionary instead of a list of dictionaries
Originally I wasn't going to add capabilities because it was going to be deprecated in Selenium 4. However, it seems enough people need it \(including my very own Workfront\) and even with Selenium 4, there will be cases where they are needed.
Also, with the refactor it became very clear that a single dictionary of capabilities was much better than a list of them. This change has been reflected in `pylenium.json` as well as in the CLI args.
{% code title="pylenium.json" %}
```python
{
"driver": {
"capabilities": {
"enableVNC": true,
"enableVideo": false,
"name": "value"
}
}
}
```
{% endcode %}
{% code title="Terminal" %}
```python
--caps = '{"name": "value", "boolean": true}'
```
{% endcode %}
## 1.5.2 - 2020-04-24
### Added
* `EdgeChromiumDriver`
* `Customize DesiredCapabilities`
### Details
* `EdgeChromiumDriver`
{% code title="pylenium.json" %}
```python
{
"driver": {
"browser": "edge"
}
}
```
{% endcode %}
{% code title="Terminal" %}
```python
--browser="chrome"
```
{% endcode %}
* `Customize DesiredCapabilities`
```javascript
// pylenium.json
{
"driver": {
"capabilities": [
{"name": "value"}
]
}
}
```
{% code title="Terminal" %}
```python
--caps [{"name1": "value1"}, {"name2": "value2"}]
```
{% endcode %}
## 1.4.1 - 2020-04-17
### Added
* `webdriver_manager`
* `configure pylenium.json`
### Details
* `webdriver_manager`
This is the biggest change made in this release. Pylenium now uses `webdriver_manager` to install the necessary driver binaries to the user's machine automatically!
> This means that the user does NOT need to worry about installing them anymore!
Of course, they will still need the actual browser installed, but that's much easier than installing the driver binaries and adding them to the PATH.
This is a great step in making UI Automation with Pylenium a pleasant experience for everyone :\)
* `pylenium.json defaults`
Prior to this release, we would install a `pylenium.json` file at the Project Root alongside our conftest.py. The issue is that this JSON file was meant to be an easy way to control Pylenium's settings \(which it was\), but is overriden every time they would update to a new version of Pylenium...
This also caused issues in CI/CD pipelines because they could not rely on this file to configure Pylenium since installing it fresh in the pipeline would give you a fresh pylenium.json...
This is now taken care of! We are using our BaseModel classes to use defaults that can be overriden two different ways:
1. They can still override them using the CLI options. For example: `pytest tests --browser='opera'`
2. They can create a `pylenium.json` at the Project Root \(same dir as conftest.py\) with the values they want to override. They can also include any other key/value pairs in the `custom` object:
```javascript
// pylenium.json
{
driver: {
"wait_time": 5
}
custom = {
"foo": "bar"
}
}
```
```python
# use it in code
py.config.custom.get('foo') # => yields "bar"
---or---
py.config.custom['foo'] # => yields "bar"
```
## 1.3.0 - 2020-04-05
### Fixed
* Updated the tests in the examples directory
* Fixed an issue when using `PyleniumWait`
### Added
* `py.should()` - A collection of expectations for the current driver \( [\#15](https://github.com/ElSnoMan/pyleniumio/issues/15) \)
* `Element.should()` - A collection of expectations for the current element \( [\#36](https://github.com/ElSnoMan/pyleniumio/issues/36) \)
* `Element.get_property()`
* `Element.is_enabled()`
* `Elements.is_empty()`
## 1.2.10 - 2020-03-24
### Added
* `Element.select(value)` - value can now be the index of the option
## 1.2.9 - 2020-03-23
### Added
* `py.scroll_to(x, y)` - Scroll x and y pixels on the page
* `Element.scroll_into_view()` - Scroll the element into the viewport
* `Element.right_click()` - Right click on the element
## 1.2.8 - 2020-03-21
### Fixed
* `DesiredCapabilities` error if user had an old version of chromedriver
* `py.switch_to.frame()` wasn't switching to frame properly
### Added
* **`PyleniumWait`**
* **Pylenium Commands > wait** - doc with Usage examples
### Changed
* Custom timeouts to some commands, like `.get()`, to override global wait\_time in pylenium.json
## 1.2.7 - 2020-03-17
### Added
* Official release of V1 to the Autobots class
* The core functionality of Pylenium
================================================
FILE: docs/cli/pylenium-cli.md
================================================
---
description: 'The CLI comes with commands to initialize and create Pylenium files, and more.'
---
# Pylenium CLI
## 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 Project Root \(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 **conftest.py** 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 **pylenium.json** 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 **pytest.ini** file with the latest defaults by using the `-i` flag.\
{% code title="Terminal $" %}
```bash
pylenium init -i
```
{% endcode %}
================================================
FILE: docs/cli/report-portal.md
================================================
---
description: CLI commands to easily setup and teardown your RP instance.
---
# Report Portal
{% hint style="success" %}
Make sure to execute these commands in your Project Root.
{% endhint %}
## portal download
Download the default `docker-compose.yml` file needed to spin up the RP instance.
{% hint style="info" %}
This will place the file at your Project Root as `docker-compose.report-portal.yml`
{% endhint %}
{% code title="Terminal $" %}
```text
pylenium portal download
```
{% endcode %}
## portal up
Spin up the RP instance using the downloaded YAML file.
{% code title="Terminal $" %}
```text
pylenium portal up
```
{% endcode %}
You can then open [http://localhost:8080](http://localhost:8080) to see your newly created instance!
## portal down
Teardown the RP instance.
{% hint style="warning" %}
This may or may not work depending on your Terminal on Windows.
{% endhint %}
{% code title="Terminal $" %}
```text
pylenium portal down
```
{% endcode %}
================================================
FILE: docs/configuration/driver.md
================================================
---
description: Configure the driver via the pylenium.json or the CLI.
---
# Driver
## The Driver Settings
Supported Drivers:
* **Chrome**
* **Firefox**
* **IE**
* **Opera**
* **Edge \(Chromium\)**
Let's take a look at the Driver Settings in `pylenium.json`
{% code title="pylenium.json" %}
```javascript
"driver": {
"browser": "chrome",
"remote_url": "",
"wait_time": 10,
"page_load_wait_time": 0,
"options": [],
"experimental_options": null,
"capabilities": {},
"extension_paths": [],
"webdriver_kwargs": {},
"version": "latest"
}
```
{% 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 **`"chrome"`**
{% endhint %}
This is the browser name - `"chrome"` or `"firefox"` or `"ie"` or `"opera"` or `"edge"`
{% code title="pylenium.json" %}
```javascript
"driver": {
"browser": "firefox"
}
```
{% endcode %}
{% code title="Terminal" %}
```bash
python -m pytest tests --browser=firefox
```
{% endcode %}
### remote\_url
{% hint style="info" %}
Default is empty or**`""`**
{% endhint %}
This is used to connect to things like **Selenium Grid.**
{% hint style="success" %}
Check out [Run Tests in Containers](../guides/run-tests-in-containers.md) for an example of how to do this locally with **Docker**
{% endhint %}
{% code title="pylenium.json" %}
```javascript
"driver": {
"remote_url": "http://localhost:4444/wd/hub"
}
```
{% endcode %}
{% code title="Terminal" %}
```bash
python -m pytest tests --remote_url="http://localhost:4444/wd/hub"
```
{% endcode %}
### wait\_time
{% hint style="info" %}
Default is **`10`**
{% 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 0
{% 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
python -m pytest tests --options="headless, incognito"
```
{% endcode %}
### experimental\_options
{% hint style="info" %}
Default is `null` or `None`
{% endhint %}
A list of experimental options to include in the driver. These can only be added using `pylenium.json`
```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
--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"]
}
}
```
### webdriver_kwargs
Arbitrary keyword arguments to pass to the Selenium webdriver.
{% hint style="info" %}
Default is empty or `{}`
{% endhint %}
```javascript
{
"driver": {
"webdriver_kwargs": {"service_log_path": "webdriver.log"}
}
}
```
### version
{% hint style="info" %}
Default is **`"latest"`**
{% endhint %}
The browser version to use.
{% code title="pylenium.json" %}
```javascript
"driver": {
"version": "latest"
}
```
{% endcode %}
{% code title="Terminal" %}
```bash
You cannot set the browser version this way
```
{% endcode %}
================================================
FILE: docs/configuration/logging.md
================================================
---
description: Configure logging via the pylenium.json or the CLI.
---
# Logging
## Levels
Pylenium has 3 logging levels:
* `'off'`
* `'info' (default)`
* `'debug'`
When you start a Test Run, Pylenium will create a **test\_results** directory at your Project Root.
{% hint style="info" %}
You should put **test\_results** in your .`gitignore`
{% endhint %}
By default, each test using the `py` fixture will have its own subdirectory with a `test_log.txt` and, if the test fails, a screenshot.
You can easily control the logging and screenshots by setting the values in `pylenium.json` or passing in command-line arguments.
### off
Like the name suggests, this will turn off all `INFO` and higher entries, which is almost everything.
{% hint style="info" %}
You will still get a `test_log.txt` file, but it will only have basic info of the test like chromedriver version.
{% endhint %}
{% code title="pylenium.json" %}
```text
"pylog_level": "off"
```
{% endcode %}
{% code title="Terminal" %}
```text
python -m pytest tests --pylog_level=off
```
{% endcode %}
### info
This will include basic info as well as the `INFO` , `ACTION`, `STEP` entries.
{% hint style="success" %}
This is the **default** configuration, so you don't need to explicitly set these.
{% endhint %}
{% code title="pylenium.json" %}
```text
"pylog_level": "info"
```
{% endcode %}
{% code title="Terminal" %}
```text
python -m pytest tests --pylog_level=info
```
{% endcode %}
### debug
This will include everything in **info** as well as the `DEBUG` , `WARNING`, `ERROR` and `FATAL` entries.
{% hint style="info" %}
You can use `py.log.debug(message)` to log debugging entries!
{% endhint %}
{% code title="pylenium.json" %}
```text
"pylog_level": "debug"
```
{% endcode %}
{% code title="Terminal" %}
```text
python -m pytest tests --pylog_level=debug
```
{% endcode %}
## Screenshots
By default, if a test fails, Pylenium will take a screenshot and include it in the respective `test_results` test directory.
This can easily be turned **off.**
{% code title="pylenium.json" %}
```text
"screenshots_on": False
```
{% endcode %}
{% code title="Terminal" %}
```text
python -m pytest tests --screenshots_on=false
```
{% endcode %}
================================================
FILE: docs/configuration/pylenium.json.md
================================================
---
description: The configuration file for Pylenium
---
# pylenium.json
## 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 **`pylenium.json`** file at the Project Root \(same directory as our `conftest.py` file\) and do it with a JSON instead.
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": [],
"experimental_options": null,
"capabilities": {},
"extension_paths": [],
"webdriver_kwargs": {},
"version": "latest"
},
"logging": {
"screenshots_on": true,
"pylog_level": "info"
},
"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 **`custom`** object to be used by **`py.config`**
{% 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]
```
================================================
FILE: docs/configuration/report-portal.md
================================================
---
description: Connect your tests to a RP instance.
---
# Report Portal
{% hint style="info" %}
This doc assumes you are already familiar with the [Report Portal CLI commands](../cli/report-portal.md)
{% endhint %}
## Configure with pytest.ini
Once you've spun up your instance of Report Portal \(RP\), there are only a few values you need to change in the `pytest.ini` file created when you ran `pylenium init`.
Here is the default pytest.ini file:
```graphql
[pytest]
;ReportPortal `pytest-reportportal` plugin
;ReportPortal (required)
rp_endpoint = http://localhost:8080
rp_uuid = [UUID from USER PROFILE section of ReportPortal]
rp_launch = EXAMPLE_TEST_RUN_NAME
rp_project = default_personal
;For more info, including other pytest.ini options, visit: https://github.com/reportportal/agent-python-pytest
;ReportPortal (optional)
rp_ignore_errors = True
rp_hierarchy_dirs = True
rp_hierarchy_module = False
rp_hierarchy_class = False
```
{% hint style="info" %}
The only value that _needs ****_be changed is the `rp_uuid` which is the **ACCESS TOKEN** to connect
{% endhint %}
### Launch and Project
* `rp_launch` is the name of the **Test Run** and is the grouping of tests that will be reported to RP under the **Launches** tab.
* `rp_project` is the name of the **Project** that you would like the Launch to report to.
By default, there is a `superadmin_personal` and a `default_personal` project when you first spin up your instance of RP. You can switch between them or create more on the **Administrate** page under the User Menu.
{% hint style="info" %}
Any of these variables _can_ be overridden via the CLI in case you need them defined at runtime.
{% endhint %}
### Get the ACCESS TOKEN
Once you have logged in to your instance of RP, go to the **Profile** page under the User Menu in the top right corner. This will have the ACCESS TOKEN for you to copy.
Paste this into the `rp_uuid` value in `pytest.ini` and save. That's it!
## Run the Tests
All that's left is to run the tests like usual, but now include the `--reportportal` flag.
{% code title="Example" %}
```bash
pytest tests/test_checkout.py --reportportal
```
{% endcode %}
Refresh the Launches tab and you should now see your Test Run!
{% hint style="success" %}
There's a lot you can do, so make sure to visit their docs for more: [https://reportportal.io/docs](https://reportportal.io/docs)
{% endhint %}
================================================
FILE: docs/contribute/clone-and-setup-the-project.md
================================================
---
description: The first step in contributing to Pylenium.io
---
# Clone and Setup the Project
## 1. Fork the Project
This GitHub Guide is simple and concise! It goes over:
* How to Fork
* Clone and add to your Fork
* Submitting a Pull Request \(PR\)
{% embed url="https://guides.github.com/activities/forking/" caption="" %}
## 2. Virtual Environment
Once cloned, open your IDE of choice and create a Virtual Environment.
{% hint style="info" %}
Follow the Pylenium guide for venvs and pytest here: [Virtual Environments](../getting-started/virtual-environments.md)
{% endhint %}
## 3. Package Management with Pipenv
Pipenv is the package manager that is used for Pylenium. Please take a moment to read this guide to understand how to use it and the Pipfiles.
{% embed url="https://realpython.com/pipenv-guide/" caption="" %}
### Install pipenv globally
{% tabs %}
{% tab title="Mac \| Linux" %}
```text
pip3 install pipenv
```
{% endtab %}
{% tab title="Windows" %}
```bash
pip install pipenv
```
{% endtab %}
{% endtabs %}
### Install the packages from Pipfile
Now open a Terminal in the pyleniumio project to install the packages.
{% code title="Terminal $" %}
```bash
pipenv sync
```
{% endcode %}
{% hint style="warning" %}
You must be using **Python 3.8.1** or greater!
{% endhint %}
## 4. Follow our Contributing Guide
You're all set! Head on over to our [CONTRIBUTING GUIDE](https://github.com/ElSnoMan/pyleniumio/tree/d887dd0028538e9416fe3fe284a75ab30a2dc744/CONTRIBUTING.md) for more information on:
* Code of Conduct
* Templates for Bugs, Enhancements, etc.
* Guidelines for Pull Requests and suggestions
* and more!
Thank you for taking the time to contribute! We're excited to have you!
================================================
FILE: docs/element-commands/check.md
================================================
---
description: The command to select checkboxes or radio buttons.
---
# check
## Syntax
```python
Element.check()
Element.check(allow_selected)
---or---
Elements.check()
Elements.check(allow_selected)
```
## Usage
{% code title="correct usage" %}
```python
# check a radio button
py.get('[type="radio"]').check()
---or---
# check all boxes on the page
py.find('[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
* `allow_selected=False (bool)` - 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
* **\(Element or Elements\)** The current instance so you can chain commands
## Raises
* **ValueError** if the element is selected already. Set `allow_selected` to **True** to ignore this.
* **ValueError** if the element is not a checkbox or radio button
================================================
FILE: docs/element-commands/children.md
================================================
---
description: The command to get the children of the element.
---
# children
## Syntax
```python
Element.children()
```
## Usage
{% code title="correct usage" %}
```python
py.get('a').children()
---or--- # store in a variable
elements = py.get('ul').children()
---or--- # chain another command
child = py.get('ul').children().first()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(Elements\)** A list of Elements. The list is empty if no children are found.
================================================
FILE: docs/element-commands/click.md
================================================
---
description: The command to click the element.
---
# click
## Syntax
```python
Element.click()
Element.click(force=True)
```
## 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
* **`force=False (bool)`** - If **True**, a JavascriptExecutor command is sent instead of Selenium's native `.click()`
## Yields
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands.
{% hint style="info" %}
You can continue the chain if `.click()` opens a new view or navigates to a different page
{% endhint %}
================================================
FILE: docs/element-commands/css_value.md
================================================
---
description: Get the CSS Value of the element given the property name.
---
# css\_value
## Syntax
```python
Element.css_value(property_name)
```
## 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
* **`property_name (str)`** - The name of the CSS Property
## Yields
* **\(Any\)** Typically strings, but this depends on the CSS Property
================================================
FILE: docs/element-commands/deselect.md
================================================
---
description: The command to deselect an <option> within a multi <select> element.
---
# deselect
## 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.
================================================
FILE: docs/element-commands/double_click.md
================================================
---
description: The command to double click the element.
---
# double\_click
## 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
================================================
FILE: docs/element-commands/drag_to.md
================================================
---
description: >-
The command to drag the current element to another element given its CSS
selector.
---
# drag\_to
## 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.
================================================
FILE: docs/element-commands/drag_to_element.md
================================================
---
description: The command to drag the current element to the given element.
---
# drag\_to\_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.
================================================
FILE: docs/element-commands/first.md
================================================
---
description: The command to get the first Element in a list of Elements.
---
# first
## Syntax
```python
Elements.first()
```
## Usage
{% code title="correct usage" %}
```python
py.find('li').first()
---or--- # store in a variable
element = py.xpath('//a').first()
---or--- # chain an Element command
py.get('ul > li').children().first().click()
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, 'get' yields Element, not Elements
py.get('ul > li').first()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(Element\)** The first Element in the list of Elements
## Raises
* **IndexError** if Elements is empty
================================================
FILE: docs/element-commands/get_attribute.md
================================================
---
description: The command to get the attribute's value with the given name.
---
# get\_attribute
## Syntax
```python
Element.get_attribute(attribute)
```
## Usage
{% code title="correct usage" %}
```python
py.get('a').get_attribute('href')
---or--- # store in a variable
href = py.get('a').get_attribute('href')
```
{% endcode %}
## Arguments
* `attribute (str)` = The name of the attribute to find in the Element
## Yields
* If the value is `'true'` or `'false"`, then this returns a bool of **True** or **False**
* If the name does not exist, return **None**
* All other values are returned as strings
================================================
FILE: docs/element-commands/get_property.md
================================================
---
description: The command to get the specified property's value of the element.
---
# get\_property
## Syntax
```python
Element.get_property(property)
```
## 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
* `property (str)`: The name of the property.
## Yields
* The value returned by the property, but this is usually a string.
================================================
FILE: docs/element-commands/hover.md
================================================
---
description: The command to hover the element.
---
# hover
## Syntax
```python
Element.hover()
```
## 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
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands
## Examples
Originally, `.hover()` returned the current Element, but most hovering scenarios revealed items that were _not_ descendants of the hovered element.
```python
py.get('.menu').hover().contains('About').click()
```
================================================
FILE: docs/element-commands/is_checked.md
================================================
---
description: The command to check if this element is checked.
---
# is\_checked
## Syntax
```python
Element.is_checked()
```
## Usage
{% code title="correct usage" %}
```python
py.get('#checkbox').is_checked()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(bool\)** True if the element is checked, else False
================================================
FILE: docs/element-commands/is_displayed.md
================================================
---
description: The command to check if this element is displayed.
---
# is\_displayed
## Syntax
```python
Element.is_displayed()
```
## Usage
{% code title="correct usage" %}
```python
py.get('#button').is_displayed()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(bool\)** True if the element is displayed, else False
{% hint style="info" %}
"displayed" means that the element is in the DOM and has a size greater than zero such that it is visible to the user.
{% endhint %}
================================================
FILE: docs/element-commands/is_empty.md
================================================
---
description: The command to check if the list of elements is empty.
---
# is\_empty
## Syntax
```python
Elements.is_empty()
```
## Usage
{% code title="correct usage" %}
```python
py.find('a.hidden-link').is_empty()
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, 'Element' is not a list
py.get('a').is_empty()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(bool\)** True if the length is zero, else False
================================================
FILE: docs/element-commands/is_enabled.md
================================================
---
description: The command to check if the element is enabled.
---
# is\_enabled
## Syntax
```python
Element.is_enabled()
```
## Usage
{% code title="correct usage" %}
```python
py.get('#button').is_enabled()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(bool\)** True if the element is enabled, else False
================================================
FILE: docs/element-commands/is_selected.md
================================================
---
description: The command that checks if the element is selected.
---
# is\_selected
## Syntax
```python
Element.is_selected()
```
## Usage
{% code title="correct usage" %}
```python
py.get('.option').is_selected()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(bool\)** True if the element is selected, else False
================================================
FILE: docs/element-commands/last.md
================================================
---
description: The command to get the last Element in a list of Elements.
---
# last
## Syntax
```python
Elements.last()
```
## Usage
{% code title="correct usage" %}
```python
py.find('li').last()
---or--- # store in variable
last = py.xpath('//a').last()
---or--- # chain an Element command
py.get('ul > li').children().last().click()
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, 'get' yields Element, not Elements
py.get('ul > li').last()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(Element\)** The last Element in a list of Elements
## Raises
* **IndexError** if Elements is empty
================================================
FILE: docs/element-commands/length.md
================================================
---
description: The command to get the length of Elements.
---
# length
## Syntax
```python
Elements.length()
```
## Usage
{% code title="correct usage" %}
```python
assert py.find('li').length() == 5
---or---
assert py.find('li').should().have_length(5)
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, 'get' yields Element, not Elements
assert py.get('li').length() == 1
```
{% endcode %}
## Arguments
* None
## Yields
* **\(int\)** The length of the Elements
================================================
FILE: docs/element-commands/open_shadow_dom.md
================================================
---
description: The command to open/expand a Shadow DOM element.
---
# open\_shadow\_dom
## Syntax
```python
Element.open_shadow_dom()
```
## Usage
Shadow DOMs are a bit tricky because, like iframes, you need to "switch" to its context to find elements or objects within it. Check out this example using `chrome://extensions`:
```python
def test_loading_extension_to_browser(py):
py.visit('chrome://extensions/')
shadow1 = py.get('extensions-manager').open_shadow_dom()
shadow2 = shadow1.get('extensions-item-list').open_shadow_dom()
extension_shadow_dom = shadow2.find('extensions-item')[1].open_shadow_dom()
assert extension_shadow_dom.get('#name-and-version').should().contain_text('Get CRX')
```
## Yields
* `The Shadow Root (Element)`. With this element you can search for things within the Shadow context.
================================================
FILE: docs/element-commands/parent.md
================================================
---
description: The command to get the parent of the element.
---
# parent
## Syntax
```python
Element.parent()
```
## Usage
{% code title="correct usage" %}
```python
py.get('li').parent()
---or--- # store in a variable
element = py.get('li').parent()
---or--- # chain an Element command
py.get('li').parent().click()
---or--- # even go up the DOM tree
grand_parent = py.get('li').parent().parent()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(Element\)** The parent Element
================================================
FILE: docs/element-commands/right_click.md
================================================
---
description: The command to right-click the element.
---
# right\_click
## Syntax
```text
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
================================================
FILE: docs/element-commands/screenshot.md
================================================
---
description: The command to take a screenshot of the element.
---
# screenshot
## Syntax
```python
Element.screenshot(filename)
```
## Usage
{% code title="correct usage" %}
```python
py.get('a[href="/about"]').screenshot('elements/about-link.png')
---or--- # chain an Element command
py.get('#save-button').screenshot('save-.png').click()
```
{% endcode %}
## Arguments
* `filename (str)` - The file path including the file name and extension like `.png`
## Yields
* **\(Element\)** The current instance of Element so you can chain commands
================================================
FILE: docs/element-commands/scroll_into_view.md
================================================
---
description: The command to scroll this element into the viewport
---
# scroll\_into\_view
## Syntax
```text
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
================================================
FILE: docs/element-commands/select.md
================================================
---
description: The command to select an <option> within a <select> element.
---
# select
## Syntax
```python
Element.select(value)
```
## Usage
{% code title="correct usage" %}
```python
py.get('select').select('option-2')
---or--- # chain an Element command
py.get('#dropdown').select('option-1').select('option-2')
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, can only perform 'select' on a <select> element
py.get('ul > li').select('option-2')
```
{% endcode %}
## Arguments
* `value (str)` - The **text** or **value** or **index** of the option to select.
## Yields
* **\(Element\)** The current instance of Element so you can chain commands.
## Examples
{% code title="select by index" %}
```python
# select the second option in the list
py.get('select').select(1)
```
{% endcode %}
{% code title="select by value or visible text" %}
```python
# select the option with the text or value
py.get('select').select('option2')
```
{% endcode %}
================================================
FILE: docs/element-commands/select_many.md
================================================
---
description: The command to select multiple <option>s in a multi <select> element.
---
# select\_many
## Syntax
```python
Element.select_many(values)
```
## Usage
{% code title="correct usage" %}
```python
py.get('select').select_many(['option-1', 2])
---or--- # chain a Pylenium command
py.get('select').select_many(['opt-1', 'opt-2']).screenshot('new_view.png')
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, can only perform 'select_many' on a multi <select> element
py.get('ul > li').select_many(['option-1', 2])
```
{% endcode %}
## Arguments
* `values (list)` - The list of texts or values of the <option>s you want to select.
## Yields
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands.
================================================
FILE: docs/element-commands/should.md
================================================
---
description: A collection of expectations for the current Element or Elements.
---
# should
## Element Expectations \(ElementShould\)
### Positive Conditions
* `.be_checked()`
* `.be_clickable()`
* `.be_disabled()`
* `.be_enabled()`
* `.be_focused()`
* `.be_hidden()`
* `.be_selected()`
* `.be_visible()`
* `.contain_text()`
* `.disappear()`
* `.have_attr()`
* `.have_class()`
* `.have_prop()`
* `.have_text()`
* `.have_value()`
### Negative Conditions
* `.not_be_focused()`
* `.not_have_attr()`
* `.not_have_text()`
* `.not_have_value()`
## Elements Expectations \(ElementsShould\)
### Positive Conditions
* `.be_empty()`
* `.have_length()`
* `.be_greater_than()`
* `.be_less_than()`
### Negative Conditions
* `.not_be_empty()`
## Syntax
```python
# use the default wait_time
Element.should().<expectation>
Elements.should().<expectation>
---or---
# customize the wait_time for this expectation
Element.should(timeout).<expectation>
Elements.should(timeout).<expectation>
---or---
# ignore exceptions that you expect to "get in the way"
Element.should(ignored_exceptions).<expectation>
Elements.should(ignored_exceptions).<expectation>
---or---
# customize both fully
Element.should(timeout, ignored_exceptions).<expectation>
Elements.should(timeout, ignored_exceptions).<expectation>
```
## Usage
{% code title="correct usage" %}
```python
py.get('#about-link').should().have_text('About')
---or---
py.find('li > a').should().have_length(10)
```
{% endcode %}
## Arguments
### No args
* `.be_checked()`
* `.be_clickable()`
* `.be_disabled()`
* `.be_enabled()`
* `.be_focused()`
* `.be_hidden()`
* `.be_selected()`
* `.be_visible()`
* `.disappear()`
* `.not_be_focused()`
* `.be_empty()`
* `.not_be_empty()`
### Have args
* `.contain_text(text)`
* `.have_attr(attribute, value=None)`
* `.have_class(class_name)`
* `.have_prop(property)`
* `.have_text(text, case_sensitive=True)`
* `.have_value(value)`
* `.not_have_attr(attribute)`
* `.not_have_text(text)`
* `.not_have_value(value)`
* `.have_length(int)`
* `.be_greater_than(int)`
* `.be_less_than(int)`
## Yields
* **\(Element\)** 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.
================================================
FILE: docs/element-commands/siblings.md
================================================
---
description: The command to get the siblings of the element.
---
# siblings
## Syntax
```python
Element.siblings()
```
## Usage
{% code title="correct usage" %}
```python
py.get('a').siblings()
---or--- # store in a variable
elements = py.get('.nav-link').siblings()
---or--- # chain an Elements command
sibling = py.get('ul').siblings().first()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(Elements\)** A list of Elements. The list is empty if no siblings are found.
================================================
FILE: docs/element-commands/submit.md
================================================
---
description: The command to submit a form.
---
# submit
## Syntax
```python
Element.submit()
```
## 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
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands.
{% hint style="info" %}
You can continue the chain if `.submit()` opens a new view or navigates to a different page
{% endhint %}
================================================
FILE: docs/element-commands/tag_name.md
================================================
---
description: The command that gets the current Element's tag name.
---
# tag\_name
## Syntax
```python
Element.tag_name()
```
## 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
* **\(str\)** The tag name of the current Element
================================================
FILE: docs/element-commands/text.md
================================================
---
description: The command to get the text of the current Element.
---
# text
## Syntax
```python
Element.text()
```
## 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
* **\(str\)** The text of the current Element
================================================
FILE: docs/element-commands/type.md
================================================
---
description: 'The command to type keys into a field, input or text box.'
---
# type
## Syntax
```python
Element.type(*args)
```
## Usage
{% code title="correct usage" %}
```python
py.get('#username').type('my-username')
---or--- # combine with other keys and strings
py.get('#search').type('puppies', py.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
* `*args (Any)` - A comma-separated list of arguments to type
{% hint style="success" %}
It's best to use **strings** and the **Keys** class
{% endhint %}
## Yields
* **\(Element\)** The current Element so you can chain commands
================================================
FILE: docs/element-commands/uncheck.md
================================================
---
description: The command to deselect checkboxes and radio buttons.
---
# uncheck
## Syntax
```python
Element.uncheck()
Element.uncheck(allow_selected)
---or---
Elements.uncheck()
Elements.uncheck(allow_selected)
```
## Usage
{% code title="correct usage" %}
```python
# uncheck a radio button
py.get('[type="radio"]').uncheck()
---or---
# uncheck all boxes on the page
py.find('[type="checkbox"]').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
* `allow_deselected=False (bool)` - 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
* **\(Element or Elements\)** The current instance so you can chain commands
## Raises
* **ValueError** if the element is not selected already. Set `allow_deselected` to **True** to ignore this.
* **ValueError** if the element is not a checkbox or radio button
================================================
FILE: docs/element-commands/webelement.md
================================================
---
description: >-
The property that is the current instance of Selenium's WebElement that
Element is wrapping.
---
# webelement
## Syntax
```python
Element.webelement
```
## Usage
{% code title="correct usage" %}
```python
py.get('a').webelement
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, 'webelement' is not a function
py.get('a').webelement()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(WebElement\)** The current instance of Selenium's **WebElement** that is wrapped by **Element**
## Examples
Most scenarios won't need this, but it's provided just in case. The biggest reasons to use `.webelement`
* access functionality that may not exist in Pylenium
* functionality that requires you pass in a WebElement
```python
# Using the expected_conditions as EC
element = py.get('.loading-spinner')
py.wait.until(EC.staleness_of(element.webelement))
```
================================================
FILE: docs/examples/test_sample.py
================================================
""" Examples will be added to this directory and its files.
However, the best source for info and details is in the
official documentation here:
https://elsnoman.gitbook.io/pylenium
You can also contact the author, @CarlosKidman
on Twitter or LinkedIn.
"""
# You can mix Selenium into some Pylenium commands
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
# pass in the `py` fixture into your test function
# this _is_ Pylenium!
def test_pylenium_basics(py):
# Use Cypress-like commands like `.visit()`
py.visit("https://google.com")
# `.get()` uses CSS to locate a single element
py.get('[name="q"]').type("puppies", py.Keys.ENTER)
# `assert` followed by a boolean expression
assert "puppies" in py.title
def test_access_selenium(py):
py.visit("https://google.com")
# access the wrapped WebDriver with `py.webdriver`
search_field = py.webdriver.find_element(By.CSS_SELECTOR, "[name='q']")
# access the wrapped WebElement with `Element.webelement`
assert py.get('[name"q"]').webelement.is_enabled()
# you can store elements and objects to be used later since
# we don't rely on Promises or chaining in Python
search_field.send_keys("puppies", py.Keys.ENTER)
assert "puppies" in py.title
def test_chaining_commands(py):
py.visit("https://google.com").get('[name="q"]').type("puppies", py.Keys.ENTER)
assert "puppies" in py.title
def test_waiting(py):
py.visit("https://google.com")
# wait using expected conditions
# default `.wait()` uses WebDriverWait which returns Selenium's WebElement objects
py.wait().until(ec.visibility_of_element_located((By.CSS_SELECTOR, '[name="q"]'))).send_keys("puppies")
# use_py=True to use a PyleniumWait which returns Pylenium's Element and Elements objects
py.wait(use_py=True).until(lambda _: py.get('[name="q"]')).type(py.Keys.ENTER)
# wait using lambda function
assert py.wait().until(lambda x: "puppies" in x.title)
================================================
FILE: docs/fixtures/axe.md
================================================
---
description: Accessibility (A11y) Testing with aXe
---
# axe
## Usage
The `axe` 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" %}
You can generate the report as a JSON and/or use the`AxeReport` 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
* `name: str` The file path \(including name and `.json` extension\) of the report to save as a JSON
* `context: Dict` The dictionary of page part\(s\), by CSS Selectors, to include or exclude in the audit
* `options: Dict` 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](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#parameters-axerun)
{% endhint %}
## Yields
* `AxeReport` object that represents the audit report in code
If you include the `name` 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 `FileNotFound` error is raised.
{% endhint %}
================================================
FILE: docs/fixtures/fake.md
================================================
---
description: A basic instance of Faker to generate test data.
---
# fake
## 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
* `py.fake` - 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 `py.fake.`, I'm not seeing `address()` 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 `py.fake.address()` 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()
```
================================================
FILE: docs/fixtures/requests.md
================================================
---
description: A library for working with HTTP Clients and APIs.
---
# api \(aka requests\)
## What is requests?
**Requests** is an elegant and simple HTTP library for Python, built for human beings.
{% embed url="https://2.python-requests.org/en/master/" %}
## Three Ways to Use it
* `py.request` - A basic **requests** instance for UI tests \(V1\)
* `api fixture` - A fixture of **requests** for any tests
* `import requests` - Simply use the import statement to bring it into any file!
## Syntax
```python
# for UI tests
py.request
---or--- # use the fixture
def test_(api)
---or--- # just import it
import requests
```
## Usage
{% code title="py.request" %}
```python
BASE_URL = 'https://statsroyale.com'
def test_py_request(py):
py.visit(BASE_URL)
response = py.request.get(f'{BASE_URL}/api/cards')
assert response.ok
assert response.json()[0]['name'] == 'Royal Ghost'
```
{% endcode %}
{% code title="api fixture" %}
```python
def test_api_fixture(api):
response = api.request.get(f'{BASE_URL}/api/cards')
```
{% endcode %}
{% code title="import" %}
```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()
```
================================================
FILE: docs/getting-started/project-structure-with-pytest.md
================================================
---
description: pytest uses specific naming conventions and project structure
---
# 3. Project Structure with pytest
## Pylenium Files
You should have created these in the previous step, but they are required for **Pylenium** to do its magic.
* `conftest.py`
* `pylenium.json`
* `pytest.ini`
{% hint style="success" %}
Make sure these are at the Project Root \(aka Workspace\)
{% endhint %}
## conftest.py
pytest uses special functions called **Fixtures** to control the **Setup** and **Teardown** of tests and runs.
{% hint style="danger" %}
If you put any other **custom** functions or fixtures in this **conftest.py**, they will be _overridden_ when you upgrade Pylenium
{% endhint %}
### Fixture Example
```python
import pytest
@pytest.fixture
def user():
new_user = user_service.create()
yield new_user
user_service.delete(new_user)
```
* `@pytest.fixture` - this decorator indicates that this function has a Setup and Teardown
* `def user():` - define the function like normal. `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" %}
```bash
def test_my_website(py, login, user):
py.visit('https://qap.dev')
login.with(user)
...
```
{% endcode %}
When this test is ran:
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 their descendants.
{% endhint %}
Take a look at the following Project Structure
* Project
* conftest.py \# 1
* pylenium.json
* api\_tests
* conftest.py \# 2
* 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
$ python -m pytest tests
# run tests in ui directory
$ python -m pytest tests/ui
# run only the payment api tests
$ python -m 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.**
================================================
FILE: docs/getting-started/setup-pytest.md
================================================
---
description: >-
pytest is a modern and powerful Test Framework and we want to get intellisense
and autocomplete
---
# 2. Setup pytest
## 1. Install pyleniumio
Install **Pylenium** into your [Virtual Environment](virtual-environments.md) if you haven't already:
{% tabs %}
{% tab title="pip" %}
{% code title="Terminal $ \(venv\)" %}
```bash
pip install pyleniumio
```
{% endcode %}
{% endtab %}
{% tab title="pipenv" %}
{% code title="Terminal $ \(venv\)" %}
```text
pipenv install pyleniumio
```
{% endcode %}
{% endtab %}
{% endtabs %}
## 2. Initialize Pylenium
{% code title="Terminal $ \(venv\)" %}
```text
pylenium init
```
{% endcode %}
{% hint style="success" %}
Execute this command at your Project Root
{% endhint %}
This creates three files:
* `conftest.py` - This has the fixtures needed for Pylenium.
* `pylenium.json` - This is the [configuration ](../configuration/pylenium.json.md)file for Pylenium.
* `pytest.ini` - This is the configuration file for pytest and is used to connect to [ReportPortal](../cli/report-portal.md)
By default, pylenium uses Chrome browser. You have to install Chrome or update the `pylenium.json` to use the browser of your choice.
## 3. Select pytest as the Test Framework
To get the most out of your IDE, you need to configure it to use **pytest** as the Test Framework. This will give you:
* Intellisense
* Autocomplete
* Run/Debug Test functionality with breakpoints
* more depending on IDE
{% tabs %}
{% tab title="PyCharm" %}
{% code title="\(RECOMMENDED IDE\)" %}
```text
Open Preferences (or Settings)
Open Tools > Python Integrated Tools
Select pytest in the "Default test runner" dropdown
```
{% endcode %}
{% endtab %}
{% tab title="VS Code" %}
```text
Open Command Palette (CMD + SHIFT + P or CTRL + SHIFT + P)
Search for "Python: Configure Tests"
Select pytest
# VS Code doesn't fully support pytest so you won't get things like IntelliSense
```
{% endtab %}
{% endtabs %}
{% hint style="info" %}
Visit the pytest docs for more info on how to use it: [https://docs.pytest.org/](https://docs.pytest.org/)
{% endhint %}
================================================
FILE: docs/getting-started/virtual-environments.md
================================================
---
description: 'This is the first, critical piece to modern software development with Python.'
---
# 1. Virtual Environments
## 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?
Without Virtual Environments \(**venv**\), 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/](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" %}
```text
Install the Python extension
Open the Command Palette (CMD + SHIFT + P or CTRL + SHIFT + P)
Search for "Python: Select Interpreter"
Select the venv for your Project
```
{% endtab %}
{% tab title="PyCharm" %}
```
Open Preferences (or Settings)
Open Project > Project Interpreter
Select the venv for your Project in the Project Interpreter dropdown
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 **\(venv\)** 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 %}
================================================
FILE: docs/getting-started/writing-tests-with-pylenium.md
================================================
---
description: Easy as py
---
# 4. Writing Tests with Pylenium
## 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 `/tests` 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_`
* 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
* 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](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](../pylenium-commands/commands.md) like `.visit()` and `.get()`
{% hint style="warning" %}
Your IDE may not do this or you may be missing an Extension or Plugin.
PyCharm has [pytest support](setup-pytest.md) out-of-the-box.
{% endhint %}
Let's move on with the steps.
* Visit [https://qap.dev](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, 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
python -m 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 `.visit()`, `.type()` and `.submit()`, but there is also a _Should_ object for:
* [Element](../element-commands/should.md)
* [Elements](../element-commands/should.md)
* [Pylenium](../pylenium-commands/should.md)
In the example above, `py.should()` uses an Explicit Wait to wait until the "driver" detects that the current page's title contains `"Pylenium"`.
* 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 %}
================================================
FILE: docs/guides/run-tests-in-containers.md
================================================
# Run Tests in Containers
## 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 **remote\_url** 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 $ \(venv\) \# example" %}
```bash
python -m pytest tests/ui -n 2 --remote_url="http://localhost:4444/wd/hub"
```
{% endcode %}
### Update pylenium.json
{% hint style="info" %}
This option is great for local development and debugging
{% endhint %}
{% code title="pylenium.json" %}
```bash
"remote_url": "http://localhost:4444/wd/hub"
```
{% 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 these Grid by going to [http://localhost:4444/grid/console](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 %}
================================================
FILE: docs/guides/run-tests-in-parallel.md
================================================
# Run Tests in Parallel
## Simple CLI
Pylenium comes with **pytest** and the **pyest-xdist** 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 $ \(venv\) \# run 2 tests concurrently" %}
```bash
python -m pytest tests -n 2
```
{% endcode %}
{% hint style="success" %}
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 %}
================================================
FILE: docs/misc/install-chromedriver.md
================================================
---
description: This is NO LONGER NEEDED in version 1.4.1+
---
# Install chromedriver
{% hint style="danger" %}
Pylenium installs these for you automatically! YOU DO NOT NEED TO DO THIS!
{% endhint %}
## Options
There are many ways to install the driver executables needed to work with Selenium. This document will go over some of them, but use the one that works for you:
* **`Chocolatey`** - Package Manager for Windows
* **`Homebrew`** - Package Manager for Mac
* **`webdriver-manager`** - A great tool to manage drivers for any OS
* **`Manual Installation`** - worst-case scenario, you can always install them manually
{% hint style="info" %}
In a future release, we will take care of this step for you :\)
{% endhint %}
## The driver must be on your PATH
Pylenium will only look for drivers that exist on your PATH. There are multiple ways to do this, so this doc will break down the most common.
{% hint style="success" %}
You can skip this step if you already have the drivers on your PATH
{% endhint %}
## Chocolatey
If you are on Windows, you can use the **Chocolatey** package manager by going to their installation page:
{% embed url="https://chocolatey.org/docs/installation" %}
{% hint style="warning" %}
Follow the instructions for your Terminal of choice: **Command Prompt** or **Powershell**
{% endhint %}
### Install chromedriver
1. Go to the installation page and copy the Command for your Terminal
2. Open your Terminal in **Administrative Mode**
3. Paste the command and execute it
4. Close the Terminal and re-open it
5. Install chromedriver
{% code title="Terminal" %}
```bash
choco --version
# 0.10.15
choco install chromedriver
# accept any prompts and choco will print where it was installed
chromedriver --version
# ChromeDriver 80.0.3987.106
```
{% endcode %}
### Add to PATH
Copy the file path where **choco** installed the chromedriver and set it in your PATH. The following link shows how you can manually add a directory to your PATH.
{% embed url="https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/" %}
{% hint style="warning" %}
After editing your PATH, restart your IDE and Terminals for the new PATH to register
{% endhint %}
## Homebrew
If you are on MacOS, you can the use the **Homebrew** package manager by going to their installation page:
{% embed url="https://brew.sh/" %}
### Install chromedriver
1. Run the script and follow the prompts
2. Install chromedriver
{% code title="Terminal" %}
```bash
brew cask install chromedriver
```
{% endcode %}
### Add to PATH
{% hint style="success" %}
brew automatically install it to your `/usr/local/bin` directory which is _already_ on your PATH
{% endhint %}
## webdriver-manager
This is a **Node** module that installs and manages different drivers. You will need [Node.js](https://nodejs.org/en/download/) installed to use this, but it works for both **Windows** and **MacOS**, making it a crowd favorite.
{% embed url="https://www.npmjs.com/package/webdriver-manager" %}
### Install chromedriver
```bash
webdriver-manager update --output_dir="file-path"
```
### Add to PATH
This doesn't automatically add it to your path, so take note where the **chromedriver** was saved so you can manually add it.
## Manual Installation
Worst-case scenario, you can always install the drivers manually.
### Install chromedriver
1. Click on the link below to go to Chrome's downloads
2. Click on the version that you want
3. Download the chromedriver
{% embed url="https://sites.google.com/a/chromium.org/chromedriver/downloads" %}
### Add to PATH
You can either copy and paste the chromedriver to a directory that is **already** on the PATH, or you can add a folder to the PATH as well. Here is a helpful doc:
{% embed url="https://zwbetz.com/download-chromedriver-binary-and-add-to-your-path-for-automated-functional-testing/" %}
{% hint style="warning" %}
After editing your PATH, restart your IDE and Terminals for the new PATH to register
{% endhint %}
================================================
FILE: docs/pylenium-commands/commands.md
================================================
---
description: Pylenium offers many commands and features out of the box.
---
# Commands
## py
This is the main object in Pylenium. This is basically the **bot** you're controlling in your tests.
{% code title="example" %}
```python
py.visit('https://qap.dev')
```
{% endcode %}
{% hint style="info" %}
This is used to interact with the browser and find elements
{% endhint %}
## element\(s\)
These commands allow you to interact and perform actions against an Element or Elements.
{% code title="You can chain commands" %}
```python
py.get('ul').find('li').first().click()
```
{% endcode %}
{% code title="or you can store them in 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 %}
================================================
FILE: docs/pylenium-commands/contains.md
================================================
---
description: The command to get the DOM element containing the given text.
---
# contains
## Syntax
```python
py.contains(text)
py.contains(text, timeout)
---or---
Element.contains(text)
Element.contains(timeout)
```
## 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 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=0 (int)` - The amount of seconds for this command to succeed.
* `None` will use the default **wait\_time** in `pylenium.json`
* Zero \(`0`\) will poll the DOM immediately with no wait
* Greater than zero will override the default **wait\_time**
{% hint style="info" %}
It does not need to be an _exact_ match
{% endhint %}
## Yields
* **\(Element\)** The first element that is found, even if multiple elements match the query
================================================
FILE: docs/pylenium-commands/delete_all_cookies.md
================================================
---
description: The command to delete all cookies in the current browser session.
---
# delete\_all\_cookies
## Syntax
```python
py.delete_all_cookies()
```
## 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()
```
{% endcode %}
## Arguments
* None
## Yields
* None
================================================
FILE: docs/pylenium-commands/delete_cookie.md
================================================
---
description: The command to delete a cookie with the given name.
---
# delete\_cookie
## Syntax
```python
py.delete_cookie(name)
```
## 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()
```
{% endcode %}
## Arguments
* `name (str)` - The name of the cookie
## Yields
* None
================================================
FILE: docs/pylenium-commands/execute_script.md
================================================
---
description: The command to execute javascript into the browser.
---
# execute\_script
## Syntax
```python
py.execute_script(javascript)
py.execute_script(javascript, *args)
```
## 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
* `javascript (str)` - The javascript to execute
* `*args (Any)` - 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
* **\(Any\)** 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')
```
================================================
FILE: docs/pylenium-commands/fake.md
================================================
---
description: A basic instance of Faker to generate test data.
---
# fake
## Syntax
```text
py.fake
```
{% hint style="success" %}
This is a **command** and a **fixture**. More details in his doc: [**Fixtures > fake**](../fixtures/fake.md)\*\*\*\*
{% endhint %}
================================================
FILE: docs/pylenium-commands/find.md
================================================
---
description: The command to get DOM elements that match the CSS selector.
---
# find
## Syntax
```python
py.find(css)
py.find(css, timeout)
---or---
Element.find(css)
Element.find(css, timeout)
```
## 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 document with id of 'button'
py.find('#button')
---or--- # store in 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=0 (int)` - The amount of seconds for this command to succeed.
* `None` will use the default **wait\_time** in `pylenium.json`
* Zero \(`0`\) will poll the DOM immediately with no wait
* Greater than zero will override the default **wait\_time**
## Yields
* **\(Elements\)** 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')
```
================================================
FILE: docs/pylenium-commands/find_xpath.md
================================================
---
description: The command to find all the elements that match the XPath selector.
---
# findx
## Syntax
```python
py.findx(xpath)
py.findx(xpath, timeout)
---or---
Element.findx(xpath)
Element.findx(xpath, timeout)
```
## 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 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 amount of seconds for this command to succeed.
* `None` will use the default **wait\_time** in `pylenium.json`
* Zero \(`0`\) will poll the DOM immediately with no wait
* Greater than zero will override the default **wait\_time**
## Yields
* **\(Elements\)** 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)
```
================================================
FILE: docs/pylenium-commands/get.md
================================================
---
description: The command to get the DOM element that matches the CSS selector.
---
# get
## Syntax
```python
py.get(css)
py.get(css, timeout)
---or---
Element.get(css)
Element.get(css, timeout)
```
## 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 document with id of 'button'
py.get('#button')
---or--- # store in 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=0 (int)` - The amount of seconds for this command to succeed.
* `None` will use the default **wait\_time** in `pylenium.json`
* Zero \(`0`\) will poll the DOM immediately with no wait
* Greater than zero will override the default **wait\_time**
## Yields
* **\(Element\)** The first element that is found, even if multiple elements match the query.
================================================
FILE: docs/pylenium-commands/get_cookie.md
================================================
---
description: The command to get the cookie with the given name.
---
# get\_cookie
## Syntax
```python
py.get_cookie(name)
```
## 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
* `name (str)` - The name of the cookie
## Yields
* **\(dict\)** The cookie as a dictionary. Cookie objects have the following properties:
* `name`
* `value`
* `path`
* `domain`
* `httpOnly`
* `secure`
* `expiry`
{% hint style="info" %}
Returns **None** if the cookie does not exist
{% endhint %}
================================================
FILE: docs/pylenium-commands/get_cookies.md
================================================
---
description: The command to get all cookies in the current browser session.
---
# get\_cookies
## Syntax
```python
py.get_cookies()
```
## Usage
{% code title="correct usage" %}
```python
py.get_cookies()
---or--- # store in variable
cookies = py.get_cookies()
```
{% endcode %}
## Arguments
* None
## Yields
* **List\[dict\]** 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_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_cookies():
print(cookie['name'])
print(cookie.get('value'))
```
================================================
FILE: docs/pylenium-commands/get_xpath.md
================================================
---
description: The command to get a single element using an XPath selector.
---
# getx
## Syntax
```python
py.getx(xpath)
py.getx(xpath, timeout)
---or---
Element.getx(xpath)
Element.getx(xpath, timeout)
```
## 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 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 amount of seconds for this command to succeed.
* `None` will use the default **wait\_time** in `pylenium.json`
* Zero \(`0`\) will poll the DOM immediately with no wait
* Greater than zero will override the default **wait\_time**
## Yields
* **\(Element\)** 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()
```
================================================
FILE: docs/pylenium-commands/go.md
================================================
---
description: Navigate forward or back in the browser's history.
---
# go
## Syntax
```python
py.go(direction)
py.go(direction, number)
```
## Usage
* Go forward one page
```python
py.go('forward')
```
* Go back two pages
```python
py.go('back', 2)
```
## Arguments
* `direction (str)` - forward or back
* `number=1 (int)` - go back or forward N pages in history
{% hint style="warning" %}
**`number`** must be a positive integer.
{% endhint %}
## Yields
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands.
================================================
FILE: docs/pylenium-commands/maximize_window.md
================================================
---
description: The command the maximize the current window.
---
# maximize\_window
## Syntax
```python
py.maximize_window()
```
## Usage
{% code title="correct usage" %}
```python
# by default, Pylenium will maximaze the window for you, but just in case...
py.maximize_window().visit('https://qap.dev')
```
{% endcode %}
## Arguments
* None
## Yields
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands
================================================
FILE: docs/pylenium-commands/quit.md
================================================
---
description: The command to quit the driver and close all associated windows.
---
# quit
## Syntax
```python
py.quit()
```
## 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
================================================
FILE: docs/pylenium-commands/request.md
================================================
---
description: >-
Requests is an elegant and simple HTTP library for Python, built for human
beings.
---
# request
## Syntax
```text
py.request
```
{% hint style="success" %}
This is a **command** and a **fixture**. More details in his doc: [**Fixtures > api**](../fixtures/requests.md)\*\*\*\*
{% endhint %}
================================================
FILE: docs/pylenium-commands/screenshot.md
================================================
---
description: The command to take a screenshot of the current window.
---
# screenshot
## Syntax
```python
py.screenshot(filename)
```
## 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
* `filename (str)` - 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
================================================
FILE: docs/pylenium-commands/scroll_to.md
================================================
---
description: The command to scroll to the given location.
---
# scroll\_to
## Syntax
```text
py.scroll_to(x, y)
```
## Usage
{% code title="correct usage" %}
```python
# scroll down 500px
py.scroll_to(0, 500)
```
{% endcode %}
## Arguments
* **`x (int)`**: The number of pixels to scroll horizontally
* **`y (int)`**: The number of pixels to scroll vertically
## Yields
* **\(Pylenium\)** so you can chain another command
================================================
FILE: docs/pylenium-commands/set_cookie.md
================================================
---
description: The command to set a cookie into the current browser session.
---
# set\_cookie
## Syntax
```python
py.set_cookie(cookie)
```
## 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('foo')
```
{% endcode %}
## Arguments
* `cookie (dict)` - A dictionary with required keys: `"name"` and `"value"`
{% hint style="info" %}
Optional keys: `"path"`, `"domain"`, `"secure"`, `"expiry"`
{% endhint %}
## Yields
* None
================================================
FILE: docs/pylenium-commands/should.md
================================================
---
description: A collection of expectations for the current driver.
---
# should
## Expectations
* `.contain_title()`
* `.contain_url()`
* `.have_title()`
* `.have_url()`
* `.not_find()`
* `.not_find_xpath()`
* `.not_contain()`
## Syntax
```python
# use the default wait_time
py.should().<expectation>
---or---
# customize the wait_time for this expectation
py.should(timeout).<expectation>
---or---
# ignore exceptions that you expect to "get in the way"
py.should(ignored_exceptions).<expectation>
---or---
# customize both fully
py.should(timeout, ignored_exceptions).<expectation>
```
## Usage
{% code title="correct usage" %}
```python
py.visit('https://qap.dev').should().have_title('QA at the Point')
```
{% endcode %}
## Arguments
* `.contain_title(string)` - The substring for the title to contain
* `.contain_url(string)` - The substring for the URL to contain
* `.have_title(title)` - The case-sensitive title to match
* `.have_url(url)` - The case-sensitive url to match
* `.not_find(css)` - The CSS selector
* `.not_find_xpath(xpath)` - The XPATH selector
* `.not_contain(text)` - The text to contain
## Yields
* **\(Pylenium\)** 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.
================================================
FILE: docs/pylenium-commands/switch_to.default_content.md
================================================
---
description: >-
The command to switch the driver's context to the default (or starting)
content.
---
# switch\_to.default\_content
## Syntax
```python
py.switch_to.default_content()
```
## 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
* **\(Pylenium\)** 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 %}
================================================
FILE: docs/pylenium-commands/switch_to.frame.md
================================================
---
description: The command to switch the driver's context to the frame given its name or id.
---
# switch\_to.frame
## Syntax
```python
py.switch_to.frame(name_or_id)
```
## 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
* `name_or_id (str)` - The **name** or **id** attribute value of the `<frame>` element
## Yields
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands
## Examples
```python
<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()
```
================================================
FILE: docs/pylenium-commands/switch_to.parent_frame.md
================================================
---
description: >-
The command to switch the driver's context to the parent frame of the current
frame.
---
# switch\_to.parent\_frame
## Syntax
```python
py.switch_to.parent_frame()
```
## 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
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands
## Examples
```python
<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()
```
================================================
FILE: docs/pylenium-commands/switch_to.window.md
================================================
---
description: >-
The command to switch the driver's context to the specified Window or Browser
Tab.
---
# switch\_to.window
## Syntax
```python
py.switch_to.window(name_or_handle)
py.switch_to.window(index)
```
## 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
* `name_or_handle='' (str)`- The **name** or **window handle** of the Window to switch to
* `index=0 (int)` - The index position of the Window Handle
{% hint style="info" %}
**index=0** will switch to the default content
{% endhint %}
## Yields
* **\(Pylenium\)** The current instance of Pylenium so you can chain commands
================================================
FILE: docs/pylenium-commands/title.md
================================================
---
description: The command to get the current page's title.
---
# title
## Syntax
```python
py.title()
```
## Usage
{% code title="correct usage" %}
```python
py.title()
```
{% endcode %}
{% code title="incorrect usage" %}
```python
py.title
```
{% endcode %}
## Arguments
* None
## Yields
* **\(str\)** The `document.title` property of the current page
## Examples
```python
assert py.title() == 'QA at the Point'
```
================================================
FILE: docs/pylenium-commands/url.md
================================================
---
description: The command to get the current page's URL.
---
# url
## Syntax
```text
py.url()
```
## Usage
{% code title="correct usage" %}
```text
py.url()
```
{% endcode %}
{% code title="incorrect usage" %}
```text
py.url
```
{% endcode %}
## Arguments
* None
## Yields
* **\(str\)** The current page's URL
## Examples
```python
assert py.url().endswith('/checkout')
```
================================================
FILE: docs/pylenium-commands/viewport.md
================================================
---
description: The command to control the size and orientation of the current browser window.
---
# viewport
## Syntax
```python
py.viewport(width, height)
py.viewport(width, height, orientation)
```
## Usage
{% code title="correct usage" %}
```python
py.viewport(1280, 800) # macbook-13 size
```
{% endcode %}
## Arguments
* `width` - The width in pixels
* `height` - The height in pixels
* `orientation='portrait' (str)` - Pass `'landscape'` to reverse the width and height
## Yields
* **\(Pylenium\)** 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
```
================================================
FILE: docs/pylenium-commands/visit.md
================================================
---
description: The command to navigate to URLs.
---
# visit
## Syntax
```python
py.visit(url)
```
## Usage
```bash
py.visit('https://qap.dev')
```
## Arguments
* `url (str)` - the URL to visit
{% hint style="info" %}
Make sure to include the protocol **http** or **https**
{% endhint %}
## Yields
* **\(Pylenium\)** 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()
```
================================================
FILE: docs/pylenium-commands/wait.md
================================================
---
description: The command to execute a method or function as a condition to wait for.
---
# wait
There are two types of Wait objects:
- **WebDriverWait \(default\)**
- returns `WebElement` and `List[WebElement]`
- **PyleniumWait**
- returns `Element` and `Elements`
- has a built-in `.sleep()` method
`wait.until(condition)` 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.
{% endhint %}
### WebDriverWait
This is the default Wait object. This will return WebElement, so you won't have Pylenium's Element commands like `.hover()` - that is what PyleniumWait 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 **`timeout`**
{% code title="WebDriverWait with 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 **`ignored_exceptions`**
By default, the only exception that is ignored is the `NoSuchElementException`. You can change this by adding a list of Exceptions that you want your condition to ignore.
{% code title="WebDriverWait with 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 `Element` and `Elements`, then set `use_py=True.` 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 %}
- PyleniumWait also includes a **`.sleep()`** 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'))
```
{% hint style="info" %}
**`title_is()`** is just one of the many pre-built conditions in this module. Give it a try!
{% endhint %}
## Yields
- Whatever the non-False value of the condition is
## Raises
- **`TimeoutException`** 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 **`ignored_exceptions`** as an argument.
================================================
FILE: docs/pylenium-commands/webdriver.md
================================================
---
description: The property to get the current instance of Selenium's WebDriver.
---
# webdriver
## Syntax
```text
py.webdriver
```
## Usage
{% code title="correct usage" %}
```python
py.webdriver
```
{% endcode %}
{% code title="incorrect usage" %}
```python
py.webdriver()
```
{% endcode %}
## Arguments
* None
## Yields
* **\(WebDriver\)** The current instance of WebDriver that Pylenium is wrapping
## Examples
Most scenarios won't need this, but it's provided just in case. The biggest reasons to use `py.webdriver`
* 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)
```
================================================
FILE: docs/pylenium-commands/window_handles.md
================================================
---
description: >-
This property gets a list of all the window handles in the current browser
session.
---
# window\_handles
## Syntax
```python
py.window_handles
```
## 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
* **List\[str\]** A list of all the window handles in the current browser session.
================================================
FILE: docs/pylenium-commands/window_size.md
================================================
---
description: This property get the size of the current window.
---
# window\_size
## Syntax
```python
py.window_size
```
## 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
* **Dict\[str, int\]** The current window's size as a dictionary
================================================
FILE: docs/pylenium-commands/xpath.md
================================================
---
description: The command to get DOM elements that match the XPath selector.
---
# \(deprecated\) xpath
{% hint style="danger" %}
This is no longer valid in version 1.8.0+
{% endhint %}
## Syntax
```python
py.xpath(xpath)
py.xpath(xpath, at_least_one)
py.xpath(xpath, timeout)
py.xpath(xpath, at_least_one, timeout)
---or---
Element.xpath(xpath)
Element.xpath(xpath, at_least_one)
Element.xpath(xpath, timeout)
Element.xpath(xpath, at_least_one, timeout)
```
## Usage
{% code title="correct usage" %}
```python
# Yield Elements in .nav with tag name of a
py.get('.nav').xpath('//a')
---or---
# Yield Elements in document with id of 'button'
py.xpath('//*[@id="button"]')
---or--- # store in variable
element = py.xpath('//*[@id="button"]')
---or--- # chain an Element(s) command
# if one element is found
py.xpath('//*[@id="button"]').click()
# if more elements are found
py.xpath('//*[@id="button"]').first().click()
---or--- # control the timeout in any of the above usages
py.xpath('//a[@href="/about"]', timeout=5).click()
```
{% endcode %}
{% code title="incorrect usage" %}
```python
# Errors, 'title' does not yield Element
py.title.xpath('//a')
---or---
# Errors, 'get_cookie' does not yield Element
py.get_cookie().xpath('//[text()="foo" and @class="bar"]')
```
{% endcode %}
## Arguments
* `xpath (str)` - The XPATH selector to use
* `at_least_one=True (bool)` - **True** if you want to wait for at least one element to be found
* `timeout=0 (int)` - The amount of seconds for this command to succeed.
* This overrides the **default** **wait\_time** in `pylenium.json()`
## Yields
* **\(Elements\)** A list of the found elements. If only one is found, return **Element** instead.
## Examples
```python
# if you expect the elements not to be present
elements = py.xpath('
gitextract_fn7bqonu/
├── .flake8
├── .gitbook.yaml
├── .github/
│ └── workflows/
│ ├── publish-pylenium.yml
│ └── test-pylenium.yml
├── .gitignore
├── .gitpod.Dockerfile
├── .gitpod.yml
├── .vscode/
│ └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── conftest.py
├── docker-compose.yml
├── docs/
│ ├── README.md
│ ├── SUMMARY.md
│ ├── changelog.md
│ ├── cli/
│ │ ├── pylenium-cli.md
│ │ └── report-portal.md
│ ├── configuration/
│ │ ├── driver.md
│ │ ├── logging.md
│ │ ├── pylenium.json.md
│ │ └── report-portal.md
│ ├── contribute/
│ │ └── clone-and-setup-the-project.md
│ ├── element-commands/
│ │ ├── check.md
│ │ ├── children.md
│ │ ├── click.md
│ │ ├── css_value.md
│ │ ├── deselect.md
│ │ ├── double_click.md
│ │ ├── drag_to.md
│ │ ├── drag_to_element.md
│ │ ├── first.md
│ │ ├── get_attribute.md
│ │ ├── get_property.md
│ │ ├── hover.md
│ │ ├── is_checked.md
│ │ ├── is_displayed.md
│ │ ├── is_empty.md
│ │ ├── is_enabled.md
│ │ ├── is_selected.md
│ │ ├── last.md
│ │ ├── length.md
│ │ ├── open_shadow_dom.md
│ │ ├── parent.md
│ │ ├── right_click.md
│ │ ├── screenshot.md
│ │ ├── scroll_into_view.md
│ │ ├── select.md
│ │ ├── select_many.md
│ │ ├── should.md
│ │ ├── siblings.md
│ │ ├── submit.md
│ │ ├── tag_name.md
│ │ ├── text.md
│ │ ├── type.md
│ │ ├── uncheck.md
│ │ └── webelement.md
│ ├── examples/
│ │ └── test_sample.py
│ ├── fixtures/
│ │ ├── axe.md
│ │ ├── fake.md
│ │ └── requests.md
│ ├── getting-started/
│ │ ├── project-structure-with-pytest.md
│ │ ├── setup-pytest.md
│ │ ├── virtual-environments.md
│ │ └── writing-tests-with-pylenium.md
│ ├── guides/
│ │ ├── run-tests-in-containers.md
│ │ └── run-tests-in-parallel.md
│ ├── misc/
│ │ └── install-chromedriver.md
│ └── pylenium-commands/
│ ├── commands.md
│ ├── contains.md
│ ├── delete_all_cookies.md
│ ├── delete_cookie.md
│ ├── execute_script.md
│ ├── fake.md
│ ├── find.md
│ ├── find_xpath.md
│ ├── get.md
│ ├── get_cookie.md
│ ├── get_cookies.md
│ ├── get_xpath.md
│ ├── go.md
│ ├── maximize_window.md
│ ├── quit.md
│ ├── request.md
│ ├── screenshot.md
│ ├── scroll_to.md
│ ├── set_cookie.md
│ ├── should.md
│ ├── switch_to.default_content.md
│ ├── switch_to.frame.md
│ ├── switch_to.parent_frame.md
│ ├── switch_to.window.md
│ ├── title.md
│ ├── url.md
│ ├── viewport.md
│ ├── visit.md
│ ├── wait.md
│ ├── webdriver.md
│ ├── window_handles.md
│ ├── window_size.md
│ └── xpath.md
├── poetry.toml
├── pylenium/
│ ├── __init__.py
│ ├── a11y.py
│ ├── cdp.py
│ ├── config.py
│ ├── driver.py
│ ├── element.py
│ ├── jquery.py
│ ├── log.py
│ ├── performance.py
│ ├── scripts/
│ │ ├── __init__.py
│ │ ├── allure_reporting.py
│ │ ├── cli.py
│ │ ├── cli_utils.py
│ │ ├── conftest.py
│ │ ├── drag_and_drop.js
│ │ ├── load_jquery.js
│ │ ├── pylenium.json
│ │ └── pytest.ini
│ ├── switch_to.py
│ ├── utils.py
│ ├── wait.py
│ └── webdriver_factory.py
├── pylenium.json
├── pyproject.toml
├── pytest.ini
└── tests/
├── __init__.py
├── performance/
│ ├── test_cdp_performance.py
│ └── test_performance.py
├── test_flows.py
├── ui/
│ ├── Get CRX.crx
│ ├── test_element.py
│ ├── test_element_actions.py
│ └── test_pydriver.py
└── unit/
├── test_config.py
├── test_faker.py
└── test_requests.py
SYMBOL INDEX (330 symbols across 28 files)
FILE: conftest.py
function fake (line 38) | def fake() -> Faker:
function api (line 44) | def api():
function project_root (line 50) | def project_root() -> Path:
function test_results_dir (line 59) | def test_results_dir(project_root: Path, request) -> Path:
function _load_pylenium_json (line 90) | def _load_pylenium_json(project_root, request) -> PyleniumConfig:
function _override_pylenium_config_values (line 126) | def _override_pylenium_config_values(_load_pylenium_json: PyleniumConfig...
function py_config (line 179) | def py_config(_override_pylenium_config_values) -> PyleniumConfig:
function pyc_config (line 188) | def pyc_config(_override_pylenium_config_values) -> PyleniumConfig:
function pys_config (line 194) | def pys_config(_override_pylenium_config_values) -> PyleniumConfig:
function test_case (line 200) | def test_case(test_results_dir: Path, request) -> TestCase:
function py (line 217) | def py(test_case: TestCase, py_config: PyleniumConfig, request):
function pyc (line 248) | def pyc(pyc_config: PyleniumConfig, request):
function pys (line 269) | def pys(pys_config: PyleniumConfig, request):
function axe (line 290) | def axe(py) -> PyleniumAxe:
function pytest_runtest_makereport (line 296) | def pytest_runtest_makereport(item, call):
function pytest_addoption (line 305) | def pytest_addoption(parser):
FILE: docs/examples/test_sample.py
function test_pylenium_basics (line 19) | def test_pylenium_basics(py):
function test_access_selenium (line 28) | def test_access_selenium(py):
function test_chaining_commands (line 40) | def test_chaining_commands(py):
function test_waiting (line 45) | def test_waiting(py):
FILE: pylenium/a11y.py
class AxeRelatedNode (line 8) | class AxeRelatedNode(BaseModel):
class AxeSubNode (line 13) | class AxeSubNode(BaseModel):
class AxeNode (line 21) | class AxeNode(BaseModel):
class AxeAudit (line 30) | class AxeAudit(BaseModel):
class AxeNodeViolation (line 40) | class AxeNodeViolation(AxeNode):
class AxeAuditViolation (line 44) | class AxeAuditViolation(AxeAudit):
class AxeReport (line 48) | class AxeReport(BaseModel):
class PyleniumAxe (line 59) | class PyleniumAxe:
method __init__ (line 62) | def __init__(self, webdriver: WebDriver):
method run (line 65) | def run(
FILE: pylenium/cdp.py
class CDP (line 13) | class CDP:
method __init__ (line 16) | def __init__(self, webdriver):
method execute_command (line 19) | def execute_command(self, cmd: str, cmd_args: Dict) -> Dict:
method get_performance_metrics (line 41) | def get_performance_metrics(self) -> Dict:
FILE: pylenium/config.py
class DriverConfig (line 10) | class DriverConfig(BaseModel):
class LoggingConfig (line 23) | class LoggingConfig(BaseModel):
class ViewportConfig (line 28) | class ViewportConfig(BaseModel):
class PyleniumConfig (line 35) | class PyleniumConfig(BaseModel):
class TestCase (line 46) | class TestCase(BaseModel):
class ConfigDict (line 50) | class ConfigDict:
FILE: pylenium/driver.py
class PyleniumShould (line 23) | class PyleniumShould:
method __init__ (line 32) | def __init__(self, py: "Pylenium", timeout: int, ignored_exceptions: l...
method have_title (line 36) | def have_title(self, title: str) -> "Pylenium":
method contain_title (line 57) | def contain_title(self, string: str) -> "Pylenium":
method have_url (line 78) | def have_url(self, url: str) -> "Pylenium":
method contain_url (line 99) | def contain_url(self, string: str) -> "Pylenium":
method not_find (line 120) | def not_find(self, css: str) -> bool:
method not_findx (line 139) | def not_findx(self, xpath: str) -> bool:
method not_contain (line 158) | def not_contain(self, text: str) -> bool:
class Pylenium (line 178) | class Pylenium:
method __init__ (line 181) | def __init__(self, config: PyleniumConfig):
method init_webdriver (line 189) | def init_webdriver(self):
method webdriver (line 218) | def webdriver(self) -> WebDriver:
method log (line 223) | def log(self) -> Logger:
method should (line 255) | def should(self, timeout: int = 0, ignored_exceptions: list = None) ->...
method axe (line 271) | def axe(self) -> PyleniumAxe:
method performance (line 287) | def performance(self) -> Performance:
method cdp (line 303) | def cdp(self) -> CDP:
method title (line 326) | def title(self) -> str:
method url (line 331) | def url(self) -> str:
method visit (line 340) | def visit(self, url: str) -> "Pylenium":
method go (line 350) | def go(self, direction: str, number: int = 1) -> "Pylenium":
method reload (line 375) | def reload(self) -> "Pylenium":
method contains (line 389) | def contains(self, text: str, timeout: int = None) -> Element:
method get (line 412) | def get(self, css: str, timeout: int = None) -> Element:
method find (line 435) | def find(self, css: str, timeout: int = None) -> Elements:
method getx (line 461) | def getx(self, xpath: str, timeout: int = None) -> Element:
method findx (line 484) | def findx(self, xpath: str, timeout: int = None) -> Elements:
method wait (line 514) | def wait(self, timeout: int = None, use_py: bool = False, ignored_exce...
method delete_cookie (line 548) | def delete_cookie(self, name: str) -> None:
method delete_all_cookies (line 559) | def delete_all_cookies(self) -> None:
method get_cookie (line 564) | def get_cookie(self, name) -> Dict:
method get_all_cookies (line 579) | def get_all_cookies(self) -> Set[Dict]:
method set_cookie (line 594) | def set_cookie(self, cookie: Dict):
method execute_script (line 616) | def execute_script(self, javascript: str, *args):
method execute_async_script (line 635) | def execute_async_script(self, javascript: str, *args):
method quit (line 653) | def quit(self):
method screenshot (line 661) | def screenshot(self, filename: str) -> str:
method scroll_to (line 676) | def scroll_to(self, x, y) -> "Pylenium":
method switch_to (line 695) | def switch_to(self) -> SwitchTo:
method maximize_window (line 706) | def maximize_window(self) -> "Pylenium":
method set_page_load_timeout (line 714) | def set_page_load_timeout(self, timeout: int) -> "Pylenium":
method viewport (line 723) | def viewport(self, width: int, height: int, orientation: str = "portra...
method window_handles (line 748) | def window_handles(self) -> List[str]:
method window_size (line 766) | def window_size(self) -> Dict:
FILE: pylenium/element.py
class ElementWait (line 15) | class ElementWait:
method __init__ (line 16) | def __init__(self, webelement, timeout: int, ignored_exceptions: list ...
method until (line 24) | def until(self, method, message=""):
class ElementsShould (line 43) | class ElementsShould:
method __init__ (line 46) | def __init__(self, py, elements: "Elements", timeout: int, ignored_exc...
method be_empty (line 53) | def be_empty(self) -> bool:
method be_greater_than (line 74) | def be_greater_than(self, length: int) -> bool:
method be_less_than (line 95) | def be_less_than(self, length: int) -> bool:
method have_length (line 116) | def have_length(self, length: int) -> bool:
method not_be_empty (line 141) | def not_be_empty(self) -> "Elements":
class ElementShould (line 165) | class ElementShould:
method __init__ (line 168) | def __init__(self, py, element: "Element", timeout: int, ignored_excep...
method be_clickable (line 175) | def be_clickable(self) -> "Element":
method be_checked (line 193) | def be_checked(self) -> "Element":
method be_disabled (line 211) | def be_disabled(self) -> "Element":
method be_enabled (line 229) | def be_enabled(self) -> "Element":
method be_focused (line 247) | def be_focused(self) -> "Element":
method be_hidden (line 266) | def be_hidden(self) -> "Element":
method be_selected (line 285) | def be_selected(self) -> "Element":
method be_visible (line 304) | def be_visible(self) -> "Element":
method have_attr (line 323) | def have_attr(self, attr: str, value: Optional[str] = None) -> "Element":
method have_class (line 354) | def have_class(self, class_name: str) -> "Element":
method have_prop (line 378) | def have_prop(self, prop: str, value: str) -> "Element":
method have_text (line 403) | def have_text(self, text, case_sensitive=True) -> "Element":
method contain_text (line 429) | def contain_text(self, text, case_sensitive=True) -> "Element":
method have_value (line 455) | def have_value(self, value) -> "Element":
method not_be_focused (line 486) | def not_be_focused(self) -> "Element":
method disappear (line 505) | def disappear(self):
method not_have_attr (line 527) | def not_have_attr(self, attr: str, value: Optional[str] = None) -> "El...
method not_have_value (line 557) | def not_have_value(self, value) -> "Element":
method not_have_text (line 584) | def not_have_text(self, text, case_sensitive=True) -> "Element":
class Elements (line 613) | class Elements(List["Element"]):
method __init__ (line 616) | def __init__(self, py, web_elements, locator: Optional[Tuple]):
method should (line 622) | def should(self, timeout: int = 0, ignored_exceptions: list = None) ->...
method length (line 638) | def length(self) -> int:
method first (line 642) | def first(self) -> "Element":
method last (line 652) | def last(self) -> "Element":
method is_empty (line 666) | def is_empty(self) -> bool:
method are_checked (line 670) | def are_checked(self) -> bool:
method check (line 682) | def check(self, allow_selected=False) -> "Elements":
method uncheck (line 697) | def uncheck(self, allow_deselected=False) -> "Elements":
class Element (line 715) | class Element:
method __init__ (line 718) | def __init__(self, py, web_element: WebElement, locator: Optional[Tupl...
method webelement (line 724) | def webelement(self) -> WebElement:
method should (line 730) | def should(self, timeout: int = 0, ignored_exceptions: list = None) ->...
method css_value (line 746) | def css_value(self, property_name: str):
method tag_name (line 755) | def tag_name(self) -> str:
method text (line 760) | def text(self) -> str:
method get_attribute (line 765) | def get_attribute(self, attribute: str):
method get_property (line 786) | def get_property(self, prop: str):
method is_checked (line 802) | def is_checked(self) -> bool:
method is_displayed (line 815) | def is_displayed(self) -> bool:
method is_enabled (line 827) | def is_enabled(self) -> bool:
method is_selected (line 836) | def is_selected(self) -> bool:
method check (line 849) | def check(self, allow_selected=False) -> "Element":
method uncheck (line 874) | def uncheck(self, allow_deselected=False) -> "Element":
method clear (line 899) | def clear(self) -> "Element":
method click (line 911) | def click(self, force=False):
method deselect (line 927) | def deselect(self, value):
method double_click (line 948) | def double_click(self):
method drag_to (line 958) | def drag_to(self, css: str) -> "Element":
method drag_to_element (line 977) | def drag_to_element(self, to_element: "Element") -> "Element":
method focus (line 996) | def focus(self) -> "Element":
method hover (line 1007) | def hover(self):
method right_click (line 1017) | def right_click(self):
method select_by_index (line 1027) | def select_by_index(self, index: int) -> "Element":
method select_by_text (line 1047) | def select_by_text(self, text: str) -> "Element":
method select_by_value (line 1067) | def select_by_value(self, value) -> "Element":
method submit (line 1087) | def submit(self):
method type (line 1099) | def type(self, *args) -> "Element":
method upload (line 1109) | def upload(self, filepath: str) -> "Element":
method contains (line 1147) | def contains(self, text: str, timeout: int = None) -> "Element":
method get (line 1168) | def get(self, css: str, timeout: int = None) -> "Element":
method find (line 1189) | def find(self, css: str, timeout: int = None) -> Elements:
method getx (line 1213) | def getx(self, xpath: str, timeout: int = None) -> "Element":
method findx (line 1235) | def findx(self, xpath: str, timeout: int = None) -> "Elements":
method children (line 1264) | def children(self) -> Elements:
method parent (line 1270) | def parent(self) -> "Element":
method siblings (line 1280) | def siblings(self) -> Elements:
method screenshot (line 1303) | def screenshot(self, filename) -> "Element":
method scroll_into_view (line 1318) | def scroll_into_view(self) -> "Element":
method open_shadow_dom (line 1327) | def open_shadow_dom(self) -> "Element":
method highlight (line 1345) | def highlight(self, effect_time=1, color="red", border=5) -> "Element":
FILE: pylenium/jquery.py
function inject (line 8) | def inject(driver: WebDriver, version="3.5.1", timeout=10):
function exists (line 31) | def exists(driver: WebDriver) -> str:
function drag_and_drop (line 41) | def drag_and_drop(driver: WebDriver, drag_element: WebElement, drop_elem...
FILE: pylenium/log.py
function command (line 38) | def command(self, message: str, *args, **kwargs) -> None:
function this (line 54) | def this(self, message: str, *args, **kwargs) -> None:
FILE: pylenium/performance.py
function stopwatch (line 11) | def stopwatch(func):
class Performance (line 48) | class Performance:
method __init__ (line 51) | def __init__(self, webdriver):
method _wait (line 54) | def _wait(self, timeout=10):
method get (line 57) | def get(self):
method get_time_origin (line 80) | def get_time_origin(self) -> float:
method get_navigation_timing (line 89) | def get_navigation_timing(self):
method get_paint_timing (line 95) | def get_paint_timing(self):
method get_resources (line 101) | def get_resources(self):
class NavigationTiming (line 113) | class NavigationTiming(BaseModel):
class PaintTiming (line 156) | class PaintTiming(BaseModel):
class ResourceTiming (line 171) | class ResourceTiming(BaseModel):
class WebPerformance (line 206) | class WebPerformance(BaseModel):
method page_load_time (line 223) | def page_load_time(self) -> float:
method time_to_first_byte (line 227) | def time_to_first_byte(self) -> float:
method time_to_first_contentful_paint (line 231) | def time_to_first_contentful_paint(self) -> float:
method time_to_interactive (line 235) | def time_to_interactive(self) -> float:
method number_of_requests (line 239) | def number_of_requests(self) -> int:
method time_to_dom_content_loaded (line 243) | def time_to_dom_content_loaded(self) -> float:
method page_weight (line 246) | def page_weight(self) -> float:
method connection_time (line 251) | def connection_time(self) -> float:
method request_time (line 255) | def request_time(self) -> float:
method fetch_time (line 259) | def fetch_time(self) -> float:
FILE: pylenium/scripts/allure_reporting.py
function _install (line 10) | def _install(commands: List[str]):
function install_for_linux (line 33) | def install_for_linux():
function install_for_mac (line 58) | def install_for_mac():
function install_for_windows (line 64) | def install_for_windows():
function check (line 74) | def check():
function install (line 91) | def install():
function serve (line 111) | def serve(folder: str = typer.Option("allure-report", "--folder", "-f", ...
FILE: pylenium/scripts/cli.py
function _copy (line 17) | def _copy(file, to_dir, message) -> str:
function init (line 31) | def init(
FILE: pylenium/scripts/cli_utils.py
function run_process (line 5) | def run_process(tokenized_command: Union[List[str], str], shell=False) -...
function parse_response (line 22) | def parse_response(response: subprocess.CompletedProcess) -> Tuple[str, ...
FILE: pylenium/scripts/conftest.py
function fake (line 38) | def fake() -> Faker:
function api (line 44) | def api():
function project_root (line 50) | def project_root() -> Path:
function test_results_dir (line 59) | def test_results_dir(project_root: Path, request) -> Path:
function _load_pylenium_json (line 90) | def _load_pylenium_json(project_root, request) -> PyleniumConfig:
function _override_pylenium_config_values (line 126) | def _override_pylenium_config_values(_load_pylenium_json: PyleniumConfig...
function py_config (line 179) | def py_config(_override_pylenium_config_values) -> PyleniumConfig:
function pyc_config (line 188) | def pyc_config(_override_pylenium_config_values) -> PyleniumConfig:
function pys_config (line 194) | def pys_config(_override_pylenium_config_values) -> PyleniumConfig:
function test_case (line 200) | def test_case(test_results_dir: Path, request) -> TestCase:
function py (line 217) | def py(test_case: TestCase, py_config: PyleniumConfig, request):
function pyc (line 248) | def pyc(pyc_config: PyleniumConfig, request):
function pys (line 269) | def pys(pys_config: PyleniumConfig, request):
function axe (line 290) | def axe(py) -> PyleniumAxe:
function pytest_runtest_makereport (line 296) | def pytest_runtest_makereport(item, call):
function pytest_addoption (line 305) | def pytest_addoption(parser):
FILE: pylenium/scripts/load_jquery.js
function insertJquery (line 6) | function insertJquery(doc) {
FILE: pylenium/switch_to.py
class FrameIsAvailable (line 8) | class FrameIsAvailable:
method __init__ (line 11) | def __init__(self, frame_name_or_id):
method __call__ (line 14) | def __call__(self, driver):
class SwitchTo (line 22) | class SwitchTo:
method __init__ (line 23) | def __init__(self, pylenium):
method frame (line 26) | def frame(self, name_or_id: str, timeout: int = 0):
method frame_by_element (line 46) | def frame_by_element(self, element: Element, timeout: int = 0):
method parent_frame (line 66) | def parent_frame(self):
method default_content (line 78) | def default_content(self):
method new_window (line 90) | def new_window(self):
method new_tab (line 100) | def new_tab(self):
method window (line 110) | def window(self, name_or_handle="", index=0):
FILE: pylenium/utils.py
function read_script_from_file (line 4) | def read_script_from_file(file_name) -> str:
FILE: pylenium/wait.py
class PyleniumWait (line 10) | class PyleniumWait:
method __init__ (line 13) | def __init__(self, py, webdriver, timeout, ignored_exceptions: Optiona...
method sleep (line 18) | def sleep(self, seconds: int):
method until (line 28) | def until(self, method, message=""):
method until_not (line 60) | def until_not(self, method, message=""):
method build (line 88) | def build(
FILE: pylenium/webdriver_factory.py
class Browser (line 20) | class Browser:
function build_capabilities (line 30) | def build_capabilities(browser: str, capabilities: Optional[Dict]) -> Dict:
function build_options (line 63) | def build_options(
function build_from_config (line 112) | def build_from_config(config: PyleniumConfig) -> WebDriver:
function build_chrome (line 155) | def build_chrome(
function build_edge (line 192) | def build_edge(
function build_safari (line 225) | def build_safari(
function build_firefox (line 258) | def build_firefox(
function build_ie (line 291) | def build_ie(
function build_remote (line 322) | def build_remote(
FILE: tests/performance/test_cdp_performance.py
function test_capture_performance_metrics (line 5) | def test_capture_performance_metrics(py: Pylenium):
FILE: tests/performance/test_performance.py
function qap_dev (line 7) | def qap_dev(py) -> Pylenium:
function test_custom_perf_metrics (line 13) | def test_custom_perf_metrics(qap_dev):
function test_get_timing_objects (line 31) | def test_get_timing_objects(qap_dev):
FILE: tests/test_flows.py
function sauce (line 6) | def sauce(pys: Pylenium) -> Pylenium:
class TestSauceDemo (line 17) | class TestSauceDemo:
method test_add_to_cart_css (line 18) | def test_add_to_cart_css(self, sauce: Pylenium):
method test_add_to_cart_xpath (line 23) | def test_add_to_cart_xpath(self, sauce: Pylenium):
FILE: tests/ui/test_element.py
function test_element_with_no_siblings (line 9) | def test_element_with_no_siblings(py: Pylenium):
function test_element_parent_and_siblings (line 15) | def test_element_parent_and_siblings(py: Pylenium):
function test_element_text (line 22) | def test_element_text(py: Pylenium):
function test_find_in_element_context (line 27) | def test_find_in_element_context(py: Pylenium):
function test_children (line 34) | def test_children(py: Pylenium):
function test_forced_click (line 40) | def test_forced_click(py: Pylenium):
function test_element_should_be_clickable (line 47) | def test_element_should_be_clickable(py: Pylenium):
function test_element_should_not_be_clickable (line 52) | def test_element_should_not_be_clickable(py: Pylenium):
function test_element_should_be_visible (line 58) | def test_element_should_be_visible(py: Pylenium):
function test_element_should_be_hidden (line 63) | def test_element_should_be_hidden(py: Pylenium):
function test_element_focus (line 68) | def test_element_focus(py: Pylenium):
function test_elements_should_be_empty (line 76) | def test_elements_should_be_empty(py: Pylenium):
function test_elements_should_not_be_empty (line 82) | def test_elements_should_not_be_empty(py: Pylenium):
function test_elements_should_have_length (line 88) | def test_elements_should_have_length(py: Pylenium):
function test_elements_should_be_greater_than (line 95) | def test_elements_should_be_greater_than(py: Pylenium):
function test_elements_should_be_less_than (line 102) | def test_elements_should_be_less_than(py: Pylenium):
function test_element_attribute (line 109) | def test_element_attribute(py: Pylenium):
function test_element_property (line 116) | def test_element_property(py: Pylenium):
function test_element_should_disappear (line 123) | def test_element_should_disappear(py: Pylenium):
function test_element_has_attribute (line 130) | def test_element_has_attribute(py: Pylenium):
function test_element_does_not_have_attribute (line 135) | def test_element_does_not_have_attribute(py: Pylenium):
function test_element_has_attribute_with_value (line 140) | def test_element_has_attribute_with_value(py: Pylenium):
function test_element_does_not_have_attribute_with_value (line 145) | def test_element_does_not_have_attribute_with_value(py: Pylenium):
function test_getx_nested_element (line 151) | def test_getx_nested_element(py: Pylenium):
function test_findx_nested_element (line 159) | def test_findx_nested_element(py: Pylenium):
function test_focus (line 168) | def test_focus(py: Pylenium):
FILE: tests/ui/test_element_actions.py
function dropdown (line 12) | def dropdown(py: Pylenium) -> Element:
function test_check_single_box (line 17) | def test_check_single_box(py):
function test_check_many_boxes (line 23) | def test_check_many_boxes(py):
function test_select_by_index_fails_if_option_not_available (line 28) | def test_select_by_index_fails_if_option_not_available(dropdown: Element):
function test_select_by_text_fails_if_option_not_available (line 33) | def test_select_by_text_fails_if_option_not_available(dropdown: Element):
function test_select_by_value_fails_if_option_not_available (line 38) | def test_select_by_value_fails_if_option_not_available(dropdown: Element):
function test_select_by_index (line 43) | def test_select_by_index(dropdown: Element):
function test_select_by_text (line 48) | def test_select_by_text(dropdown: Element):
function test_select_by_value (line 53) | def test_select_by_value(dropdown: Element):
function test_drag_to_with_selector (line 58) | def test_drag_to_with_selector(py):
function test_drag_to_with_element (line 64) | def test_drag_to_with_element(py):
function test_jquery (line 71) | def test_jquery(py):
function test_hover (line 79) | def test_hover(py):
function test_radio_buttons (line 84) | def test_radio_buttons(py):
function test_checkbox_buttons (line 93) | def test_checkbox_buttons(py):
function test_upload_file (line 102) | def test_upload_file(py: Pylenium, project_root):
FILE: tests/ui/test_pydriver.py
function test_jit_webdriver (line 12) | def test_jit_webdriver(py: Pylenium):
function test_browser_options (line 19) | def test_browser_options(py: Pylenium):
function test_execute_script (line 25) | def test_execute_script(py: Pylenium):
function test_new_window_and_tab (line 31) | def test_new_window_and_tab(py: Pylenium):
function test_cookies (line 39) | def test_cookies(py: Pylenium):
function test_viewport (line 59) | def test_viewport(py: Pylenium):
function test_hover_and_click_to_page_transition (line 65) | def test_hover_and_click_to_page_transition(py: Pylenium):
function test_pylenium_wait_until (line 71) | def test_pylenium_wait_until(py: Pylenium):
function test_webdriver_wait_until (line 79) | def test_webdriver_wait_until(py: Pylenium):
function test_switch_to_frame_by_element_then_back (line 85) | def test_switch_to_frame_by_element_then_back(py: Pylenium):
function test_have_url (line 101) | def test_have_url(py: Pylenium):
function test_loading_extension_to_browser (line 107) | def test_loading_extension_to_browser(py: Pylenium, project_root):
function test_should_not_find (line 116) | def test_should_not_find(py: Pylenium):
function test_axe_run (line 123) | def test_axe_run(py: Pylenium):
function test_axe_fixture (line 131) | def test_axe_fixture(axe):
FILE: tests/unit/test_config.py
function test_py_config_defaults (line 1) | def test_py_config_defaults(py_config):
FILE: tests/unit/test_faker.py
function test_fake_name (line 12) | def test_fake_name(fake):
function test_fake_first_name (line 16) | def test_fake_first_name(fake):
function test_fake_last_name (line 20) | def test_fake_last_name(fake):
function test_fake_email (line 24) | def test_fake_email(fake):
function test_fake_address (line 28) | def test_fake_address(fake):
function test_fake_text (line 32) | def test_fake_text(fake):
function test_fake_ssn (line 36) | def test_fake_ssn(fake):
function test_fake_locale (line 40) | def test_fake_locale(fake):
function test_fake_country (line 44) | def test_fake_country(fake):
function test_fake_postal_code (line 48) | def test_fake_postal_code(fake):
FILE: tests/unit/test_requests.py
function test_api_fixture (line 1) | def test_api_fixture(api):
Condensed preview — 139 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (374K chars).
[
{
"path": ".flake8",
"chars": 123,
"preview": "[flake8]\nextend-ignore = E203, C901\nexclude =\n .git,\n __pycache__,\n docs,\n old,\n dist\nmax-line-length = 1"
},
{
"path": ".gitbook.yaml",
"chars": 73,
"preview": "root: ./docs\n\nstructure:\n readme: README.md\n summary: SUMMARY.md\n"
},
{
"path": ".github/workflows/publish-pylenium.yml",
"chars": 832,
"preview": "name: Upload Pylenium Package to pypi\n\non:\n release:\n types: [created]\n workflow_dispatch:\n inputs:\n versio"
},
{
"path": ".github/workflows/test-pylenium.yml",
"chars": 1343,
"preview": "# This workflow will install Python dependencies, run tests and lint with a single version of Python\n# For more informat"
},
{
"path": ".gitignore",
"chars": 1999,
"preview": "# General\n.idea\ntest_results\nallure-report\ntest_env\ntests/data\ndata\n\n# LambdaTest\n.hyperexecute\nlogs\n.updatedhyperexecut"
},
{
"path": ".gitpod.Dockerfile",
"chars": 2628,
"preview": "FROM gitpod/workspace-full-vnc\n\nUSER root\n\n# RUN apt-get update -qqy && apt-get install -y wget curl gnupg2\n\n# So we can"
},
{
"path": ".gitpod.yml",
"chars": 385,
"preview": "vscode:\n extensions:\n - ms-python.python\n - bungcip.better-toml\n - PKief.material-icon-theme\n\nimage:\n file: ."
},
{
"path": ".vscode/settings.json",
"chars": 668,
"preview": "{\n \"python.testing.pytestArgs\": [\"tests\"],\n \"python.defaultInterpreterPath\": \".venv/bin/python\",\n \"python.testing.uni"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3277,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 19771,
"preview": "# Contributing to Pylenium\n\n:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:\n\n\nThe following i"
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2020 Carlos Kidman\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 6599,
"preview": "# Pylenium: Easy Python Web Test Automation\n\n- [The Mission](#the-mission-is-simple)\n - [Test Example](#test-example)"
},
{
"path": "conftest.py",
"chars": 12103,
"preview": "\"\"\"\nThe `conftest.py` and `pylenium.json` files generated by Pylenium should stay at your Workspace Root (aka Project Ro"
},
{
"path": "docker-compose.yml",
"chars": 1165,
"preview": "# To execute this docker-compose yml file use `docker-compose -f docker-compose-v3.yml up`\n# Add the `-d` flag at the en"
},
{
"path": "docs/README.md",
"chars": 6185,
"preview": "---\ndescription: Web Test Automation made easy\n---\n\n# Welcome to the Pylenium.io Docs\n\n[\n* [Changelog](changelog.md)\n\n## Getting Started\n\n* ["
},
{
"path": "docs/changelog.md",
"chars": 22173,
"preview": "---\ndescription: Summary of notable changes and fixes.\n---\n\n# Changelog\n\n## 1.12.3 - 2021-14-04\n\n### Overview\n\n`.select("
},
{
"path": "docs/cli/pylenium-cli.md",
"chars": 1191,
"preview": "---\ndescription: 'The CLI comes with commands to initialize and create Pylenium files, and more.'\n---\n\n# Pylenium CLI\n\n#"
},
{
"path": "docs/cli/report-portal.md",
"chars": 983,
"preview": "---\ndescription: CLI commands to easily setup and teardown your RP instance.\n---\n\n# Report Portal\n\n{% hint style=\"succes"
},
{
"path": "docs/configuration/driver.md",
"chars": 4434,
"preview": "---\ndescription: Configure the driver via the pylenium.json or the CLI.\n---\n\n# Driver\n\n## The Driver Settings\n\nSupported"
},
{
"path": "docs/configuration/logging.md",
"chars": 2232,
"preview": "---\ndescription: Configure logging via the pylenium.json or the CLI.\n---\n\n# Logging\n\n## Levels\n\nPylenium has 3 logging l"
},
{
"path": "docs/configuration/pylenium.json.md",
"chars": 2193,
"preview": "---\ndescription: The configuration file for Pylenium\n---\n\n# pylenium.json\n\n## Configure with a JSON File\n\nIf you don't w"
},
{
"path": "docs/configuration/report-portal.md",
"chars": 2405,
"preview": "---\ndescription: Connect your tests to a RP instance.\n---\n\n# Report Portal\n\n{% hint style=\"info\" %}\nThis doc assumes you"
},
{
"path": "docs/contribute/clone-and-setup-the-project.md",
"chars": 1726,
"preview": "---\ndescription: The first step in contributing to Pylenium.io\n---\n\n# Clone and Setup the Project\n\n## 1. Fork the Projec"
},
{
"path": "docs/element-commands/check.md",
"chars": 1117,
"preview": "---\ndescription: The command to select checkboxes or radio buttons.\n---\n\n# check\n\n## Syntax\n\n```python\nElement.check()\nE"
},
{
"path": "docs/element-commands/children.md",
"chars": 482,
"preview": "---\ndescription: The command to get the children of the element.\n---\n\n# children\n\n## Syntax\n\n```python\nElement.children("
},
{
"path": "docs/element-commands/click.md",
"chars": 819,
"preview": "---\ndescription: The command to click the element.\n---\n\n# click\n\n## Syntax\n\n```python\nElement.click()\nElement.click(forc"
},
{
"path": "docs/element-commands/css_value.md",
"chars": 732,
"preview": "---\ndescription: Get the CSS Value of the element given the property name.\n---\n\n# css\\_value\n\n## Syntax\n\n```python\nEleme"
},
{
"path": "docs/element-commands/deselect.md",
"chars": 695,
"preview": "---\ndescription: The command to deselect an <option> within a multi <select> element.\n---\n\n# deselect\n\n## Syntax\n\n```pyt"
},
{
"path": "docs/element-commands/double_click.md",
"chars": 581,
"preview": "---\ndescription: The command to double click the element.\n---\n\n# double\\_click\n\n## Syntax\n\n```python\nElement.double_clic"
},
{
"path": "docs/element-commands/drag_to.md",
"chars": 744,
"preview": "---\ndescription: >-\n The command to drag the current element to another element given its CSS\n selector.\n---\n\n# drag\\_"
},
{
"path": "docs/element-commands/drag_to_element.md",
"chars": 807,
"preview": "---\ndescription: The command to drag the current element to the given element.\n---\n\n# drag\\_to\\_element\n\n## Syntax\n\n```p"
},
{
"path": "docs/element-commands/first.md",
"chars": 657,
"preview": "---\ndescription: The command to get the first Element in a list of Elements.\n---\n\n# first\n\n## Syntax\n\n```python\nElements"
},
{
"path": "docs/element-commands/get_attribute.md",
"chars": 619,
"preview": "---\ndescription: The command to get the attribute's value with the given name.\n---\n\n# get\\_attribute\n\n## Syntax\n\n```pyth"
},
{
"path": "docs/element-commands/get_property.md",
"chars": 556,
"preview": "---\ndescription: The command to get the specified property's value of the element.\n---\n\n# get\\_property\n\n## Syntax\n\n```p"
},
{
"path": "docs/element-commands/hover.md",
"chars": 685,
"preview": "---\ndescription: The command to hover the element.\n---\n\n# hover\n\n## Syntax\n\n```python\nElement.hover()\n```\n\n## Usage\n\n{% "
},
{
"path": "docs/element-commands/is_checked.md",
"chars": 330,
"preview": "---\ndescription: The command to check if this element is checked.\n---\n\n# is\\_checked\n\n## Syntax\n\n```python\nElement.is_ch"
},
{
"path": "docs/element-commands/is_displayed.md",
"chars": 496,
"preview": "---\ndescription: The command to check if this element is displayed.\n---\n\n# is\\_displayed\n\n## Syntax\n\n```python\nElement.i"
},
{
"path": "docs/element-commands/is_empty.md",
"chars": 453,
"preview": "---\ndescription: The command to check if the list of elements is empty.\n---\n\n# is\\_empty\n\n## Syntax\n\n```python\nElements."
},
{
"path": "docs/element-commands/is_enabled.md",
"chars": 327,
"preview": "---\ndescription: The command to check if the element is enabled.\n---\n\n# is\\_enabled\n\n## Syntax\n\n```python\nElement.is_ena"
},
{
"path": "docs/element-commands/is_selected.md",
"chars": 335,
"preview": "---\ndescription: The command that checks if the element is selected.\n---\n\n# is\\_selected\n\n## Syntax\n\n```python\nElement.i"
},
{
"path": "docs/element-commands/last.md",
"chars": 642,
"preview": "---\ndescription: The command to get the last Element in a list of Elements.\n---\n\n# last\n\n## Syntax\n\n```python\nElements.l"
},
{
"path": "docs/element-commands/length.md",
"chars": 500,
"preview": "---\ndescription: The command to get the length of Elements.\n---\n\n# length\n\n## Syntax\n\n```python\nElements.length()\n```\n\n#"
},
{
"path": "docs/element-commands/open_shadow_dom.md",
"chars": 842,
"preview": "---\ndescription: The command to open/expand a Shadow DOM element.\n---\n\n# open\\_shadow\\_dom\n\n## Syntax\n\n```python\nElement"
},
{
"path": "docs/element-commands/parent.md",
"chars": 502,
"preview": "---\ndescription: The command to get the parent of the element.\n---\n\n# parent\n\n## Syntax\n\n```python\nElement.parent()\n```\n"
},
{
"path": "docs/element-commands/right_click.md",
"chars": 435,
"preview": "---\ndescription: The command to right-click the element.\n---\n\n# right\\_click\n\n## Syntax\n\n```text\nElement.right_click()\n`"
},
{
"path": "docs/element-commands/screenshot.md",
"chars": 558,
"preview": "---\ndescription: The command to take a screenshot of the element.\n---\n\n# screenshot\n\n## Syntax\n\n```python\nElement.screen"
},
{
"path": "docs/element-commands/scroll_into_view.md",
"chars": 467,
"preview": "---\ndescription: The command to scroll this element into the viewport\n---\n\n# scroll\\_into\\_view\n\n## Syntax\n\n```text\nElem"
},
{
"path": "docs/element-commands/select.md",
"chars": 991,
"preview": "---\ndescription: The command to select an <option> within a <select> element.\n---\n\n# select\n\n## Syntax\n\n```python\nElemen"
},
{
"path": "docs/element-commands/select_many.md",
"chars": 771,
"preview": "---\ndescription: The command to select multiple <option>s in a multi <select> element.\n---\n\n# select\\_many\n\n## Syntax\n\n`"
},
{
"path": "docs/element-commands/should.md",
"chars": 2277,
"preview": "---\ndescription: A collection of expectations for the current Element or Elements.\n---\n\n# should\n\n## Element Expectation"
},
{
"path": "docs/element-commands/siblings.md",
"chars": 495,
"preview": "---\ndescription: The command to get the siblings of the element.\n---\n\n# siblings\n\n## Syntax\n\n```python\nElement.siblings("
},
{
"path": "docs/element-commands/submit.md",
"chars": 639,
"preview": "---\ndescription: The command to submit a form.\n---\n\n# submit\n\n## Syntax\n\n```python\nElement.submit()\n```\n\n## Usage\n\n{% co"
},
{
"path": "docs/element-commands/tag_name.md",
"chars": 459,
"preview": "---\ndescription: The command that gets the current Element's tag name.\n---\n\n# tag\\_name\n\n## Syntax\n\n```python\nElement.ta"
},
{
"path": "docs/element-commands/text.md",
"chars": 502,
"preview": "---\ndescription: The command to get the text of the current Element.\n---\n\n# text\n\n## Syntax\n\n```python\nElement.text()\n``"
},
{
"path": "docs/element-commands/type.md",
"chars": 846,
"preview": "---\ndescription: 'The command to type keys into a field, input or text box.'\n---\n\n# type\n\n## Syntax\n\n```python\nElement.t"
},
{
"path": "docs/element-commands/uncheck.md",
"chars": 1150,
"preview": "---\ndescription: The command to deselect checkboxes and radio buttons.\n---\n\n# uncheck\n\n## Syntax\n\n```python\nElement.unch"
},
{
"path": "docs/element-commands/webelement.md",
"chars": 912,
"preview": "---\ndescription: >-\n The property that is the current instance of Selenium's WebElement that\n Element is wrapping.\n---"
},
{
"path": "docs/examples/test_sample.py",
"chars": 2027,
"preview": "\"\"\" Examples will be added to this directory and its files.\n\nHowever, the best source for info and details is in the\noff"
},
{
"path": "docs/fixtures/axe.md",
"chars": 1720,
"preview": "---\ndescription: Accessibility (A11y) Testing with aXe\n---\n\n# axe\n\n## Usage\n\nThe `axe` fixture is the recommended way to"
},
{
"path": "docs/fixtures/fake.md",
"chars": 2408,
"preview": "---\ndescription: A basic instance of Faker to generate test data.\n---\n\n# fake\n\n## What is Faker?\n\nPut simply, Faker is a"
},
{
"path": "docs/fixtures/requests.md",
"chars": 1510,
"preview": "---\ndescription: A library for working with HTTP Clients and APIs.\n---\n\n# api \\(aka requests\\)\n\n## What is requests?\n\n**"
},
{
"path": "docs/getting-started/project-structure-with-pytest.md",
"chars": 4960,
"preview": "---\ndescription: pytest uses specific naming conventions and project structure\n---\n\n# 3. Project Structure with pytest\n\n"
},
{
"path": "docs/getting-started/setup-pytest.md",
"chars": 2100,
"preview": "---\ndescription: >-\n pytest is a modern and powerful Test Framework and we want to get intellisense\n and autocomplete\n"
},
{
"path": "docs/getting-started/virtual-environments.md",
"chars": 2455,
"preview": "---\ndescription: 'This is the first, critical piece to modern software development with Python.'\n---\n\n# 1. Virtual Envir"
},
{
"path": "docs/getting-started/writing-tests-with-pylenium.md",
"chars": 5418,
"preview": "---\ndescription: Easy as py\n---\n\n# 4. Writing Tests with Pylenium\n\n## Create a Test File\n\nA **Test File** is just that -"
},
{
"path": "docs/guides/run-tests-in-containers.md",
"chars": 3155,
"preview": "# Run Tests in Containers\n\n## Configure the Test Run\n\nRegardless of the scaling option you go with \\(Selenoid, Zalenium,"
},
{
"path": "docs/guides/run-tests-in-parallel.md",
"chars": 844,
"preview": "# Run Tests in Parallel\n\n## Simple CLI\n\nPylenium comes with **pytest** and the **pyest-xdist** plugin to run tests concu"
},
{
"path": "docs/misc/install-chromedriver.md",
"chars": 4020,
"preview": "---\ndescription: This is NO LONGER NEEDED in version 1.4.1+\n---\n\n# Install chromedriver\n\n{% hint style=\"danger\" %}\nPylen"
},
{
"path": "docs/pylenium-commands/commands.md",
"chars": 1115,
"preview": "---\ndescription: Pylenium offers many commands and features out of the box.\n---\n\n# Commands\n\n## py\n\nThis is the main obj"
},
{
"path": "docs/pylenium-commands/contains.md",
"chars": 1426,
"preview": "---\ndescription: The command to get the DOM element containing the given text.\n---\n\n# contains\n\n## Syntax\n\n```python\npy."
},
{
"path": "docs/pylenium-commands/delete_all_cookies.md",
"chars": 436,
"preview": "---\ndescription: The command to delete all cookies in the current browser session.\n---\n\n# delete\\_all\\_cookies\n\n## Synta"
},
{
"path": "docs/pylenium-commands/delete_cookie.md",
"chars": 443,
"preview": "---\ndescription: The command to delete a cookie with the given name.\n---\n\n# delete\\_cookie\n\n## Syntax\n\n```python\npy.dele"
},
{
"path": "docs/pylenium-commands/execute_script.md",
"chars": 1936,
"preview": "---\ndescription: The command to execute javascript into the browser.\n---\n\n# execute\\_script\n\n## Syntax\n\n```python\npy.exe"
},
{
"path": "docs/pylenium-commands/fake.md",
"chars": 275,
"preview": "---\ndescription: A basic instance of Faker to generate test data.\n---\n\n# fake\n\n## Syntax\n\n```text\npy.fake\n```\n\n{% hint s"
},
{
"path": "docs/pylenium-commands/find.md",
"chars": 1452,
"preview": "---\ndescription: The command to get DOM elements that match the CSS selector.\n---\n\n# find\n\n## Syntax\n\n```python\npy.find("
},
{
"path": "docs/pylenium-commands/find_xpath.md",
"chars": 1632,
"preview": "---\ndescription: The command to find all the elements that match the XPath selector.\n---\n\n# findx\n\n## Syntax\n\n```python\n"
},
{
"path": "docs/pylenium-commands/get.md",
"chars": 1306,
"preview": "---\ndescription: The command to get the DOM element that matches the CSS selector.\n---\n\n# get\n\n## Syntax\n\n```python\npy.g"
},
{
"path": "docs/pylenium-commands/get_cookie.md",
"chars": 716,
"preview": "---\ndescription: The command to get the cookie with the given name.\n---\n\n# get\\_cookie\n\n## Syntax\n\n```python\npy.get_cook"
},
{
"path": "docs/pylenium-commands/get_cookies.md",
"chars": 885,
"preview": "---\ndescription: The command to get all cookies in the current browser session.\n---\n\n# get\\_cookies\n\n## Syntax\n\n```pytho"
},
{
"path": "docs/pylenium-commands/get_xpath.md",
"chars": 1517,
"preview": "---\ndescription: The command to get a single element using an XPath selector.\n---\n\n# getx\n\n## Syntax\n\n```python\npy.getx("
},
{
"path": "docs/pylenium-commands/go.md",
"chars": 551,
"preview": "---\ndescription: Navigate forward or back in the browser's history.\n---\n\n# go\n\n## Syntax\n\n```python\npy.go(direction)\npy."
},
{
"path": "docs/pylenium-commands/maximize_window.md",
"chars": 441,
"preview": "---\ndescription: The command the maximize the current window.\n---\n\n# maximize\\_window\n\n## Syntax\n\n```python\npy.maximize_"
},
{
"path": "docs/pylenium-commands/quit.md",
"chars": 390,
"preview": "---\ndescription: The command to quit the driver and close all associated windows.\n---\n\n# quit\n\n## Syntax\n\n```python\npy.q"
},
{
"path": "docs/pylenium-commands/request.md",
"chars": 323,
"preview": "---\ndescription: >-\n Requests is an elegant and simple HTTP library for Python, built for human\n beings.\n---\n\n# reques"
},
{
"path": "docs/pylenium-commands/screenshot.md",
"chars": 818,
"preview": "---\ndescription: The command to take a screenshot of the current window.\n---\n\n# screenshot\n\n## Syntax\n\n```python\npy.scre"
},
{
"path": "docs/pylenium-commands/scroll_to.md",
"chars": 436,
"preview": "---\ndescription: The command to scroll to the given location.\n---\n\n# scroll\\_to\n\n## Syntax\n\n```text\npy.scroll_to(x, y)\n`"
},
{
"path": "docs/pylenium-commands/set_cookie.md",
"chars": 739,
"preview": "---\ndescription: The command to set a cookie into the current browser session.\n---\n\n# set\\_cookie\n\n## Syntax\n\n```python\n"
},
{
"path": "docs/pylenium-commands/should.md",
"chars": 1384,
"preview": "---\ndescription: A collection of expectations for the current driver.\n---\n\n# should\n\n## Expectations\n\n* `.contain_title("
},
{
"path": "docs/pylenium-commands/switch_to.default_content.md",
"chars": 618,
"preview": "---\ndescription: >-\n The command to switch the driver's context to the default (or starting)\n content.\n---\n\n# switch\\_"
},
{
"path": "docs/pylenium-commands/switch_to.frame.md",
"chars": 994,
"preview": "---\ndescription: The command to switch the driver's context to the frame given its name or id.\n---\n\n# switch\\_to.frame\n\n"
},
{
"path": "docs/pylenium-commands/switch_to.parent_frame.md",
"chars": 942,
"preview": "---\ndescription: >-\n The command to switch the driver's context to the parent frame of the current\n frame.\n---\n\n# swit"
},
{
"path": "docs/pylenium-commands/switch_to.window.md",
"chars": 880,
"preview": "---\ndescription: >-\n The command to switch the driver's context to the specified Window or Browser\n Tab.\n---\n\n# switch"
},
{
"path": "docs/pylenium-commands/title.md",
"chars": 436,
"preview": "---\ndescription: The command to get the current page's title.\n---\n\n# title\n\n## Syntax\n\n```python\npy.title()\n```\n\n## Usag"
},
{
"path": "docs/pylenium-commands/url.md",
"chars": 390,
"preview": "---\ndescription: The command to get the current page's URL.\n---\n\n# url\n\n## Syntax\n\n```text\npy.url()\n```\n\n## Usage\n\n{% co"
},
{
"path": "docs/pylenium-commands/viewport.md",
"chars": 751,
"preview": "---\ndescription: The command to control the size and orientation of the current browser window.\n---\n\n# viewport\n\n## Synt"
},
{
"path": "docs/pylenium-commands/visit.md",
"chars": 586,
"preview": "---\ndescription: The command to navigate to URLs.\n---\n\n# visit\n\n## Syntax\n\n```python\npy.visit(url)\n```\n\n## Usage\n\n```bas"
},
{
"path": "docs/pylenium-commands/wait.md",
"chars": 4572,
"preview": "---\ndescription: The command to execute a method or function as a condition to wait for.\n---\n\n# wait\n\nThere are two type"
},
{
"path": "docs/pylenium-commands/webdriver.md",
"chars": 818,
"preview": "---\ndescription: The property to get the current instance of Selenium's WebDriver.\n---\n\n# webdriver\n\n## Syntax\n\n```text\n"
},
{
"path": "docs/pylenium-commands/window_handles.md",
"chars": 625,
"preview": "---\ndescription: >-\n This property gets a list of all the window handles in the current browser\n session.\n---\n\n# windo"
},
{
"path": "docs/pylenium-commands/window_size.md",
"chars": 403,
"preview": "---\ndescription: This property get the size of the current window.\n---\n\n# window\\_size\n\n## Syntax\n\n```python\npy.window_s"
},
{
"path": "docs/pylenium-commands/xpath.md",
"chars": 1904,
"preview": "---\ndescription: The command to get DOM elements that match the XPath selector.\n---\n\n# \\(deprecated\\) xpath\n\n{% hint sty"
},
{
"path": "poetry.toml",
"chars": 32,
"preview": "[virtualenvs]\nin-project = true\n"
},
{
"path": "pylenium/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pylenium/a11y.py",
"chars": 3109,
"preview": "from typing import Any, Dict, List, Optional\nfrom axe_selenium_python.axe import Axe\nfrom pydantic.main import BaseModel"
},
{
"path": "pylenium/cdp.py",
"chars": 2252,
"preview": "\"\"\" Chrome DevTools Protocol (CDP) introduced in Selenium 4.\n\nResources:\n - https://www.selenium.dev/documentation/we"
},
{
"path": "pylenium/config.py",
"chars": 1084,
"preview": "from pathlib import Path\nfrom typing import List, Dict, Optional\n\nfrom pydantic import BaseModel\n\n# PYLENIUM CONFIG #\n##"
},
{
"path": "pylenium/driver.py",
"chars": 26858,
"preview": "from logging import Logger\nfrom typing import Dict, List, Set, Union\n\nfrom faker import Faker\nfrom selenium.common.excep"
},
{
"path": "pylenium/element.py",
"chars": 47459,
"preview": "import time\nfrom typing import List, Optional, Tuple\n\nfrom selenium.common.exceptions import NoSuchElementException, Tim"
},
{
"path": "pylenium/jquery.py",
"chars": 2293,
"preview": "from selenium.webdriver.remote.webdriver import WebDriver, By\nfrom selenium.webdriver.remote.webelement import WebElemen"
},
{
"path": "pylenium/log.py",
"chars": 1967,
"preview": "\"\"\"Custom Logging for Pylenium\n\nLog Levels:\n CRITICAL = 50,\n ERROR = 40,\n WARNING = 30,\n USER = 25, "
},
{
"path": "pylenium/performance.py",
"chars": 10812,
"preview": "import time\nfrom typing import List, Union\n\nfrom pydantic import BaseModel, Field\nfrom selenium.common.exceptions import"
},
{
"path": "pylenium/scripts/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pylenium/scripts/allure_reporting.py",
"chars": 4685,
"preview": "\"\"\" Allure reporting integration \"\"\"\nimport platform\nfrom typing import List\n\nimport typer\n\nfrom pylenium.scripts.cli_ut"
},
{
"path": "pylenium/scripts/cli.py",
"chars": 3178,
"preview": "\"\"\" The Pylenium CLI using Typer.\n\nFor more information, visit their official docs: https://typer.tiangolo.com/\n\"\"\"\n\nimp"
},
{
"path": "pylenium/scripts/cli_utils.py",
"chars": 915,
"preview": "import subprocess\nfrom typing import List, Tuple, Union\n\n\ndef run_process(tokenized_command: Union[List[str], str], shel"
},
{
"path": "pylenium/scripts/conftest.py",
"chars": 12103,
"preview": "\"\"\"\nThe `conftest.py` and `pylenium.json` files generated by Pylenium should stay at your Workspace Root (aka Project Ro"
},
{
"path": "pylenium/scripts/drag_and_drop.js",
"chars": 2390,
"preview": "(function( $ ) {\n $.fn.simulateDragDrop = function(options) {\n return this.each(function() {\n "
},
{
"path": "pylenium/scripts/load_jquery.js",
"chars": 1185,
"preview": "(function(jqueryUrl, iframe, callback) {\n if (typeof jqueryUrl != 'string') {\n jqueryUrl = 'https://ajax.googl"
},
{
"path": "pylenium/scripts/pylenium.json",
"chars": 450,
"preview": "{\n \"driver\": {\n \"browser\": \"chrome\",\n \"remote_url\": \"\",\n \"wait_time\": 10,\n \"page_load_wait_time\": 0,\n \"o"
},
{
"path": "pylenium/scripts/pytest.ini",
"chars": 265,
"preview": "[pytest]\n; Configuring pytest\n; More info: https://docs.pytest.org/en/6.2.x/customize.html\n\n;Logging\n; DATE FORMAT EXAMP"
},
{
"path": "pylenium/switch_to.py",
"chars": 4953,
"preview": "from selenium.common.exceptions import NoSuchFrameException\nfrom selenium.webdriver.support import expected_conditions a"
},
{
"path": "pylenium/utils.py",
"chars": 467,
"preview": "import pathlib\n\n\ndef read_script_from_file(file_name) -> str:\n \"\"\" Get the script string from a file in the scripts d"
},
{
"path": "pylenium/wait.py",
"chars": 3862,
"preview": "import time\nfrom typing import Tuple, Optional, Union\n\nfrom selenium.webdriver.remote.webelement import WebElement\nfrom "
},
{
"path": "pylenium/webdriver_factory.py",
"chars": 12492,
"preview": "\"\"\" Factory to build WebDrivers, leveraging Selenium Manager.\n\nThe Pylenium class asks for a PyleniumConfig object to bu"
},
{
"path": "pylenium.json",
"chars": 448,
"preview": "{\n \"driver\": {\n \"browser\": \"chrome\",\n \"remote_url\": \"\",\n \"wait_time\": 10,\n \"page_load_wait_time\": 0,\n \"o"
},
{
"path": "pyproject.toml",
"chars": 1148,
"preview": "[tool.poetry]\nname = \"pyleniumio\"\nversion = \"1.21.0\"\ndescription = \"The best of Selenium and Cypress in a single Python "
},
{
"path": "pytest.ini",
"chars": 287,
"preview": "[pytest]\n; Configuring pytest\n; More info: https://docs.pytest.org/en/6.2.x/customize.html\ntestpaths =\n tests\n\n;Loggi"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/performance/test_cdp_performance.py",
"chars": 353,
"preview": "\"\"\" Chrome DevTools Protocol - Performance Tab \"\"\"\nfrom pylenium.driver import Pylenium\n\n\ndef test_capture_performance_m"
},
{
"path": "tests/performance/test_performance.py",
"chars": 1123,
"preview": "import pytest\n\nfrom pylenium.driver import Pylenium\n\n\n@pytest.fixture\ndef qap_dev(py) -> Pylenium:\n py.visit('https:/"
},
{
"path": "tests/test_flows.py",
"chars": 1159,
"preview": "import pytest\nfrom pylenium.driver import Pylenium\n\n\n@pytest.fixture(scope=\"session\")\ndef sauce(pys: Pylenium) -> Pyleni"
},
{
"path": "tests/ui/test_element.py",
"chars": 5666,
"preview": "import pytest\nfrom pylenium.driver import Pylenium\n\n\nTHE_INTERNET = \"https://the-internet.herokuapp.com\"\nDEMO_QA = \"http"
},
{
"path": "tests/ui/test_element_actions.py",
"chars": 3226,
"preview": "import pytest\nfrom selenium.common.exceptions import NoSuchElementException\nfrom pylenium import jquery\nfrom pylenium.dr"
},
{
"path": "tests/ui/test_pydriver.py",
"chars": 4335,
"preview": "import os\nimport pytest\nfrom selenium.webdriver.common.by import By\nfrom pylenium.a11y import PyleniumAxe\nfrom pylenium."
},
{
"path": "tests/unit/test_config.py",
"chars": 855,
"preview": "def test_py_config_defaults(py_config):\n # driver settings\n assert py_config.driver.browser == \"chrome\"\n assert"
},
{
"path": "tests/unit/test_faker.py",
"chars": 842,
"preview": "\"\"\" Tests to see what could be faked.\n\nFaker docs: https://faker.readthedocs.io/en/stable/providers.html\n\n* You don't ge"
},
{
"path": "tests/unit/test_requests.py",
"chars": 95,
"preview": "def test_api_fixture(api):\n response = api.get('https://google.com')\n assert response.ok\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the ElSnoMan/pyleniumio GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 139 files (340.2 KB), approximately 86.4k tokens, and a symbol index with 330 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.