我最近开始尝试容器化 django 应用程序。几天来我一直在努力让测试发挥作用,但没有成功。我没有使用 Selenium Grid 的经验,但这似乎是在 docker 容器中使用 Django 的方式,特别是当我需要能够查看正在发生的情况时。
# docker-compose.yml
version: "3.9"
services:
myapp:
build: .
command: bash -c "
npm run build
&& python manage.py migrate
&& python manage.py runserver 0.0.0.0:8000"
volumes:
- ./:/myapp
env_file:
- .env
container_name: myapp-container
ports:
- "8000:8000"
networks:
mynetwork:
ipv4_address: 171.20.0.3
selenium-hub:
image: selenium/hub:4.1
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
networks:
mynetwork:
ipv4_address: 171.20.0.4
chrome:
image: selenium/node-chrome:4.1
container_name: chrome-worker
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
networks:
mynetwork:
ipv4_address: 171.20.0.8
networks:
mynetwork:
ipam:
config:
- subnet: 171.20.0.0/24
gateway: 171.20.0.1
在我的代码中,只需将
context.selenium = webdriver.Chrome(chrome_options=chrome_options)
更改为以下内容即可:
# environment.py - for Behave BDD testing
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8000'
def before_all(context):
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--window-size=3840,2160")
chrome_options.add_argument("--allow-insecure-localhost")
# chrome_options.add_experimental_option("detach", True)
# context.selenium = webdriver.Chrome(chrome_options=chrome_options)
context.selenium = webdriver.Remote(command_executor='http://localhost:4444', options=chrome_options)
context.selenium.implicitly_wait(1)
TransactionTestCase.serialized_rollback = True
但是这会导致此错误:
HOOK-ERROR in before_all: MaxRetryError: HTTPConnectionPool(host='localhost', port=4444): Max retries exceeded with url: /session (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fb9ddfe15b0>: Failed to establish a new connection: [Errno 111] Connection refused'))
我更新 command_executer 以使用服务主机名,如下所示:
context.selenium = webdriver.Remote(command_executor='http://selenium-hub:4444', options=chrome_options)
这似乎取得了进展,测试开始运行,但每个单独的测试都出现了不同的连接错误:
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: unknown error:
net::ERR_CONNECTION_REFUSED (Session info: headless chrome=96.0.4664.45)
此外,现在似乎有来自 selenium-hub 和 chrome 服务的活动
selenium-hub | 16:07:16.940 INFO [LocalDistributor.newSession] - Session created by the distributor. Id: 444dbca185db76e7e8780c98680b7859, Caps: Capabilities {acceptInsecureCerts: false, browserName: chrome, browserVersion: 96.0.4664.45, chrome: {chromedriverVersion: 96.0.4664.45 (76e4c1bb2ab46..., userDataDir: /tmp/.com.google.Chrome.2RTzs7}, goog:chromeOptions: {debuggerAddress: localhost:40633}, networkConnectionEnabled: false, pageLoadStrategy: normal, platformName: ANY, proxy: {}, se:cdp: ws://171.20.0.8:4444/sessio..., se:cdpVersion: 96.0.4664.45, se:vnc: ws://171.20.0.8:4444/sessio..., se:vncEnabled: true, se:vncLocalAddress: ws://171.20.0.8:7900, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, webauthn:extension:credBlob: true, webauthn:extension:largeBlob: true, webauthn:virtualAuthenticators: true}
chrome-worker | 16:07:28.510 WARN [SeleniumSpanExporter$1.lambda$export$0] - {"traceId": "ae4768e55f596539cf20032361104584","eventTime": 1637770048509670200,"eventName": "HTTP request execution complete","attributes": {"http.flavor": 1,"http.handler_class": "org.openqa.selenium.remote.http.Route$PredicatedRoute","http.host": "171.20.0.4:4444","http.method": "POST","http.request_content_length": "41","http.scheme": "HTTP","http.status_code": 500,"http.target": "\u002fsession\u002f444dbca185db76e7e8780c98680b7859\u002furl","http.user_agent": "selenium\u002f3.141.0 (python linux)"}}
chrome-worker |
chrome-worker | 16:07:51.014 WARN [SeleniumSpanExporter$1.lambda$export$0] - {"traceId": "338e4e8e48d62681ed1cb1e729d490d6","eventTime": 1637770071013768000,"eventName": "HTTP request execution complete","attributes": {"http.flavor": 1,"http.handler_class": "org.openqa.selenium.remote.http.Route$PredicatedRoute","http.host": "171.20.0.4:4444","http.method": "POST","http.request_content_length": "41","http.scheme": "HTTP","http.status_code": 500,"http.target": "\u002fsession\u002f444dbca185db76e7e8780c98680b7859\u002furl","http.user_agent": "selenium\u002f3.141.0 (python linux)"}}
我尝试删除为处理另一个问题而添加的自定义网络,但这没有帮助。我也尝试过使用特定的 IP 地址。当我尝试使用
myapp
从
curl -I http://selenium-hub/4444
容器连接到其他容器时
HTTP/1.1 302 Found
content-length: 0
Location: /ui/index.html
我不知道从这里该去哪里。
编辑:一些额外的上下文:
我正在使用 Behave 测试框架以及Behavior-django 包。安装和拆卸位于环境.py 文件中,如上所示。所有测试都位于
features
模块中并使用 docker,我从 docker exec -it myapp-container ./manage.py behave ./myapp/features/account.feature
开始。这在 docker 之外与常规 webdriver 完全兼容。我在上面添加了一行,显示 DJANGO_LIVE_TEST_SERVER_ADDRESS
环境变量的设置位置。似乎这对于使其发挥作用的其他问题很重要。我尝试将其设置为 django 服务主机名 - myapp
以及分配的 IP 地址。 Behavior-django 库使用 LiveServerTestCase 启动测试服务器
我今天在对使用Behavior-django和Selenium Grid的应用程序进行docker化时遇到了这个确切的问题。
核心问题是Selenium/Firefox容器需要使用'myapp'主机而不是'localhost'。
我猴子修补了我的
LiveServerTestCase
中的 environment.py
类,如下所示:
import os
from django.test.testcases import LiveServerTestCase
from selenium import webdriver
os.environ["DJANGO_SETTINGS_MODULE"] = "backend.settings"
def before_all(context):
options = webdriver.FirefoxOptions()
if os.path.exists("/.dockerenv"):
LiveServerTestCase.host = "myapp"
context.driver = webdriver.Remote(
command_executor="http://firefox:4444/wd/hub",
desired_capabilities=options.to_capabilities(),
)
else:
# needs geckodriver on system $PATH to control local firefox
context.driver = webdriver.Firefox(options=options)
def after_all(context):
context.driver.quit()
不要覆盖LiveServerTestCase
的
port
属性。每个测试都会在随机端口上实例化一个新服务器。我不知道这一点,并且花了很长时间想知道为什么我的第一个测试会通过,但第二个测试总是出现“地址正在使用”错误。当您在行为步骤中使用
context.get_url()
时,URL 将使用“myapp”主机和该测试的随机端口进行更新。例如。
http://myapp:378936
。进一步注意 if-else 逻辑,它检查我是否处于 Docker 环境中。这样,我就不会失去在本地计算机上以通常的方式运行项目的能力。