feat: add comprehensive Playwright E2E test suite
Some checks failed
CI / nightly-e2e (pull_request) Has been skipped
CI / ci (pull_request) Successful in 1m22s
CI / pr-e2e (pull_request) Failing after 3m28s

- Create e2e/ directory with 7 test modules covering:
  - Home page: title, nav links, theme toggle, newsletter form
  - Cookie consent: accept all, reject all, granular prefs, persistence
  - Article index: loads, tag filter, click-through navigation
  - Article detail: title/read-time, share section, comments, newsletter aside, related
  - Comments: valid submit → redirect, empty body → error display, disabled check
  - Newsletter: JS confirmation message, invalid email error, aside form, duplicate
  - Feeds: RSS/sitemap/robots.txt validity, tag feed, seeded content present
- Extend seed_e2e_content management command with tagged article, about page,
  no-comments article, and legal pages for richer test coverage
- Add seed command tests (create + idempotency) to keep coverage ≥ 90%
- Add pr-e2e CI job (runs on pull_request): builds image, starts postgres + app,
  installs playwright, runs pytest e2e/
- Update nightly-e2e to run full e2e/ suite alongside legacy journey test
- Add --ignore=e2e to unit-test pytest step (coverage must not include browser tests)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
codex_a
2026-02-28 19:30:43 +00:00
parent aeb0afb2ea
commit 9d323d2040
12 changed files with 643 additions and 8 deletions

View File

@@ -1,21 +1,25 @@
from __future__ import annotations
from django.core.management.base import BaseCommand
from taggit.models import Tag
from wagtail.models import Page, Site
from apps.authors.models import Author
from apps.blog.models import ArticleIndexPage, ArticlePage, HomePage
from apps.blog.models import AboutPage, ArticleIndexPage, ArticlePage, HomePage, TagMetadata
from apps.legal.models import LegalIndexPage, LegalPage
class Command(BaseCommand):
help = "Seed deterministic content for nightly Playwright E2E checks."
help = "Seed deterministic content for E2E checks."
def handle(self, *args, **options):
import datetime
root = Page.get_first_root_node()
home = HomePage.objects.child_of(root).first()
if home is None:
home = HomePage(title="Nightly Home", slug="nightly-home")
home = HomePage(title="No Hype AI", slug="nohype-home")
root.add_child(instance=home)
home.save_revision().publish()
@@ -33,6 +37,7 @@ class Command(BaseCommand):
},
)
# Primary article — comments enabled, used by nightly journey test
article = ArticlePage.objects.child_of(article_index).filter(slug="nightly-playwright-journey").first()
if article is None:
article = ArticlePage(
@@ -46,6 +51,67 @@ class Command(BaseCommand):
article_index.add_child(instance=article)
article.save_revision().publish()
# Tagged article — used by tag-filter E2E tests
tag, _ = Tag.objects.get_or_create(name="AI Tools", slug="ai-tools")
TagMetadata.objects.get_or_create(tag=tag, defaults={"colour": "cyan"})
tagged_article = ArticlePage.objects.child_of(article_index).filter(slug="e2e-tagged-article").first()
if tagged_article is None:
tagged_article = ArticlePage(
title="Tagged Article",
slug="e2e-tagged-article",
author=author,
summary="An article with tags for E2E filter tests.",
body=[("rich_text", "<p>This article is tagged with AI Tools.</p>")],
comments_enabled=True,
)
article_index.add_child(instance=tagged_article)
tagged_article.save_revision().publish()
tagged_article.tags.add(tag)
tagged_article.save()
# Third article — comments disabled
no_comments_article = ArticlePage.objects.child_of(article_index).filter(slug="e2e-no-comments").first()
if no_comments_article is None:
no_comments_article = ArticlePage(
title="No Comments Article",
slug="e2e-no-comments",
author=author,
summary="An article with comments disabled.",
body=[("rich_text", "<p>Comments are disabled on this one.</p>")],
comments_enabled=False,
)
article_index.add_child(instance=no_comments_article)
no_comments_article.save_revision().publish()
# About page
if not AboutPage.objects.child_of(home).filter(slug="about").exists():
about = AboutPage(
title="About",
slug="about",
mission_statement="Honest AI coding tool reviews for developers.",
body="<p>We benchmark, so you don't have to.</p>",
)
home.add_child(instance=about)
about.save_revision().publish()
# Legal pages
legal_index = LegalIndexPage.objects.child_of(home).filter(slug="legal").first()
if legal_index is None:
legal_index = LegalIndexPage(title="Legal", slug="legal")
home.add_child(instance=legal_index)
legal_index.save_revision().publish()
if not LegalPage.objects.child_of(legal_index).filter(slug="privacy-policy").exists():
privacy = LegalPage(
title="Privacy Policy",
slug="privacy-policy",
body="<p>We take your privacy seriously.</p>",
last_updated=datetime.date.today(),
show_in_footer=True,
)
legal_index.add_child(instance=privacy)
privacy.save_revision().publish()
site, _ = Site.objects.get_or_create(
hostname="127.0.0.1",
port=8000,
@@ -60,4 +126,4 @@ class Command(BaseCommand):
site.site_name = "No Hype AI"
site.save()
self.stdout.write(self.style.SUCCESS("Seeded nightly E2E content."))
self.stdout.write(self.style.SUCCESS("Seeded E2E content."))

View File

@@ -2,7 +2,7 @@ import pytest
from django.core.management import call_command
from django.core.management.base import CommandError
from apps.blog.models import ArticleIndexPage, ArticlePage
from apps.blog.models import AboutPage, ArticleIndexPage, ArticlePage
from apps.blog.tests.factories import AuthorFactory
@@ -28,3 +28,29 @@ def test_check_content_integrity_fails_for_blank_summary(home_page):
with pytest.raises(CommandError, match="empty summary"):
call_command("check_content_integrity")
@pytest.mark.django_db
def test_seed_e2e_content_creates_expected_pages():
call_command("seed_e2e_content")
assert ArticlePage.objects.filter(slug="nightly-playwright-journey").exists()
assert ArticlePage.objects.filter(slug="e2e-tagged-article").exists()
assert ArticlePage.objects.filter(slug="e2e-no-comments").exists()
assert AboutPage.objects.filter(slug="about").exists()
# Tagged article must carry the seeded tag
tagged = ArticlePage.objects.get(slug="e2e-tagged-article")
assert tagged.tags.filter(slug="ai-tools").exists()
# No-comments article must have comments disabled
no_comments = ArticlePage.objects.get(slug="e2e-no-comments")
assert no_comments.comments_enabled is False
@pytest.mark.django_db
def test_seed_e2e_content_is_idempotent():
"""Running the command twice must not raise or create duplicates."""
call_command("seed_e2e_content")
call_command("seed_e2e_content")
assert ArticlePage.objects.filter(slug="nightly-playwright-journey").count() == 1