Skip to content
Merged
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
Expand Down
20 changes: 20 additions & 0 deletions package-e2e/test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ReporterOptions } from 'jest-allure2-reporter';

import listener from 'detox-allure2-adapter';
import type { DetoxAllure2AdapterOptions, DetoxAllure2AdapterDeviceLogsOptions, DetoxAllure2AdapterDeviceScreenshotOptions } from 'detox-allure2-adapter';
import DetoxAllurePathBuilder from 'detox-allure2-adapter/path-builder';
import presetAllure from 'detox-allure2-adapter/preset-allure';
import presetDetox from 'detox-allure2-adapter/preset-detox';
Expand All @@ -10,6 +11,25 @@ function assertType<T>(_actual: T): void {
// no-op
}

assertType<unknown>(presetAllure);
assertType<unknown>(presetDetox);

assertType<EnvironmentListenerFn>(listener);
assertType<DetoxAllurePathBuilder>(new DetoxAllurePathBuilder());
assertType<ReporterOptions>(presetAllure);
assertType<DetoxAllure2AdapterOptions>({
useSteps: true,
deviceLogs: true,
deviceScreenshots: true,
});

assertType<DetoxAllure2AdapterDeviceLogsOptions>({
ios: () => true,
android: () => true,
override: true,
saveAll: true,
});

assertType<DetoxAllure2AdapterDeviceScreenshotOptions>({
saveAll: true,
});
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
"node 16"
],
"dependencies": {
"archiver": "^6.0.1"
"archiver": "^6.0.1",
"logkitten": "^1.3.0",
"screenkitten": "^1.0.0"
}
}
93 changes: 6 additions & 87 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,7 @@
import fs from 'node:fs';
import path from 'node:path';
export type {
DetoxAllure2AdapterOptions,
DetoxAllure2AdapterDeviceLogsOptions,
DetoxAllure2AdapterDeviceScreenshotOptions,
} from './types';

// eslint-disable-next-line import/no-internal-modules
import detox from 'detox';
// eslint-disable-next-line import/no-internal-modules
import { worker } from 'detox/internals';
// eslint-disable-next-line import/no-internal-modules
import { allure, type MIMEInferer } from 'jest-allure2-reporter/api';
// eslint-disable-next-line node/no-extraneous-import
import type { EnvironmentListenerFn } from 'jest-environment-emit';

import { createLogHandler, createZipHandler } from './file-handlers';
import { wrapWithSteps } from './steps';

export type DetoxAllure2AdapterOptions = {
/**
* Whether to wrap device, element and other actions in Allure steps
* @default false
*/
useSteps?: boolean;
};

const listener: EnvironmentListenerFn = (
{ testEvents },
{ useSteps = false }: DetoxAllure2AdapterOptions = {},
) => {
let logHandler: ReturnType<typeof createLogHandler>;
let zipHandler: ReturnType<typeof createZipHandler>;
let inferMimeType: MIMEInferer;
let $test: ReturnType<typeof allure.$bind> | undefined;
let artifactsManager: any;

testEvents
.on('setup', () => {
allure.$plug((context) => {
logHandler = createLogHandler(context);
zipHandler = createZipHandler(context);
inferMimeType = context.inferMimeType;
});

artifactsManager = (worker as any)._artifactsManager;
artifactsManager.on('trackArtifact', onTrackArtifact);
})
.on('setup', async () => {
if (useSteps) wrapWithSteps(detox, worker, allure);
})
.on('test_start', () => {
$test = allure.$bind();
})
.on('test_done', () => {
$test = undefined;
})
.on('teardown', flushArtifacts, -1)
.on('test_environment_teardown', flushArtifacts, -1);

async function flushArtifacts() {
await artifactsManager?._idlePromise;
artifactsManager = undefined;
}

function onTrackArtifact(artifact: any) {
const $step = allure.$bind();
const $$test = $test;
const originalSave = artifact.doSave.bind(artifact);

artifact.doSave = async (artifactPath: string, ...args: unknown[]) => {
const result = await originalSave(artifactPath, ...args);
const isDirectory = fs.lstatSync(artifactPath).isDirectory();
const isLog = path.extname(artifactPath) === '.log';
const isVideo = !!inferMimeType({ sourcePath: artifactPath })?.startsWith('video/');
const handler = isDirectory ? zipHandler : isLog ? logHandler : 'copy';
const mimeType = isLog ? 'text/plain' : isDirectory ? 'application/zip' : undefined;
const $allure = (isLog || isVideo ? $$test : $step) ?? $step;
const name = path.basename(artifactPath);

$allure.fileAttachment(artifactPath, {
name,
mimeType,
handler,
});

return result;
};
}
};

export default listener;
export { listener as default } from './listener';
119 changes: 119 additions & 0 deletions src/listener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import fs from 'node:fs';
import path from 'node:path';

// eslint-disable-next-line import/no-internal-modules
import detox from 'detox';
// eslint-disable-next-line import/no-internal-modules
import { worker } from 'detox/internals';
// eslint-disable-next-line import/no-internal-modules
import { allure, type MIMEInferer } from 'jest-allure2-reporter/api';
// eslint-disable-next-line node/no-extraneous-import
import type { EnvironmentListenerFn } from 'jest-environment-emit';

import { createLogHandler, createZipHandler } from './file-handlers';
import { LogBuffer } from './logs';
import { ScreenshotHelper } from './screenshots';
import { wrapWithSteps } from './steps';
import type { DetoxAllure2AdapterOptions } from './types';

export const listener: EnvironmentListenerFn = (
{ testEvents },
{
useSteps = false,
deviceLogs = false,
deviceScreenshots = false,
}: DetoxAllure2AdapterOptions = {},
) => {
let logHandler: ReturnType<typeof createLogHandler>;
let zipHandler: ReturnType<typeof createZipHandler>;
let inferMimeType: MIMEInferer;
let $test: ReturnType<typeof allure.$bind> | undefined;
let artifactsManager: any;
let logs: LogBuffer | undefined;
let screenshots: ScreenshotHelper | undefined;

testEvents
.on('setup', () => {
allure.$plug((context) => {
logHandler = createLogHandler(context);
zipHandler = createZipHandler(context);
inferMimeType = context.inferMimeType;
});

artifactsManager = (worker as any)._artifactsManager;
artifactsManager.on('trackArtifact', onTrackArtifact);

if (deviceLogs) {
logs = new LogBuffer({
device: detox.device,
options: deviceLogs,
});
}

if (deviceScreenshots) {
screenshots = new ScreenshotHelper({
device: detox.device,
options: deviceScreenshots,
});
}
})
.on('setup', async () => {
if (useSteps) {
wrapWithSteps({ detox, worker, allure, logs, screenshots });
}
})
.on('test_start', () => {
$test = allure.$bind();
logs?.attachBefore(allure);
})
.on('hook_start', () => {
logs?.attachBefore(allure);
})
.on('hook_failure', async () => {
await screenshots?.attachFailure(allure);
logs?.attachAfterFailure(allure);
})
.on('hook_success', async () => {
await screenshots?.attachSuccess(allure);
logs?.attachAfterSuccess(allure);
})
.on('test_done', async ({ event }) => {
await screenshots?.attach(allure, event.test.failing);
logs?.attachAfter(allure, event.test.failing);
$test = undefined;
})
.on('teardown', flushArtifacts, -1)
.on('test_environment_teardown', flushArtifacts, -1);

async function flushArtifacts() {
await artifactsManager?._idlePromise;
await logs?.close();
artifactsManager = undefined;
logs = undefined;
}

function onTrackArtifact(artifact: any) {
const $step = allure.$bind();
const $$test = $test;
const originalSave = artifact.doSave.bind(artifact);

artifact.doSave = async (artifactPath: string, ...args: unknown[]) => {
const result = await originalSave(artifactPath, ...args);
const isDirectory = fs.lstatSync(artifactPath).isDirectory();
const isLog = path.extname(artifactPath) === '.log';
const isVideo = !!inferMimeType({ sourcePath: artifactPath })?.startsWith('video/');
const handler = isDirectory ? zipHandler : isLog ? logHandler : 'copy';
const mimeType = isLog ? 'text/plain' : isDirectory ? 'application/zip' : undefined;
const $allure = (isLog || isVideo ? $$test : $step) ?? $step;
const name = path.basename(artifactPath);

$allure.fileAttachment(artifactPath, {
name,
mimeType,
handler,
});

return result;
};
}
};
1 change: 1 addition & 0 deletions src/logs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './log-buffer';
Loading