Skip to content

Commit 6d3a8a7

Browse files
authored
feat: Video recording with dynamic file name based on metadata in tests (#2249)
[deploy] Signed-off-by: Viet Nguyen Duc <[email protected]>
1 parent 409a46f commit 6d3a8a7

24 files changed

+327
-133
lines changed

Diff for: .github/workflows/helm-chart-test.yml

+17-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ jobs:
3737
fail-fast: false
3838
matrix:
3939
include:
40+
- k8s-version: 'v1.25.16'
41+
test-strategy: disabled
42+
cluster: 'minikube'
43+
helm-version: 'v3.10.3'
44+
test-existing-keda: false
45+
test-upgrade: true
4046
- k8s-version: 'v1.26.15'
4147
test-strategy: job
4248
cluster: 'minikube'
@@ -158,7 +164,7 @@ jobs:
158164
timeout_minutes: 30
159165
max_attempts: 3
160166
command: |
161-
NAME=${IMAGE_REGISTRY} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} TEST_EXISTING_KEDA=${TEST_EXISTING_KEDA} make chart_test_autoscaling_${{ matrix.test-strategy }}
167+
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 }}
162168
- name: Test chart upgrade
163169
if: (matrix.test-upgrade == true)
164170
run: |
@@ -179,3 +185,13 @@ jobs:
179185
name: ${{ env.ARTIFACT_NAME }}-artifacts
180186
path: ./tests/tests/
181187
if-no-files-found: ignore
188+
- name: Check video integrity
189+
if: always()
190+
run: |
191+
make test_video_integrity
192+
- name: Upload test video artifacts
193+
if: always()
194+
uses: actions/upload-artifact@main
195+
with:
196+
name: ${{ env.ARTIFACT_NAME }}-videos
197+
path: ./tests/videos/

Diff for: .github/workflows/test-video.yml

+13-17
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,15 @@ jobs:
3636
strategy:
3737
fail-fast: false
3838
matrix:
39-
test-strategy: [test_video, test_parallel, test_node_docker]
39+
include:
40+
- test-strategy: test_video
41+
test-video: true
42+
- test-strategy: test_video_dynamic_name
43+
test-video: true
44+
- test-strategy: test_node_docker
45+
test-video: true
46+
- test-strategy: test_parallel
47+
test-video: false
4048
steps:
4149
- uses: actions/checkout@main
4250
- name: Set up QEMU
@@ -99,21 +107,9 @@ jobs:
99107
REQUEST_TIMEOUT: ${{ github.event.inputs.request-timeout || '400' }}
100108
- name: Run Docker Compose to ${{ matrix.test-strategy }}
101109
run: USE_RANDOM_USER_ID=${USE_RANDOM_USER} VERSION=${BRANCH} BUILD_DATE=${BUILD_DATE} make ${{ matrix.test-strategy }}
102-
- name: Upload recorded Chrome video
103-
if: matrix.test-strategy == 'test_video'
110+
- name: Upload recorded video
111+
if: matrix.test-video == true
104112
uses: actions/upload-artifact@main
105113
with:
106-
name: chrome_video
107-
path: ./tests/videos/chrome_video.mp4
108-
- name: Upload recorded Edge video
109-
if: matrix.test-strategy == 'test_video'
110-
uses: actions/upload-artifact@main
111-
with:
112-
name: edge_video
113-
path: ./tests/videos/edge_video.mp4
114-
- name: Upload recorded Firefox video
115-
if: matrix.test-strategy == 'test_video'
116-
uses: actions/upload-artifact@main
117-
with:
118-
name: firefox_video
119-
path: ./tests/videos/firefox_video.mp4
114+
name: "${{ matrix.test-strategy }}_artifacts"
115+
path: ./tests/videos/

Diff for: Makefile

+44-21
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,10 @@ test_parallel: hub chrome firefox edge
447447
docker compose -f docker-compose-v3-test-parallel.yml up --no-log-prefix --exit-code-from tests --build ; \
448448
done
449449

450+
test_video_dynamic_name:
451+
VIDEO_FILE_NAME=auto TEST_DELAY_AFTER_TEST=10 \
452+
make test_video
453+
450454
# This should run on its own CI job. There is no need to combine it with the other tests.
451455
# Its main purpose is to check that a video file was generated.
452456
test_video: video hub chrome firefox edge
@@ -460,31 +464,31 @@ test_video: video hub chrome firefox edge
460464
echo NODE=$$node >> .env ; \
461465
echo UID=$$(id -u) >> .env ; \
462466
echo BINDING_VERSION=$(BINDING_VERSION) >> .env ; \
467+
echo TEST_DELAY_AFTER_TEST=$(or $(TEST_DELAY_AFTER_TEST), 0) >> .env ; \
463468
if [ $$node = "NodeChrome" ] ; then \
464469
echo BROWSER=chrome >> .env ; \
465-
echo VIDEO_FILE_NAME=chrome_video.mp4 >> .env ; \
470+
echo VIDEO_FILE_NAME=$${VIDEO_FILE_NAME:-"chrome_video.mp4"} >> .env ; \
471+
echo VIDEO_FILE_NAME_SUFFIX=$${VIDEO_FILE_NAME_SUFFIX:-"true"} >> .env ; \
466472
fi ; \
467473
if [ $$node = "NodeEdge" ] ; then \
468474
echo BROWSER=edge >> .env ; \
469-
echo VIDEO_FILE_NAME=edge_video.mp4 >> .env ; \
475+
echo VIDEO_FILE_NAME=$${VIDEO_FILE_NAME:-"edge_video.mp4"} >> .env ; \
476+
echo VIDEO_FILE_NAME_SUFFIX=$${VIDEO_FILE_NAME_SUFFIX:-"false"} >> .env ; \
470477
fi ; \
471478
if [ $$node = "NodeFirefox" ] ; then \
472479
echo BROWSER=firefox >> .env ; \
473-
echo VIDEO_FILE_NAME=firefox_video.mp4 >> .env ; \
480+
echo VIDEO_FILE_NAME=$${VIDEO_FILE_NAME:-"firefox_video.mp4"} >> .env ; \
481+
echo VIDEO_FILE_NAME_SUFFIX=$${VIDEO_FILE_NAME_SUFFIX:-"true"} >> .env ; \
474482
fi ; \
475483
docker compose -f docker-compose-v3-test-video.yml up --abort-on-container-exit --build ; \
476484
done
477-
# Using ffmpeg to verify file integrity
478-
# https://superuser.com/questions/100288/how-can-i-check-the-integrity-of-a-video-file-avi-mpeg-mp4
479-
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
480-
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
481-
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
485+
make test_video_integrity
482486

483487
test_node_docker: hub standalone_docker standalone_chrome standalone_firefox standalone_edge video
484488
sudo rm -rf ./tests/tests
485489
sudo rm -rf ./tests/videos; mkdir -p ./tests/videos/Downloads
486490
sudo chmod -R 777 ./tests/videos
487-
for node in DeploymentAutoscaling JobAutoscaling ; do \
491+
for node in NodeChrome NodeFirefox NodeEdge ; do \
488492
cd tests || true ; \
489493
DOWNLOADS_DIR="./videos/Downloads" ; \
490494
sudo rm -rf $$DOWNLOADS_DIR/* ; \
@@ -496,6 +500,7 @@ test_node_docker: hub standalone_docker standalone_chrome standalone_firefox sta
496500
echo LOG_LEVEL=$(or $(LOG_LEVEL), "INFO") >> .env ; \
497501
echo REQUEST_TIMEOUT=$(or $(REQUEST_TIMEOUT), 300) >> .env ; \
498502
echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), "false") >> .env ; \
503+
echo TEST_DELAY_AFTER_TEST=$(or $(TEST_DELAY_AFTER_TEST), 5) >> .env ; \
499504
echo NODE=$$node >> .env ; \
500505
echo UID=$$(id -u) >> .env ; \
501506
echo BINDING_VERSION=$(BINDING_VERSION) >> .env ; \
@@ -509,6 +514,7 @@ test_node_docker: hub standalone_docker standalone_chrome standalone_firefox sta
509514
exit 1 ; \
510515
fi ; \
511516
done
517+
make test_video_integrity
512518

513519
test_custom_ca_cert:
514520
VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/customCACert/bootstrap.sh
@@ -528,20 +534,37 @@ chart_build_nightly:
528534
chart_build:
529535
VERSION=$(TAG_VERSION) ./tests/charts/make/chart_build.sh
530536

537+
test_video_integrity:
538+
# Using ffmpeg to verify file integrity
539+
# https://superuser.com/questions/100288/how-can-i-check-the-integrity-of-a-video-file-avi-mpeg-mp4
540+
list_files=$$(find ./tests/videos -type f -name "*.mp4"); \
541+
echo "Number of video files: $$(echo $$list_files | wc -w)"; \
542+
number_corrupted_files=0; \
543+
if [ -z "$$list_files" ]; then \
544+
echo "No video files found"; \
545+
exit 1; \
546+
fi; \
547+
for file in $$list_files; do \
548+
echo "Checking video file: $$file"; \
549+
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 - ; \
550+
if [ $$? -ne 0 ]; then \
551+
echo "Video file $$file is corrupted"; \
552+
number_corrupted_files=$$((number_corrupted_files+1)); \
553+
fi; \
554+
echo "------"; \
555+
done; \
556+
if [ $$((number_corrupted_files)) -gt 0 ]; then \
557+
echo "Number of corrupted video files: $$number_corrupted_files"; \
558+
exit 1; \
559+
fi
560+
531561
chart_test_template:
532562
./tests/charts/bootstrap.sh
533563

534-
chart_test_chrome:
535-
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
536-
./tests/charts/make/chart_test.sh NodeChrome
537-
538-
chart_test_firefox:
539-
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
540-
./tests/charts/make/chart_test.sh NodeFirefox
541-
542-
chart_test_edge:
564+
chart_test_autoscaling_disabled:
565+
SELENIUM_GRID_AUTOSCALING=false TEST_DELAY_AFTER_TEST=15 CHART_ENABLE_TRACING=true SELENIUM_GRID_HOST=$$(hostname -i) RELEASE_NAME=selenium \
543566
VERSION=$(TAG_VERSION) VIDEO_TAG=$(FFMPEG_TAG_VERSION)-$(BUILD_DATE) NAMESPACE=$(NAMESPACE) BINDING_VERSION=$(BINDING_VERSION) \
544-
./tests/charts/make/chart_test.sh NodeEdge
567+
./tests/charts/make/chart_test.sh NoAutoscaling
545568

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

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

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

Diff for: NodeBase/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ RUN apt-get update -qqy \
7272
# Xvfb
7373
#==============
7474
xvfb \
75+
libxcb1 \
7576
xauth \
7677
pulseaudio \
7778
#=====

Diff for: README.md

+55
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Talk to us at https://www.selenium.dev/support/
4040
* [Hub and Nodes](#hub-and-nodes)
4141
* [Fully distributed mode - Router, Queue, Distributor, EventBus, SessionMap and Nodes](#fully-distributed-mode---router-queue-distributor-eventbus-sessionmap-and-nodes)
4242
* [Video recording](#video-recording)
43+
* [Video recording with dynamic file name based on metadata in tests](#video-recording-with-dynamic-file-name-based-on-metadata-in-tests)
4344
* [Video recording and uploading](#video-recording-and-uploading)
4445
* [Dynamic Grid](#dynamic-grid)
4546
* [Configuration example](#configuration-example)
@@ -554,6 +555,41 @@ Here is an example using a Hub and a few Nodes:
554555

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

558+
## Video recording with dynamic file name based on metadata in tests
559+
560+
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.
561+
562+
For example in Python binding:
563+
564+
```python
565+
from selenium.webdriver.chrome.options import Options as ChromeOptions
566+
from selenium import webdriver
567+
568+
options = ChromeOptions()
569+
options.set_capability('se:name', 'test_visit_basic_auth_secured_page (ChromeTests)')
570+
driver = webdriver.Remote(options=options, command_executor="http://localhost:4444")
571+
driver.get("https://selenium.dev")
572+
driver.quit()
573+
```
574+
575+
The output video file name will be `test_visit_basic_auth_secured_page_ChromeTests_<sessionId>.mp4`.
576+
577+
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`.
578+
579+
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.
580+
581+
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.
582+
583+
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:
584+
585+
```python
586+
options.set_capability('se:recordVideo', False)
587+
```
588+
589+
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.
590+
591+
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`.
592+
557593
## Video recording and uploading
558594

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

837873
After running a test, check the path you mounted to the Docker container,
838874
(`${PWD}/assets`), and you should see videos and session information.
875+
876+
From language bindings, you can set the `se:name` capability to change output video file name dynamically. For example, in Python binding:
877+
878+
```python
879+
from selenium.webdriver.chrome.options import Options as ChromeOptions
880+
from selenium import webdriver
881+
882+
options = ChromeOptions()
883+
options.set_capability('se:recordVideo', True)
884+
options.set_capability('se:screenResolution', '1920x1080')
885+
options.set_capability('se:name', 'test_visit_basic_auth_secured_page (ChromeTests)')
886+
driver = webdriver.Remote(options=options, command_executor="http://localhost:4444")
887+
driver.get("https://selenium.dev")
888+
driver.quit()
889+
```
890+
891+
After test executed, under (`${PWD}/assets`) you can see the video file name in path `/<sessionId>/test_visit_basic_auth_secured_page_ChromeTests.mp4`
892+
893+
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)
839894
___
840895

841896
## Deploying to Kubernetes

Diff for: Video/Dockerfile

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
3030
RUN apt-get -qqy update \
3131
&& apt-get upgrade -yq \
3232
&& apt-get -qqy --no-install-recommends install \
33-
supervisor x11-xserver-utils x11-utils curl jq python3-pip tzdata acl unzip \
33+
supervisor x11-xserver-utils x11-utils libxcb1-dev curl jq python3-pip tzdata acl unzip \
3434
&& python3 -m pip install --upgrade pip \
3535
&& python3 -m pip install --upgrade setuptools \
3636
&& python3 -m pip install --upgrade wheel \
@@ -108,5 +108,6 @@ ENV SE_FRAME_RATE 15
108108
ENV SE_CODEC libx264
109109
ENV SE_PRESET "-preset ultrafast"
110110
ENV SE_VIDEO_FILE_NAME video.mp4
111+
ENV SE_VIDEO_FILE_NAME_TRIM_REGEX "[:alnum:]-_"
111112

112113
EXPOSE 9000

Diff for: Video/upload.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ function graceful_exit() {
4848
wait ${pid}
4949
done
5050
rm -rf ${FORCE_EXIT_FILE}
51+
rm -rf ${UPLOAD_PIPE_FILE} || true
5152
echo "Uploader is ready to shutdown"
5253
}
53-
trap graceful_exit EXIT
54+
trap graceful_exit SIGTERM SIGINT EXIT
5455

5556
while [ ! -p ${UPLOAD_PIPE_FILE} ];
5657
do

0 commit comments

Comments
 (0)