-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Selenium downloadFile command (#12923)
- Loading branch information
Showing
6 changed files
with
282 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import fs from 'node:fs' | ||
import path from 'node:path' | ||
import JSZip from 'jszip' | ||
import logger from '@wdio/logger' | ||
|
||
const log = logger('webdriverio') | ||
|
||
/** | ||
* | ||
* Download a file from the remote computer running Selenium node to local file system | ||
* by using the [`downloadFile`](https://webdriver.io/docs/api/selenium#downloadFile) command. | ||
* | ||
* :::info | ||
* Note that this command is only supported if you use a | ||
* [Selenium Grid](https://www.selenium.dev/documentation/en/grid/) with Chrome, Edge or Firefox | ||
* and have the `se:downloadsEnabled` flag set in the capabilities. | ||
* ::: | ||
* | ||
* <example> | ||
:downloadFile.js | ||
it('should download a file', async () => { | ||
await browser.url('https://www.selenium.dev/selenium/web/downloads/download.html') | ||
await $('#file-1').click() | ||
await browser.waitUntil(async function () { | ||
return (await browser.getDownloadableFiles()).names.includes('file_1.txt') | ||
}, {timeout: 5000}) | ||
const files = await browser.getDownloadableFiles() | ||
const downloaded = await browser.downloadFile(files.names[0], process.cwd()) | ||
await browser.deleteDownloadableFiles() | ||
}) | ||
* </example> | ||
* | ||
* @alias browser.downloadFile | ||
* @param {string} fileName remote path to file | ||
* @param {string} targetDirectory target location on local computer | ||
* @type utility | ||
* @uses protocol/download | ||
* | ||
*/ | ||
export async function downloadFile( | ||
this: WebdriverIO.Browser, | ||
fileName: string, | ||
targetDirectory: string | ||
): Promise<object> { | ||
/** | ||
* parameter check | ||
*/ | ||
if (typeof fileName !== 'string' || typeof targetDirectory !== 'string') { | ||
throw new Error('number or type of arguments don\'t agree with downloadFile command') | ||
} | ||
|
||
/** | ||
* check if command is available | ||
*/ | ||
if (typeof this.download !== 'function') { | ||
throw new Error(`The downloadFile command is not available in ${(this.capabilities as WebdriverIO.Capabilities).browserName} and only available when using Selenium Grid`) | ||
} | ||
|
||
const response = await this.download(fileName) | ||
const base64Content = response.contents | ||
|
||
if (!targetDirectory.endsWith('/')) { | ||
targetDirectory += '/' | ||
} | ||
|
||
fs.mkdirSync(targetDirectory, { recursive: true }) | ||
const zipFilePath = path.join(targetDirectory, `${fileName}.zip`) | ||
fs.writeFileSync(zipFilePath, Buffer.from(base64Content, 'base64')) | ||
|
||
const zipData = fs.readFileSync(zipFilePath) | ||
const filesData: string[] = [] | ||
|
||
try { | ||
const zip = await JSZip.loadAsync(zipData) | ||
const keys = Object.keys(zip.files) | ||
|
||
// Iterate through each file in the zip archive | ||
for (let i = 0; i < keys.length; i++) { | ||
const fileData = await zip.files[keys[i]].async('nodebuffer') | ||
const dir = path.resolve(targetDirectory, keys[i]) | ||
fs.writeFileSync(dir, fileData) | ||
log.info(`File extracted: ${keys[i]}`) | ||
filesData.push(dir) | ||
} | ||
} catch (error) { | ||
log.error('Error unzipping file:', error) | ||
} | ||
|
||
return Promise.resolve({ | ||
files: filesData | ||
}) | ||
} |
110 changes: 110 additions & 0 deletions
110
packages/webdriverio/tests/commands/browser/downloadFile.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { expect, describe, it, afterEach, vi } from 'vitest' | ||
|
||
import fs from 'node:fs' | ||
import path from 'node:path' | ||
import JSZip from 'jszip' | ||
import { remote } from '../../../src/index.js' | ||
import logger from '@wdio/logger' | ||
|
||
vi.mock('node:fs', () => ({ | ||
default: { | ||
mkdirSync: vi.fn(), | ||
readFileSync: vi.fn(), | ||
writeFileSync: vi.fn() | ||
} | ||
})) | ||
vi.mock('got') | ||
vi.mock('JSZip', () => ({ | ||
default: { | ||
loadAsync: vi.fn() | ||
} | ||
})) | ||
vi.mock('devtools') | ||
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) | ||
|
||
describe('downloadFile', () => { | ||
it('should throw if browser does not support it', async function () { | ||
const browser = await remote({ | ||
baseUrl: 'http://webdriver.io', | ||
capabilities: { | ||
browserName: 'safari' | ||
} | ||
}) | ||
|
||
await expect(browser.downloadFile('bar.jpg', '/foo/bar')).rejects.toEqual( | ||
new Error('The downloadFile command is not available in mockBrowser and only available when using Selenium Grid')) | ||
}) | ||
|
||
it('should throw if path is not a string', async function () { | ||
const browser = await remote({ | ||
baseUrl: 'http://webdriver.io', | ||
capabilities: { | ||
browserName: 'chrome' | ||
} | ||
}) | ||
|
||
// @ts-expect-error wrong parameter | ||
await expect(browser.downloadFile(123, 456)).rejects.toEqual( | ||
new Error('number or type of arguments don\'t agree with downloadFile command')) | ||
}) | ||
|
||
it('should unzip the file and use downloadFile command', async () => { | ||
vi.spyOn(JSZip, 'loadAsync').mockReturnValue(Promise.resolve( | ||
{ | ||
files: { | ||
'file_1': { | ||
async: vi.fn() | ||
} | ||
} | ||
}) | ||
) | ||
|
||
const browser = await remote({ | ||
baseUrl: 'http://webdriver.io', | ||
capabilities: { | ||
browserName: 'chrome' | ||
} | ||
}) | ||
browser.download = vi.fn().mockReturnValue({ | ||
fileName: 'test', | ||
contents: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' | ||
}) | ||
|
||
await browser.downloadFile('toDownload.jpg', __dirname) | ||
const log = logger('test') | ||
|
||
expect(log.info).toHaveBeenCalledWith( | ||
expect.stringContaining('File extracted: file_1') | ||
) | ||
expect(fs.writeFileSync).toHaveBeenCalledTimes(2) | ||
}) | ||
|
||
it('reject on error', async () => { | ||
vi.spyOn(JSZip, 'loadAsync').mockRejectedValue('test' | ||
) | ||
const browser = await remote({ | ||
baseUrl: 'http://webdriver.io', | ||
capabilities: { | ||
browserName: 'chrome' | ||
} | ||
}) | ||
browser.download = vi.fn().mockReturnValue({ | ||
fileName: 'test', | ||
contents: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==' | ||
}) | ||
|
||
await browser.downloadFile('toDownload.jpg', __dirname) | ||
const log = logger('test') | ||
|
||
expect(log.error).toHaveBeenCalledWith('Error unzipping file:', 'test') | ||
}) | ||
|
||
afterEach(() => { | ||
const log = logger('test') | ||
vi.mocked(log.info).mockClear() | ||
vi.mocked(log.error).mockClear() | ||
vi.mocked(fs.mkdirSync).mockClear() | ||
vi.mocked(fs.readFileSync).mockClear() | ||
vi.mocked(fs.writeFileSync).mockClear() | ||
}) | ||
}) |
Oops, something went wrong.