Skip to content

Commit 3c8006c

Browse files
authoredMay 14, 2024··
fix: Add selenium video support #6 (#364)
Support video in selenium testcontainer. Changes made: - Added network to default container. - Added video to selenium by write `with_video`.
1 parent 38946d4 commit 3c8006c

File tree

3 files changed

+109
-2
lines changed

3 files changed

+109
-2
lines changed
 

‎modules/selenium/testcontainers/selenium/__init__.py

+48-2
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@
1010
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1111
# License for the specific language governing permissions and limitations
1212
# under the License.
13-
13+
from pathlib import Path
1414
from typing import Optional
1515

1616
import urllib3
17+
from typing_extensions import Self
1718

1819
from selenium import webdriver
1920
from selenium.webdriver.common.options import ArgOptions
2021
from testcontainers.core.container import DockerContainer
22+
from testcontainers.core.network import Network
2123
from testcontainers.core.waiting_utils import wait_container_is_ready
24+
from testcontainers.selenium.video import SeleniumVideoContainer
2225

23-
IMAGES = {"firefox": "selenium/standalone-firefox-debug:latest", "chrome": "selenium/standalone-chrome-debug:latest"}
26+
IMAGES = {"firefox": "selenium/standalone-firefox:latest", "chrome": "selenium/standalone-chrome:latest"}
2427

2528

2629
def get_image_name(capabilities: str) -> str:
@@ -51,6 +54,8 @@ def __init__(
5154
self.image = image or get_image_name(capabilities)
5255
self.port = port
5356
self.vnc_port = vnc_port
57+
self.video = None
58+
self.__video_network = None
5459
super().__init__(image=self.image, **kwargs)
5560
self.with_exposed_ports(self.port, self.vnc_port)
5661

@@ -72,3 +77,44 @@ def get_connection_url(self) -> str:
7277
ip = self.get_container_host_ip()
7378
port = self.get_exposed_port(self.port)
7479
return f"http://{ip}:{port}/wd/hub"
80+
81+
def with_video(self, image: Optional[str] = None, video_path: Optional[Path] = None) -> Self:
82+
video_path = video_path or Path.cwd()
83+
84+
self.video = SeleniumVideoContainer(image)
85+
86+
video_folder_path = video_path.parent if video_path.suffix else video_path
87+
self.video.set_videos_host_path(str(video_folder_path.resolve()))
88+
89+
if video_path.name:
90+
self.video.set_video_name(video_path.name)
91+
92+
return self
93+
94+
def start(self) -> "DockerContainer":
95+
if not self.video:
96+
super().start()
97+
return self
98+
99+
self.__video_network = Network().__enter__()
100+
101+
self.with_kwargs(network=self.__video_network.name)
102+
super().start()
103+
104+
self.video.with_kwargs(network=self.__video_network.name).set_selenium_container_host(
105+
self.get_wrapped_container().short_id
106+
).start()
107+
108+
return self
109+
110+
def stop(self, force=True, delete_volume=True) -> None:
111+
if self.video:
112+
# get_wrapped_container().stop -> stop the container
113+
# video.stop -> remove the container
114+
self.video.get_wrapped_container().stop()
115+
self.video.stop(force, delete_volume)
116+
117+
super().stop(force, delete_volume)
118+
119+
if self.__video_network:
120+
self.__video_network.remove()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
from typing import Optional
14+
15+
from testcontainers.core.container import DockerContainer
16+
17+
VIDEO_DEFAULT_IMAGE = "selenium/video:ffmpeg-6.1-20240402"
18+
19+
20+
class SeleniumVideoContainer(DockerContainer):
21+
"""
22+
Selenium video container.
23+
"""
24+
25+
def __init__(self, image: Optional[str] = None, **kwargs) -> None:
26+
self.image = image or VIDEO_DEFAULT_IMAGE
27+
super().__init__(image=self.image, **kwargs)
28+
29+
def set_video_name(self, video_name: str) -> "DockerContainer":
30+
self.with_env("FILE_NAME", video_name)
31+
return self
32+
33+
def set_videos_host_path(self, host_path: str) -> "DockerContainer":
34+
self.with_volume_mapping(host_path, "/videos", "rw")
35+
return self
36+
37+
def set_selenium_container_host(self, host: str) -> "DockerContainer":
38+
self.with_env("DISPLAY_CONTAINER_NAME", host)
39+
return self

‎modules/selenium/tests/test_selenium.py

+22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import os
2+
import tempfile
3+
from pathlib import Path
4+
15
import pytest
26
from selenium.webdriver import DesiredCapabilities
37
from selenium.webdriver.common.by import By
@@ -23,3 +27,21 @@ def test_selenium_custom_image():
2327
chrome = BrowserWebDriverContainer(DesiredCapabilities.CHROME, image=image)
2428
assert "image" in dir(chrome), "`image` attribute was not instantialized."
2529
assert chrome.image == image, "`image` attribute was not set to the user provided value"
30+
31+
32+
@pytest.mark.parametrize("caps", [DesiredCapabilities.CHROME, DesiredCapabilities.FIREFOX])
33+
def test_selenium_video(caps, workdir):
34+
video_path = workdir / Path("video.mp4")
35+
with BrowserWebDriverContainer(caps).with_video(video_path=video_path) as chrome:
36+
chrome.get_driver().get("https://google.com")
37+
38+
assert video_path.exists(), "Selenium video file does not exist"
39+
40+
41+
@pytest.fixture
42+
def workdir() -> Path:
43+
tmpdir = tempfile.TemporaryDirectory()
44+
# Enable write permissions for the Docker user container.
45+
os.chmod(tmpdir.name, 0o777)
46+
yield Path(tmpdir.name)
47+
tmpdir.cleanup()

0 commit comments

Comments
 (0)
Please sign in to comment.