Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic screenshots on error? #269

Closed
max-degterev opened this issue Aug 12, 2014 · 18 comments
Closed

Automatic screenshots on error? #269

max-degterev opened this issue Aug 12, 2014 · 18 comments

Comments

@max-degterev
Copy link

Hi!

I'm trying to set up automatic screenshots creation on error. I have following folder structure:

test/
  screenshots/
  suite/

I am creating a webdriverio client passing screenshotPath: "./test/screenshots" as an option, but that doesn't seem to affect anything.

I was trying to add

client.on('error', (err)-> @capture('error'))

but this doesn't get called at all.

The only thing that actually works is

process.on('uncaughtException', (err)-> client?.capture('error'))

but sometimes it fails too.

This is an example if a test that fails:

  it 'should have correct number of cards', (done)->
    client
      .waitForExist('.p-h-promoted .b-p-list', 3000)
      .isVisible cards, (err, isVisible)-> expect(isVisible).to.have.length(25)
      .isVisible doubleCards, (err, isVisible)-> expect(isVisible).to.have.length(5)
      .call(done)

What would be the best way to handle this?

@nickyout
Copy link

Seeing that it has been 6 days since you posted this yielding no suggestions, I'm just going to drop my thoughts.

As far as I can remember from my experiences, the following rules hold:

  • normally, you are supposed to catch any error in a callback function, and trigger a snapshot there. This would mean callbacking every command afaik
  • if the asynchronous webdriverio command unexpectedly throws an error (instead of returning it on callback, you are (probably) out of luck.

Frankly, if there is a better solution than this, I would like to know as well. Maybe yours is, and you are just looking at one of the cases where webdriverio unexpectedly throws an error.

Also, this is one of the reasons I went for the synchronize solution. Pass all webdriverio commands (except event handling methods) through, start up a fiber, try catch over the whole block to catch all its errors at once (and have the ease of an synchronous API). It still does not catch the unexpected errors through.

To the maintainers, I will be more specific about any errors I find as soon as I know for sure they are there and they are caused by webdriverio. All I have right now are hunches, and they generally coincide with mistakes I've made as well (webdriverio unexpected behavior --> try to debug --> find own error --> fix error --> webdriverio works).

@max-degterev
Copy link
Author

Thanks @nickyout. I was trying to catch all errors automatically with one event/callback. uncaughtException catches most, but the problem is that webdriver is sometimes dead by the time it's triggered, and can't take a screenshot in that case.

@nickyout
Copy link

Yeah I get that. I just wonder if webdriverio is supposed to shut down when you do not add a callback to catch the error, or that the fact that nodejs kills the process on an uncaught error does that. If nodejs does that, you might have found a bug in webdriverio that causes it to die before it can report an error. That would explain the symptoms - process.on('uncaughtError') catches everything afaik.

@ctumolosus
Copy link

I'm a bit new here, so forgive my ignorance, what you are saying is that errors passed to command callbacks, such as .getTitle, will not reject the webdriverio promise chain unless you threw the error? For example:

require('webdriverio').remote().init().url('www.example.com').click('#selector', function (err) {
    if (err) throw err; // will this stop `.getTitle` from being executed?
}).getTitle(function (err, title) {
    if (err) throw err;
    if (title !== 'title') throw new Error('Title should match `title`');
}).end();

@nickyout
Copy link

I don't know, but I certainly expect an uncaught error to kill the node process, and webdriverio with it. Afaik, all callback passed errors must be received in a user defined callback function (like your example) or else webdriverio will stop itself. So I would expect that the throw err would indeed stop .getTitle() from being executed. Or .end(), for that matter. And if you leave the throw out, .getTitle() would be called because you received the error in a callback function. Afaik.

@christian-bromann
Copy link
Member

@nickyout you're right, thanks for clarification.

So basically if you have an custom callback like:

client.command(parameter1, function callback(err, val, res) {
    // ...
});

You need to check if an error was thrown. This happens when the selenium server wasn't able to execute the command properly. But things like:

client.command(parameter1, function callback(err, val, res) {
    if (err) throw err;
});

look pretty verbose and not nice. Since WebdriverIO (since v2.0.0) all commands without an custom callback will throw the error automatically, you don't have to check it manually. You can get rid of the callback:

client.command(parameter1);

In case you want to work with the response values (e.g. when calling getTitle and you want to check if title equals something), you still have to check manually if an error was passed to the callback function. But usually when an error occurs the callback value is null or undefined, so your test would break anyway (in most cases).

Hope that clarifies it a bit more. Let me know if you still have questions!

@suprMax what test framework do you use? If you want to make a screenshot if a command fails I would recommend to do that within a teardown function (like after) it should still get executed when something fails during the test. I know that in the earlier days WebdriverJS was supposed to do that automatically. That is why you can pass a screenshot directory as option to the remote function. Seems to be that after all these changes it doesn't work anymore properly. I will dig into it and will make it work again. I'll keep you posted.

@max-degterev
Copy link
Author

@christian-bromann I'm using mocha. I think after would be too late as it's executed after all steps are done. So if there is a test failing in the middle it'll be too late, you won't capture the problem.

@christian-bromann
Copy link
Member

Yeah but was I meant is something like this:

describe('my app', function(

    describe('has a certain feature which', function(
        it('should do this', function(done) {
            //... do some selenium stuff here
        })
        // capture screenshot when it fails
        after(captureScreenshot)
    });

    describe('has a certain other feature which', function(
        it('should do this', function(done) {
            //... do some selenium stuff here
        })
        // capture screenshot when it fails
        after(captureScreenshot)
    })
})

Like I said, I will dig into it later. So it should do a screenshot first and then throw the error.

@nickyout
Copy link

I would like to add here that in almost all cases I can catch an error and create a screenshot, as long as webdriverio returns the error as it is supposed to. I'm using the synchronize trick, but essentially it would mean that you should still be able to make a screenshot in the error receiving callback. As long as you catch any potential error after every step... tedious, perhaps, but if you make a dedicated function that makes a screenshot on error and pass that as callback to every command you can at least get your screenshots with webdriverio's current functionality.

@christian-bromann
Copy link
Member

@suprMax I looked into it and discovered that a screenshot only gets taken when a Selenium command wasn't executed properly. In this case the server returns a base64 encoded image which gets saved at your desired screenshotPath.

Have you found a desirable solution for that? Your goal is to take a screenshot if an assertion fails, right? The think is that it is hard to detect when this happens. We could introduce own assertion functions but I this is not part of the job of WebdriverIO. What do you thing? How we can improve WebdriverIO to do something like this? I guess many people want to know how the application looks like when something fails to get an idea what's broken.

@max-degterev
Copy link
Author

@christian-bromann I think it's best to rely on mocha and chai for assertions. It's quite easy to catch assertion errors and it seems to work perfectly now.

  client.addCommand 'capture', (name)->
    done = _.last(arguments)

    date = (new Date).toString().replace(/\s/g, '-').replace(/\-\(\w+\)/, '')
    location = "#{screenshotsLocation}/#{screenshotsCount++}-#{name}_#{date}.png"
    @saveScreenshot(location, false, done)

process.on('uncaughtException', (err)-> client?.capture('error'))

You can probably capture these automatically as well, having an option to enable them. Failing assertions always throw.

@shadiabuhilal
Copy link

@suprMax , you can do it by using afterTest function in wdio.conf.js file, like this:

/**
     * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
     * @param {Object} test test details
     */
    afterTest: function (test) {
        // if test passed, ignore, else take and save screenshot.
        if (test.passed) {
            return;
        }
        // get current test title and clean it, to use it as file name
        var filename = encodeURIComponent(test.title.replace(/\s+/g, '-'));
        // build file path
        var filePath = this.screenshotPath + filename + '.png';
        // save screenshot
        browser.saveScreenshot(filePath);
        console.log('\n\tScreenshot location:', filePath, '\n');
    },

@zeljkofilipin
Copy link

For anybody landing here after a search, @christian-bromann said today at https://gitter.im/webdriverio/webdriverio "the last comment actually nailed it" cc @shadiabuhilal

@alexandlazaris
Copy link

@shadiabuhilal - Ahh nice work! This worked for me (albeit, if I had 2 browsers within capabilities, it would error out and screenshot the failure from only 1 browser).

This code was on webdriver.io however it didn't work for me, no screenshot found:

jasmineNodeOpts: {
        defaultTimeoutInterval: 10000,
        expectationResultHandler: function(passed, assertion) {
            if(passed){ return; }
            browser.screenshot('assertionError_' + assertion.error.message + '.png');
        }
    },

@vincebowdren
Copy link

@shadiabuhilal Thanks for that code suggestion. I've got a revised version, after I ran into a problem with saving screenshots like that( if a file already exists with that name, it fails to save the screenshot). The important change is to use a timestamp in the filename:

    var path = require('path');
    ...
    afterTest: function (test) {
        // if test passed, ignore, else take and save screenshot.
        if (test.passed) {
            return;
        }
        // Construct file name:
        var browserName = browser.desiredCapabilities.browserName;
        var timestamp = new Date().toJSON().replace(/:/g, '-');
        var filename = 'TESTFAIL_' + browserName + '_' + timestamp + '.png';
        var filePath = path.join(this.screenshotPath, filename);
        // save screenshot
        browser.saveScreenshot(filePath);
        console.log('\tSaved screenshot: ', filePath);
    }

Note: I based this code on the webdriverio code (function saveScreenshotSync in webdriverio.js) which takes screenshots when a command errors.

@jcollum-autodesk
Copy link

jcollum-autodesk commented Sep 30, 2019

I can't get this to work. I'm using Mocha

       "@wdio/allure-reporter": "^5.12.0",
        "@wdio/cli": "^5.11.13",
        "@wdio/junit-reporter": "^5.12.1",
        "@wdio/local-runner": "^5.11.13",
        "@wdio/mocha-framework": "^5.11.0",
        "@wdio/runner": "^5.11.13",
        "@wdio/sauce-service": "^5.11.1",
        "@wdio/spec-reporter": "^5.11.7",
        "@wdio/sync": "^5.11.13",
        "chai": "^4.2.0",
        "chromedriver": "^77.0.0",
        "deepmerge": "^4.0.0",
        "geckodriver": "^1.16.2",
        "mocha": "^6.2.0",
        "wdio-chromedriver-service": "^5.0.2",
        "wdio-geckodriver-service": "^1.0.2",
        "wdio-video-reporter": "^1.4.4",
        "wdio-webdriver-service": "^0.1.7",
        "webdriverio": "^5.11.13"

my code looks like this:

          afterTest: function(test) {
		console.log('firing afterTest', '\ntest', test);
		// if test passed, ignore, else take and save screenshot.
		if (test.passed) {
			return;
		}
		console.log('test failed, taking screenshot');
... 

But the second console.log is never reached, despite 3 of my tests failing. Does this not work with Mocha or something?

@mgrybyk
Copy link
Member

mgrybyk commented Sep 30, 2019

Why do you think it should work?

Please see an example in docs https://webdriver.io/docs/allure-reporter.html#add-screenshots

You can always put breakpoint and inspect available arg properties

@Shreya76
Copy link

@suprMax , you can do it by using afterTest function in wdio.conf.js file, like this:

/**
     * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
     * @param {Object} test test details
     */
    afterTest: function (test) {
        // if test passed, ignore, else take and save screenshot.
        if (test.passed) {
            return;
        }
        // get current test title and clean it, to use it as file name
        var filename = encodeURIComponent(test.title.replace(/\s+/g, '-'));
        // build file path
        var filePath = this.screenshotPath + filename + '.png';
        // save screenshot
        browser.saveScreenshot(filePath);
        console.log('\n\tScreenshot location:', filePath, '\n');
    },

Hi I am using this code to take screenshot of the failed test. But I see there are few more screenshots taken for the passes tests as well. It starts with the name ERRORCHROME.
I tried to comment AtrerTest{} , still the screenshot is been taken and stored in the same location.
Please help me on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests