diff --git a/docs/helpers/Puppeteer.md b/docs/helpers/Puppeteer.md index f5421f52e..ea49b630a 100644 --- a/docs/helpers/Puppeteer.md +++ b/docs/helpers/Puppeteer.md @@ -470,6 +470,21 @@ I.doubleClick('.btn.edit'); - `locator` clickable link or button located by text, or any element located by CSS|XPath|strict locator. - `context` (optional) element to search in CSS|XPath|Strict locator. +### downloadFile + +Performs a download file on an element matched by link|button|CSS or XPath. +File is downloaded by default to output folder. +If no custom file name is provided, the default name will be used + +```js +I.downloadFile('td[class="text-right file-link"] a', 'thisIsCustomName'); +``` + +#### Parameters + +- `locator` clickable link or button located by CSS|XPath locator. +- `string` custom file name. + ### dragAndDrop Drag an item to a destination element. diff --git a/docs/webapi/downloadFile.mustache b/docs/webapi/downloadFile.mustache new file mode 100644 index 000000000..b5ea29ab1 --- /dev/null +++ b/docs/webapi/downloadFile.mustache @@ -0,0 +1,11 @@ +Performs a download file on an element matched by link|button|CSS or XPath. +File is downloaded by default to output folder. +If no custom file name is provided, the default name will be used + + +```js +I.downloadFile('td[class="text-right file-link"] a', 'thisIsCustomName'); +``` + +@param locator clickable link or button located by CSS|XPath locator. +@param string custom file name. \ No newline at end of file diff --git a/lib/command/definitions.js b/lib/command/definitions.js index 96b1afdd9..fdfd5eeae 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -206,9 +206,9 @@ module.exports = function (genPath, options) { function addAllMethodsInObject(supportObj, actions, methods, translations) { for (const action of methodsOfObject(supportObj)) { - let fn = supportObj[action]; + const fn = supportObj[action]; if (!fn.name) { - Object.defineProperty(fn, "name", {value: action}); + Object.defineProperty(fn, 'name', { value: action }); } const actionAlias = translations ? translations.actionAliasFor(action) : action; if (!actions[actionAlias]) { diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 98451ab83..46ed772ac 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -25,6 +25,8 @@ const ElementNotFound = require('./errors/ElementNotFound'); const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused'); const Popup = require('./extras/Popup'); const Console = require('./extras/Console'); +const axios = require('axios'); +const fs = require('fs'); let puppeteer; @@ -851,6 +853,55 @@ class Puppeteer extends Helper { return proceedClick.call(this, locator, context, { waitForNavigation: true }); } + /** + * {{> ../webapi/downloadFile }} + */ + async downloadFile(locator, customName) { + let fileName; + await this.page.setRequestInterception(true); + this.click(locator); + + const xRequest = await new Promise((resolve) => { + this.page.on('request', (request) => { + const grabbedFileName = request.url().split('/')[request.url().split('/').length - 1]; + const fileExtension = request.url().split('/')[request.url().split('/').length - 1].split('.')[1]; + customName ? fileName = `${customName}.${fileExtension}` : fileName = grabbedFileName; + request.abort(); + resolve(request); + }); + }); + + const options = { + encoding: null, + method: xRequest._method, + uri: xRequest._url, + body: xRequest._postData, + headers: xRequest._headers, + }; + + const cookies = await this.page.cookies(); + options.headers.Cookie = cookies.map(ck => `${ck.name}=${ck.value}`).join(';'); + + const response = await axios({ + method: options.method, url: options.uri, headers: options.headers, responseType: 'arraybuffer', + }); + + const outputFile = path.join(`${global.output_dir}/${fileName}`); + + try { + return new Promise((resolve, reject) => { + const wstream = fs.createWriteStream(outputFile); + wstream.write(response.data); + wstream.end(); + this.debug(`File is downloaded in ${outputFile}`); + wstream.on('finish', () => { resolve(fileName); }); + wstream.on('error', reject); + }); + } catch (error) { + throw new Error(`There is something wrong with downloaded file. ${error}`); + } + } + /** * {{> ../webapi/doubleClick }} */ diff --git a/lib/parser.js b/lib/parser.js index 7583eb050..99eaa4460 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -87,7 +87,7 @@ function getParams(fn) { }); return params; } catch (err) { - console.log('Error in ' + newFn.toString()); + console.log(`Error in ${newFn.toString()}`); console.error(err); } } diff --git a/lib/utils.js b/lib/utils.js index 1f02abd7c..9b6b5c28e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -40,7 +40,7 @@ const isGenerator = module.exports.isGenerator = function (fn) { }; const isFunction = module.exports.isFunction = function (fn) { - return typeof fn === 'function' + return typeof fn === 'function'; }; const isAsyncFunction = module.exports.isAsyncFunction = function (fn) { diff --git a/test/helper/Puppeteer_test.js b/test/helper/Puppeteer_test.js index cf9b887a7..b2f648b08 100644 --- a/test/helper/Puppeteer_test.js +++ b/test/helper/Puppeteer_test.js @@ -10,10 +10,12 @@ const AssertionFailedError = require('../../lib/assert/error'); const formContents = require('../../lib/utils').test.submittedData(path.join(__dirname, '/../data/app/db')); const expectError = require('../../lib/utils').test.expectError; const webApiTests = require('./webapi'); +const FileSystem = require('../../lib/helper/FileSystem'); let I; let browser; let page; +let FS; const siteUrl = TestHelper.siteUrl(); describe('Puppeteer', function () { @@ -22,6 +24,7 @@ describe('Puppeteer', function () { before(() => { global.codecept_dir = path.join(__dirname, '/../data'); + I = new Puppeteer({ url: siteUrl, windowSize: '500x700', @@ -558,6 +561,50 @@ describe('Puppeteer', function () { assert.notEqual(before, after); }); }); + + describe('#downloadFile', () => { + before(() => { + // create download folder; + fs.mkdir(path.join(`${__dirname}/../data/download`), () => { + + }); + global.output_dir = path.join(`${__dirname}/../data/download`); + + FS = new FileSystem(); + FS._before(); + FS.amInPath('download'); + }); + + after(() => { + // Remove the test dir + fs.readdir(global.output_dir, (err, files) => { + if (err) throw err; + + for (const file of files) { + if (file.includes('.mp4')) { + fs.unlink(path.join(global.output_dir, file), (err) => { + if (err) throw err; + }); + } + } + }); + }); + + + it('should dowload file', async () => { + await I.amOnPage('http://file-examples.com/index.php/sample-video-files/sample-mp4-files/'); + const fileName = await I.downloadFile('td[class="text-right file-link"] a'); + + FS.seeFile(fileName); + }); + + it('should dowload file with custom name', async () => { + await I.amOnPage('http://file-examples.com/index.php/sample-video-files/sample-mp4-files/'); + const fileName = await I.downloadFile('td[class="text-right file-link"] a', 'thisisacustomname'); + + FS.seeFile(fileName); + }); + }); }); let remoteBrowser;