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

selenium support #650

Closed
KhodeN opened this issue Mar 31, 2015 · 20 comments
Closed

selenium support #650

KhodeN opened this issue Mar 31, 2015 · 20 comments

Comments

@KhodeN
Copy link

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 selenium supports selenium support Mar 31, 2015
@joshribakoff
Copy link

@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
Copy link
Author

KhodeN commented Apr 1, 2015

I take my hat off to you) Big thx.

@KhodeN
Copy link
Author

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
Copy link
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
Copy link
Author

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
Copy link

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
Copy link
Owner

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

@danialfarid
Copy link
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
Copy link
Author

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
Copy link

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
Copy link
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
Copy link
Author

KhodeN commented Apr 13, 2015

OK, today or tomorrow I'd check

@KhodeN
Copy link
Author

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
Copy link

@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
Copy link
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 pushed a commit that referenced this issue Jun 4, 2015
@danialfarid
Copy link
Owner

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

@KhodeN
Copy link
Author

KhodeN commented Jun 5, 2015

thx

@danialfarid
Copy link
Owner

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

@kbangur
Copy link

kbangur commented Jul 21, 2017

This thread went to a lot of discussions and I am finding it difficult to summarize! So can someone please help me understand what the problem was altogether? And also, how the solution was achieved? I am testing a similar AngularJS custom directive using Sahi web driver. The approach was very similar to that of Selenium where in Sahi proxy would 'inject' the file to be uploaded into the Http request against the file input element and can be submitted as usual. But the same is not working in my case only when I was testing an AngularJS file upload component.

@sirkkalap
Copy link

My workaround was to find the hidden file-upload input and inject javascript to unhide it before using Selenium Webdriver .type() function to enter the upload filename to the field. I am unsure about any other working solutions.

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

5 participants