Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<allure-testng.version>2.29.1</allure-testng.version>
<maven-failsafe.version>3.5.2</maven-failsafe.version>
<central.sonatype>0.7.0</central.sonatype>
<recorder.version>0.7.7.0</recorder.version>
</properties>

<organization>
Expand Down Expand Up @@ -276,6 +277,11 @@
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>com.github.stephenc.monte</groupId>
<artifactId>monte-screen-recorder</artifactId>
<version>${recorder.version}</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* MIT License
*
* Copyright (c) 2025, Boyka Framework
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*/

package io.github.boykaframework.actions.drivers;

import static io.github.boykaframework.actions.drivers.WindowActions.onWindow;
import static io.github.boykaframework.enums.Message.RECORDING_NOT_STARTED;
import static io.github.boykaframework.enums.Message.RECORDING_NOT_STOPPED;
import static io.github.boykaframework.manager.ParallelSession.getSession;
import static io.github.boykaframework.utils.ErrorHandler.handleAndThrow;
import static java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment;
import static java.text.MessageFormat.format;
import static java.util.Objects.isNull;
import static org.apache.commons.io.FileUtils.createParentDirectories;
import static org.apache.logging.log4j.LogManager.getLogger;
import static org.monte.media.FormatKeys.EncodingKey;
import static org.monte.media.FormatKeys.FrameRateKey;
import static org.monte.media.FormatKeys.KeyFrameIntervalKey;
import static org.monte.media.FormatKeys.MIME_AVI;
import static org.monte.media.FormatKeys.MediaTypeKey;
import static org.monte.media.FormatKeys.MimeTypeKey;
import static org.monte.media.VideoFormatKeys.CompressorNameKey;
import static org.monte.media.VideoFormatKeys.DepthKey;
import static org.monte.media.VideoFormatKeys.ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE;
import static org.monte.media.VideoFormatKeys.QualityKey;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import io.github.boykaframework.config.ui.web.VideoSetting;
import org.apache.logging.log4j.Logger;
import org.monte.media.Format;
import org.monte.media.FormatKeys;
import org.monte.media.Registry;
import org.monte.media.math.Rational;
import org.monte.screenrecorder.ScreenRecorder;

class CustomVideoRecorder extends ScreenRecorder {
private static final Logger LOGGER = getLogger ();
private static final VideoSetting RECORDER_SETTING = getSession ().getWebSetting ()
.getVideo ();
private static ScreenRecorder screenRecorder;

static void startRecording () {
if (checkIfEnabled ()) {
LOGGER.info ("Started Video screen recording...");
final var file = new File (RECORDER_SETTING.getPath ());

final var screenSize = onWindow ().viewportSize ();
final var width = screenSize.getWidth ();
final var height = screenSize.getHeight ();

final var captureSize = new Rectangle (0, 0, width, height);

final var gc = getLocalGraphicsEnvironment ().getDefaultScreenDevice ()
.getDefaultConfiguration ();

try {
final Format fileFormat = new Format (MediaTypeKey, FormatKeys.MediaType.FILE, MimeTypeKey, MIME_AVI);
final Format screenFormat = new Format (MediaTypeKey, FormatKeys.MediaType.VIDEO, EncodingKey,
ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, CompressorNameKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE,
DepthKey, 24, FrameRateKey, Rational.valueOf (15), QualityKey, 1.0f, KeyFrameIntervalKey,
(15 * 60));
final Format mouseFormat = new Format (MediaTypeKey, FormatKeys.MediaType.VIDEO, EncodingKey,
ScreenRecorder.ENCODING_BLACK_CURSOR, FrameRateKey, Rational.valueOf (30));

screenRecorder = new ScreenRecorder (gc, null, fileFormat, screenFormat, mouseFormat, null, file);
screenRecorder.start ();
} catch (final IOException | AWTException e) {
handleAndThrow (RECORDING_NOT_STARTED, e);
}
} else {
LOGGER.warn ("Video screen recording is Disabled, cannot start...");
}
}

static void stopRecording () {
if (checkIfEnabled ()) {
LOGGER.info ("Stopping Video screen recording...");
try {
screenRecorder.stop ();
} catch (final IOException e) {
handleAndThrow (RECORDING_NOT_STOPPED, e);
}
} else {
LOGGER.warn ("Video screen recording is Disabled, cannot stop...");
}
}

private static boolean checkIfEnabled () {
return !isNull (RECORDER_SETTING) && RECORDER_SETTING.isEnabled ();
}

private final String prefix;

private CustomVideoRecorder (final GraphicsConfiguration cfg, final Rectangle captureArea, final Format fileFormat,
final Format screenFormat, final Format mouseFormat, final Format audioFormat, final File movieFolder)
throws IOException, AWTException {
super (cfg, captureArea, fileFormat, screenFormat, mouseFormat, audioFormat, movieFolder);
this.prefix = RECORDER_SETTING.getPrefix ();
}

@Override
protected File createMovieFile (final Format fileFormat) throws IOException {
if (!this.movieFolder.exists ()) {
createParentDirectories (this.movieFolder);
} else if (!this.movieFolder.isDirectory ()) {
throw new IOException (format ("\"{0}\" is not a directory.", this.movieFolder));
}
final var dateFormat = new SimpleDateFormat ("yyyyMMdd-HHmmss");
return new File (this.movieFolder, format ("{0}-{1}.{2}", this.prefix, dateFormat.format (new Date ()),
Registry.getInstance ()
.getExtension (fileFormat)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,24 @@ public void minimize () {
LOGGER.traceExit ();
}

@Override
public void startRecording () {
LOGGER.traceEntry ();
LOGGER.info ("Starting video recording...");
ofNullable (this.listener).ifPresent (IWindowActionsListener::onStartRecording);
CustomVideoRecorder.startRecording ();
LOGGER.traceExit ();
}

@Override
public void stopRecording () {
LOGGER.traceEntry ();
LOGGER.info ("Stopping video recording...");
ofNullable (this.listener).ifPresent (IWindowActionsListener::onStopRecording);
CustomVideoRecorder.stopRecording ();
LOGGER.traceExit ();
}

@Override
public void switchTo (final String nameOrHandle) {
LOGGER.traceEntry ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ public interface IWindowActions {
*/
void minimize ();

/**
* \ Starts video recording of the window.
*/
void startRecording ();

/**
* Stops video recording of the window.
*/
void stopRecording ();

/**
* Switch to window for specific name / handle.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ default void onMinimize () {
// not implemented.
}

/**
* Handle start recording method.
*/
default void onStartRecording () {
// not implemented.
}

/**
* Handle stop recording method.
*/
default void onStopRecording () {
// not implemented.
}

/**
* Handles switch to window method.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
*/
@Data
public class VideoSetting {
private AndroidVideoSetting android = new AndroidVideoSetting ();
private boolean enabled = false;
private IOSVideoSetting ios = new IOSVideoSetting ();
private String path = "./videos";
private String prefix = "VID";
private AndroidVideoSetting android = new AndroidVideoSetting ();
private boolean enabled = false;
private IOSVideoSetting ios = new IOSVideoSetting ();
private String path = "./videos";
private String prefix = "VID";
private String size;
private int timeLimit;
private int timeLimit = 1800;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* MIT License
*
* Copyright (c) 2025, Boyka Framework
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*/

package io.github.boykaframework.config.ui.web;

import lombok.Data;

/**
* Video recording settings for Web and Mobile.
*
* @author Wasiq Bhamla
* @since 18-Jan-2025
*/
@Data
public class VideoSetting {
private boolean enabled = false;
private String path = "./videos";
private String prefix = "VID";
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public class WebSetting {
private TargetProviders target = LOCAL;
private String userName;
private String version = "stable";
private VideoSetting video = new VideoSetting ();

/**
* Gets cloud password.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ public enum Message {
* Protocol is required for host name
*/
PROTOCOL_REQUIRED_FOR_HOST ("Protocol is required for host ({0})..."),
/**
* Error when starting the recording.
*/
RECORDING_NOT_STARTED ("Error while starting video recording."),
/**
* Error stopping the recording.
*/
RECORDING_NOT_STOPPED ("Error while stopping video recording."),
/**
* Schema validation assert failure
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ public void afterMethod (final ITestResult result) {
public void setupTestClass (final PlatformType platformType, final String chromeConfig) {
createSession (HOST_PERSONA, platformType, chromeConfig);
createSession (GUEST_PERSONA, platformType, chromeConfig);
onWindow ().startRecording ();
}

@AfterClass
public void tearDownClass () {
onWindow ().stopRecording ();
clearAllSessions ();
}

Expand Down
7 changes: 5 additions & 2 deletions core-java/src/test/resources/boyka-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@
"base_url": "https://the-internet.herokuapp.com/",
"browser": "CHROME",
"page_load_strategy": "NORMAL",
"highlight": false,
"headless": true,
"highlight": true,
"headless": false,
"resize": "CUSTOM",
"custom_size": {
"width": 1580,
"height": 1080
},
"video": {
"enabled": true
},
"browser_options": [
"use-fake-device-for-media-stream",
"use-fake-ui-for-media-stream"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"eslint-config-prettier": "^10.0.1",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0",
"husky": "^9.1.7",
Expand Down Expand Up @@ -99,5 +99,5 @@
"pnpm format"
]
},
"packageManager": "pnpm@9.15.0"
"packageManager": "pnpm@9.15.4"
}
Loading