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)