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
38 changes: 22 additions & 16 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Tests
on:
push:
branches:
- main
- main
pull_request:
schedule:
- cron: "37 1 1 * *"
Expand All @@ -16,20 +16,26 @@ jobs:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.12"
- "3.13"
- '3.10'
- '3.12'
- '3.13'

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel setuptools tox
- name: Run tox targets for ${{ matrix.python-version }}
run: |
ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}")
TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') python -m tox
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip tox pytest-playwright

# Install playwright system dependencies
- name: Install Playwright browsers
run: |
playwright install --with-deps chromium

- name: Run tox targets for ${{ matrix.python-version }}
run: |
ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}")
TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') python -m tox
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ dist
htmlcov
node_modules
yarn.lock
test-results
43 changes: 20 additions & 23 deletions content_editor/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import itertools
import json

from django import forms
from django.contrib.admin.checks import InlineModelAdminChecks, ModelAdminChecks
Expand Down Expand Up @@ -185,28 +184,26 @@ def _content_editor_context(self, request, context):
for region in instance.regions
]

return json.dumps(
{
"plugins": plugins,
"regions": regions,
"allowChange": allow_change,
"messages": {
"createNew": gettext("Add new item"),
"empty": gettext("No items."),
"emptyInherited": gettext("No items. Region may inherit content."),
"noRegions": gettext("No regions available."),
"noPlugins": gettext("No plugins allowed in this region."),
"newItem": gettext("New item"),
"unknownRegion": gettext("Unknown region"),
"collapseAll": gettext("Collapse all items"),
"uncollapseAll": gettext("Uncollapse all items"),
"forDeletion": gettext("marked for deletion"),
"selectMultiple": gettext(
"Use Ctrl-Click to select and move multiple items."
),
},
}
)
return {
"plugins": plugins,
"regions": regions,
"allowChange": allow_change,
"messages": {
"createNew": gettext("Add new item"),
"empty": gettext("No items."),
"emptyInherited": gettext("No items. Region may inherit content."),
"noRegions": gettext("No regions available."),
"noPlugins": gettext("No plugins allowed in this region."),
"newItem": gettext("New item"),
"unknownRegion": gettext("Unknown region"),
"collapseAll": gettext("Collapse all items"),
"uncollapseAll": gettext("Uncollapse all items"),
"forDeletion": gettext("marked for deletion"),
"selectMultiple": gettext(
"Use Ctrl-Click to select and move multiple items."
),
},
}

def _content_editor_media(self, request, context):
return forms.Media(
Expand Down
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ dependencies = [
optional-dependencies.tests = [
"coverage",
"pytest",
"pytest-asyncio",
"pytest-cov",
"pytest-django",
"pytest-playwright",
]
urls.Homepage = "https://github.com/matthiask/django-content-editor/"

Expand Down Expand Up @@ -116,4 +119,6 @@ lint.mccabe.max-complexity = 15
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "testapp.settings"
python_files = [ "tests.py", "test_*.py" ]
addopts = "--reuse-db"
addopts = "--strict-markers"
asyncio_mode = "strict"
asyncio_default_fixture_loop_scope = "function"
55 changes: 55 additions & 0 deletions tests/README_integration_tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Integration Tests with Playwright

This directory contains integration tests for django-content-editor using Playwright.

## Overview

The integration tests use Playwright to test the content editor's functionality in a real browser environment, interacting with the actual UI components of the Django admin interface.

## Running Tests

To run the integration tests:

```bash
# Run with tox
tox -e py313-dj52-playwright

# Run tests directly (after installing dependencies)
cd tests
python -m pytest testapp/test_playwright.py -v
```

## Test Structure

The tests are organized as follows:

- `test_playwright.py`: Main test file containing test cases for various content editor features
- `test_playwright_helpers.py`: Helper functions for common operations like login and creating articles
- `conftest.py`: Pytest configuration and shared fixtures

## Test Coverage

The integration tests cover the following functionality:

1. **Basic Content Editor Loading**: Verifies that the content editor loads correctly in the admin interface
2. **Adding Content**: Tests adding rich text and other content types to an article
3. **Drag-and-Drop Ordering**: Tests the drag-and-drop functionality for reordering content items
4. **Tabbed Fieldsets**: Tests the tabbed interface if present
5. **Multiple Content Types**: Tests adding different types of content to an article
6. **Save Shortcut**: Tests the Ctrl+S keyboard shortcut for saving changes

## Troubleshooting

If you encounter issues with the tests:

- **Browser not found**: Make sure Playwright browsers are installed (`playwright install chromium`)
- **Element not found**: The selectors may need adjustment based on the actual DOM structure
- **Timeouts**: Increase timeout values if operations take longer than expected

## Extending the Tests

To add new tests:

1. Add new test functions to `test_playwright.py`
2. Use the helper functions from `test_playwright_helpers.py` for common operations
3. If needed, add new helper functions for specific interactions
106 changes: 106 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import os

import pytest
from django.contrib.auth.models import User


# Set DJANGO_ALLOW_ASYNC_UNSAFE
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

# Default browser to run tests with
os.environ.setdefault("PLAYWRIGHT_BROWSER_NAME", "chromium")


@pytest.fixture
def user():
"""Create a test superuser."""
u = User.objects.create(
username="test", is_active=True, is_staff=True, is_superuser=True
)
u.set_password("test")
u.save()
return u


@pytest.fixture
def client(client, user):
"""Login the test client."""
client.login(username="test", password="test")
return client


@pytest.fixture(scope="session")
def browser_type_launch_args():
"""Configure browser launch arguments."""
return {
"headless": True,
"args": [
"--no-sandbox",
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
],
}


@pytest.fixture(scope="function")
def browser_context_args(browser_context_args):
"""Modify browser context arguments for tracing."""
return {
**browser_context_args,
"record_video_dir": os.path.join(os.getcwd(), "test-results/videos/"),
"record_har_path": os.path.join(os.getcwd(), "test-results/har/", "test.har"),
"ignore_https_errors": True,
"java_script_enabled": True,
}


@pytest.fixture
def page(page):
"""Add console and network error logging to the page."""
# Capture console logs
page.on("console", lambda msg: print(f"BROWSER CONSOLE {msg.type}: {msg.text}"))

# Capture JavaScript errors
page.on("pageerror", lambda err: print(f"BROWSER JS ERROR: {err}"))

# Capture request failures
page.on(
"requestfailed",
lambda request: print(f"NETWORK ERROR: {request.url} {request.failure}"),
)

return page


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""Handle reporting and artifact generation."""
outcome = yield
report = outcome.get_result()

# Take screenshot of failed tests
if report.when == "call" and report.failed:
try:
page = item.funcargs["page"]
# Take screenshot and save it with test name
screenshot_dir = os.path.join(os.getcwd(), "test-results/screenshots/")
os.makedirs(screenshot_dir, exist_ok=True)
screenshot_path = os.path.join(screenshot_dir, f"{item.name}_failed.png")
page.screenshot(path=screenshot_path)
# Save page HTML
html_path = os.path.join(screenshot_dir, f"{item.name}_failed.html")
with open(html_path, "w", encoding="utf-8") as f:
f.write(page.content())

# Add to report
report.extra = [
{
"name": "Screenshot",
"content": screenshot_path,
"mime_type": "image/png",
},
{"name": "HTML", "content": html_path, "mime_type": "text/html"},
]
except Exception as e:
print(f"Failed to capture artifacts: {e}")
3 changes: 0 additions & 3 deletions tests/testapp/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class RichTextInline(ContentEditorInline):
fieldsets = [(None, {"fields": ("text", "region", "ordering")})]
regions = allow_regions({"main"})

class Media:
js = ("//cdn.ckeditor.com/4.5.6/standard/ckeditor.js", "app/plugin_ckeditor.js")


class ThingInline(admin.TabularInline):
model = Thing
Expand Down
3 changes: 0 additions & 3 deletions tests/testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
"django.contrib.contenttypes",
"django.contrib.messages",
"django.contrib.sessions",
"django.contrib.sitemaps",
"django.contrib.sites",
"django.contrib.staticfiles",
"testapp",
"content_editor",
Expand All @@ -33,7 +31,6 @@
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.locale.LocaleMiddleware",
)
SILENCED_SYSTEM_CHECKS = ["1_10.W001"]
USE_TZ = True
LANGUAGES = (("en", "English"), ("de", "German"))
TEMPLATES = [
Expand Down
Loading