E2E scenario testing framework for Wagtail applications
Project description
wagtail-scenario-test
E2E scenario testing framework for Wagtail applications using Playwright.
Features
- WagtailAdmin Facade: Simple, intuitive API for Wagtail admin operations
- Page Object Pattern: Maintainable abstractions for Wagtail admin UI
- Pytest Fixtures: Ready-to-use fixtures for authenticated browser sessions
- Factory Support: Base factories for test data creation
Installation
pip install wagtail-scenario-test
# Install Playwright browsers
playwright install
Quick Start
1. Write your first E2E test
import pytest
from wagtail_scenario_test import WagtailAdmin
@pytest.mark.e2e
@pytest.mark.django_db(transaction=True)
def test_create_snippet(authenticated_page, server_url):
"""Test creating a snippet through the admin UI."""
admin = WagtailAdmin(authenticated_page, server_url)
# Get snippet admin for your model
snippet = admin.snippet("myapp.mymodel")
# Create a new snippet
snippet.create(name="My New Item")
# Verify success
snippet.assert_success_message()
assert snippet.item_exists_in_list("My New Item")
2. Configure Django settings
# tests/settings.py (or conftest.py)
DJANGO_SETTINGS_MODULE = "tests.settings"
# Required settings
LANGUAGE_CODE = "en"
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "testserver"]
WAGTAIL_CONTENT_LANGUAGES = [("en", "English")]
3. Run tests
# Headless (for CI)
pytest tests/ -m e2e
# With browser visible
pytest tests/ -m e2e --headed
# With slow motion (for debugging)
pytest tests/ -m e2e --headed --slowmo=500
WagtailAdmin Facade
The WagtailAdmin class is the main entry point for E2E testing:
from wagtail_scenario_test import WagtailAdmin
def test_admin_operations(authenticated_page, server_url):
admin = WagtailAdmin(authenticated_page, server_url)
# Navigate
admin.go_to_dashboard()
# Search
admin.global_search("my query")
# Work with snippets
snippet = admin.snippet("blog.category")
snippet.create(name="Technology")
# Assertions
admin.assert_success_message()
admin.assert_success_message(contains="created")
Snippet Operations
snippet = admin.snippet("myapp.mymodel")
# Navigation
snippet.go_to_list() # Go to list view
snippet.go_to_add() # Go to add form
snippet.go_to_edit(123) # Go to edit form for ID 123
# CRUD
snippet.create(name="New Item") # Create with name field
snippet.create(name="Item", id_slug="item") # Create with additional fields
snippet.save() # Click Save button
snippet.delete() # Delete with confirmation
# List operations
count = snippet.get_item_count() # Count items in list
items = snippet.get_list_items() # Get all item titles
exists = snippet.item_exists_in_list("Name") # Check if item exists
snippet.click_item_in_list("Name") # Click to edit
# Assertions
snippet.assert_success_message()
snippet.assert_item_created("New Item")
snippet.assert_validation_error("This field is required")
Fixtures
All fixtures are automatically available when the package is installed:
| Fixture | Scope | Description |
|---|---|---|
authenticated_page |
function | Playwright page logged into Wagtail admin |
authenticated_page_fast |
function | Faster auth using saved storage state |
authenticated_browser_context |
function | Browser context with pre-auth state |
server_url |
function | Base URL of the live test server |
admin_user_e2e |
function | Creates admin user for E2E testing |
admin_credentials |
function | Default admin credentials |
wagtail_site |
function | Creates Wagtail site with root page |
storage_state_path |
session | Path for storing auth state |
Usage Example
@pytest.mark.e2e
@pytest.mark.django_db(transaction=True)
def test_with_fixtures(authenticated_page, server_url):
# authenticated_page is already logged in
# server_url is the test server URL (e.g., "http://localhost:12345")
admin = WagtailAdmin(authenticated_page, server_url)
admin.go_to_dashboard()
Customizing Credentials
# conftest.py
import pytest
@pytest.fixture
def admin_credentials():
return {"username": "custom_admin", "password": "custom_password"}
Authentication Persistence (Storage State)
For faster test execution, use authenticated_page_fast which saves and reuses
login state across tests:
@pytest.mark.e2e
@pytest.mark.django_db(transaction=True)
def test_fast_auth(authenticated_page_fast, server_url):
# Uses saved storage state - no login required after first test
authenticated_page_fast.goto(f"{server_url}/admin/")
For multiple pages with shared auth:
def test_multi_page(authenticated_browser_context, server_url):
page1 = authenticated_browser_context.new_page()
page2 = authenticated_browser_context.new_page()
# Both pages are already logged in
You can also save storage state manually:
from wagtail_scenario_test import WagtailAdmin
def test_save_state(authenticated_page, server_url):
admin = WagtailAdmin(authenticated_page, server_url)
admin.save_storage_state("auth_state.json")
Page Objects
BasePage
Base class with common browser interactions:
from wagtail_scenario_test.page_objects import BasePage
class MyPage(BasePage):
def do_something(self):
self.goto("/my-path/")
self.click_button("Submit")
self.wait_for_navigation()
self.assert_visible("Success!")
Available Methods:
| Method | Description |
|---|---|
goto(path) |
Navigate to path |
reload() |
Reload current page |
current_path() |
Get current URL path |
wait_for_navigation() |
Wait for page load |
wait_for_element(selector) |
Wait for element visibility |
click_button(name) |
Click button by name |
click_link(name) |
Click link by name |
fill_field(label, value) |
Fill textbox by label |
fill_field_by_id(id, value) |
Fill field by ID |
select_option(label, value) |
Select dropdown option |
check_checkbox(label) |
Check checkbox |
assert_visible(text) |
Assert text is visible |
assert_url_contains(path) |
Assert URL contains path |
screenshot(name) |
Take screenshot |
get_page_content() |
Get page HTML |
Extending Page Objects
Create custom Page Objects for your specific models:
from wagtail_scenario_test.page_objects import SnippetAdminPage
class CategoryAdmin(SnippetAdminPage):
"""Page Object for Category snippet."""
def __init__(self, page, base_url):
super().__init__(
page, base_url,
app_name="blog",
model_name="category",
)
def create_with_color(self, name: str, color: str):
"""Create category with color field."""
self.go_to_add()
self.page.locator("#id_name").fill(name)
self.page.locator("#id_color").fill(color)
self.save()
Factory Support
from wagtail_scenario_test.utils import WagtailUserFactory, WagtailSuperUserFactory
# Create regular user
user = WagtailUserFactory()
# Create superuser
admin = WagtailSuperUserFactory()
# Custom attributes
admin = WagtailSuperUserFactory(username="custom_admin")
Video Recording & GIF Conversion
Record test videos using pytest-playwright's built-in --video option and automatically convert them to GIF format for documentation and debugging.
Record Videos
# Record videos for failed tests only
pytest tests/ -m e2e --video=retain-on-failure
# Record all test videos
pytest tests/ -m e2e --video=on
# Videos are saved to test-results/ directory
Convert Videos to GIF
Use the --gif option to automatically convert recorded videos to GIF format:
# Record and convert to GIF
pytest tests/ -m e2e --video=on --gif
# Customize GIF quality
pytest tests/ -m e2e --video=on --gif --gif-fps=15 --gif-width=1024
GIF Options:
| Option | Default | Description |
|---|---|---|
--gif |
off | Enable automatic GIF conversion |
--gif-fps |
10 | Frames per second (lower = smaller file) |
--gif-width |
800 | Width in pixels (height auto-scaled) |
Requirements: ffmpeg must be installed for GIF conversion.
# macOS
brew install ffmpeg
# Ubuntu/Debian
apt-get install ffmpeg
Programmatic Conversion
from wagtail_scenario_test.utils import convert_video_to_gif, is_ffmpeg_available
if is_ffmpeg_available():
gif_path = convert_video_to_gif("test-results/video.webm")
print(f"Created: {gif_path}")
Best Practices
1. Use the Facade
Prefer WagtailAdmin facade over direct Page Object usage:
# Good
admin = WagtailAdmin(page, url)
admin.snippet("myapp.mymodel").create(name="Test")
# Avoid (unless extending)
snippet = SnippetAdminPage(page, url, app_name="myapp", model_name="mymodel")
2. Mark E2E Tests
Always mark E2E tests for selective running:
@pytest.mark.e2e
@pytest.mark.django_db(transaction=True)
def test_something(authenticated_page, server_url):
...
3. Use Headed Mode for Debugging
# See what's happening
pytest tests/ -m e2e --headed --slowmo=500
# Pause on failure
pytest tests/ -m e2e --headed -x
4. Create Data via UI
For editing tests, create data through the UI to avoid transaction issues:
def test_edit_snippet(authenticated_page, server_url):
admin = WagtailAdmin(authenticated_page, server_url)
snippet = admin.snippet("myapp.mymodel")
# Create first, then edit
snippet.create(name="Original Name")
snippet.click_item_in_list("Original Name")
# Edit
snippet.page.locator("#id_name").fill("Updated Name")
snippet.save()
snippet.assert_success_message()
Requirements
- Python 3.10+
- Django 4.2+
- Wagtail 5.0+
- pytest 8.0+
- pytest-django 4.8+
- pytest-playwright 0.6.2+
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wagtail_scenario_test-0.1.0.tar.gz.
File metadata
- Download URL: wagtail_scenario_test-0.1.0.tar.gz
- Upload date:
- Size: 39.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05ac31b6c15502d0278881d8e853724ee8407103c8bd15675c1d38c982a2a9e9
|
|
| MD5 |
44252fcd0316facee5be3f32bf2b04b0
|
|
| BLAKE2b-256 |
5018202d874809ac6e96a00d0f270b4b603bbb0f9e4214e046b2da71a1b25381
|
Provenance
The following attestation bundles were made for wagtail_scenario_test-0.1.0.tar.gz:
Publisher:
publish.yml on kkm-horikawa/wagtail-scenario-test
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wagtail_scenario_test-0.1.0.tar.gz -
Subject digest:
05ac31b6c15502d0278881d8e853724ee8407103c8bd15675c1d38c982a2a9e9 - Sigstore transparency entry: 774970957
- Sigstore integration time:
-
Permalink:
kkm-horikawa/wagtail-scenario-test@aed569e7b3196c79c9ad7b0f551ff6b42a0ee077 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kkm-horikawa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aed569e7b3196c79c9ad7b0f551ff6b42a0ee077 -
Trigger Event:
push
-
Statement type:
File details
Details for the file wagtail_scenario_test-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wagtail_scenario_test-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6ac3934e362908c66f12c9da0a58c81d3dcba36ed8033f8de67b374673c6a3a
|
|
| MD5 |
6e2b0d32ddb395ed747d58a31bbdefc3
|
|
| BLAKE2b-256 |
4528d62ee5d0675a063957c844345a1888975c9ffeee033040f843a7c606aea7
|
Provenance
The following attestation bundles were made for wagtail_scenario_test-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on kkm-horikawa/wagtail-scenario-test
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wagtail_scenario_test-0.1.0-py3-none-any.whl -
Subject digest:
b6ac3934e362908c66f12c9da0a58c81d3dcba36ed8033f8de67b374673c6a3a - Sigstore transparency entry: 774970958
- Sigstore integration time:
-
Permalink:
kkm-horikawa/wagtail-scenario-test@aed569e7b3196c79c9ad7b0f551ff6b42a0ee077 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kkm-horikawa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aed569e7b3196c79c9ad7b0f551ff6b42a0ee077 -
Trigger Event:
push
-
Statement type: