Evilweed / protractor-beautiful-reporter

An npm module which provides html reports of your Protractor tests with screenshots
MIT License
169 stars 40 forks source link

Feature Request: Add the option to be able to append multiple photos into a test #102

Open NicoForce opened 5 years ago

NicoForce commented 5 years ago

Currently, there's only one screenshot taken per test, but I'd like to be able to add multiple screenshots per test and be able to see them all in the report.

I was going to start modifying the library just for the small project I'm doing, but I guesses it would be better to create an issue here first.

I'll attach a picture example of how I want it to work to make my request more understandable.

report change

miller45 commented 5 years ago

@NicoForce Do you have idea how to do that? Because currently by rely on jasmine calling our specDone function... You are happy to modify the library and make a pullrequest (or at least show me how you would do it). Related to #85 #71

NicoForce commented 5 years ago

I'll give it a try on the weekend to see if I can make it work. I'll comment here on any update.

miller45 commented 5 years ago

Merging same request from issue #71 (now closed because included here)

Citation from @tocDK:

First thanks for a wonderful tool.

It could be nice to add the possibility to add more images per test, so if a test have multiple steps, it >takes a image pr test, and add it to the report

Citation from @aalokit

I also have same issue.. need to take more screenshots during the test case automation.. If there is any >solution please guide

miller45 commented 5 years ago

Merging similar request from issue #88 (now closed because included here) Citation from @KaiReichart

For my Tests I need Screenshots of expectations, would that be at all possible? Like the Plugin protractor-screenshoter-plugin has it.

miller45 commented 5 years ago

Merging similar request from issue #85 (now closed because included here)

While this could be a even more specialized variant because we would have to check the element locator itself and not just the failed expectation

Citation from @amedvedjev

When we have failed test and want to investigate fail reason by changed element locator HTML screenshot will be quite useful to quick check.

miller45 commented 5 years ago

Merging similar request from issue #61 (now closed because included here) Citation from @anudeepsinghpatelgithub

I am always getting screentshot of the page which is defind in afterEach method. how to take screenshot on failure step, not in afterEach method. below is my sample code

See original issue below for full content (including source code)

NicoForce commented 5 years ago

Just came back from long vacations,

Found myself modifying the index.js file but it seems autogenerated, can you explain how this index.js gets all its content?

miller45 commented 5 years ago

"Long vacations"...Happy you Welcome back... For testing it can be sufficient to edit index.js directly but the actual source code is in the 'app' folder (files reporter.js and util.js) The index.js is generated when you start the npm task app:compile. Do you have own testing project? or do you use the one in the examples folder? I think you should use "npm link" during development (see https://docs.npmjs.com/cli/link.html).

miller45 commented 5 years ago

The npm link will enable you to use you local development npm package instead the npm servers, so dont have to hack in your node_modules folder directly....

NicoForce commented 5 years ago

Yeah, I have my own testing project, I was modifying the app folder, no wonder things weren't changing when I run my test many times. I had to do app:compile 😝

Thanks for suggesting the npm link, I've had to use it before for some hacky mainframe automation npm libraries.

I'll update if I make anything work.

NicoForce commented 5 years ago

I'm going to update what I've changed so far in protractor beautiful reporter.

I didn't manage to append multiple photos yet, but I did make the report not only take browser screenshots, but to take screenshot of whatever is on the screen. I made this due to personal requirements, since I'm also automating an AS400 screen, simultaneously.

Examples as to why I would want to do this are, to add screenshots of applications that are outside the browser, such as an AS400 screen, a PDF, Word Document, Excel, Desktop Application and so on.

In the reporter.js file, I imported the following libraries:

const cp = require('child_process');
const base64Img = require('base64-img');

In the _takeScreenShotAndAddMetaData method, I changed the last if containing the browser screenshot to the following, using the above mentioned libraries:

if ((result.status !== 'pending' && result.status !== 'disabled') && !(this._screenshotReporter.takeScreenShotsOnlyForFailedSpecs && result.status === 'passed')) {
    let imageName = __dirname+'\\image\\prueba';
    cp.execSync('%cd%\\node_modules\\protractor-beautiful-reporter\\Captura.exe '+imageName);
    let image64= base64Img.base64Sync(imageName+'.jpg');
    let array = image64.replace(',',' ').split(' ');
    cp.execSync('del '+imageName+'.jpg');
    util.storeScreenShot(array[1], screenShotPath);
}

Take notice of 'Captura.exe', which will take care of taking the screenshot. Since the library expects an image in base64 encoding, I convert the .jpg generated by the .exe to store it for the report.

For reference, I tried with the following libraries in order to take the screenshot, but none of these worked:

PD: This Solution only works for Windows OS, I'm particularly working on Windows 7.

NicoForce commented 5 years ago

As for how to change between application screens in the middle of the test, I used the following:

In a class I use as a Base for all other classes:

import * as ffi from 'ffi';

export class BasePage {
    private user32 = new ffi.Library('user32', {
        AttachThreadInput: ['bool', ['int', 'long', 'bool']],
        BringWindowToTop: ['bool', ['long']],
        FindWindowA: ['long', ['string', 'string']],
        GetForegroundWindow: ['long', []],
        GetTopWindow: ['long', ['long']],
        GetWindowThreadProcessId: ['int', ['long', 'int']],
        SetActiveWindow: ['long', ['long']],
        SetFocus: ['long', ['long']],
        SetForegroundWindow: ['bool', ['long']],
        SetWindowPos: ['bool', ['long', 'long', 'int', 'int', 'int', 'int', 'uint']],
        ShowWindow: ['bool', ['long', 'int']],
        SwitchToThisWindow: ['void', ['long', 'bool']],
    });

    private kernel32 = new ffi.Library('Kernel32.dll', {
        GetCurrentThreadId: ['int', []],
    });

    public activateWindow(window = null, title = null) {
        const winToSetOnTop = this.user32.FindWindowA(window, title);
        const foregroundHWnd = this.user32.GetForegroundWindow();
        const currentThreadId = this.kernel32.GetCurrentThreadId();
        const windowThreadProcessId = this.user32.GetWindowThreadProcessId(foregroundHWnd, null);
        this.user32.ShowWindow(winToSetOnTop, 9);
        this.user32.SetWindowPos(winToSetOnTop, -1, 0, 0, 0, 0, 3);
        this.user32.SetWindowPos(winToSetOnTop, -2, 0, 0, 0, 0, 3);
        this.user32.SetForegroundWindow(winToSetOnTop);
        this.user32.AttachThreadInput(windowThreadProcessId, currentThreadId, 0);
        this.user32.SetFocus(winToSetOnTop);
        this.user32.SetActiveWindow(winToSetOnTop);
    }

    public activateMainframe() {
        this.activateWindow('PCSWS:Main:00400000', null);
    }

    public activateBrowser() {
        this.activateWindow(null, 'data:, - Google Chrome');
    }
}

The function user32.findWindowA will ask for either the class or the title of the window you want to bring to the foreground, so I used AutoIt Inspector to get either the class or title of whatever window I plan to take a screenshot of.

PD: This Solution only works for Windows OS, I'm particularly working on Windows 7. x2 PD2: The class uses typescript, since all my protractor project uses it.

In case of tl;dr, I still haven't managed to solve the issue's main problem but I'll keep working on it.

britvik commented 5 years ago

Could we get screenshots for failed expectations? Protractor FAQ mentions how to do this:

For failures of individual expectations, you can override jasmine's addMatcherResult/addExpectationResult function as such:

// Jasmine 2
var originalAddExpectationResult = jasmine.Spec.prototype.addExpectationResult;
jasmine.Spec.prototype.addExpectationResult = function() {
  if (!arguments[0]) {
    // take screenshot
    // this.description and arguments[1].message can be useful to constructing the filename.
  }
  return originalAddExpectationResult.apply(this, arguments);
};
Moke96 commented 5 years ago

Is this still being worked on? I would like to help but have not idea where to start tbh.

Moke96 commented 5 years ago

I did manage to make it work somehow, but it's super ugly. test

I changed my conf.js :

onPrepare: async function () {

    var reporter = new HtmlReporter({
      baseDirectory: 'beautifulReporter/'
      , takeScreenShotsOnlyForFailedSpecs: true
      , docTitle: 'Test'
      , preserveDirectory: false
    });
    jasmine.getEnv().addReporter(
      reporter
        .getJasmine2Reporter());

    ExpectFailed(reporter);
} 
function ExpectFailed(rep) {
      var originalAddExpectationResult = jasmine.Spec.prototype.addExpectationResult;
      jasmine.Spec.prototype.addExpectationResult = function (passed, expectation) {
        var self = rep;

        if (!passed) {
          let tst = util.generateGuid();
          let screenShotFileName = path.basename(tst + '.png');
          let screenShotFilePath = path.join(path.dirname(self.baseName + '.png'), self.screenshotsSubfolder);
          let screenShotPath = path.join(self.baseDirectory, screenShotFilePath, screenShotFileName);
          self.screenshotArray.push(path.join(self.screenshotsSubfolder, screenShotFileName));
          try {
            browser.takeScreenshot().then(png => {
              util.storeScreenShot(png, screenShotPath);
            });
          }
          catch (ex) {
            if (ex['name'] === 'NoSuchWindowError') {
              console.warn('Protractor-beautiful-reporter could not take the screenshot because target window is already closed');
            } else {
              console.error(ex);
              console.error('Protractor-beautiful-reporter could not take the screenshot');
            }
            metaData.screenShotFile = void 0;
          }
        }
        return originalAddExpectationResult.apply(this, arguments);
      }
    }

Notice that I use util.js to generate a Guid, also the metaData.ScreenshotFile is now an Array.

In index.js I added a screenshotArray option (mainly because I had no better idea on how to reach the Array in my ExpectFailed(rep) function) and changed some code in "_takeScreenShotAndAddMetaData" if-statement :

this.screenshotArray = options.screenshotArray || [];
if (!(this._screenshotReporter.takeScreenShotsOnlyForFailedSpecs && result.status === 'passed')) {
                                  this._screenshotReporter.screenshotArray.push(path.join(this._screenshotReporter.screenshotsSubfolder, screenShotFileName));
                                  metaData.screenShotFile = [...this._screenshotReporter.screenshotArray];
                                  this._screenshotReporter.screenshotArray.length = 0;
                                }

The combined.json has an Array of screenshots at the screenShotFile tag. Now we 'only' need to adjust the index.html file to iterate through the array

<td ng-repeat="screenShotFile in result.screenShotFile track by $index"
                    ng-class="{'mediumColumn': ctrl.inlineScreenshots && screenShotFile}">
                    <div ng-if="!result.pending">
                        <span ng-if="!ctrl.inlineScreenshots && screenShotFile"
                            class="label label-white ng-binding pull-right" data-toggle="modal"
                            data-target="#imageModal{{($parent.$parent.$parent.$index *100) + $index}}">
                            <span class="glyphicon glyphicon-picture black"></span>
                        </span>
                        <a href="" ng-if="ctrl.inlineScreenshots && screenShotFile" data-toggle="modal"
                            data-target="#imageModal{{($parent.$parent.$parent.$index *100) + $index}}">
                            <img ng-src="{{screenShotFile}}" class="screenshotLink" /></a>
                        <!-- Screenshot Modal -->
                        <div class="modal" id="imageModal{{($parent.$parent.$index *100) + $index}}" tabindex="-1" role="dialog"
                            aria-labelledby="imageModalLabel{{$index}}">
                            <div class="modal-dialog modal-lg" role="document">
                                <div class="modal-content">
                                    <div class="modal-header">
                                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                            <span aria-hidden="true">&times;</span>
                                        </button>
                                        <h6 class="modal-title" id="imageModalLabel{{$index}}">
                                            {{ctrl.getParent(result.description)}}</h6>
                                        <h5 class="modal-title" id="imageModalLabel{{$index}}">
                                            {{ctrl.getShortDescription(result.description)}}</h5>
                                    </div>
                                    <div class="modal-body">
                                        <img class="screenshotImage" ng-src="{{screenShotFile}}">
                                    </div>
                                    <div class="modal-footer">
                                        <div class="pull-left">
                                            <button ng-disabled="!($index > 0)" class="btn btn-default"
                                                data-dismiss="modal" data-toggle="modal"
                                                data-target="#imageModal{{$index - 1}}">
                                                Prev
                                            </button>
                                            <button ng-disabled="!($index < ctrl.results.length - 1)"
                                                class="btn btn-default" data-dismiss="modal" data-toggle="modal"
                                                data-target="#imageModal{{$index + 1}}">
                                                Next
                                            </button>
                                        </div>
                                        <a class="btn btn-primary" href="{{screenShotFile}}" target="_blank">
                                            Open Image in New Tab
                                            <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
                                        </a>
                                        <button type="button" class="btn btn-default"
                                            data-dismiss="modal">Close</button>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </td>

And thats it! Like I said, it's a super ugly hacked together mess but it works somehow.

OldShaterhan commented 4 years ago

Quite interesting, would be nice to be implemented.