Selenium не использует тестовую базу данных во время функциональных тестов Django в Docker или не может получить доступ к контейнеру из-за проблем с портом

Выпуск

Мой браузер Selenium Chrome не может получить доступ к моему веб-приложению Django, когда он использует тестовую базу данных Djangos. Djangos StaticLiveServerTestCase создает случайные порты при создании тестовых баз данных. С помощью docker я могу выставить только жестко закодированные порты:

host_ip = socket.gethostbyname(socket.gethostname())
host_port = urlparse(self.live_server_url).port
self.live_server_url = f"http://{host_ip}:{host_port}"

# results in
# selenium.common.exceptions.WebDriverException: Message: unknown error: net::ERR_CONNECTION_REFUSED

Я могу установить один фиксированный порт в своих тестах. Но тогда они жалуются, что порт уже занят.

class FunctionalTest(StaticLiveServerTestCase):
    """Functional Testing base class."""
    port = 50500

# resulsts in:
# OSError: [Errno 98] Address in use

Другой вариант, о котором я подумал, - установить URL для Selenium на мое работающее веб-приложение, но тогда тестовая база данных, созданная StaticLiveServerTestCase, не будет использоваться. Вместо нее используется контейнер postgres, в котором объекты, созданные на этапе настройки, не могут быть найдены. В моем тестовом примере пользователь не может войти в систему, потому что Selenium обращается к версии приложения, обслуживающей базу данных postgres, а не тестовую базу данных.

self.live_server_url = "http://web:8000"

# results in
# AssertionError: 'Profile' not found in 'Sign In'
# the surrounding code is setup correctly, so this normally results in success.

Настройка

Я пытаюсь создать конвейер для своей разработки, управляемой тестами. Для этого я выбрал Django для веб-приложения, postgres в качестве базы данных (разработка & production) и использую Selenium для функциональных тестов. Все это происходит в контейнерах docker, созданных с помощью docker compose. Когда я размещаю код на своем хостинге Gitlab, конвейер собирает контейнер и запускает все тесты. Ошибка также возникает на моей машине разработки.

Я тестирую следующим образом: docker compose up -d docker compose exec web python3 manage.py test

Я впервые устанавливаю что-то подобное, поэтому, возможно, я просто путаю разные понятия.

Код

docker-compose.yml

services:
  web:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /src
        - action: rebuild
          path: requirements.txt
    ports:
      - 8000:8000
    environment:
      - SECRET_KEY='django-insecure-5&!h2y%p8)l1than6$$jsl$$jb^*7sp+446b7gjfd_fu8=tx_=&9'
      - DEBUG=1
      - DB_HOST=db
      - DB_NAME=devdb
      - DB_USER=devuser
      - DB_PASS=changeme
      - DJANGO_SUPERUSER_PASSWORD=testpass123
      - DJANGO_SUPERUSER_USERNAME=admin
      - DJANGO_SUPERUSER_EMAIL=admin@mail.com
      - ALLOWED_HOSTS=*
    depends_on:
      db:
        condition: service_healthy
    command: >
      sh -c "python manage.py wait_for_db &&
             python manage.py migrate &&
             python manage.py createsuperuser --noinput &&
             python manage.py runserver 0.0.0.0:8000"
  db:
    image: postgres:13-alpine
    environment:
      - POSTGRES_DB=devdb
      - POSTGRES_USER=devuser
      - POSTGRES_PASSWORD=changeme
    ports:
      - 5432:5432
    healthcheck:
      test: ["CMD-SHELL", "pg_isready", "-d", "db_prod"]
      interval: 30s
      timeout: 60s
      retries: 5
      start_period: 80s
  selenium-chrome:
    image: selenium/standalone-chrome:131.0
    ports:
      - 4444:4444
      - 5900:5900
      - 7900:7900

test.py

"""Base functional testing class."""

import os
import socket
import uuid
from urllib.parse import urlparse

from django.contrib.auth import get_user_model
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.by import By


class FunctionalTest(StaticLiveServerTestCase):
    """Functional Testing base class."""

    def setUp(self):
        """Setup Selenium and test database access."""
        super().setUpClass()
        
        host_ip = socket.gethostbyname(socket.gethostname())
        host_port = urlparse(self.live_server_url).port

        # Selenium cannot access the website because the random port is not open.
        self.live_server_url = f"http://{host_ip}:{host_port}"

        # # Selenium can access the web site with this
        # # but Django does not use the testing database created by testcase
        # self.live_server_url = "http://web:8000"

        options = webdriver.ChromeOptions()
        self.browser = webdriver.Remote(
            command_executor="http://selenium-chrome:4444/wd/hub",
            options=options
        )

    def tearDown(self):
        """Shutdown browser."""
        self.browser.quit()
        super().tearDown()
        
    def create_test_user(self):
        """Creates a test user."""
        User = get_user_model()
        self.normal_user_credentials = {
            "username": f"normal_user{str(uuid.uuid4())}",
            "password": "testpass1234",
            "email": "normal_user@email.com"
        }
        self.normal_user = User.objects.create_user(
            username=self.normal_user_credentials["username"],
            email=self.normal_user_credentials["email"],
            password=self.normal_user_credentials["password"]
        )
        self.normal_user_credentials["id"] = self.normal_user.id
        
    def test_login(self):
        """Login as existing user."""
        self.browser.get(f"{self.live_server_url}/accounts/login/")
        
        # Find input fields
        username_input = self.browser.find_element(self, By.NAME,"login")
        password_input = self.browser.find_element(self, By.NAME,"password")
        
        # Fill out form
        username_input.send_keys(self.normal_user_credentials["username"])
        password_input.send_keys(self.normal_user_credentials["password"])
        
        # Submit form
        self.browser.find_element(self, By.XPATH, "//button[@type='submit']").click()
        header_text = self.browser.find_element(self, By.TAG_NAME, "h1").text
        
        # Check if login was successful
        self.assertIn("Profile", header_text)
Вернуться на верх