selenium support #650

Closed
KhodeN opened this Issue Mar 31, 2015 · 18 comments

Projects

None yet

4 participants

@KhodeN
KhodeN commented Mar 31, 2015

Need ability to select file without open dialog (for selenium tests).
Usually, selenium send keys (file path) to <input type="file">, then submits form.

WebElement upload = driver.findElement(By.id("myfile"));
upload.sendKeys("/Users/sso/the/local/path/to/darkbulb.jpg");
driver.findElement(By.id("submit")).click();

How can I do this with ng-file-upload? input elements not exists until click :-(.

@KhodeN KhodeN changed the title from selenium supports to selenium support Mar 31, 2015
@joshribakoff

@KhodeN This directive did work in Selenium if you revert to 3.1

I actually had a ton of Protractor tests that use this directive. On Friday, they suddenly started failing. After spending dozens of man hours, our team finally found this in Intellij's local history:

selection_238

Someone must have added an unrelated bower dependency to our project, which changed the logic on which version of this directive got checked out. It appears that somewhere between 3.1 & 3.2, Selenium support was broken. In the end I fix it by changing from:

"ng-file-upload": "*",

to this:

"ng-file-upload": "~3.1.2",

I guess on Friday "something" happened which caused Bower to decide to give us 3.2 instead of 3.1 as it was doing. (If anyone has suggestions to "lock" bower's versions like you do with Composer, please PM me!)

I also typed up a stack overflow post that I never actually submitted, while I was debugging. I still have the tab open & I'll paste it here in case it helps anyone debug:

We had a Protractor test that uses sendKeys to type filename in to a <input type="file">. The test has been successfully uploading files for months. Today it stopped working.

    $('input[type="file"]').sendKeys(absolutePath);
    browser.actions().sendKeys(protractor.Key.ENTER).perform();

Today we noticed that this test began failing. We have reverted our code & all dependencies (composer, bower, npm) back to where we knew it was working. The tests still fail.

This lead us to believe the issue was a regression in Chrome, webdriver, selenium or Protractor itself. One by one we reverted each of these & the tests still fail.

In order to debug, I've setup a page called test.html that contains nothing but the upload directive. I've output the value of the ng-model for the upload directive as json. I also set the text of a <div> to "changed", and log an error to the console saying "got here".

Here is what I see when I select a file manually:
enter image description here

Here is what I see when Protractor selects the file (used to work):
enter image description here

As you can see, it appears the value of the input was set. I can also bind to it via jquery & confirm the change event is firing:

jQuery(document).on('ready', function() {
    jQuery('input[type="file"]').on('change', function () {
        console.error('yay!');
    });
});

I'm then observing the console errors in my test like this:

browser.manage().logs().get('browser').then(function(browserLog) {
        console.log('\n log: ' + require('util').inspect(browserLog));
    });

I see this output to my terminal when running the tests, which seems to indicate the change event of my input is indeed firing correctly:
enter image description here

##test-spec.js##:

var path = require('path');

describe('test', function() {

    // wait for angular
    beforeEach(function() {
        browser.ignoreSynchronization = false;
    });

    it('test', function() {
        browser.get('/test.html');
        var fileToUpload = 'fixtures/slide1.png';
        var absolutePath = path.resolve(__dirname, fileToUpload);
        $('input[type="file"]').sendKeys(absolutePath);
        browser.actions().sendKeys(protractor.Key.ENTER).perform();


        expect(element(by.css('#myLog')).getText()).toEqual('changed');

        browser.manage().logs().get('browser').then(function(browserLog) {
            console.log('\n log: ' + require('util').inspect(browserLog));
        });

        browser.pause();
    });

});

##test.html##:

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://code.jquery.com/jquery-1.11.2.js"></script>
    <script src="/vendor/angular/angular.js"></script>
    <script src="/vendor/ng-file-upload/angular-file-upload-all.js"></script>
</head>
<body ng-app="fileUpload" ng-controller="MyCtrl">

<pre>{{files|json}}</pre>
<input type="file" ng-file-select ng-model="files" name="file" accept="image/*" required>

<button ng-click="uploadPic(picFile)">Submit</button>


<div id="myLog"></div>

<script>

    jQuery(document).on('ready', function() {
        jQuery('input[type="file"]').on('change', function () {
            console.error('yay!');
        });
    });

    var app = angular.module('fileUpload', [ 'ngFileUpload' ]);
    var version = '3.2.4';

    app.controller('MyCtrl', [ '$scope', '$http', '$timeout', '$compile', '$upload', function($scope, $http, $timeout, $compile, $upload) {

        $scope.changeAngularVersion = function() {
            window.location.hash = $scope.angularVersion;
            window.location.reload(true);
        };

        $scope.angularVersion = window.location.hash.length > 1 ? (window.location.hash.indexOf('/') === 1 ?
                window.location.hash.substring(2): window.location.hash.substring(1)) : '1.2.20';

        $scope.$watch('files', function(files) {
            if(undefined !== files) {
                $scope.upload(files);
            }
        });

        $scope.upload = function (files) {
            jQuery('#myLog').html('changed');
            if (files && files.length) {
                for (var i = 0; i < files.length; i++) {
                    var file = files[i];
                    $upload.upload({
                        url: 'upload/url',
                        fields: {'username': $scope.username},
                        file: file
                    }).progress(function (evt) {
                        var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
                        console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name);
                    }).success(function (data, status, headers, config) {
                        console.log('file ' + config.file.name + 'uploaded. Response: ' + data);
                    });
                }
            }
        };



    } ]);
</script>
</body>
</html>

You should be able to copy & paste this & run Protractor. If you give v3.1 of the directive, tests are green & pass. If you put v3.2 of the upload directive, you get no side effects from the "change" event & tests fail.

@KhodeN
KhodeN commented Apr 1, 2015

I take my hat off to you) Big thx.

@KhodeN
KhodeN commented Apr 1, 2015

3.1.2 has bug - do not work correctly with accept="{{ ctrl.accept }}" attribute (with dynamic value). Rollback to 3.0.7.
Thanks for help and detailed explanation.

@danialfarid
Owner

From version 3.2.x a new file input element will be created and clicked on when you click on the file upload element. So the change event is firing on the newly created element.

@KhodeN
KhodeN commented Apr 1, 2015

Yes, I read source. But this behavior is not compatible with Protractor or webdriver (selenium). How can I write e2e test for your control?

@joshribakoff

I'd also imagine that breaks other things besides just Protractor. Probably older & mobile browsers, screen readers, maybe other libraries that expect events to work, etc...

Could it be made to "forward" events from one element to the other, to fix the issue(s)?

Also I'm not sure why a hidden element affects anything? Protractor will not interact with any hidden elements. Protractor is sending keys to the visible element, the same way a user would. It should be firing the same events on the same elements whether it is Protractor or a human user.

I even used jquery to filter for all file inputs in the source & only found 1 element. So I doubt that this explains things @danialfarid are you sure its not something more complex? If that were the case I would have gotten an error from Protractor stating that it refuses to click a hidden element. I would have then changed my selectors to simply match the visible newly created element. But this was not the issue I experienced. I also use Protractor to tests lots of directives that transclude and/or replace, replacing DOM elements doesn't cause this issue from my experience.

@danialfarid
Owner

Do you get the same result for input type file element and div?

@danialfarid
Owner

When you click on the ng-file-select element, if it is file input, it would replace it with a new file input element and would click on the new element. If it is a div or button element it would insert a file input element before that element and click on it and then remove it once you select the file and change event is fired.

@KhodeN
KhodeN commented Apr 12, 2015

Yes. But when user clicks on <div ng-file-select> the click event triggered on new <input type=file> and the window for select files shows. It's ok for real user, but for selenium (webdriver) tests the window blocks tests: test is expecting for user answer (selecting file).

P.S. My English is terrible, I know this)

@joshribakoff

But when user clicks on

the click event triggered on new and the window for select files shows.

In Selenium we're not to click on the file input. You "send keys" to the file input, which bypasses the file selection dialog altogether. Selenium is sending keys to the element that is visible that matched my selector. Even if the element is replaced, Selenium is interacting with the same replacement element that the real user is. So it would make sense to me that the issue is with "sending keys" to an input rather than clicking it. Its not likely that the issue is Selenium interacting with the wrong element. Its worth elaborating and explaining that Selenium is not using keyboard shortcuts to navigate to the file dialog either. From what I understand, its a special way Selenium modifies the state of the file input, bypassing certain events like click or key down. They actually say to blur (loose focus) to trigger the upload.... For example - I'm sending the "enter" key instead of doing a blur in my example, which works in the old version.

I've reverted the verison of this dependency in my project to a working state & don't have time to debug much further on the newer version.

@danialfarid
Owner

Since version 3.3.0 the underlying input file element will not be removed after the change event. That might fix the selenium problem, could you verify?

@KhodeN
KhodeN commented Apr 13, 2015

OK, today or tomorrow I'd check

@KhodeN
KhodeN commented Apr 14, 2015

No

  1. need to remove input element on scope $destroy
  2. need to input element already existed before starting the test
  3. alternatively you can listen some custom DOM event for call createFileInput to create just input without click on it. Thus test will can to create element to use it.
@sirkkalap

@danialfarid do I read you correctly at Apr 14, that you suggest we:

  1. use selenium to click the ngf-select button
  2. Wait for the dialog to dismiss
  3. use selenium "send keys" to type into the "not removed" "input type=file"
  4. submit somehow the upload

I have trouble implementing 2 and 4.

-Pete

@danialfarid
Owner

Please follow this thread: #710 (comment)
The change listener is registered after you click on the button, so you need to click first and then send key to the input with the same id.

@danialfarid danialfarid pushed a commit that referenced this issue Jun 4, 2015
Danial Farid Fixed #783 #764 #777 #766 #763 #761 #721 #687 #650 #710 #784 #768 #789 39fa784
@danialfarid
Owner

since version 5.0.0 you can set ngf-reset-on-click to false for testing

@danialfarid danialfarid closed this Jun 4, 2015
@KhodeN
KhodeN commented Jun 5, 2015

thx

@danialfarid
Owner

Since version 7 there won't be a new element created on each click so that would make the e2e testing easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment