fix: run E2E tests properly with mounted browsers and real postgres
All checks were successful
CI / nightly-e2e (pull_request) Has been skipped
CI / pr-e2e (pull_request) Successful in 1m33s
CI / ci (pull_request) Successful in 2m18s

- Mount /opt/playwright-tools/browsers into web container (docker-compose.yml
  and CI docker run) — never download browsers, use the ones on this host
- Set PLAYWRIGHT_BROWSERS_PATH in all container envs (compose + CI)
- Drop 'playwright install chromium' steps from pr-e2e and nightly-e2e jobs
- Bump playwright requirement to ~1.57.0 to match the installed browser builds
- Fix seed_e2e_content: de-duplicate default Site entries left by unit test
  fixtures so Wagtail always routes to the seeded home page
- Fix test_comments_section_absent_when_disabled: use exact=True on heading
  locator to avoid matching 'No Comments Article' h1 as 'Comments' heading
- Fix test_copy_link_button_updates_text: use [data-copy-link] data-attr
  locator (stable across text change) and force-override clipboard.writeText
  via page.evaluate() rather than relying on init_script polyfill

E2E suite verified locally: 34 passed via docker compose exec

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
codex_a
2026-02-28 20:20:18 +00:00
parent 4992b0cb9d
commit f6edcadd46
6 changed files with 15 additions and 10 deletions

View File

@@ -97,12 +97,14 @@ jobs:
- name: Start app with seeded content - name: Start app with seeded content
run: | run: |
docker run -d --name pr-e2e-app --network container:pr-e2e-postgres \ docker run -d --name pr-e2e-app --network container:pr-e2e-postgres \
-v /opt/playwright-tools/browsers:/opt/playwright-tools/browsers:ro \
-e SECRET_KEY=ci-secret-key \ -e SECRET_KEY=ci-secret-key \
-e DATABASE_URL=postgres://nohype:nohype@127.0.0.1:5432/nohype \ -e DATABASE_URL=postgres://nohype:nohype@127.0.0.1:5432/nohype \
-e CONSENT_POLICY_VERSION=1 \ -e CONSENT_POLICY_VERSION=1 \
-e EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend \ -e EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend \
-e DEFAULT_FROM_EMAIL=hello@nohypeai.com \ -e DEFAULT_FROM_EMAIL=hello@nohypeai.com \
-e NEWSLETTER_PROVIDER=buttondown \ -e NEWSLETTER_PROVIDER=buttondown \
-e PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-tools/browsers \
"$CI_IMAGE" \ "$CI_IMAGE" \
sh -lc "python manage.py migrate --noinput && python manage.py seed_e2e_content && python manage.py runserver 0.0.0.0:8000" sh -lc "python manage.py migrate --noinput && python manage.py seed_e2e_content && python manage.py runserver 0.0.0.0:8000"
for i in $(seq 1 40); do for i in $(seq 1 40); do
@@ -114,9 +116,6 @@ jobs:
docker logs pr-e2e-app || true docker logs pr-e2e-app || true
exit 1 exit 1
- name: Install Playwright browsers
run: docker exec pr-e2e-app python -m playwright install chromium
- name: Run E2E tests - name: Run E2E tests
run: | run: |
docker exec -e E2E_BASE_URL=http://127.0.0.1:8000 pr-e2e-app \ docker exec -e E2E_BASE_URL=http://127.0.0.1:8000 pr-e2e-app \
@@ -159,12 +158,14 @@ jobs:
- name: Start dev server with seeded content - name: Start dev server with seeded content
run: | run: |
docker run -d --name nightly-e2e --network container:nightly-postgres \ docker run -d --name nightly-e2e --network container:nightly-postgres \
-v /opt/playwright-tools/browsers:/opt/playwright-tools/browsers:ro \
-e SECRET_KEY=ci-secret-key \ -e SECRET_KEY=ci-secret-key \
-e DATABASE_URL=postgres://nohype:nohype@127.0.0.1:5432/nohype \ -e DATABASE_URL=postgres://nohype:nohype@127.0.0.1:5432/nohype \
-e CONSENT_POLICY_VERSION=1 \ -e CONSENT_POLICY_VERSION=1 \
-e EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend \ -e EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend \
-e DEFAULT_FROM_EMAIL=hello@nohypeai.com \ -e DEFAULT_FROM_EMAIL=hello@nohypeai.com \
-e NEWSLETTER_PROVIDER=buttondown \ -e NEWSLETTER_PROVIDER=buttondown \
-e PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-tools/browsers \
"$CI_IMAGE" \ "$CI_IMAGE" \
sh -lc "python manage.py migrate --noinput && python manage.py seed_e2e_content && python manage.py runserver 0.0.0.0:8000" sh -lc "python manage.py migrate --noinput && python manage.py seed_e2e_content && python manage.py runserver 0.0.0.0:8000"
for i in $(seq 1 40); do for i in $(seq 1 40); do
@@ -177,7 +178,6 @@ jobs:
exit 1 exit 1
- name: Run Playwright E2E tests - name: Run Playwright E2E tests
run: | run: |
docker exec nightly-e2e python -m playwright install chromium
docker exec -e E2E_BASE_URL=http://127.0.0.1:8000 nightly-e2e \ docker exec -e E2E_BASE_URL=http://127.0.0.1:8000 nightly-e2e \
pytest e2e/ apps/core/tests/test_nightly_e2e_playwright.py -o addopts='' -q --tb=short pytest e2e/ apps/core/tests/test_nightly_e2e_playwright.py -o addopts='' -q --tb=short
- name: Remove nightly container - name: Remove nightly container

View File

@@ -129,5 +129,7 @@ class Command(BaseCommand):
site.is_default_site = True site.is_default_site = True
site.site_name = "No Hype AI" site.site_name = "No Hype AI"
site.save() site.save()
# Remove any other conflicting default-site entries left by test fixtures
Site.objects.exclude(pk=site.pk).filter(is_default_site=True).update(is_default_site=False)
self.stdout.write(self.style.SUCCESS("Seeded E2E content.")) self.stdout.write(self.style.SUCCESS("Seeded E2E content."))

View File

@@ -7,6 +7,7 @@ services:
python manage.py runserver 0.0.0.0:8000" python manage.py runserver 0.0.0.0:8000"
volumes: volumes:
- .:/app - .:/app
- /opt/playwright-tools/browsers:/opt/playwright-tools/browsers:ro
ports: ports:
- "8035:8000" - "8035:8000"
environment: environment:
@@ -21,6 +22,7 @@ services:
EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
DEFAULT_FROM_EMAIL: hello@nohypeai.com DEFAULT_FROM_EMAIL: hello@nohypeai.com
NEWSLETTER_PROVIDER: buttondown NEWSLETTER_PROVIDER: buttondown
PLAYWRIGHT_BROWSERS_PATH: /opt/playwright-tools/browsers
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy

View File

@@ -41,7 +41,7 @@ def test_article_share_section_present(page: Page, base_url: str) -> None:
def test_article_comments_section_present(page: Page, base_url: str) -> None: def test_article_comments_section_present(page: Page, base_url: str) -> None:
_go_to_article(page, base_url) _go_to_article(page, base_url)
# The article has comments_enabled=True # The article has comments_enabled=True
expect(page.get_by_role("heading", name="Comments")).to_be_visible() expect(page.get_by_role("heading", name="Comments", exact=True)).to_be_visible()
expect(page.get_by_role("button", name="Post comment")).to_be_visible() expect(page.get_by_role("button", name="Post comment")).to_be_visible()
@@ -64,8 +64,9 @@ def test_article_related_section_present(page: Page, base_url: str) -> None:
@pytest.mark.e2e @pytest.mark.e2e
def test_copy_link_button_updates_text(page: Page, base_url: str) -> None: def test_copy_link_button_updates_text(page: Page, base_url: str) -> None:
_go_to_article(page, base_url) _go_to_article(page, base_url)
copy_btn = page.get_by_role("button", name="Copy link") copy_btn = page.locator("[data-copy-link]")
expect(copy_btn).to_be_visible() expect(copy_btn).to_be_visible()
# Force-override clipboard so writeText always resolves, even in non-HTTPS headless context
page.evaluate("navigator.clipboard.writeText = () => Promise.resolve()")
copy_btn.click() copy_btn.click()
# Clipboard polyfill in conftest ensures writeText resolves; button shows "Copied"
expect(copy_btn).to_have_text("Copied") expect(copy_btn).to_have_text("Copied")

View File

@@ -54,6 +54,6 @@ def test_comments_section_absent_when_disabled(page: Page, base_url: str) -> Non
) )
# Confirm we're on the right page # Confirm we're on the right page
expect(page.get_by_role("heading", level=1)).to_have_text("No Comments Article") expect(page.get_by_role("heading", level=1)).to_have_text("No Comments Article")
# Comments section must be absent # Comments section must be absent — exact=True prevents matching "No Comments Article" h1
expect(page.get_by_role("heading", name="Comments")).to_have_count(0) expect(page.get_by_role("heading", name="Comments", exact=True)).to_have_count(0)
expect(page.get_by_role("button", name="Post comment")).to_have_count(0) expect(page.get_by_role("button", name="Post comment")).to_have_count(0)

View File

@@ -17,7 +17,7 @@ pytest-benchmark~=4.0.0
factory-boy~=3.3.0 factory-boy~=3.3.0
wagtail-factories~=4.2.0 wagtail-factories~=4.2.0
feedparser~=6.0.0 feedparser~=6.0.0
playwright~=1.52.0 playwright~=1.57.0
pytest-playwright~=0.7.0 pytest-playwright~=0.7.0
ruff~=0.6.0 ruff~=0.6.0
mypy~=1.11.0 mypy~=1.11.0