Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions .github/workflows/monthly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ on:
- cron: "0 0 1 * *"
workflow_dispatch:
inputs:
pytest_command:
description: "Custom pytest command"
pytest_marker:
description: "Pytest marker expression"
required: true
default: '-m "not devRun"'
default: "not devRun"
type: string
parallelism:
description: "Number of machines to split tests"
Expand All @@ -31,10 +31,17 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
env:
PARALLELISM: ${{ github.event.inputs.parallelism || 2 }}
run: |
count=${{ github.event.inputs.parallelism || 2 }}
matrix=$(seq -s ',' 1 $count)
echo "matrix=$(jq -cn --argjson groups "[$matrix]" '{group: $groups}')" >> $GITHUB_OUTPUT
count="${PARALLELISM:-2}"
if ! [[ "$count" =~ ^[1-9][0-9]*$ ]]; then
echo "parallelism must be a positive integer" >&2
exit 1
fi
groups=$(seq 1 "$count" | jq -Rsc 'split("\n")[:-1] | map(tonumber)')
matrix_json=$(jq -cn --argjson groups "$groups" '{group: $groups}')
echo "matrix=$matrix_json" >> "$GITHUB_OUTPUT"
monthly-test:
needs: setup-matrix
runs-on: ubuntu-latest
Expand All @@ -55,17 +62,20 @@ jobs:
uses: astral-sh/setup-uv@v8.2.0
with:
enable-cache: true
- name: Create venv & install dependencies
run: |
uv venv
uv sync --all-extras --dev
- name: Install dependencies
run: uv sync --locked --all-extras --dev
- name: Run Tests
env:
BASE_URL: ${{ vars.BASE_URL }}
GROUP: ${{ matrix.group }}
PARALLELISM: ${{ github.event.inputs.parallelism || 2 }}
PYTEST_MARKER: ${{ github.event.inputs.pytest_marker || 'not devRun' }}
run: >
xvfb-run .venv/bin/python -m pytest
${{ github.event.inputs.pytest_command || '-m "not devRun"' }}
--base-url ${{ vars.BASE_URL }}
--splits ${{ github.event.inputs.parallelism || 2 }}
--group ${{ matrix.group }}
xvfb-run uv run pytest
-m "$PYTEST_MARKER"
--base-url "$BASE_URL"
--splits "$PARALLELISM"
--group "$GROUP"
- name: Upload shard allure-results
if: always()
uses: actions/upload-artifact@v7
Expand All @@ -91,15 +101,19 @@ jobs:
- name: Link Git Information And Browser Version To Allure Results
if: always()
working-directory: allure-results
env:
BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GIT_BRANCH: ${{ github.ref_name }}
GIT_COMMIT_ID: ${{ github.sha }}
run: |
{
echo BUILD_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
echo GIT_BRANCH=${{ github.ref_name }}
echo GIT_COMMIT_ID=${{ github.sha }}
echo GIT_COMMIT_MESSAGE="$(git show -s --format=%s HEAD)"
echo GIT_COMMIT_AUTHOR_NAME="$(git show -s --format='%ae' HEAD)"
echo GIT_COMMIT_TIME="$(git show -s --format=%ci HEAD)"
echo CHROME_VERSION="$(google-chrome --product-version 2>/dev/null || true)"
printf 'BUILD_URL=%s\n' "$BUILD_URL"
printf 'GIT_BRANCH=%s\n' "$GIT_BRANCH"
printf 'GIT_COMMIT_ID=%s\n' "$GIT_COMMIT_ID"
printf 'GIT_COMMIT_MESSAGE=%s\n' "$(git show -s --format=%s HEAD)"
printf 'GIT_COMMIT_AUTHOR_NAME=%s\n' "$(git show -s --format='%ae' HEAD)"
printf 'GIT_COMMIT_TIME=%s\n' "$(git show -s --format=%ci HEAD)"
printf 'CHROME_VERSION=%s\n' "$(google-chrome --product-version 2>/dev/null || true)"
} >> environment.properties
- name: Generate Allure HTML report
if: always()
Expand Down
35 changes: 21 additions & 14 deletions .github/workflows/devRun.yml → .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Pre merge test
name: Pull Request Tests

on:
pull_request:
Expand All @@ -7,7 +7,7 @@ on:
- "**/*.py"
- "**/*.toml"
- "**/*.lock"
- ".github/workflows/devRun.yml"
- ".github/workflows/pr.yml"

env:
FLAKINESS_PROJECT: nirtal85/Selenium-Python-Example
Expand All @@ -33,30 +33,37 @@ jobs:
uses: astral-sh/setup-uv@v8.2.0
with:
enable-cache: true
- name: Create venv & install dependencies
run: |
uv venv
uv sync --all-extras --dev
- name: Install dependencies
run: uv sync --locked --all-extras --dev
- name: Ruff check
run: uv run ruff check .
- name: Ruff format check
run: uv run ruff format --check .
- name: Test with pytest
env:
EMAIL: ${{ secrets.EMAIL }}
PASSWORD: ${{ secrets.PASSWORD }}
run: xvfb-run .venv/bin/python -m pytest -m devRun --base-url ${{ vars.BASE_URL }}
BASE_URL: ${{ vars.BASE_URL }}
run: xvfb-run uv run pytest -m devRun --base-url "$BASE_URL"
- name: Auto-assign reviewers
uses: kentaro-m/auto-assign-action@v2.0.2
if: success()
- name: Link Git Information And Browser Version To Allure Results
if: always()
working-directory: allure-results
env:
BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GIT_BRANCH: ${{ github.head_ref || github.ref_name }}
GIT_COMMIT_ID: ${{ github.sha }}
run: |
{
echo BUILD_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
echo GIT_BRANCH=${{ github.head_ref || github.ref_name }}
echo GIT_COMMIT_ID=${{ github.sha }}
echo GIT_COMMIT_MESSAGE="$(git show -s --format=%s HEAD)"
echo GIT_COMMIT_AUTHOR_NAME="$(git show -s --format='%ae' HEAD)"
echo GIT_COMMIT_TIME="$(git show -s --format=%ci HEAD)"
echo CHROME_VERSION="$(google-chrome --product-version 2>/dev/null || true)"
printf 'BUILD_URL=%s\n' "$BUILD_URL"
printf 'GIT_BRANCH=%s\n' "$GIT_BRANCH"
printf 'GIT_COMMIT_ID=%s\n' "$GIT_COMMIT_ID"
printf 'GIT_COMMIT_MESSAGE=%s\n' "$(git show -s --format=%s HEAD)"
printf 'GIT_COMMIT_AUTHOR_NAME=%s\n' "$(git show -s --format='%ae' HEAD)"
printf 'GIT_COMMIT_TIME=%s\n' "$(git show -s --format=%ci HEAD)"
printf 'CHROME_VERSION=%s\n' "$(google-chrome --product-version 2>/dev/null || true)"
} >> environment.properties
- name: Generate Allure HTML report (Allure 3 CLI via npx)
if: always()
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![Twitter Follow](https://img.shields.io/twitter/follow/NirTal2?style=social)](https://twitter.com/NirTal2)
[![YouTube](https://img.shields.io/youtube/channel/subscribers/UCQjS-eoKl0a1nuP_dvpLsjQ?style=social)](https://www.youtube.com/channel/UCQjS-eoKl0a1nuP_dvpLsjQ)
[![Architecture by TestShift](https://raw.githubusercontent.com/nirtal85/TestShift-AI/main/assets/testshift-architecture-badge.svg)](https://www.test-shift.com/)
![CI Status](https://github.com/nirtal85/Selenium-Python-Example/actions/workflows/devRun.yml/badge.svg)
![CI Status](https://github.com/nirtal85/Selenium-Python-Example/actions/workflows/pr.yml/badge.svg)
![Monthly Build](https://github.com/nirtal85/Selenium-Python-Example/actions/workflows/monthly.yml/badge.svg)
[![Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fflakiness.io%2Fapi%2Fbadge%3Finput%3D%257B%2522badgeToken%2522%253A%2522badge-AlsJROG5SIAxYaV68SL8K%2522%257D)](https://flakiness.io/nirtal85/Selenium-Python-Example)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
Expand Down Expand Up @@ -90,7 +90,7 @@ Windows:
```bash
pip install uv
uv venv
.\env\Scripts\activate
.\.venv\Scripts\activate
uv sync --all-extras --dev
```

Expand All @@ -117,19 +117,19 @@ PASSWORD="your_secret_password"
Run all tests (Headless Chrome by default):

```bash
pytest
uv run pytest
```

Run with specific browser:

```bash
pytest --driver firefox
uv run pytest --driver firefox
```

Run specific suite (Tags):

```bash
pytest -m sanity
uv run pytest -m sanity
```

## 📊 Results & Reporting
Expand Down
16 changes: 12 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ dev = [
name = "selenium-python-example"
description = "Selenium Python example project with pytest and Allure report"
version = "0.1.0"
readme = "README.md"
license = "MIT"
authors = [{ name = "Nir Tal" }]
dependencies = [
"allure-pytest==2.16.0",
"assertpy==1.1",
Expand Down Expand Up @@ -51,22 +54,27 @@ addopts = [
base_url = 'https://www.involve.me/'
markers = [
"security: marks security tests",
"devRun: marks tests that run before merge to the main branch"
"devRun: marks tests that run before merge to the main branch",
"flaky: marks tests that may be rerun",
"run: marks ordered test execution",
"sanity: marks sanity tests"
]
testpaths = [
"tests"
]

[tool.ruff]
exclude = [".venv", "env"]
line-length = 100
target-version = "py311"

[tool.ruff.lint]
ignore = [
"D203", # One blank line required before class docstring (conflicts with D211)
"D213", # Multi-line docstring summary should start at the second line
"COM812"
]
line-length = 100
select = ["ALL"]
target-version = "py311"
select = ["E", "F", "B", "I", "UP", "SIM", "PT"]

[tool.ruff.format]
docstring-code-format = true
Expand Down
21 changes: 14 additions & 7 deletions src/pages/base_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def set_geo_location(self, latitude: float, longitude: float) -> None:

Note:
This method uses the Chrome DevTools Protocol (CDP) to override the geolocation
in the web browser, allowing simulation of a specific geographic location for testing purposes.
in the web browser, allowing simulation of a specific geographic location for
testing purposes.
The accuracy is set to 1 for simplicity in this method.

"""
Expand Down Expand Up @@ -99,15 +100,21 @@ def is_elem_displayed(self, webelement: WebElement) -> bool:
def _highlight_element(self, webelement: WebElement, color: str) -> None:
original_style = webelement.get_attribute("style")
new_style = f"background-color:yellow;border: 1px solid {color}{original_style}"
set_style_script = (
"var tmpArguments = arguments;"
"setTimeout(function () {"
"tmpArguments[0].setAttribute('style', tmpArguments[1]);"
"}, tmpArguments[2]);"
)
self.driver.execute_script(
"var tmpArguments = arguments;setTimeout(function () {tmpArguments[0].setAttribute('style', '"
+ new_style
+ "');},0);",
set_style_script,
webelement,
new_style,
0,
)
self.driver.execute_script(
"var tmpArguments = arguments;setTimeout(function () {tmpArguments[0].setAttribute('style', '"
+ original_style
+ "');},400);",
set_style_script,
webelement,
original_style,
400,
)
4 changes: 2 additions & 2 deletions src/utilities/excel_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def read_from_excel(self, sheet_name):
num_cols = sheet.ncols # Number of columns
for row_idx, col_idx in itertools.product(range(1, sheet.nrows), range(num_cols)):
cell_obj = sheet.cell(row_idx, col_idx) # Get cell object by row, col
# Convert cell to string,split it according to "'" and take the second cell in the array created
# e.g.: cell_obj == "text:'something'" --> after convert and splitting == "something"
# Convert cell to string, split on "'" and take the value segment.
# e.g.: cell_obj == "text:'something'" --> "something"
rows_val.append(str(cell_obj).split("'")[1])
return rows_val
10 changes: 5 additions & 5 deletions src/utilities/mailinator_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ def __get_message_id(self, user_email: str, email_subject: str) -> str:
email_subject (str): The subject of the email to wait for.

Returns:
str or None: The message ID of the email with the specified subject, or None if the email
is not found within the inbox.
str or None: The message ID of the email with the specified subject, or
None if the email is not found within the inbox.

Raises:
Any exceptions raised by the underlying `self.mailinator.request` method when
Expand All @@ -66,13 +66,13 @@ def __get_message_id(self, user_email: str, email_subject: str) -> str:
messages = self.mailinator.request(
GetInboxRequest(
domain=self.mailinator_domain,
inbox=user_email.split("@")[0],
inbox=user_email.split("@", maxsplit=1)[0],
)
).msgs
filtered_messages: list[Message] = [
message
for message in messages
if message.to == user_email.split("@")[0]
if message.to == user_email.split("@", maxsplit=1)[0]
and message.subject.casefold() == email_subject.casefold()
]
return filtered_messages[0].id if filtered_messages else None
Expand Down Expand Up @@ -160,7 +160,7 @@ def count_messages_by_subject(self, user_email: str) -> dict[str, int]:
messages = self.mailinator.request(
GetInboxRequest(
domain=self.mailinator_domain,
inbox=user_email.split("@")[0],
inbox=user_email.split("@", maxsplit=1)[0],
)
).msgs
# Use a list comprehension to extract the subjects from each message
Expand Down
4 changes: 3 additions & 1 deletion src/utilities/vrt_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ def shoot_page(self, baseline_name: str) -> None:
TestRunStatus.OK.name,
)

def shoot_page_ang_ignore_elements(self, baseline_name: str, elements: list[WebElement]) -> None:
def shoot_page_ang_ignore_elements(
self, baseline_name: str, elements: list[WebElement]
) -> None:
"""Capture a screenshot of the current page, define areas to be ignored
within the screenshot, compare the captured screenshot with a baseline
image stored in Visual Regression tracker.
Expand Down
Loading