From 79727eabbf870cb5943d4ee4d523aa8423080a91 Mon Sep 17 00:00:00 2001 From: Josef Date: Mon, 22 Apr 2024 13:14:53 +0300 Subject: [PATCH] Rebase and rewrite --- .../testcontainers/selenium/__init__.py | 51 ++++++++++++++++++- .../selenium/testcontainers/selenium/video.py | 39 ++++++++++++++ modules/selenium/tests/test_selenium.py | 19 +++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 modules/selenium/testcontainers/selenium/video.py diff --git a/modules/selenium/testcontainers/selenium/__init__.py b/modules/selenium/testcontainers/selenium/__init__.py index b46d46155..5a1a6e103 100644 --- a/modules/selenium/testcontainers/selenium/__init__.py +++ b/modules/selenium/testcontainers/selenium/__init__.py @@ -10,17 +10,22 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +from pathlib import Path from typing import Optional +from typing_extensions import Self + import urllib3 from selenium import webdriver from selenium.webdriver.common.options import ArgOptions + +from core.network import Network from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.selenium.video import SeleniumVideoContainer -IMAGES = {"firefox": "selenium/standalone-firefox-debug:latest", "chrome": "selenium/standalone-chrome-debug:latest"} +IMAGES = {"firefox": "selenium/standalone-firefox:latest", "chrome": "selenium/standalone-chrome:latest"} def get_image_name(capabilities: str) -> str: @@ -51,6 +56,7 @@ def __init__( self.image = image or get_image_name(capabilities) self.port = port self.vnc_port = vnc_port + self.video = None super().__init__(image=self.image, **kwargs) self.with_exposed_ports(self.port, self.vnc_port) @@ -72,3 +78,44 @@ def get_connection_url(self) -> str: ip = self.get_container_host_ip() port = self.get_exposed_port(self.port) return f"http://{ip}:{port}/wd/hub" + + def with_video(self, image: Optional[str] = None, video_path: Optional[Path] = None) -> Self: + video_path = video_path or Path.cwd() + + self.video = SeleniumVideoContainer(image) + video_folder_path = video_path.parent.resolve() + self.video.set_videos_host_path(str(video_folder_path)) + + if video_path.name: + self.video.set_video_name(video_path.name) + + return self + + def start(self) -> 'DockerContainer': + if not self.video: + super().start() + return self + + network = Network().__enter__() + + self.with_kwargs(network=network.name) + super().start() + + self.video \ + .with_kwargs(network=network.name) \ + .set_selenium_container_host(self.get_wrapped_container().short_id) \ + .start() + + return self + + def stop(self, force=True, delete_volume=True) -> None: + if self.video: + # get_wrapped_container().stop -> stop the container + # video.stop -> remove the container + self.video.get_wrapped_container().stop() + self.video.stop(force, delete_volume) + + super().stop(force, delete_volume) + + if self._network: + self._network.remove() diff --git a/modules/selenium/testcontainers/selenium/video.py b/modules/selenium/testcontainers/selenium/video.py new file mode 100644 index 000000000..d9c292c3e --- /dev/null +++ b/modules/selenium/testcontainers/selenium/video.py @@ -0,0 +1,39 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from typing import Optional + +from testcontainers.core.container import DockerContainer + +VIDEO_DEFAULT_IMAGE = "selenium/video:ffmpeg-6.1-20240402" + + +class SeleniumVideoContainer(DockerContainer): + """ + Selenium video container. + """ + + def __init__(self, image: Optional[str] = None, **kwargs) -> None: + self.image = image or VIDEO_DEFAULT_IMAGE + super().__init__(image=self.image, **kwargs) + + def set_video_name(self, video_name: str) -> 'DockerContainer': + self.with_env("FILE_NAME", video_name) + return self + + def set_videos_host_path(self, host_path: str) -> 'DockerContainer': + self.with_volume_mapping(host_path, "/videos", "rw") + return self + + def set_selenium_container_host(self, host: str) -> 'DockerContainer': + self.with_env("DISPLAY_CONTAINER_NAME", host) + return self diff --git a/modules/selenium/tests/test_selenium.py b/modules/selenium/tests/test_selenium.py index 61c1bb326..1c292ada4 100644 --- a/modules/selenium/tests/test_selenium.py +++ b/modules/selenium/tests/test_selenium.py @@ -1,3 +1,6 @@ +import tempfile +from pathlib import Path + import pytest from selenium.webdriver import DesiredCapabilities from selenium.webdriver.common.by import By @@ -23,3 +26,19 @@ def test_selenium_custom_image(): chrome = BrowserWebDriverContainer(DesiredCapabilities.CHROME, image=image) assert "image" in dir(chrome), "`image` attribute was not instantialized." assert chrome.image == image, "`image` attribute was not set to the user provided value" + + +@pytest.mark.parametrize("caps", [DesiredCapabilities.CHROME, DesiredCapabilities.FIREFOX]) +def test_selenium_video(caps, workdir): + video_path = workdir / Path("video.mp4") + with BrowserWebDriverContainer(caps).with_video(video_path=video_path) as chrome: + chrome.get_driver().get("https://google.com") + + assert video_path.exists(), "Selenium video file does not exists" + + +@pytest.fixture +def workdir() -> Path: + tmpdir = tempfile.TemporaryDirectory() + yield Path(tmpdir.name) + tmpdir.cleanup()