From 4992b0cb9d8cc414503b1b707c55d47cd9b3d9e0 Mon Sep 17 00:00:00 2001 From: codex_a Date: Sat, 28 Feb 2026 19:47:13 +0000 Subject: [PATCH] fix: resolve 5 E2E test failures from first CI run - test_homepage_title_contains_brand: to_have_title() requires a string or regex, not a lambda; switch to re.compile('No Hype AI') - test_granular_preferences_save_dismisses_banner: wrong element clicked to open
; use 'details summary' locator directly - test_subscribe_invalid_email_shows_error: browser HTML5 email validation swallows the submit event before the JS handler fires; add 'novalidate' via evaluate() so the fetch still runs and the server returns 400 - test_copy_link_button_updates_text: clipboard API unavailable in headless Docker; add polyfill + pre-grant permissions in conftest page fixture so the JS success path runs and button text becomes 'Copied' - test_comments_section_absent_when_disabled: guard against Wagtail's add_child() resetting BooleanField defaults by calling an explicit .update(comments_enabled=False) + re-setting on the instance before save_revision().publish(); also tighten test to assert 200 + correct title Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../management/commands/seed_e2e_content.py | 4 ++++ e2e/conftest.py | 21 +++++++++++++++++-- e2e/test_article_detail.py | 4 +--- e2e/test_comments.py | 8 ++++++- e2e/test_cookie_consent.py | 10 ++++----- e2e/test_home.py | 6 +++--- e2e/test_newsletter.py | 3 +++ 7 files changed, 41 insertions(+), 15 deletions(-) diff --git a/apps/core/management/commands/seed_e2e_content.py b/apps/core/management/commands/seed_e2e_content.py index d33e21f..04ecea9 100644 --- a/apps/core/management/commands/seed_e2e_content.py +++ b/apps/core/management/commands/seed_e2e_content.py @@ -81,6 +81,10 @@ class Command(BaseCommand): comments_enabled=False, ) article_index.add_child(instance=no_comments_article) + # Explicitly persist False after add_child (which internally calls save()) + # to guard against any field reset in the page tree insertion path. + ArticlePage.objects.filter(pk=no_comments_article.pk).update(comments_enabled=False) + no_comments_article.comments_enabled = False no_comments_article.save_revision().publish() # About page diff --git a/e2e/conftest.py b/e2e/conftest.py index a2b9ade..736cf98 100644 --- a/e2e/conftest.py +++ b/e2e/conftest.py @@ -33,8 +33,25 @@ def _browser(base_url: str) -> Generator[Browser, None, None]: # noqa: ARG001 @pytest.fixture() def page(_browser: Browser) -> Generator[Page, None, None]: - """Fresh browser context + page per test — no shared state between tests.""" - ctx: BrowserContext = _browser.new_context() + """Fresh browser context + page per test — no shared state between tests. + + Clipboard permissions are pre-granted so copy-link and similar interactions + work in headless Chromium without triggering the permissions dialog. + """ + ctx: BrowserContext = _browser.new_context( + permissions=["clipboard-read", "clipboard-write"], + ) + # Polyfill clipboard in environments where the native API is unavailable + # (e.g. non-HTTPS Docker CI). The polyfill stores writes in a variable so + # the JS success path still runs and button text updates as expected. + ctx.add_init_script(""" + if (!navigator.clipboard || !navigator.clipboard.writeText) { + Object.defineProperty(navigator, 'clipboard', { + value: { writeText: () => Promise.resolve() }, + configurable: true, + }); + } + """) pg: Page = ctx.new_page() yield pg ctx.close() diff --git a/e2e/test_article_detail.py b/e2e/test_article_detail.py index 1e07d69..2afef72 100644 --- a/e2e/test_article_detail.py +++ b/e2e/test_article_detail.py @@ -66,8 +66,6 @@ def test_copy_link_button_updates_text(page: Page, base_url: str) -> None: _go_to_article(page, base_url) copy_btn = page.get_by_role("button", name="Copy link") expect(copy_btn).to_be_visible() - # Grant clipboard permission and click - page.context.grant_permissions(["clipboard-read", "clipboard-write"]) copy_btn.click() - # Button text should change to "Copied" after click + # Clipboard polyfill in conftest ensures writeText resolves; button shows "Copied" expect(copy_btn).to_have_text("Copied") diff --git a/e2e/test_comments.py b/e2e/test_comments.py index dc38ae4..1b365c4 100644 --- a/e2e/test_comments.py +++ b/e2e/test_comments.py @@ -48,6 +48,12 @@ def test_empty_body_shows_form_errors(page: Page, base_url: str) -> None: @pytest.mark.e2e def test_comments_section_absent_when_disabled(page: Page, base_url: str) -> None: """Article with comments_enabled=False must not show the comments section.""" - page.goto(f"{base_url}/articles/e2e-no-comments/", wait_until="networkidle") + response = page.goto(f"{base_url}/articles/e2e-no-comments/", wait_until="networkidle") + assert response is not None and response.status == 200, ( + f"Expected 200 for e2e-no-comments article, got {response and response.status}" + ) + # Confirm we're on the right page + expect(page.get_by_role("heading", level=1)).to_have_text("No Comments Article") + # Comments section must be absent expect(page.get_by_role("heading", name="Comments")).to_have_count(0) expect(page.get_by_role("button", name="Post comment")).to_have_count(0) diff --git a/e2e/test_cookie_consent.py b/e2e/test_cookie_consent.py index 3cfb887..0b5d248 100644 --- a/e2e/test_cookie_consent.py +++ b/e2e/test_cookie_consent.py @@ -43,13 +43,11 @@ def test_granular_preferences_save_dismisses_banner(page: Page, base_url: str) - banner = page.locator("#cookie-banner") expect(banner).to_be_visible() - # Expand the "Manage preferences" details section - banner.get_by_role("group").first.click() - # Or use the summary text - page.get_by_text("Manage preferences").click() + # Click the element to expand
inside the banner + banner.locator("details summary").click() - # Check analytics, leave advertising unchecked - analytics_checkbox = page.locator('input[name="analytics"]') + # Analytics checkbox is now revealed; check it and save + analytics_checkbox = banner.locator('input[name="analytics"]') expect(analytics_checkbox).to_be_visible() analytics_checkbox.check() diff --git a/e2e/test_home.py b/e2e/test_home.py index 13a9870..73077fb 100644 --- a/e2e/test_home.py +++ b/e2e/test_home.py @@ -2,6 +2,8 @@ from __future__ import annotations +import re + import pytest from playwright.sync_api import Page, expect @@ -9,9 +11,7 @@ from playwright.sync_api import Page, expect @pytest.mark.e2e def test_homepage_title_contains_brand(page: Page, base_url: str) -> None: page.goto(f"{base_url}/", wait_until="networkidle") - expect(page).to_have_title(lambda t: "No Hype AI" in t or len(t) > 0) - # At minimum the page must load without error - assert page.url.startswith(base_url) + expect(page).to_have_title(re.compile("No Hype AI")) @pytest.mark.e2e diff --git a/e2e/test_newsletter.py b/e2e/test_newsletter.py index 00326b5..5b2042c 100644 --- a/e2e/test_newsletter.py +++ b/e2e/test_newsletter.py @@ -26,6 +26,9 @@ def test_subscribe_valid_email_shows_confirmation(page: Page, base_url: str) -> def test_subscribe_invalid_email_shows_error(page: Page, base_url: str) -> None: page.goto(f"{base_url}/", wait_until="networkidle") form = _nav_newsletter_form(page) + # Disable the browser's native HTML5 email validation so the JS handler + # fires and sends the bad value to the server (which returns 400). + page.evaluate("document.querySelector('nav form[data-newsletter-form]').setAttribute('novalidate', '')") form.locator('input[type="email"]').fill("not-an-email") form.get_by_role("button", name="Subscribe").click()