Skip to content

Commit 201a1bb

Browse files
committed
feat: video recordings
1 parent 302f075 commit 201a1bb

File tree

9 files changed

+281
-8
lines changed

9 files changed

+281
-8
lines changed

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ module.exports = {
4949
useSteps: true,
5050
deviceLogs: true,
5151
deviceScreenshots: true,
52+
deviceVideos: true,
5253
}],
5354
],
5455
},
@@ -62,6 +63,89 @@ Here's a brief explanation of what you just added:
6263

6364
- `testEnvironmentOptions` section: We added three event listener modules that will run during our tests — `jest-metadata`, `jest-allure2-reporter`, and `detox-allure2-adapter`. These listeners will collect necessary metadata and feed test result data to our Allure reports.
6465

66+
## Adapter Options
67+
68+
### `useSteps: boolean`
69+
70+
If set to true, the adapter will wrap all Detox device interactions (like `device.launchApp()`, `element(by.id('loginButton')).tap()`) into Allure steps. This provides a detailed, step-by-step report of your test execution.
71+
72+
### `deviceLogs: boolean | DetoxAllure2AdapterDeviceLogsOptions`
73+
74+
Enables capturing device (iOS/Android) logs for each step. This feature uses the [`logkitten`](https://www.npmjs.com/package/logkitten) library.
75+
76+
**Configuration:**
77+
78+
- **`true`**: Enables log capture with default settings.
79+
- **`false`** (default): Disables log capture.
80+
- **`DetoxAllure2AdapterDeviceLogsOptions`** (object): Enables log capture and provides fine-grained control over the settings.
81+
82+
- `ios: (entry: IosEntry) => boolean`: Filter function for iOS logs. Return `true` to include the log entry.
83+
- `android: (entry: AndroidEntry) => boolean`: Filter function for Android logs. Return `true` to include the log entry.
84+
- `override: boolean`: Whether to override existing log handlers.
85+
- `saveAll: boolean` (default: `false`): If `true`, saves logs for all steps. By default, only logs for failed steps are kept.
86+
- `syncDelay: number | { ios?: number; android?: number }` (default: `500`): Synchronization delay in milliseconds for log collection. Set to `0` to disable, or provide per-platform delays.
87+
88+
### `deviceScreenshots: boolean | DetoxAllure2AdapterDeviceScreenshotOptions`
89+
90+
Enables taking screenshots for each step. This feature uses the [`screenkitten`](https://www.npmjs.com/package/screenkitten) library.
91+
92+
**Configuration:**
93+
94+
- **`true`**: Enables screenshot capture with default settings.
95+
- **`false`** (default): Disables screenshot capture.
96+
- **`DetoxAllure2AdapterDeviceScreenshotOptions`** (object): Enables screenshot capture and provides fine-grained control over the settings.
97+
98+
- `saveAll: boolean` (default: `false`): If `true`, saves screenshots for all steps. By default, only screenshots for failed steps are kept.
99+
100+
### `deviceVideos: boolean | DetoxAllure2AdapterDeviceVideoOptions`
101+
102+
Enables "on-demand" video recording for your tests. This feature uses the [`videokitten`](https://www.npmjs.com/package/videokitten) library.
103+
The recording starts automatically upon the first interaction with the device and stops when the test is complete.
104+
105+
**Configuration:**
106+
107+
- **`true`**: Enables video recording with default settings.
108+
- **`false`** (default): Disables video recording.
109+
- **`DetoxAllure2AdapterDeviceVideoOptions`** (object): Enables recording and provides fine-grained control over the settings.
110+
111+
- `saveAll: boolean` (default: `false`): If `true`, saves videos for all tests. By default, only videos for failed tests are kept.
112+
- `ios: Partial<VideokittenOptionsIOS>`: Custom options for iOS, as defined by `videokitten`.
113+
- `android: Partial<VideokittenOptionsAndroid>`: Custom options for Android, as defined by `videokitten`.
114+
115+
**Example with custom options:**
116+
117+
```js
118+
// jest.config.js
119+
module.exports = {
120+
eventListeners: [
121+
'jest-metadata/environment-listener',
122+
'jest-allure2-reporter/environment-listener',
123+
['detox-allure2-adapter', {
124+
useSteps: true,
125+
deviceLogs: {
126+
saveAll: true,
127+
ios: (entry) => entry.level === 'error',
128+
android: (entry) => entry.priority === 'E',
129+
},
130+
deviceScreenshots: {
131+
saveAll: true,
132+
},
133+
deviceVideos: {
134+
saveAll: true,
135+
ios: {
136+
codec: 'hevc',
137+
},
138+
android: {
139+
bitRate: 4_000_000,
140+
}
141+
}
142+
}],
143+
],
144+
},
145+
```
146+
147+
Refer to the [`videokitten` documentation](https://www.npmjs.com/package/videokitten) for a full list of options for each platform.
148+
65149
## Running Tests
66150

67151
After making these changes, you can run your tests as usual. The tests will run with Detox and Jest, and the results will be reported using Allure. Configure your `npm test` script in the `package.json` file to run your Detox tests.

package-e2e/test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import type { ReporterOptions } from 'jest-allure2-reporter';
22

33
import listener from 'detox-allure2-adapter';
4-
import type { DetoxAllure2AdapterOptions, DetoxAllure2AdapterDeviceLogsOptions, DetoxAllure2AdapterDeviceScreenshotOptions } from 'detox-allure2-adapter';
4+
import type {
5+
DetoxAllure2AdapterOptions,
6+
DetoxAllure2AdapterDeviceLogsOptions,
7+
DetoxAllure2AdapterDeviceScreenshotOptions,
8+
DetoxAllure2AdapterDeviceVideoOptions,
9+
} from 'detox-allure2-adapter';
510
import DetoxAllurePathBuilder from 'detox-allure2-adapter/path-builder';
611
import presetAllure from 'detox-allure2-adapter/preset-allure';
712
import presetDetox from 'detox-allure2-adapter/preset-detox';
@@ -21,6 +26,7 @@ assertType<DetoxAllure2AdapterOptions>({
2126
useSteps: true,
2227
deviceLogs: true,
2328
deviceScreenshots: true,
29+
deviceVideos: true,
2430
});
2531

2632
assertType<DetoxAllure2AdapterDeviceLogsOptions>({
@@ -38,3 +44,15 @@ assertType<DetoxAllure2AdapterDeviceLogsOptions>({
3844
assertType<DetoxAllure2AdapterDeviceScreenshotOptions>({
3945
saveAll: true,
4046
});
47+
48+
assertType<DetoxAllure2AdapterDeviceVideoOptions>({
49+
saveAll: true,
50+
ios: {
51+
codec: 'hevc',
52+
},
53+
android: {
54+
recording: {
55+
bitRate: 4_000_000,
56+
},
57+
},
58+
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"dependencies": {
110110
"archiver": "^6.0.1",
111111
"logkitten": "^1.3.0",
112-
"screenkitten": "^1.0.0"
112+
"screenkitten": "^1.0.0",
113+
"videokitten": "^1.0.0"
113114
}
114115
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type {
22
DetoxAllure2AdapterOptions,
33
DetoxAllure2AdapterDeviceLogsOptions,
44
DetoxAllure2AdapterDeviceScreenshotOptions,
5+
DetoxAllure2AdapterDeviceVideoOptions,
56
} from './types';
67

78
export { listener as default } from './listener';

src/listener.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import { ScreenshotHelper } from './screenshots';
1616
import { wrapWithSteps } from './steps';
1717
import type { DetoxAllure2AdapterOptions } from './types';
1818
import { DeviceWrapper, WorkerWrapper } from './utils';
19+
import { VideoManager } from './video';
1920

2021
export const listener: EnvironmentListenerFn = (
2122
{ testEvents },
2223
{
2324
useSteps = false,
2425
deviceLogs = false,
2526
deviceScreenshots = false,
27+
deviceVideos = false,
2628
onError,
2729
}: DetoxAllure2AdapterOptions = {},
2830
) => {
@@ -33,6 +35,9 @@ export const listener: EnvironmentListenerFn = (
3335
let artifactsManager: any;
3436
let logs: LogBuffer | undefined;
3537
let screenshots: ScreenshotHelper | undefined;
38+
let videoManager: VideoManager | undefined;
39+
40+
let isFirstTest = true;
3641

3742
testEvents
3843
.on('setup', () => {
@@ -65,15 +70,30 @@ export const listener: EnvironmentListenerFn = (
6570
onError,
6671
});
6772
}
73+
74+
if (deviceVideos) {
75+
videoManager = VideoManager.getInstance(device, deviceVideos);
76+
}
6877
})
6978
.on('setup', async () => {
7079
if (useSteps) {
71-
wrapWithSteps({ detox, worker, allure, logs, screenshots });
80+
wrapWithSteps({ detox, worker, allure, logs, screenshots, videoManager });
7281
}
7382
})
74-
.on('test_start', () => {
83+
.on('run_start', async () => {
84+
await videoManager?.ensureRecording(true);
85+
})
86+
.on('test_start', async () => {
7587
$test = allure.$bind();
7688
logs?.attachBefore(allure);
89+
90+
if (isFirstTest) {
91+
isFirstTest = false;
92+
} else {
93+
await videoManager?.stopAndAttach();
94+
}
95+
96+
videoManager?.setAllureContext($test);
7797
})
7898
.on('hook_start', () => {
7999
logs?.attachBefore(allure);
@@ -90,17 +110,20 @@ export const listener: EnvironmentListenerFn = (
90110
.on('test_fn_success', async () => {
91111
await Promise.all([logs?.attachAfterSuccess(allure), screenshots?.attachSuccess(allure)]);
92112
})
93-
.on('test_done', async () => {
113+
.on('run_finish', async () => {
114+
await videoManager?.stopAndAttach();
115+
videoManager?.setAllureContext();
94116
$test = undefined;
95117
})
96118
.on('teardown', flushArtifacts, -1)
97119
.on('test_environment_teardown', flushArtifacts, -1);
98120

99121
async function flushArtifacts() {
100122
await artifactsManager?._idlePromise;
101-
await logs?.close();
123+
await Promise.all([logs?.close(), videoManager?.stopAndAttach()]);
102124
artifactsManager = undefined;
103125
logs = undefined;
126+
videoManager = undefined;
104127
}
105128

106129
function onTrackArtifact(artifact: any) {

src/steps/wrapWithSteps.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { AllureRuntime } from 'jest-allure2-reporter/api';
33
import { type StepLogRecorder } from '../logs';
44
import { type ScreenshotHelper } from '../screenshots';
5+
import { type VideoManager } from '../video';
56
import { androidDescriptionMaker, iosDescriptionMaker } from './description-maker';
67
import type { StepDescriptionMaker } from './description-maker';
78

@@ -11,10 +12,11 @@ export interface WrapWithStepsOptions {
1112
allure: AllureRuntime;
1213
logs?: StepLogRecorder;
1314
screenshots?: ScreenshotHelper;
15+
videoManager?: VideoManager;
1416
}
1517

1618
export function wrapWithSteps(options: WrapWithStepsOptions) {
17-
const { detox, worker, allure, logs, screenshots } = options;
19+
const { detox, worker, allure, logs, screenshots, videoManager } = options;
1820
const { device } = detox;
1921
const platform = device.getPlatform();
2022

@@ -57,6 +59,7 @@ export function wrapWithSteps(options: WrapWithStepsOptions) {
5759
? allure.step(desc.message, async () => {
5860
if (desc.args) allure.parameters(desc.args);
5961
logs?.attachBefore(allure);
62+
await videoManager?.ensureRecording();
6063

6164
try {
6265
const result = await send(...args);
@@ -84,7 +87,7 @@ function initDescriptionMaker(platform: string): StepDescriptionMaker | undefine
8487
}
8588

8689
function wrapDeviceMethod(
87-
{ detox, allure, logs, screenshots }: WrapWithStepsOptions,
90+
{ detox, allure, logs, screenshots, videoManager }: WrapWithStepsOptions,
8891
methodName: string,
8992
stepDescription: string,
9093
) {
@@ -93,6 +96,8 @@ function wrapDeviceMethod(
9396
if (typeof originalMethod !== 'function') return;
9497

9598
device[methodName] = async (...args: any[]) => {
99+
await videoManager?.ensureRecording();
100+
96101
return await allure.step(stepDescription, async () => {
97102
try {
98103
logs?.attachBefore(allure);

src/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { AndroidEntry, IosEntry } from 'logkitten';
2+
import type { VideokittenOptionsIOS, VideokittenOptionsAndroid } from 'videokitten';
23

34
export type DetoxAllure2AdapterOptions = {
45
/**
@@ -14,6 +15,10 @@ export type DetoxAllure2AdapterOptions = {
1415
* Device screenshots configuration for per-step logging
1516
*/
1617
deviceScreenshots?: boolean | DetoxAllure2AdapterDeviceScreenshotOptions;
18+
/**
19+
* Device video recording for failed tests
20+
*/
21+
deviceVideos?: boolean | DetoxAllure2AdapterDeviceVideoOptions;
1722
/**
1823
* Callback to handle errors
1924
*/
@@ -33,5 +38,19 @@ export interface DetoxAllure2AdapterDeviceLogsOptions {
3338
}
3439

3540
export interface DetoxAllure2AdapterDeviceScreenshotOptions {
41+
/**
42+
* Whether to save all screenshots
43+
* @default false
44+
*/
45+
saveAll?: boolean;
46+
}
47+
48+
export interface DetoxAllure2AdapterDeviceVideoOptions {
49+
/**
50+
* Whether to save all videos
51+
* @default false
52+
*/
3653
saveAll?: boolean;
54+
ios?: Partial<VideokittenOptionsIOS>;
55+
android?: Partial<VideokittenOptionsAndroid>;
3756
}

src/video/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './manager';

0 commit comments

Comments
 (0)