How do you test uploading a file with Capybara and Dropzone.js? - testing

I've switched to using the Dropzone.js plugin for drag-and-drop file uploads. How can I write a Capybara test to ensure this functionality keeps on working?
Previously I had a template with an input file element:
<input type="file" name="attachments">
And the test was simple:
When(/^I upload "([^"]*)"$/) do |filename|
attach_file("attachments", File.expand_path(filename))
# add assertion here
end
However this no longer works because Dropzone doesn't have a visible file input.

To solve this, simulate a drop event to trigger dropping an attachment onto Dropzone. First add this function to your step definition:
# Upload a file to Dropzone.js
def drop_in_dropzone(file_path)
# Generate a fake input selector
page.execute_script <<-JS
fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type:'file'}
).appendTo('body');
JS
# Attach the file to the fake input selector
attach_file("fakeFileInput", file_path)
# Add the file to a fileList array
page.execute_script("var fileList = [fakeFileInput.get(0).files[0]]")
# Trigger the fake drop event
page.execute_script <<-JS
var e = jQuery.Event('drop', { dataTransfer : { files : [fakeFileInput.get(0).files[0]] } });
$('.dropzone')[0].dropzone.listeners[0].events.drop(e);
JS
end
Then test with:
When(/^I upload "([^"]*)"$/) do |filename|
drop_in_dropzone File.expand_path(filename)
# add assertion here
end
NOTE: You need to have jQuery loaded, and the Dropzone element requires the dropzone class.

These days I find this way more graceful
page.attach_file(Rails.root.join('spec/fixtures/files/avatar.png')) do
page.find('#avatar-clickable').click
end
Where is in my case #avatar-clickable is a div which contain Dropzone form tag.

Building off of #deepwell's answer which didn't quite work for me, here is a solution using vanilla JS for the events and event dispatching, and a neutral selector for the dropzone:
def drop_in_dropzone(file_path, zone_selector)
# Generate a fake input selector
page.execute_script <<-JS
fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type:'file'}
).appendTo('body');
JS
# Attach the file to the fake input selector
attach_file("fakeFileInput", file_path)
# Add the file to a fileList array
page.execute_script("fileList = [fakeFileInput.get(0).files[0]]")
# Trigger the fake drop event
page.execute_script <<-JS
dataTransfer = new DataTransfer()
dataTransfer.items.add(fakeFileInput.get(0).files[0])
testEvent = new DragEvent('drop', {bubbles:true, dataTransfer: dataTransfer })
$('#{zone_selector}')[0].dispatchEvent(testEvent)
JS
end
uses global vars on purpose, so I could test in js console, but feel free to scope them.

In case anyone is interested, I ported #deepwell's function to javascript, to use it with javascript flavoured selenium:
this.dropInDropzone = function(filePath) {
var script = "fakeFileInput = $('#fakeFileInput'); if (fakeFileInput.length === 0) fakeFileInput = window.$('<input/>').attr({id: 'fakeFileInput', type:'file'}).appendTo('body');";
// Generate a fake input selector
return driver.executeScript(script).then(function() {
// Attach the file to the fake input selector
return driver.findElement(webdriver.By.css('#fakeFileInput')).sendKeys(filePath);
}).then(function() {
// Add the file to a fileList array
return driver.executeScript("var fileList = [fakeFileInput.get(0).files[0]]");
}).then(function() {
// Trigger the fake drop event
script = "var e = jQuery.Event('drop', { dataTransfer : { files : [fakeFileInput.get(0).files[0]] } }); $('.dropzone')[0].dropzone.listeners[0].events.drop(e);"
return driver.executeScript(script);
});
};

Since Capybara 3.21.0, you can drop files on elements like this:
find(".dropzone").drop(Rails.root.join("spec/fixtures/file.txt"))
See the Element#drop source for details.

Related

Is it possible to use Inertia form helper for upload files so, that no file input is involved?

WHat I did is, that through drag' drop i took the value of dataTransfer files, and made form files field equal to that:
const form = useForm({
files: [],
});
form.files = e.dataTransfer.files;
Is this a valid method, or for some reason it is mandatory to use file input?

How to add an uploaded file to a list of already uploaded files in Vue?

I currently have a file uploader that accepts a single CSV file. Then with axios I POST such file to the server and everything works just fine. What I'm not being able to achieve is being able to upload another CSV that will get added to the list of CSVs uploaded. I'm not talking about uploading various files at once, I'm taking about uploading different files at different points in time.
This is the method that is used to select a CSV file in the .vue file.
staticCampaignCSVSelected: function (file) {
console.log('campaign-detail.vue#staticCampaignCSVSelected', file)
let vc = this
vc.selectedHeuristicId = -1
Campaign.uploadStaticCSV(vc.campaign, file[0])
.then(
function (data) {
alert('CSV cargado con exito')
}
)
.catch(
function (err, data) {
console.log("campaign-detail#staticCampaignCSVSelected - catch", err.response)
alert(err.response.data.error)
}
)
},
This is the function that I have in some other JS file to POST to the API:
function uploadStaticCSV (campaign, csv) {
console.log('Campaign#uploadStaticCSV', campaign, csv)
//long list of assertions
let formData = new FormData()
formData.append('csv', csv)
return axios.post(API.campaignUploadStaticCSV(campaign.id), formData)
}
And this is the function I have in my endpoints.js file:
campaignUploadStaticCSV: function (id) { return this.campaign(id) + '' + '/csv' },
I haven't found a way to properly pass a[file] array as a parameter to the functions, which is what I believe I need to somehow do.
Any help would be appreciated :)
As far as i understood your question you need a way to pass a file from browser interface to your staticCampaignCSVSelected(file) method. If so why not to use an input model or a simple event or a watcher. E.g.
<input type="file" #input="staticCampaignCSVSelected($event.target.files[0])" />
But also i see a mistake in your code. You should append .then().catch() callbacks to axios.post() itself but not to Campaign.uploadStaticCSV() method.
And
return axios.post()
will not return a server response. You have to handle it in
axios.post().then(response => {})
callback

Sensenet: Check Out Documents on Upload

When a user upload a document to a document library is possible to this documents automatically remain in the "Check Out" state?
Check the /Root/System/SystemPlugins/Portlets/IntraUploadDialog.ascx. There's a huge Javascript to handle the upload process. You can add additional functionality after upload in the fileupload's done branch.
$('#sn-upload-fileupload').fileupload({
...
done: function (e, data) {
inProgress = false;
var json = (data.jqXHR.responseText) ? jQuery.parseJSON(data.jqXHR.responseText) : data.result;
$('.sn-upload-bar', data.context).addClass('sn-upload-uploadedbar');
var filename = json.Name;
var url = json.Url;
$('.sn-upload-filetitle', data.context).html('' + filename + '');
SN.Upload.uploadFinished(data.formData.ChunkToken);
**//call an action or add custom functionality**
}
});
There are built-in odata actions in Sense/Net with which you can call actions trough ajax and there's an action for checking-out content.
http://wiki.sensenet.com/Built-in_OData_actions_and_functions#Check_out_action_-_from_version_6.3

Windows 8 Javascript reading html into data.js

I'm using the sample grid javascript template to build a win8 application.
I'm also using the data.js file to load data. However this uses :
var content = "test content";
var sampleItems = [
{group: sampleGroups[0], title: "Title", description: "DESC", content: content},
However, my content text is getting longer and I would also like to put in html syntax like IMG and P etc.
Whats the simplest way to load a local html file into the content variable above?
Thanks!
To read an HTML file in the local storage of the application you would use the readText method of the WinJS.Application.local object.
var loc = WinJS.Application.local;
loc.readText("fileName", "failed").done( /* Your success and error handlers */ );
For reading a file stored in the app package you would execute something more like this:
var myText;
var url = new Windows.Foundation.Uri("ms-appx:///html/filename.html");
Windows.Storage.StorageFile.getFileFromApplicationUriAsync(url).then(function (file) {
Windows.Storage.FileIO.readTextAsync(file).then(function (text) {
myText=text;
});
});

Grab the resource contents in CasperJS or PhantomJS

I see that CasperJS has a "download" function and an "on resource received" callback but I do not see the contents of a resource in the callback, and I don't want to download the resource to the filesystem.
I want to grab the contents of the resource so that I can do something with it in my script. Is this possible with CasperJS or PhantomJS?
This problem has been in my way for the last couple of days. The proxy solution wasn't very clean in my environment so I found out where phantomjs's QTNetworking core put the resources when it caches them.
Long story short, here is my gist. You need the cache.js and mimetype.js files:
https://gist.github.com/bshamric/4717583
//for this to work, you have to call phantomjs with the cache enabled:
//usage: phantomjs --disk-cache=true test.js
var page = require('webpage').create();
var fs = require('fs');
var cache = require('./cache');
var mimetype = require('./mimetype');
//this is the path that QTNetwork classes uses for caching files for it's http client
//the path should be the one that has 16 folders labeled 0,1,2,3,...,F
cache.cachePath = '/Users/brandon/Library/Caches/Ofi Labs/PhantomJS/data7/';
var url = 'http://google.com';
page.viewportSize = { width: 1300, height: 768 };
//when the resource is received, go ahead and include a reference to it in the cache object
page.onResourceReceived = function(response) {
//I only cache images, but you can change this
if(response.contentType.indexOf('image') >= 0)
{
cache.includeResource(response);
}
};
//when the page is done loading, go through each cachedResource and do something with it,
//I'm just saving them to a file
page.onLoadFinished = function(status) {
for(index in cache.cachedResources) {
var file = cache.cachedResources[index].cacheFileNoPath;
var ext = mimetype.ext[cache.cachedResources[index].mimetype];
var finalFile = file.replace("."+cache.cacheExtension,"."+ext);
fs.write('saved/'+finalFile,cache.cachedResources[index].getContents(),'b');
}
};
page.open(url, function () {
page.render('saved/google.pdf');
phantom.exit();
});
Then when you call phantomjs, just make sure the cache is enabled:
phantomjs --disk-cache=true test.js
Some notes:
I wrote this for the purpose of getting the images on a page without using the proxy or taking a low res snapshot. QT uses compression on certain text file resources and you will have to deal with the decompression if you use this for text files. Also, I ran a quick test to pull in html resources and it didn't parse the http headers out of the result. But, this is useful to me, hopefully someone else will find it so, modify it if you have problems with a specific content type.
I've found that until the phantomjs matures a bit, according to the issue 158 http://code.google.com/p/phantomjs/issues/detail?id=158 this is a bit of a headache for them.
So you want to do it anyways? I've opted to go a bit higher to accomplish this and have grabbed PyMiProxy over at https://github.com/allfro/pymiproxy, downloaded, installed, set it up, took their example code and made this in proxy.py
from miproxy.proxy import RequestInterceptorPlugin, ResponseInterceptorPlugin, AsyncMitmProxy
from mimetools import Message
from StringIO import StringIO
class DebugInterceptor(RequestInterceptorPlugin, ResponseInterceptorPlugin):
def do_request(self, data):
data = data.replace('Accept-Encoding: gzip\r\n', 'Accept-Encoding:\r\n', 1);
return data
def do_response(self, data):
#print '<< %s' % repr(data[:100])
request_line, headers_alone = data.split('\r\n', 1)
headers = Message(StringIO(headers_alone))
print "Content type: %s" %(headers['content-type'])
if headers['content-type'] == 'text/x-comma-separated-values':
f = open('data.csv', 'w')
f.write(data)
print ''
return data
if __name__ == '__main__':
proxy = AsyncMitmProxy()
proxy.register_interceptor(DebugInterceptor)
try:
proxy.serve_forever()
except KeyboardInterrupt:
proxy.server_close()
Then I fire it up
python proxy.py
Next I execute phantomjs with the proxy specified...
phantomjs --ignore-ssl-errors=yes --cookies-file=cookies.txt --proxy=127.0.0.1:8080 --web-security=no myfile.js
You may want to turn your security on or such, it was needless for me currently as I'm scraping just one source. You should now see a bunch of text flowing through your proxy console and if it lands on something with the mime type of "text/x-comma-separated-values" it'll save it as data.csv. This will also save all the headers and everything, but if you've come this far I'm sure you can figure out how to pop those off.
One other detail, I've found that I've had to disable gzip encoding, I could use zlib and decompress data in gzip from my own apache webserver, but if it comes out of IIS or such the decompression will get errors and I'm not sure about that part of it.
So my power company won't offer me an API? Fine! We do it the hard way!
Did not realize I could grab the source from the document object like this:
casper.start(url, function() {
var js = this.evaluate(function() {
return document;
});
this.echo(js.all[0].outerHTML);
});
More info here.
You can use Casper.debugHTML() to print out contents of a HTML resource:
var casper = require('casper').create();
casper.start('http://google.com/', function() {
this.debugHTML();
});
casper.run();
You can also store the HTML contents in a var using casper.getPageContent(): http://casperjs.org/api.html#casper.getPageContent (available in lastest master)