Skip to content

Commit

Permalink
feat: Video recording with dynamic file name based on metadata in tes…
Browse files Browse the repository at this point in the history
…ts (#2249)

[deploy]

Signed-off-by: Viet Nguyen Duc <nguyenducviet4496@gmail.com>
  • Loading branch information
VietND96 committed May 5, 2024
1 parent 409a46f commit 6d3a8a7
Show file tree
Hide file tree
Showing 24 changed files with 327 additions and 133 deletions.
18 changes: 17 additions & 1 deletion .github/workflows/helm-chart-test.yml
Expand Up @@ -37,6 +37,12 @@ jobs:
fail-fast: false
matrix:
include:
- k8s-version: 'v1.25.16'
test-strategy: disabled
cluster: 'minikube'
helm-version: 'v3.10.3'
test-existing-keda: false
test-upgrade: true
- k8s-version: 'v1.26.15'
test-strategy: job
cluster: 'minikube'
Expand Down Expand Up @@ -158,7 +164,7 @@ jobs:
timeout_minutes: 30
max_attempts: 3
command: |
NAME=${IMAGE_REGISTRY} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} TEST_EXISTING_KEDA=${TEST_EXISTING_KEDA} make chart_test_autoscaling_${{ matrix.test-strategy }}
NAME=${IMAGE_REGISTRY} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} TEST_EXISTING_KEDA=${TEST_EXISTING_KEDA} TEST_UPGRADE_CHART=false make chart_test_autoscaling_${{ matrix.test-strategy }}
- name: Test chart upgrade
if: (matrix.test-upgrade == true)
run: |
Expand All @@ -179,3 +185,13 @@ jobs:
name: ${{ env.ARTIFACT_NAME }}-artifacts
path: ./tests/tests/
if-no-files-found: ignore
- name: Check video integrity
if: always()
run: |
make test_video_integrity
- name: Upload test video artifacts
if: always()
uses: actions/upload-artifact@main
with:
name: ${{ env.ARTIFACT_NAME }}-videos
path: ./tests/videos/
30 changes: 13 additions & 17 deletions .github/workflows/test-video.yml
Expand Up @@ -36,7 +36,15 @@ jobs:
strategy:
fail-fast: false
matrix:
test-strategy: [test_video, test_parallel, test_node_docker]
include:
- test-strategy: test_video
test-video: true
- test-strategy: test_video_dynamic_name
test-video: true
- test-strategy: test_node_docker
test-video: true
- test-strategy: test_parallel
test-video: false
steps:
- uses: actions/checkout@main
- name: Set up QEMU
Expand Down Expand Up @@ -99,21 +107,9 @@ jobs:
REQUEST_TIMEOUT: ${{ github.event.inputs.request-timeout || '400' }}
- name: Run Docker Compose to ${{ matrix.test-strategy }}
run: USE_RANDOM_USER_ID=${USE_RANDOM_USER} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} make ${{ matrix.test-strategy }}
- name: Upload recorded Chrome video
if: matrix.test-strategy == 'test_video'
- name: Upload recorded video
if: matrix.test-video == true
uses: actions/upload-artifact@main
with:
name: chrome_video
path: ./tests/videos/chrome_video.mp4
- name: Upload recorded Edge video
if: matrix.test-strategy == 'test_video'
uses: actions/upload-artifact@main
with:
name: edge_video
path: ./tests/videos/edge_video.mp4
- name: Upload recorded Firefox video
if: matrix.test-strategy == 'test_video'
uses: actions/upload-artifact@main
with:
name: firefox_video
path: ./tests/videos/firefox_video.mp4
name: "${{ matrix.test-strategy }}_artifacts"
path: ./tests/videos/
65 changes: 44 additions & 21 deletions Makefile
Expand Up @@ -447,6 +447,10 @@ test_parallel: hub chrome firefox edge
docker compose -f docker-compose-v3-test-parallel.yml up --no-log-prefix --exit-code-from tests --build ; \
done

test_video_dynamic_name:
VIDEO_FILE_NAME=auto TEST_DELAY_AFTER_TEST=10 \
make test_video

# This should run on its own CI job. There is no need to combine it with the other tests.
# Its main purpose is to check that a video file was generated.
test_video: video hub chrome firefox edge
Expand All @@ -460,31 +464,31 @@ test_video: video hub chrome firefox edge
echo NODE=$$node >> .env ; \
echo UID=$$(id -u) >> .env ; \
echo BINDING_VERSION=$(BINDING_VERSION) >> .env ; \
echo TEST_DELAY_AFTER_TEST=$(or $(TEST_DELAY_AFTER_TEST), 0) >> .env ; \
if [ $$node = "NodeChrome" ] ; then \
echo BROWSER=chrome >> .env ; \
echo VIDEO_FILE_NAME=chrome_video.mp4 >> .env ; \
echo VIDEO_FILE_NAME=$${VIDEO_FILE_NAME:-"chrome_video.mp4"} >> .env ; \
echo VIDEO_FILE_NAME_SUFFIX=$${VIDEO_FILE_NAME_SUFFIX:-"true"} >> .env ; \
fi ; \
if [ $$node = "NodeEdge" ] ; then \
echo BROWSER=edge >> .env ; \
echo VIDEO_FILE_NAME=edge_video.mp4 >> .env ; \
echo VIDEO_FILE_NAME=$${VIDEO_FILE_NAME:-"edge_video.mp4"} >> .env ; \
echo VIDEO_FILE_NAME_SUFFIX=$${VIDEO_FILE_NAME_SUFFIX:-"false"} >> .env ; \
fi ; \
if [ $$node = "NodeFirefox" ] ; then \
echo BROWSER=firefox >> .env ; \
echo VIDEO_FILE_NAME=firefox_video.mp4 >> .env ; \
echo VIDEO_FILE_NAME=$${VIDEO_FILE_NAME:-"firefox_video.mp4"} >> .env ; \
echo VIDEO_FILE_NAME_SUFFIX=$${VIDEO_FILE_NAME_SUFFIX:-"true"} >> .env ; \
fi ; \
docker compose -f docker-compose-v3-test-video.yml up --abort-on-container-exit --build ; \
done
# Using ffmpeg to verify file integrity
# https://superuser.com/questions/100288/how-can-i-check-the-integrity-of-a-video-file-avi-mpeg-mp4
docker run -u $$(id -u) -v $$(pwd):$$(pwd) -w $$(pwd) --entrypoint="" $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) ffmpeg -v error -i ./tests/videos/chrome_video.mp4 -f null - 2>error.log
docker run -u $$(id -u) -v $$(pwd):$$(pwd) -w $$(pwd) --entrypoint="" $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) ffmpeg -v error -i ./tests/videos/firefox_video.mp4 -f null - 2>error.log
docker run -u $$(id -u) -v $$(pwd):$$(pwd) -w $$(pwd) --entrypoint="" $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) ffmpeg -v error -i ./tests/videos/edge_video.mp4 -f null - 2>error.log
make test_video_integrity

test_node_docker: hub standalone_docker standalone_chrome standalone_firefox standalone_edge video
sudo rm -rf ./tests/tests
sudo rm -rf ./tests/videos; mkdir -p ./tests/videos/Downloads
sudo chmod -R 777 ./tests/videos
for node in DeploymentAutoscaling JobAutoscaling ; do \
for node in NodeChrome NodeFirefox NodeEdge ; do \
cd tests || true ; \
DOWNLOADS_DIR="./videos/Downloads" ; \
sudo rm -rf $$DOWNLOADS_DIR/* ; \
Expand All @@ -496,6 +500,7 @@ test_node_docker: hub standalone_docker standalone_chrome standalone_firefox sta
echo LOG_LEVEL=$(or $(LOG_LEVEL), "INFO") >> .env ; \
echo REQUEST_TIMEOUT=$(or $(REQUEST_TIMEOUT), 300) >> .env ; \
echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), "false") >> .env ; \
echo TEST_DELAY_AFTER_TEST=$(or $(TEST_DELAY_AFTER_TEST), 5) >> .env ; \
echo NODE=$$node >> .env ; \
echo UID=$$(id -u) >> .env ; \
echo BINDING_VERSION=$(BINDING_VERSION) >> .env ; \
Expand All @@ -509,6 +514,7 @@ test_node_docker: hub standalone_docker standalone_chrome standalone_firefox sta
exit 1 ; \
fi ; \
done
make test_video_integrity

test_custom_ca_cert:
VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/customCACert/bootstrap.sh
Expand All @@ -528,20 +534,37 @@ chart_build_nightly:
chart_build:
VERSION=$(TAG_VERSION) ./tests/charts/make/chart_build.sh

test_video_integrity:
# Using ffmpeg to verify file integrity
# https://superuser.com/questions/100288/how-can-i-check-the-integrity-of-a-video-file-avi-mpeg-mp4
list_files=$$(find ./tests/videos -type f -name "*.mp4"); \
echo "Number of video files: $$(echo $$list_files | wc -w)"; \
number_corrupted_files=0; \
if [ -z "$$list_files" ]; then \
echo "No video files found"; \
exit 1; \
fi; \
for file in $$list_files; do \
echo "Checking video file: $$file"; \
docker run -u $$(id -u) -v $$(pwd):$$(pwd) -w $$(pwd) --entrypoint="" $(FFMPEG_BASED_NAME)/ffmpeg:$(FFMPEG_BASED_TAG) ffmpeg -v error -i "$$file" -f null - ; \
if [ $$? -ne 0 ]; then \
echo "Video file $$file is corrupted"; \
number_corrupted_files=$$((number_corrupted_files+1)); \
fi; \
echo "------"; \
done; \
if [ $$((number_corrupted_files)) -gt 0 ]; then \
echo "Number of corrupted video files: $$number_corrupted_files"; \
exit 1; \
fi

chart_test_template:
./tests/charts/bootstrap.sh

chart_test_chrome:
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
./tests/charts/make/chart_test.sh NodeChrome

chart_test_firefox:
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
./tests/charts/make/chart_test.sh NodeFirefox

chart_test_edge:
chart_test_autoscaling_disabled:
SELENIUM_GRID_AUTOSCALING=false TEST_DELAY_AFTER_TEST=15 CHART_ENABLE_TRACING=true SELENIUM_GRID_HOST=$$(hostname -i) RELEASE_NAME=selenium \
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
./tests/charts/make/chart_test.sh NodeEdge
./tests/charts/make/chart_test.sh NoAutoscaling

chart_test_autoscaling_deployment_https:
CHART_FULL_DISTRIBUTED_MODE=true CHART_ENABLE_INGRESS_HOSTNAME=true CHART_ENABLE_BASIC_AUTH=true SELENIUM_GRID_PROTOCOL=https SELENIUM_GRID_PORT=443 \
Expand All @@ -550,13 +573,13 @@ chart_test_autoscaling_deployment_https:
./tests/charts/make/chart_test.sh DeploymentAutoscaling

chart_test_autoscaling_deployment:
CHART_ENABLE_TRACING=true SELENIUM_GRID_TEST_HEADLESS=true SELENIUM_GRID_HOST=$$(hostname -i) RELEASE_NAME=selenium \
CHART_ENABLE_TRACING=true SELENIUM_GRID_HOST=$$(hostname -i) RELEASE_NAME=selenium \
SELENIUM_GRID_AUTOSCALING_MIN_REPLICA=1 \
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
./tests/charts/make/chart_test.sh DeploymentAutoscaling

chart_test_autoscaling_job_https:
SELENIUM_GRID_TEST_HEADLESS=true SELENIUM_GRID_PROTOCOL=https CHART_ENABLE_BASIC_AUTH=true RELEASE_NAME=selenium SELENIUM_GRID_PORT=443 SUB_PATH=/ \
SELENIUM_GRID_PROTOCOL=https CHART_ENABLE_BASIC_AUTH=true RELEASE_NAME=selenium SELENIUM_GRID_PORT=443 SUB_PATH=/ \
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
./tests/charts/make/chart_test.sh JobAutoscaling

Expand Down
1 change: 1 addition & 0 deletions NodeBase/Dockerfile
Expand Up @@ -72,6 +72,7 @@ RUN apt-get update -qqy \
# Xvfb
#==============
xvfb \
libxcb1 \
xauth \
pulseaudio \
#=====
Expand Down
55 changes: 55 additions & 0 deletions README.md
Expand Up @@ -40,6 +40,7 @@ Talk to us at https://www.selenium.dev/support/
* [Hub and Nodes](#hub-and-nodes)
* [Fully distributed mode - Router, Queue, Distributor, EventBus, SessionMap and Nodes](#fully-distributed-mode---router-queue-distributor-eventbus-sessionmap-and-nodes)
* [Video recording](#video-recording)
* [Video recording with dynamic file name based on metadata in tests](#video-recording-with-dynamic-file-name-based-on-metadata-in-tests)
* [Video recording and uploading](#video-recording-and-uploading)
* [Dynamic Grid](#dynamic-grid)
* [Configuration example](#configuration-example)
Expand Down Expand Up @@ -554,6 +555,41 @@ Here is an example using a Hub and a few Nodes:

[`docker-compose-v3-video.yml`](docker-compose-v3-video.yml)

## Video recording with dynamic file name based on metadata in tests

Based on the support of [Metadata in tests](https://www.selenium.dev/documentation/grid/getting_started/#metadata-in-tests). When the video recorder is sidecar deployed with the browser node with enabling `SE_VIDEO_FILE_NAME=auto` and adding metadata to your tests, video file name will extract value of capability `se:name` and use it as the video file name.

For example in Python binding:

```python
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium import webdriver

options = ChromeOptions()
options.set_capability('se:name', 'test_visit_basic_auth_secured_page (ChromeTests)')
driver = webdriver.Remote(options=options, command_executor="http://localhost:4444")
driver.get("https://selenium.dev")
driver.quit()
```

The output video file name will be `test_visit_basic_auth_secured_page_ChromeTests_<sessionId>.mp4`.

If your test name is handled by the test framework, and it is unique for sure, you also can disable the session id appends to the video file name by setting `SE_VIDEO_FILE_NAME_SUFFIX=false`.

File name will be trimmed to 255 characters to avoid long file names. Moreover, `space` character will be replaced by `_` and only characters alphabets, numbers, `-` (hyphen), `_` (underscore) are retained in the file name.

The trim regex is able to be customized by setting `SE_VIDEO_FILE_NAME_TRIM_REGEX` environment variable. The default value is `[:alnum:]-_`. The regex should be compatible with the `tr` command in bash.

At deployment level, the recorder container is up always. In addition, you can disable video recording process via session capability `se:recordVideo`. For example in Python binding:

```python
options.set_capability('se:recordVideo', False)
```

In recorder container will perform query GraphQL in Hub based on Node SessionId and extract the value of `se:recordVideo` in capabilities before deciding to start video recording process or not.

Notes: To reach the GraphQL endpoint, the recorder container needs to know the Hub URL. The Hub URL can be passed via environment variable `SE_NODE_GRID_URL`. For example `SE_NODE_GRID_URL` is `http://selenium-hub:4444`.

## Video recording and uploading

[RCLONE](https://rclone.org/) is installed in the video recorder image. You can use it to upload the videos to a cloud storage service.
Expand Down Expand Up @@ -836,6 +872,25 @@ for example:

After running a test, check the path you mounted to the Docker container,
(`${PWD}/assets`), and you should see videos and session information.

From language bindings, you can set the `se:name` capability to change output video file name dynamically. For example, in Python binding:

```python
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium import webdriver

options = ChromeOptions()
options.set_capability('se:recordVideo', True)
options.set_capability('se:screenResolution', '1920x1080')
options.set_capability('se:name', 'test_visit_basic_auth_secured_page (ChromeTests)')
driver = webdriver.Remote(options=options, command_executor="http://localhost:4444")
driver.get("https://selenium.dev")
driver.quit()
```

After test executed, under (`${PWD}/assets`) you can see the video file name in path `/<sessionId>/test_visit_basic_auth_secured_page_ChromeTests.mp4`

The file name will be trimmed to 255 characters to avoid long file names. Moreover, the `space` character will be replaced by `_`, and only the characters alphabets, numbers, `-` (hyphen), and `_` (underscore) are retained in the file name. (This feat is available once this [PR](https://github.com/SeleniumHQ/selenium/pull/13907) merged)
___

## Deploying to Kubernetes
Expand Down
3 changes: 2 additions & 1 deletion Video/Dockerfile
Expand Up @@ -30,7 +30,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
RUN apt-get -qqy update \
&& apt-get upgrade -yq \
&& apt-get -qqy --no-install-recommends install \
supervisor x11-xserver-utils x11-utils curl jq python3-pip tzdata acl unzip \
supervisor x11-xserver-utils x11-utils libxcb1-dev curl jq python3-pip tzdata acl unzip \
&& python3 -m pip install --upgrade pip \
&& python3 -m pip install --upgrade setuptools \
&& python3 -m pip install --upgrade wheel \
Expand Down Expand Up @@ -108,5 +108,6 @@ ENV SE_FRAME_RATE 15
ENV SE_CODEC libx264
ENV SE_PRESET "-preset ultrafast"
ENV SE_VIDEO_FILE_NAME video.mp4
ENV SE_VIDEO_FILE_NAME_TRIM_REGEX "[:alnum:]-_"

EXPOSE 9000
3 changes: 2 additions & 1 deletion Video/upload.sh
Expand Up @@ -48,9 +48,10 @@ function graceful_exit() {
wait ${pid}
done
rm -rf ${FORCE_EXIT_FILE}
rm -rf ${UPLOAD_PIPE_FILE} || true
echo "Uploader is ready to shutdown"
}
trap graceful_exit EXIT
trap graceful_exit SIGTERM SIGINT EXIT

while [ ! -p ${UPLOAD_PIPE_FILE} ];
do
Expand Down

0 comments on commit 6d3a8a7

Please sign in to comment.