Skip to content

Commit a8fbddf

Browse files
committed
Initial commit: Playwright Python E2E tests for Bistro Delivery
Migrated from TypeScript Playwright to Python using pytest-playwright. 6 test cases (BD-022, BD-023, BD-026, BD-038, BD-052, BD-055) with Page Object Model, Pydantic validation, and QA Sphere integration.
0 parents  commit a8fbddf

20 files changed

Lines changed: 779 additions & 0 deletions

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
DEMO_BASE_URL='https://hypersequent.github.io/bistro/'
2+
3+
# QAS_TOKEN=<QA Sphere API Token>
4+
# Get your token in QA Sphere -> Settings -> API Keys
5+
6+
# QAS_URL=<QA Sphere Company URL>
7+
# Example: https://qasdemo.eu2.qasphere.com

.github/workflows/playwright.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Playwright Tests
2+
3+
permissions:
4+
contents: read
5+
checks: write
6+
id-token: write
7+
8+
on:
9+
push:
10+
branches: [main]
11+
pull_request:
12+
branches: [main]
13+
14+
jobs:
15+
test:
16+
timeout-minutes: 60
17+
runs-on: ubuntu-latest
18+
defaults:
19+
run:
20+
working-directory: bistro-e2e-python
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.13"
27+
28+
- name: Install dependencies
29+
run: pip install -e ".[dev]"
30+
31+
- name: Install Playwright browsers
32+
run: playwright install --with-deps
33+
34+
- name: Create .env file
35+
run: echo "DEMO_BASE_URL=https://hypersequent.github.io/bistro/" > .env
36+
37+
- name: Run Playwright tests
38+
run: pytest --browser chromium -v
39+
40+
- uses: actions/upload-artifact@v4
41+
if: always()
42+
with:
43+
name: test-results
44+
path: bistro-e2e-python/test-results/
45+
46+
- uses: actions/upload-artifact@v4
47+
if: always()
48+
with:
49+
name: junit-results
50+
path: bistro-e2e-python/junit-results/
51+
52+
- name: Publish JUnit Report
53+
uses: mikepenz/action-junit-report@v4
54+
if: always()
55+
with:
56+
report_paths: "bistro-e2e-python/junit-results/*.xml"

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.DS_Store
2+
__pycache__/
3+
*.pyc
4+
.pytest_cache/
5+
test-results/
6+
junit-results/
7+
.env
8+
.env.*
9+
!.env.example
10+
.venv/
11+
*.egg-info/
12+
dist/
13+
build/
14+
.ruff_cache/
15+
.qaspherecli

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# E2E Tests for Bistro Delivery (Python)
2+
3+
This repository contains end-to-end tests for [Bistro Delivery](https://github.com/hypersequent/bistro), implemented using [Playwright for Python](https://playwright.dev/python/) with pytest.
4+
5+
Prerequisites: Python 3.13+
6+
7+
## Getting Started
8+
9+
1. Clone the repository:
10+
11+
```bash
12+
git clone git@github.com:Hypersequent/bistro-e2e-python.git
13+
cd bistro-e2e-python
14+
```
15+
16+
2. Create and activate a virtual environment:
17+
18+
```bash
19+
python3 -m venv .venv
20+
source .venv/bin/activate
21+
```
22+
23+
3. Install dependencies:
24+
25+
```bash
26+
pip install -e ".[dev]"
27+
```
28+
29+
4. Install Playwright browsers:
30+
31+
```bash
32+
playwright install --with-deps
33+
```
34+
35+
## Running Tests
36+
37+
### Basic Test Execution
38+
39+
```bash
40+
pytest --browser chromium -v # Run tests in Chromium
41+
pytest --browser chromium -v --headed # Run tests in headed mode
42+
```
43+
44+
### Upload testing results to QA Sphere
45+
46+
1. Add your QA Sphere credentials to a `.qaspherecli` file or `.env`:
47+
48+
```bash
49+
QAS_TOKEN=<QA Sphere API Token>
50+
# Get your token in QA Sphere -> Settings -> API Keys
51+
52+
QAS_URL=<QA Sphere Company URL>
53+
# Example: https://qasdemo.eu2.qasphere.com
54+
```
55+
56+
2. Upload results:
57+
58+
```bash
59+
npx qas-cli junit-upload --project-code BD --attachments junit-results/results.xml
60+
```
61+
62+
## Additional Commands
63+
64+
Different browsers:
65+
66+
```bash
67+
pytest --browser chromium -v # Run tests in Chromium
68+
pytest --browser firefox -v # Run tests in Firefox
69+
pytest --browser webkit -v # Run tests in WebKit
70+
```
71+
72+
Linting and formatting:
73+
74+
```bash
75+
ruff check tests/ utils/ # Check for lint errors
76+
ruff format tests/ utils/ # Format code
77+
```
78+
79+
## License
80+
81+
This project is licensed under the 0BSD License - see the [LICENSE](LICENSE) file for details.
82+
83+
---
84+
85+
Maintained by [Hypersequent](https://github.com/Hypersequent)

pyproject.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[project]
2+
name = "bistro-e2e-python"
3+
version = "1.0.0"
4+
description = "E2E tests for Bistro Delivery using Playwright for Python"
5+
requires-python = ">=3.13"
6+
dependencies = [
7+
"playwright>=1.50",
8+
"pytest>=8.0",
9+
"pytest-playwright>=0.7",
10+
"pydantic>=2.12",
11+
"faker>=33.0",
12+
"python-dotenv>=1.0",
13+
]
14+
15+
[dependency-groups]
16+
dev = [
17+
"ruff>=0.9",
18+
]
19+
20+
[tool.pytest.ini_options]
21+
testpaths = ["tests"]
22+
addopts = [
23+
"--screenshot=on",
24+
"--tracing=retain-on-failure",
25+
"--junitxml=junit-results/results.xml",
26+
]
27+
28+
[tool.ruff]
29+
target-version = "py313"
30+
line-length = 100
31+
32+
[tool.ruff.lint]
33+
select = ["E", "F", "I", "W"]

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
import re
3+
4+
import pytest
5+
from dotenv import load_dotenv
6+
7+
load_dotenv()
8+
9+
# Map test function names to QA Sphere-friendly display names with BD-XXX markers.
10+
# qas-cli matches markers in the format PROJECT-SEQUENCE (e.g. BD-023).
11+
_QAS_NAMES: dict[str, str] = {
12+
"test_bd023_cart_operations": "BD-023: Cart operations",
13+
"test_bd022_order_with_cash_payment": "BD-022: Order with cash payment",
14+
"test_bd055_about_page_content": "BD-055: About page content",
15+
"test_bd026_navbar_navigation_state": "BD-026: Navbar navigation state",
16+
"test_bd038_menu_tabs_and_products": "BD-038: Menu tabs and products",
17+
"test_bd052_welcome_banner": "BD-052: Welcome banner",
18+
}
19+
20+
21+
def pytest_itemcollected(item: pytest.Item) -> None:
22+
"""Rewrite test node IDs so JUnit XML contains QA Sphere markers (BD-XXX)."""
23+
# item.name looks like "test_bd023_cart_operations[chromium]"
24+
base_name = re.sub(r"\[.*\]$", "", item.name)
25+
if base_name in _QAS_NAMES:
26+
suffix = item.name[len(base_name) :] # e.g. "[chromium]"
27+
item._nodeid = item._nodeid.replace(item.name, _QAS_NAMES[base_name] + suffix)
28+
item.name = _QAS_NAMES[base_name] + suffix
29+
30+
31+
@pytest.fixture(scope="session")
32+
def demo_base_url() -> str:
33+
url = os.getenv("DEMO_BASE_URL", "https://hypersequent.github.io/bistro/")
34+
return url.rstrip("/")
35+
36+
37+
@pytest.fixture()
38+
def browser_context_args(browser_context_args: dict) -> dict:
39+
return {**browser_context_args, "java_script_enabled": True}
40+
41+
42+
@pytest.fixture(scope="session")
43+
def browser_type_launch_args(browser_type_launch_args: dict) -> dict:
44+
return {**browser_type_launch_args}

tests/pageobjects/__init__.py

Whitespace-only changes.

tests/pageobjects/about.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from playwright.sync_api import Page
2+
3+
4+
class About:
5+
def __init__(self, page: Page, base_url: str) -> None:
6+
self.page = page
7+
self.base_url = base_url
8+
self.heading = page.locator("h1")
9+
self.body = page.locator("article")
10+
self.navbar_items = page.locator("nav ul > li")
11+
12+
def goto(self) -> None:
13+
self.page.goto(self.base_url + "/about")
14+
15+
def get_heading(self) -> str:
16+
return self.heading.inner_text().strip()
17+
18+
def get_body(self) -> str:
19+
return self.body.inner_text().strip()
20+
21+
def get_navbar_items(self) -> list[dict]:
22+
items = self.navbar_items.all()
23+
assert len(items) == 3
24+
25+
result = []
26+
for item in items:
27+
text = item.inner_text().strip()
28+
cls = item.get_attribute("class") or ""
29+
is_active = "active" in cls
30+
result.append({"text": text, "isActive": is_active})
31+
return result

0 commit comments

Comments
 (0)