In my project i use AngularJS so a directive for downloading files was created. It contains the following:
scope.$on('downloaded', function(event, data) {
var hiddenLink = document.createElement('a');
$(hiddenLink).attr({
href: 'data:application/tiff;base64,' + data.Attachment,
download: data.AttachmentFileName
});
if (isIEorFirefox) {
$(hiddenLink).click(function(event){
event.preventDefault();
var byteString = atob(data.Attachment);
var buffer = new ArrayBuffer(byteString.length);
var intArray = new Uint8Array(buffer);
for (var i = 0; i < byteString.length; i++) {
intArray[i] = byteString.charCodeAt(i);
}
var blob = new Blob([buffer],{type:'image/tiff'});
window.navigator.msSaveOrOpenBlob(blob, data.AttachmentFileName);
});
$(hiddenLink).trigger('click');
} else {
hiddenLink.click();
}
});
Previously there was an issue - download in IE simply didn't start - but for now as you can it has been eliminated. Though another issue remains - currently this code doesn't start download in Firefox. There is only one question - why?
UPDATE:
I've updated initial code because it didn't save file properly in IE. Now it does. Searching over the web i still cannot find a way to make file download in FF. Moreover FF still seems not to have any native way to save files according to this article https://hacks.mozilla.org/2012/07/why-no-filesystem-api-in-firefox/. I would be grateful if someone prove me wrong.
hiddenLink.click();
should perhaps be:
$(hiddenLink).click();
or same as other:
$(hiddenLink).trigger('click');
Assume you also need the event handler added as well...
Related
I'm implementing file download using AngularJS and WCF. My back-end is a .NET project hosted in IIS. The file is serialized as an array of bytes and then on the client side I utilize the File API to save the content.
To simplify the problem, back-end is like:
[WebInvoke(Method = "GET", UriTemplate = "FileService?path={path}")]
[OperationContract]
public byte[] DownloadFileBaseOnPath(string path)
{
using (var memoryStream = new MemoryStream())
{
var fileStream = File.OpenRead(path);
fileStream.CopyTo(memoryStream);
fileStream.Close();
WebOperationContext.Current.OutgoingResponse.Headers["Content-Disposition"] = "attachment; filename=\"Whatever\"";
WebOperationContext.Current.OutgoingResponse.ContentType = "application/octet-stream"; // treat all files as binary file
return memoryStream.ToArray();
}
}
And on client side, it just sends a GET request to get those bytes, converts in into a blob and save it.
function sendGetReq(url, config) {
return $http.get(url, config).then(function(response) {
return response.data;
});
}
Save the file then:
function SaveFile(url) {
var downloadRequest = sendGetReq(url);
downloadRequest.then(function(data){
var aLink = document.createElement('a');
var byteArray = new Uint8Array(data);
var blob = new Blob([byteArray], { type: 'application/octet-stream'});
var downloadUrl = URL.createObjectURL(blob);
aLink.setAttribute('href', downloadUrl);
aLink.setAttribute('download', fileNameDoesNotMatter);
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', false, false);
aLink.dispatchEvent(event);
}
else {
aLink.click();
}
setTimeout(function () {
URL.revokeObjectURL(downloadUrl);
}, 1000); // cleanup
});
}
This approach works fine with small files. I could successfully download files up to 64MB. But when I try to download a file larger than 64MB, the response.body is empty in Chrome. I also used Fiddler to capture the traffic. According to Fiddler, Back-end has successfully serialized the byte array and returned it. Please refer to the screenshot below.
In this example, I was trying to download a 70MB file:
And the response.data is empty:
Any idea why this is empty for file over 70MB? Though the response itself is more than 200MB, I do have enough memory for that.
Regarding to the WCF back-end, I know I should use Stream Mode when it comes to large files. But the typical use of my application is to download files less than 10MB. So I hope to figure this out first.
Thanks
Answer my own question.
Honestly I don't know what's going wrong. The issue still persists if I transfer it as a byte array. I eventually gave up this approach by returning a stream instead. Then on the client side, adding the following configuration
{responseType : blob}
and save it as a blob.
I currently take a base 64 encoded string that represents a PDF file and convert it to a blob in AngularJS using the following code (simplified):
var base64PdfToBlob = function(b64Data: string, contentType: string) {
var sliceSize = 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, { type: contentType });
return blob;
}
var pdfBlob = base64PdfToBlob(base64Pdf, "application/pdf");
var blobUrl = URL.createObjectURL(pdfBlob);
$scope.pdfDoc = $sce.trustAsResourceUrl(blobUrl);
I then embed the pdf in the html using the following code:
<object data="{{pdfDoc}}" type="application/pdf" width="100%" height="100%"></object>
This works in chrome, firefox, and safari. This does NOT work in Edge or IE11. I feel like I have tried everything with nothing working.
Things I have tried:
1. Playing with the embed and object tags.
2. Uninstalling and reinstalling adobe reader. Messing with the settings and configurations between adobe reader and IE.
3. Changing security settings in IE.
Doing an iFrame, new tab, new window, downloading pdf is not an option. The PDF must be embedded on the current page. Some documentation I am reading seems to state this is NOT possible in Edge but should work in IE.
My machine is Windows 10, 64 bit, IE11, Adobe Acrobat Reader DC 2015.017.20050
URL.createObjectURL() is not compatible with IE11. The Mozilla docs state that this function is compatible starting with IE12, though.
I do not believe that it is possible to embed the inline blob data representing a PDF in IE11 or Edge. There is a separate function for handling blobs in IE called msSaveOrOpenBlob, but this has the drawback of prompting the user if they want to save or open the file.
See also: Open links made by createObjectURL in IE11
To my understanding Protractor is meant to run on top of WebDriver on a Node.js server and send commands to a selenium server (feel free to correct me if I'm wrong).
Anyway, I was wondering if it is possible to load Protractor into a web page as a JavaScript library (like you would load jQuery for example) so it would be accessible from the JavaScript code in the page.
Can it be done? If so, how? What files do I need? What dependencies?
My goal is to use its capabilities of selecting elements by their various angular bindings, and its waiting for angular events capabilities.
I don't think you can. You can write your own locators, or use directives to isolate element/binding. Another good thing to do would be to dive into the protractor source code and see how they do it. Particularly check out their clientsidescripts.js file. Here is an example of how they find bindings. You would call it like this: findBindings('exampleBinding');
clientSideScripts.findBindings = function() {
var binding = arguments[0];
var using = arguments[1] || document;
var bindings = using.getElementsByClassName('ng-binding');
var matches = [];
for (var i = 0; i < bindings.length; ++i) {
var dataBinding = angular.element(bindings[i]).data('$binding');
if(dataBinding) {
var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding;
if (bindingName.indexOf(binding) != -1) {
matches.push(bindings[i]);
}
}
}
return matches; // Return the whole array for webdriver.findElements.
};
I want to test file uploading using an angularjs e2e test. How do you do this in e2e tests? I run my test script through grunt karma.
This is how I do it:
var path = require('path');
it('should upload a file', function() {
var fileToUpload = '../some/path/foo.txt',
absolutePath = path.resolve(__dirname, fileToUpload);
element(by.css('input[type="file"]')).sendKeys(absolutePath);
element(by.id('uploadButton')).click();
});
Use the path module to resolve the full path of the file that you want to upload.
Set the path to the input type="file" element.
Click on the upload button.
This will not work on firefox. Protractor will complain because the element is not visible. To upload in firefox you need to make the input visible. This is what I do:
browser.executeAsyncScript(function(callback) {
// You can use any other selector
document.querySelectorAll('#input-file-element')[0]
.style.display = 'inline';
callback();
});
// Now you can upload.
$('input[type="file"]').sendKeys(absolutePath);
$('#uploadButton').click();
You can't directly.
For security reason, you can not simulate a user that is choosing a file on the system within a functional testing suite like ngScenario.
With Protractor, since it is based on WebDriver, it should be possible to use this trick
Q: Does WebDriver support file uploads? A: Yes.
You can't interact with the native OS file browser dialog directly,
but we do some magic so that if you call
WebElement#sendKeys("/path/to/file") on a file upload element, it does
the right thing. Make sure you don't WebElement#click() the file
upload element, or the browser will probably hang.
This works just fine:
$('input[type="file"]').sendKeys("/file/path")
Here is a combo of Andres D and davidb583's advice that would have helped me as I worked through this...
I was trying to get protractor tests executed against the flowjs controls.
// requires an absolute path
var fileToUpload = './testPackages/' + packageName + '/' + fileName;
var absolutePath = path.resolve(__dirname, fileToUpload);
// Find the file input element
var fileElem = element(by.css('input[type="file"]'));
// Need to unhide flowjs's secret file uploader
browser.executeScript(
"arguments[0].style.visibility = 'visible'; arguments[0].style.height = '1px'; arguments[0].style.width = '1px'; arguments[0].style.opacity = 1",
fileElem.getWebElement());
// Sending the keystrokes will ultimately submit the request. No need to simulate the click
fileElem.sendKeys(absolutePath);
// Not sure how to wait for the upload and response to return first
// I need this since I have a test that looks at the results after upload
// ... there is probably a better way to do this, but I punted
browser.sleep(1000);
var imagePath = 'http://placehold.it/120x120&text=image1';
element(by.id('fileUpload')).sendKeys(imagePath);
This is working for me.
This is what I do to upload file on firefox, this script make the element visible to set the path value:
browser.executeScript("$('input[type=\"file\"]').parent().css('visibility', 'visible').css('height', 1).css('width', 1).css('overflow', 'visible')");
If above solutions don't work, read this
First of all, in order to upload the file there should be an input element that takes the path to the file. Normally, it's immediately next to the 'Upload' button... BUT I've seen this, when the button doesn't have an input around the button which may seem to be confusing. Keep clam, the input has to be on the page! Try look for input element in the DOM, that has something like 'upload', or 'file', just keep in mind it can be anywhere.
When you located it, get it's selector, and type in a path to a file. Remember, it has to be absolute path, that starts from you root directory (/something/like/this for MAC users and C:/some/file in Windows)
await $('input[type="file"]').sendKeys("/file/path")
this may not work, if...
protractor's sendKeys can only type in an input that's visible. Often, the input will be hidden or have 0 pixels size. You can fix that too
let $input = $('input[type="file"]');
await browser.executeScript(
"arguments[0].style.visibility = 'visible'; arguments[0].style.height = '1px'; arguments[0].style.width = '1px'; arguments[0].style.opacity = 1",
$input.getWebElement()
);
I realized that the file input in the web app I'm testing is only visible in Firefox when it is scrolled into view using JavaScript, so I added scrollIntoView() in Andres D's code to make it work for my app:
browser.executeAsyncScript(function (callback) {
document.querySelectorAll('input')[2]
.style = '';
document.querySelectorAll('input')[2].scrollIntoView();
callback();
});
(I also removed all of the styles for the file input element)
// To upload a file from C:\ Directory
{
var path = require('path');
var dirname = 'C:/';
var fileToUpload = '../filename.txt';
var absolutePath = path.resolve('C:\filename.txt');
var fileElem = ptor.element.all(protractor.By.css('input[type="file"]'));
fileElem.sendKeys(absolutePath);
cb();
};
If you want to select a file without opening the popup below is the answer :
var path = require('path');
var remote = require('../../node_modules/selenium-webdriver/remote');
browser.setFileDetector(new remote.FileDetector());
var fileToUpload = './resume.docx';
var absolutePath = path.resolve(process.cwd() + fileToUpload);
element(by.css('input[type="file"]')).sendKeys(absolutePath);
the current documented solutions would work only if users are loading jQuery. i all different situations users will get an error such:Failed: $ is not defined
i would suggest to document a solution using native angularjs code.
e.g. i would suggest instead of suggesting:
$('input[type="file"]') .....
to suggest:
angular.element(document.querySelector('input[type="file"]')) .....
the latter is more standard, atop of angular and more important not require jquery
This should be quite simple but I just cannot get it to work
Szenario:
I want to iterate through my folders with the phonegap file API
Problem:
I can not get the getDirectory() function wo work
Very simple example: (to illustrate my problem)
var fileSystem, basePath;
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, doStuff, function(error) {
notificationservice.log('Failed to get local filesystem: ' + error.code);
});
function doStuff(fs) {
fileSystem = fs;
basePath = fileSystem.root.fullPath;
var directoryEntry = new DirectoryEntry('', basePath);
readDirectory(directoryEntry);
}
function readDirectory(directoryEntry) {
var directoryReader = directoryEntry.createReader();
directoryReader.readEntries(function(entries) {
for (var i = 0 ; i < entries.length ; i++) {
notificationservice.log(entries[i].fullPath);
fileSystem.root.getDirectory(entries[i].fullPath, {create: false}, function(dir) {
notificationservice.log('SUCCESS');
}, function (error) {
notificationservice.log('Failed to get directory');
});
}
});
}
I can access my folder with the new DirectoryEntry() but whenever I try the access a directory with the getDirectory() function I fail - if anyone could help me correct the above code so that the fileSystem.root.getDirectory() would not return an error I´d be very thanksfull !
Please note:
I use the eclipse editor for deployment and deploy to a nexus 7
(if possible the code should work an plattforms like iOS or win as well)
thanks,
matthias
by the way: I am sure there are a lot of questions which actually solve this issue - however, I haven´t been able to find ANYTHING working for me...
according to https://meta.stackexchange.com/questions/17845/etiquette-for-answering-your-own-question
Silly me - was hooked on script which I got from the web - I have to use the .name property (relative path from the current directory) like this fileSystem.root.getDirectory(name, {create: false}, function(dir) {... - THIS QUESTION IS SOLVED (and sorry if anyone wasted time on this)