Skip to content

Commit 999188f

Browse files
authored
Merge pull request #10 from AutomateThePlanet/add-web-tests
Add Screenshot on fail plugin
2 parents c6641f7 + f201943 commit 999188f

File tree

11 files changed

+116
-7
lines changed

11 files changed

+116
-7
lines changed

@bellatrix/appium/src/android/AndroidDriver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export class AndroidDriver extends AppiumDriver {
135135
});
136136
}
137137

138-
async getScreenshot(): Promise<Image> {
138+
async takeScreenshot(): Promise<Image> {
139139
const base64image = await this.commandExecutor.execute<Promise<string>>(MobileCommands.SCREENSHOT);
140140
return Image.fromBase64(base64image);
141141
}

@bellatrix/core/src/image/Image.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ export class Image {
5454
return `data:image/${this.type};base64,${this.base64}`;
5555
}
5656

57+
get buffer() {
58+
return this._buffer;
59+
}
60+
5761
get width(): number {
5862
switch (this._type) {
5963
case 'png':
@@ -172,10 +176,6 @@ export class Image {
172176
}
173177
}
174178

175-
protected get buffer() {
176-
return this._buffer;
177-
}
178-
179179
protected determineType(buffer: Buffer): keyof typeof this.SIGNATURES {
180180
for (const [format, signature] of Object.entries(this.SIGNATURES)) {
181181
if (buffer.subarray(0, signature.length).equals(signature)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Plugin } from '@bellatrix/core/infrastructure';
2+
import { TestMetadata } from '@bellatrix/core/test/props';
3+
import { ServiceLocator } from '@bellatrix/core/utilities';
4+
import { Image } from '@bellatrix/core/image';
5+
import { App } from '@bellatrix/web/infrastructure';
6+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
7+
import { dirname, extname, join } from 'path';
8+
import { BellatrixSettings } from '@bellatrix/core/settings';
9+
10+
export class ScreenshotOnFailPlugin extends Plugin {
11+
override async preAfterTest(metadata: TestMetadata): Promise<void> {
12+
const pluginSettings = BellatrixSettings.get().screenshotOnFailPluginSettings;
13+
14+
if (!pluginSettings?.isPluginEnabled) {
15+
return;
16+
}
17+
18+
if (!metadata.error) {
19+
return;
20+
}
21+
22+
const app = ServiceLocator.resolve(App);
23+
const screenshotImage = await app.browser.takeScreenshot();
24+
25+
const outputPath = pluginSettings?.outputPath;
26+
27+
if (!outputPath) {
28+
console.error('Output path for screenshots is not defined in the configuration.');
29+
return;
30+
}
31+
32+
try {
33+
const projectRoot = process.env['BELLATRIX_CONFIGURAITON_ROOT']!; // TODO: find a better way to get the project root
34+
const pathArray = [projectRoot, outputPath];
35+
if (pluginSettings?.shouldCreateFolderPerSuite) {
36+
pathArray.push(metadata.suiteName);
37+
}
38+
pathArray.push(metadata.testName);
39+
const savePath = this.saveImageToFile(screenshotImage, join(...pathArray));
40+
console.info('\n Screenshot for failed test ' + metadata.testName + ': ' + savePath + '\n');
41+
} catch (error) {
42+
if (error instanceof Error) {
43+
console.error('Error saving screenshot:', error.message);
44+
} else {
45+
console.error('Error saving screenshot');
46+
}
47+
}
48+
}
49+
50+
/**
51+
* Save an Image class instance as a file
52+
* @param image - The Image instance to be saved
53+
* @param outputPath - The path to save the image file
54+
*/
55+
private saveImageToFile(image: Image, outputPath: string): string {
56+
const outputDir = dirname(outputPath);
57+
if (!existsSync(outputDir)) {
58+
mkdirSync(outputDir, { recursive: true });
59+
}
60+
61+
const outputFilePath = extname(outputPath) ? outputPath : `${outputPath}.${image.type}`;
62+
63+
const binaryData = image.buffer;
64+
const arrayBufferView = new Uint8Array(binaryData.buffer, binaryData.byteOffset, binaryData.length);
65+
writeFileSync(outputFilePath, arrayBufferView);
66+
return outputFilePath;
67+
}
68+
}
69+
70+
declare module '@bellatrix/core/types' {
71+
interface BellatrixConfiguration {
72+
screenshotOnFailPluginSettings?: ScreenshotOnFailPluginSettings;
73+
}
74+
}
75+
76+
interface ScreenshotOnFailPluginSettings {
77+
isPluginEnabled: boolean;
78+
outputPath: string,
79+
shouldCreateFolderPerSuite?: boolean,
80+
shouldCaptureFullPage?: boolean,
81+
}
+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './LogLifecyclePlugin';
2+
export * from './ScreenshotOnFailPlugin';

@bellatrix/runner/bellatrix.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ const configs = [
8181
'.bellatrix.json',
8282
];
8383

84-
const configFileURL = pathToFileURL(findFilePath(configs));
84+
const configFilePath = findFilePath(configs);
85+
const configFileURL = pathToFileURL(configFilePath);
86+
process.env.BELLATRIX_CONFIGURAITON_ROOT = dirname(configFilePath);
8587
let config;
8688

8789
if (configFileURL.href.endsWith('.ts') || configFileURL.href.endsWith('.mts')) {

@bellatrix/web/src/infrastructure/browsercontroller/core/BrowserController.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Locator, SearchContext, WebElement } from '.';
2+
import { Image } from '@bellatrix/core/image';
23

34
export type Cookie = {
45
name: string;
@@ -30,6 +31,7 @@ export abstract class BrowserController implements SearchContext {
3031
abstract getCookie(name: string): Promise<Cookie | null>;
3132
abstract getAllCookies(): Promise<Cookie[]>;
3233
abstract clearCookies(): Promise<void>;
34+
abstract takeScreenshot(): Promise<Image>;
3335
abstract executeJavascript<T, VarArgs extends unknown[] = []>(script: string | ((...args: VarArgs) => T), ...args: VarArgs): Promise<T>;
3436
abstract waitUntil(condition: (browserController: Omit<BrowserController, 'waitUntil'>) => boolean | Promise<boolean>, timeout: number, pollingInterval: number): Promise<void>
3537

@bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightBrowserController.ts

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Cookie, BrowserController, WebElement, Locator } from '@bellatrix/web/i
44
import { PlaywrightWebElement } from '@bellatrix/web/infrastructure/browsercontroller/playwright';
55
import { BellatrixSettings } from '@bellatrix/core/settings';
66
import { HttpClient } from '@bellatrix/core/http';
7+
import { Image } from '@bellatrix/core/image';
78

89
export class PlaywrightBrowserController extends BrowserController {
910
private _browser: Browser;
@@ -48,6 +49,10 @@ export class PlaywrightBrowserController extends BrowserController {
4849
return await this._page.content();
4950
}
5051

52+
override async takeScreenshot(): Promise<Image> {
53+
return await Image.fromBase64((await this._page.screenshot()).toString('base64'));
54+
}
55+
5156
override async back(): Promise<void> {
5257
await this._page.goBack();
5358
}

@bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumBrowserController.ts

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { By, WebDriver as NativeWebDriver, until } from 'selenium-webdriver';
33
import { Cookie, BrowserController, WebElement, Locator } from '@bellatrix/web/infrastructure/browsercontroller/core';
44
import { SeleniumShadowRootWebElement, SeleniumWebElement } from '@bellatrix/web/infrastructure/browsercontroller/selenium';
55
import { BellatrixSettings } from '@bellatrix/core/settings';
6+
import { Image } from '@bellatrix/core/image';
67

78
export class SeleniumBrowserController extends BrowserController {
89
private _driver: NativeWebDriver;
@@ -32,6 +33,11 @@ export class SeleniumBrowserController extends BrowserController {
3233
return await this.wrappedDriver.getPageSource();
3334
}
3435

36+
override async takeScreenshot(): Promise<Image> {
37+
const base64image = (await this.wrappedDriver.takeScreenshot());
38+
return Image.fromBase64(base64image);
39+
}
40+
3541
override async back(): Promise<void> {
3642
await this.wrappedDriver.navigate().back();
3743
}

@bellatrix/web/src/services/BrowserService.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core';
22
import { BellatrixSettings } from '@bellatrix/core/settings';
33
import { BellatrixWebService } from '@bellatrix/web/services/decorators';
4+
import { Image } from '@bellatrix/core/image';
45
import { WebService } from '.';
56

67
@BellatrixWebService
@@ -21,6 +22,11 @@ export class BrowserService extends WebService {
2122
return await this.driver.getPageSource();
2223
}
2324

25+
async takeScreenshot(): Promise<Image> {
26+
const base64image = (await this.driver.takeScreenshot()).base64;
27+
return Image.fromBase64(base64image);
28+
}
29+
2430
async back(): Promise<void> {
2531
return await this.driver.back();
2632
}

example/bellatrix.config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ const config: BellatrixConfigurationOverride = {
4343
browserName: 'chrome'
4444
}
4545
}
46+
},
47+
screenshotOnFailPluginSettings: {
48+
isPluginEnabled: true,
49+
outputPath: `./reports/screenshots${Date.now()}`,
50+
shouldCreateFolderPerSuite: false,
4651
}
4752
};
4853

example/tests/ProductPurchaseTests.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Test, TestClass } from '@bellatrix/web/test';
22
import { WebTest } from '@bellatrix/web/infrastructure';
33
import { Button } from '@bellatrix/web/components';
44
import { ExtraWebHooks } from '@bellatrix/extras/hooks';
5-
import { LogLifecyclePlugin } from '@bellatrix/extras/plugins';
5+
import { LogLifecyclePlugin, ScreenshotOnFailPlugin } from '@bellatrix/extras/plugins';
66
import { MainPage, CartPage, CheckoutPage, PurchaseInfo } from '../src/pages';
77
import { PluginExecutionEngine } from '@bellatrix/core/infrastructure';
88
import { WebServiceHooks } from '@bellatrix/web/services/utilities';
@@ -14,6 +14,7 @@ export class ProductPurchaseTests extends WebTest {
1414
await super.configure();
1515
ExtraWebHooks.addComponentBDDLogging();
1616
PluginExecutionEngine.addPlugin(LogLifecyclePlugin);
17+
PluginExecutionEngine.addPlugin(ScreenshotOnFailPlugin);
1718
WebServiceHooks.addListenerTo(NavigationService).before('navigate', (_, url) => console.log(`navigating to ${url}`));
1819
}
1920

0 commit comments

Comments
 (0)