2020-08-03 23:41:48 +03:00
/ * *
* Copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2020-08-17 05:19:52 +03:00
2022-03-26 02:05:50 +03:00
import { browserTest as it , expect } from '../config/browserTest' ;
2020-08-07 21:19:15 +03:00
import fs from 'fs' ;
import path from 'path' ;
2021-04-29 00:54:51 +03:00
import crypto from 'crypto' ;
2021-10-11 17:52:17 +03:00
import type { Download } from 'playwright-core' ;
2020-08-03 23:41:48 +03:00
2021-04-05 19:18:56 +03:00
it . describe ( 'download event' , ( ) = > {
2021-10-14 20:41:03 +03:00
it . skip ( ( { mode } ) = > mode === 'service' , 'download.path() is not available in remote mode' ) ;
2021-09-27 19:58:08 +03:00
it . beforeEach ( async ( { server } ) = > {
2020-10-12 19:16:02 +03:00
server . setRoute ( '/download' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . setHeader ( 'Content-Disposition' , 'attachment' ) ;
res . end ( ` Hello world ` ) ;
} ) ;
server . setRoute ( '/downloadWithFilename' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . setHeader ( 'Content-Disposition' , 'attachment; filename=file.txt' ) ;
res . end ( ` Hello world ` ) ;
} ) ;
2021-06-12 23:23:22 +03:00
server . setRoute ( '/downloadWithDelay' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . setHeader ( 'Content-Disposition' , 'attachment; filename=file.txt' ) ;
// Chromium requires a large enough payload to trigger the download event soon enough
res . write ( 'a' . repeat ( 4096 ) ) ;
res . write ( 'foo' ) ;
res . uncork ( ) ;
} ) ;
2021-09-28 19:56:07 +03:00
server . setRoute ( '/downloadWithCOOP' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . setHeader ( 'Content-Disposition' , 'attachment' ) ;
res . setHeader ( 'Cross-Origin-Opener-Policy' , 'same-origin' ) ;
res . end ( ` Hello world ` ) ;
} ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2020-10-12 19:16:02 +03:00
2022-03-10 21:42:52 +03:00
it ( 'should report download when navigation turns into download @smoke' , async ( { browser , server , browserName } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-08-12 19:34:32 +03:00
const [ download , responseOrError ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . goto ( server . PREFIX + '/download' ) . catch ( e = > e )
] ) ;
expect ( download . page ( ) ) . toBe ( page ) ;
expect ( download . url ( ) ) . toBe ( ` ${ server . PREFIX } /download ` ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
if ( browserName === 'chromium' ) {
expect ( responseOrError instanceof Error ) . toBeTruthy ( ) ;
expect ( responseOrError . message ) . toContain ( 'net::ERR_ABORTED' ) ;
expect ( page . url ( ) ) . toBe ( 'about:blank' ) ;
} else if ( browserName === 'webkit' ) {
expect ( responseOrError instanceof Error ) . toBeTruthy ( ) ;
expect ( responseOrError . message ) . toContain ( 'Download is starting' ) ;
expect ( page . url ( ) ) . toBe ( 'about:blank' ) ;
} else {
expect ( responseOrError . status ( ) ) . toBe ( 200 ) ;
expect ( page . url ( ) ) . toBe ( server . PREFIX + '/download' ) ;
}
await page . close ( ) ;
} ) ;
2021-09-28 19:56:07 +03:00
it ( 'should work with Cross-Origin-Opener-Policy' , async ( { browser , server , browserName } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-09-28 19:56:07 +03:00
const [ download , responseOrError ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . goto ( server . PREFIX + '/downloadWithCOOP' ) . catch ( e = > e )
] ) ;
expect ( download . page ( ) ) . toBe ( page ) ;
expect ( download . url ( ) ) . toBe ( ` ${ server . PREFIX } /downloadWithCOOP ` ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
if ( browserName === 'chromium' ) {
expect ( responseOrError instanceof Error ) . toBeTruthy ( ) ;
expect ( responseOrError . message ) . toContain ( 'net::ERR_ABORTED' ) ;
expect ( page . url ( ) ) . toBe ( 'about:blank' ) ;
} else if ( browserName === 'webkit' ) {
expect ( responseOrError instanceof Error ) . toBeTruthy ( ) ;
expect ( responseOrError . message ) . toContain ( 'Download is starting' ) ;
expect ( page . url ( ) ) . toBe ( 'about:blank' ) ;
} else {
expect ( responseOrError . status ( ) ) . toBe ( 200 ) ;
expect ( page . url ( ) ) . toBe ( server . PREFIX + '/downloadWithCOOP' ) ;
}
await page . close ( ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should report downloads with acceptDownloads: false' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( { acceptDownloads : false } ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /downloadWithFilename">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
let error ;
2021-05-14 00:18:21 +03:00
expect ( download . page ( ) ) . toBe ( page ) ;
2020-10-12 19:16:02 +03:00
expect ( download . url ( ) ) . toBe ( ` ${ server . PREFIX } /downloadWithFilename ` ) ;
expect ( download . suggestedFilename ( ) ) . toBe ( ` file.txt ` ) ;
await download . path ( ) . catch ( e = > error = e ) ;
expect ( await download . failure ( ) ) . toContain ( 'acceptDownloads' ) ;
expect ( error . message ) . toContain ( 'acceptDownloads: true' ) ;
2021-04-05 19:18:56 +03:00
await page . close ( ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should report downloads with acceptDownloads: true' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should report proper download url when download is from download attribute' , async ( { browser , server , browserName } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-02-22 20:02:50 +03:00
await page . goto ( server . PREFIX + '/empty.html' ) ;
await page . setContent ( ` <a href=" ${ server . PREFIX } /chromium-linux.zip" download="foo.zip">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
expect ( download . url ( ) ) . toBe ( ` ${ server . PREFIX } /chromium-linux.zip ` ) ;
await page . close ( ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should report downloads for download attribute' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-02-22 20:02:50 +03:00
await page . goto ( server . PREFIX + '/empty.html' ) ;
await page . setContent ( ` <a href=" ${ server . PREFIX } /chromium-linux.zip" download="foo.zip">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
expect ( download . suggestedFilename ( ) ) . toBe ( ` foo.zip ` ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
await page . close ( ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should save to user-specified path without updating original path' , async ( { browser , server } , testInfo ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const userPath = testInfo . outputPath ( 'download.txt' ) ;
await download . saveAs ( userPath ) ;
expect ( fs . existsSync ( userPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( userPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
2020-08-03 23:41:48 +03:00
2020-10-12 19:16:02 +03:00
const originalPath = await download . path ( ) ;
expect ( fs . existsSync ( originalPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( originalPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should save to two different paths with multiple saveAs calls' , async ( { browser , server } , testInfo ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const userPath = testInfo . outputPath ( 'download.txt' ) ;
await download . saveAs ( userPath ) ;
expect ( fs . existsSync ( userPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( userPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
2020-08-03 23:41:48 +03:00
2020-10-12 19:16:02 +03:00
const anotherUserPath = testInfo . outputPath ( 'download (2).txt' ) ;
await download . saveAs ( anotherUserPath ) ;
expect ( fs . existsSync ( anotherUserPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( anotherUserPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should save to overwritten filepath' , async ( { browser , server } , testInfo ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const dir = testInfo . outputPath ( 'downloads' ) ;
const userPath = path . join ( dir , 'download.txt' ) ;
await download . saveAs ( userPath ) ;
2021-06-03 19:55:33 +03:00
expect ( ( await fs . promises . readdir ( dir ) ) . length ) . toBe ( 1 ) ;
2020-10-12 19:16:02 +03:00
await download . saveAs ( userPath ) ;
2021-06-03 19:55:33 +03:00
expect ( ( await fs . promises . readdir ( dir ) ) . length ) . toBe ( 1 ) ;
2020-10-12 19:16:02 +03:00
expect ( fs . existsSync ( userPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( userPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should create subdirectories when saving to non-existent user-specified path' , async ( { browser , server } , testInfo ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const nestedPath = testInfo . outputPath ( path . join ( 'these' , 'are' , 'directories' , 'download.txt' ) ) ;
await download . saveAs ( nestedPath ) ;
expect ( fs . existsSync ( nestedPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( nestedPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should error when saving with downloads disabled' , async ( { browser , server } , testInfo ) = > {
2020-10-12 19:16:02 +03:00
const page = await browser . newPage ( { acceptDownloads : false } ) ;
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const userPath = testInfo . outputPath ( 'download.txt' ) ;
const { message } = await download . saveAs ( userPath ) . catch ( e = > e ) ;
expect ( message ) . toContain ( 'Pass { acceptDownloads: true } when you are creating your browser context' ) ;
await page . close ( ) ;
} ) ;
2020-08-26 22:46:30 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should error when saving after deletion' , async ( { browser , server } , testInfo ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const userPath = testInfo . outputPath ( 'download.txt' ) ;
await download . delete ( ) ;
const { message } = await download . saveAs ( userPath ) . catch ( e = > e ) ;
2021-04-25 06:39:48 +03:00
expect ( message ) . toContain ( 'Target page, context or browser has been closed' ) ;
2020-10-12 19:16:02 +03:00
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should report non-navigation downloads' , async ( { browser , server } ) = > {
2020-10-12 19:16:02 +03:00
// Mac WebKit embedder does not download in this case, although Safari does.
server . setRoute ( '/download' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . end ( ` Hello world ` ) ;
} ) ;
2020-08-26 22:46:30 +03:00
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( ` <a download="file.txt" href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
expect ( download . suggestedFilename ( ) ) . toBe ( ` file.txt ` ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2021-09-27 19:58:08 +03:00
it ( ` should report download path within page.on('download', …) handler for Files ` , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
const onDownloadPath = new Promise < string > ( res = > {
page . on ( 'download' , dl = > {
dl . path ( ) . then ( res ) ;
} ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
await page . click ( 'a' ) ;
const path = await onDownloadPath ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2021-04-05 19:18:56 +03:00
2021-09-27 19:58:08 +03:00
it ( ` should report download path within page.on('download', …) handler for Blobs ` , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
const onDownloadPath = new Promise < string > ( res = > {
page . on ( 'download' , dl = > {
dl . path ( ) . then ( res ) ;
} ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2020-10-12 19:16:02 +03:00
await page . goto ( server . PREFIX + '/download-blob.html' ) ;
await page . click ( 'a' ) ;
const path = await onDownloadPath ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2021-04-05 19:18:56 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should report alt-click downloads' , async ( { browser , server , browserName } ) = > {
2021-04-05 19:18:56 +03:00
it . fixme ( browserName === 'firefox' || browserName === 'webkit' ) ;
2020-10-12 19:16:02 +03:00
// Firefox does not download on alt-click by default.
// Our WebKit embedder does not download on alt-click, although Safari does.
server . setRoute ( '/download' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . end ( ` Hello world ` ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
2021-09-27 19:58:08 +03:00
page . click ( 'a' , { modifiers : [ 'Alt' ] } )
2020-10-12 19:16:02 +03:00
] ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should report new window downloads' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a target=_blank href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should delete file' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
await download . delete ( ) ;
expect ( fs . existsSync ( path ) ) . toBeFalsy ( ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should expose stream' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const stream = await download . createReadStream ( ) ;
let content = '' ;
stream . on ( 'data' , data = > content += data . toString ( ) ) ;
await new Promise ( f = > stream . on ( 'end' , f ) ) ;
expect ( content ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should delete downloads on context destruction' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download1 ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const [ download2 ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const path1 = await download1 . path ( ) ;
const path2 = await download2 . path ( ) ;
expect ( fs . existsSync ( path1 ) ) . toBeTruthy ( ) ;
expect ( fs . existsSync ( path2 ) ) . toBeTruthy ( ) ;
await page . context ( ) . close ( ) ;
expect ( fs . existsSync ( path1 ) ) . toBeFalsy ( ) ;
expect ( fs . existsSync ( path2 ) ) . toBeFalsy ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
2021-10-28 05:00:06 +03:00
it ( 'should delete downloads on browser gone' , async ( { server , browserType } ) = > {
const browser = await browserType . launch ( ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2020-10-12 19:16:02 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download1 ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const [ download2 ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const path1 = await download1 . path ( ) ;
const path2 = await download2 . path ( ) ;
expect ( fs . existsSync ( path1 ) ) . toBeTruthy ( ) ;
expect ( fs . existsSync ( path2 ) ) . toBeTruthy ( ) ;
await browser . close ( ) ;
expect ( fs . existsSync ( path1 ) ) . toBeFalsy ( ) ;
expect ( fs . existsSync ( path2 ) ) . toBeFalsy ( ) ;
expect ( fs . existsSync ( path . join ( path1 , '..' ) ) ) . toBeFalsy ( ) ;
} ) ;
2021-02-15 03:46:26 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should close the context without awaiting the failed download' , async ( { browser , server , httpsServer , browserName , headless } , testInfo ) = > {
2021-04-05 19:18:56 +03:00
it . skip ( browserName !== 'chromium' , 'Only Chromium downloads on alt-click' ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-02-15 03:46:26 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( ` <a href=" ${ httpsServer . PREFIX } /downloadWithFilename" download="file.txt">click me</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
// Use alt-click to force the download. Otherwise browsers might try to navigate first,
// probably because of http -> https link.
2021-09-27 19:58:08 +03:00
page . click ( 'a' , { modifiers : [ 'Alt' ] } )
2021-02-15 03:46:26 +03:00
] ) ;
const [ downloadPath , saveError ] = await Promise . all ( [
download . path ( ) ,
download . saveAs ( testInfo . outputPath ( 'download.txt' ) ) . catch ( e = > e ) ,
page . context ( ) . close ( ) ,
] ) ;
expect ( downloadPath ) . toBe ( null ) ;
2021-09-23 03:28:17 +03:00
expect ( [
'download.saveAs: File not found on disk. Check download.failure() for details.' ,
'download.saveAs: canceled' ,
] ) . toContain ( saveError . message ) ;
2021-02-15 03:46:26 +03:00
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should close the context without awaiting the download' , async ( { browser , server , browserName , platform } , testInfo ) = > {
2021-04-05 19:18:56 +03:00
it . skip ( browserName === 'webkit' && platform === 'linux' , 'WebKit on linux does not convert to the download immediately upon receiving headers' ) ;
2021-02-15 03:46:26 +03:00
server . setRoute ( '/downloadStall' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . setHeader ( 'Content-Disposition' , 'attachment; filename=file.txt' ) ;
res . writeHead ( 200 ) ;
res . flushHeaders ( ) ;
res . write ( ` Hello world ` ) ;
} ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-02-15 03:46:26 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( ` <a href=" ${ server . PREFIX } /downloadStall" download="file.txt">click me</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const [ downloadPath , saveError ] = await Promise . all ( [
download . path ( ) ,
download . saveAs ( testInfo . outputPath ( 'download.txt' ) ) . catch ( e = > e ) ,
page . context ( ) . close ( ) ,
] ) ;
expect ( downloadPath ) . toBe ( null ) ;
2021-06-15 02:41:53 +03:00
// The exact error message is racy, because sometimes browser is fast enough
// to cancel the download.
expect ( [
'download.saveAs: canceled' ,
'download.saveAs: File deleted upon browser context closure.' ,
] ) . toContain ( saveError . message ) ;
2021-02-15 03:46:26 +03:00
} ) ;
2021-04-09 04:56:09 +03:00
2021-10-28 05:00:06 +03:00
it ( 'should throw if browser dies' , async ( { server , browserType , browserName , platform } , testInfo ) = > {
2021-04-09 04:56:09 +03:00
it . skip ( browserName === 'webkit' && platform === 'linux' , 'WebKit on linux does not convert to the download immediately upon receiving headers' ) ;
server . setRoute ( '/downloadStall' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . setHeader ( 'Content-Disposition' , 'attachment; filename=file.txt' ) ;
res . writeHead ( 200 ) ;
res . flushHeaders ( ) ;
res . write ( ` Hello world ` ) ;
} ) ;
2021-10-28 05:00:06 +03:00
const browser = await browserType . launch ( ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-04-09 04:56:09 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /downloadStall">click me</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const [ downloadPath , saveError ] = await Promise . all ( [
download . path ( ) ,
download . saveAs ( testInfo . outputPath ( 'download.txt' ) ) . catch ( e = > e ) ,
( browser as any ) . _channel . killForTests ( ) ,
] ) ;
expect ( downloadPath ) . toBe ( null ) ;
expect ( saveError . message ) . toContain ( 'File deleted upon browser context closure.' ) ;
2021-09-07 14:05:29 +03:00
await browser . close ( ) ;
2021-04-09 04:56:09 +03:00
} ) ;
2021-04-29 00:54:51 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should download large binary.zip' , async ( { browser , server , browserName } , testInfo ) = > {
2021-04-29 00:54:51 +03:00
const zipFile = testInfo . outputPath ( 'binary.zip' ) ;
const content = crypto . randomBytes ( 1 << 20 ) ;
fs . writeFileSync ( zipFile , content ) ;
server . setRoute ( '/binary.zip' , ( req , res ) = > server . serveFile ( req , res , zipFile ) ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-04-29 00:54:51 +03:00
await page . goto ( server . PREFIX + '/empty.html' ) ;
await page . setContent ( ` <a href=" ${ server . PREFIX } /binary.zip" download="binary.zip">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const downloadPath = await download . path ( ) ;
const fileContent = fs . readFileSync ( downloadPath ) ;
expect ( fileContent . byteLength ) . toBe ( content . byteLength ) ;
expect ( fileContent . equals ( content ) ) . toBe ( true ) ;
const stream = await download . createReadStream ( ) ;
const data = await new Promise < Buffer > ( ( fulfill , reject ) = > {
const bufs = [ ] ;
stream . on ( 'data' , d = > bufs . push ( d ) ) ;
stream . on ( 'error' , reject ) ;
stream . on ( 'end' , ( ) = > fulfill ( Buffer . concat ( bufs ) ) ) ;
} ) ;
expect ( data . byteLength ) . toBe ( content . byteLength ) ;
expect ( data . equals ( content ) ) . toBe ( true ) ;
await page . close ( ) ;
} ) ;
2021-05-14 23:28:42 +03:00
2021-09-27 19:58:08 +03:00
it ( 'should be able to cancel pending downloads' , async ( { browser , server , browserName , browserVersion } ) = > {
2021-06-12 23:23:22 +03:00
// The exact upstream change is in b449b5c, which still does not appear in the first few 91.* tags until 91.0.4437.0.
it . fixme ( browserName === 'chromium' && Number ( browserVersion . split ( '.' ) [ 0 ] ) < 91 , 'The upstream Browser.cancelDownload command is not available before Chrome 91' ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-06-12 23:23:22 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /downloadWithDelay">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
2021-07-06 10:38:50 +03:00
await download . cancel ( ) ;
2021-06-12 23:23:22 +03:00
const failure = await download . failure ( ) ;
expect ( failure ) . toBe ( 'canceled' ) ;
await page . close ( ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should not fail explicitly to cancel a download even if that is already finished' , async ( { browser , server , browserName , browserVersion } ) = > {
2021-06-12 23:23:22 +03:00
// The exact upstream change is in b449b5c, which still does not appear in the first few 91.* tags until 91.0.4437.0.
it . fixme ( browserName === 'chromium' && Number ( browserVersion . split ( '.' ) [ 0 ] ) < 91 , 'The upstream Browser.cancelDownload command is not available before Chrome 91' ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-06-12 23:23:22 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
2021-07-06 10:38:50 +03:00
await download . cancel ( ) ;
2021-06-12 23:23:22 +03:00
const failure = await download . failure ( ) ;
expect ( failure ) . toBe ( null ) ;
await page . close ( ) ;
} ) ;
2021-09-27 19:58:08 +03:00
it ( 'should report downloads with interception' , async ( { browser , server } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-05-14 23:28:42 +03:00
await page . route ( /.*/ , r = > r . continue ( ) ) ;
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
const path = await download . path ( ) ;
expect ( fs . existsSync ( path ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( path ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2021-11-01 19:45:22 +03:00
it ( 'should emit download event from nested iframes' , async ( { server , browser , browserName } , testInfo ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-11-01 19:45:22 +03:00
server . setRoute ( '/1' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'text/html' ) ;
res . end ( ` <iframe src=" ${ server . PREFIX } /2"></iframe> ` ) ;
} ) ;
server . setRoute ( '/2' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'text/html' ) ;
res . end ( ` <iframe src=" ${ server . PREFIX } /3"></iframe> ` ) ;
} ) ;
server . setRoute ( '/3' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'text/html' ) ;
res . end ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
} ) ;
await page . goto ( server . PREFIX + '/1' ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . frame ( {
url : server.PREFIX + '/3'
} ) . click ( 'text=download' )
] ) ;
const userPath = testInfo . outputPath ( 'download.txt' ) ;
await download . saveAs ( userPath ) ;
expect ( fs . existsSync ( userPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( userPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2020-08-03 23:41:48 +03:00
} ) ;
2021-09-30 18:50:13 +03:00
it ( 'should be able to download a PDF file' , async ( { browser , server , asset } ) = > {
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-09-30 18:50:13 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( `
< a href = "/empty.pdf" download > download < / a >
` );
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' ) ,
] ) ;
await assertDownloadToPDF ( download , asset ( 'empty.pdf' ) ) ;
await page . close ( ) ;
} ) ;
2021-10-01 00:12:33 +03:00
it ( 'should be able to download a inline PDF file' , async ( { browser , server , asset , browserName } ) = > {
it . fixme ( browserName === 'webkit' ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-09-30 18:50:13 +03:00
await page . goto ( server . EMPTY_PAGE ) ;
await page . route ( '**/empty.pdf' , async route = > {
2021-10-06 02:36:15 +03:00
const response = await page . context ( ) . request . fetch ( route . request ( ) ) ;
2021-09-30 18:50:13 +03:00
await route . fulfill ( {
response ,
headers : {
. . . response . headers ( ) ,
'Content-Disposition' : 'attachment' ,
}
} ) ;
} ) ;
await page . setContent ( `
< a href = "/empty.pdf" > open < / a >
` );
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' ) ,
] ) ;
await assertDownloadToPDF ( download , asset ( 'empty.pdf' ) ) ;
await page . close ( ) ;
} ) ;
2021-10-14 20:41:03 +03:00
it ( 'should save to user-specified path' , async ( { browser , server , mode } , testInfo ) = > {
server . setRoute ( '/download' , ( req , res ) = > {
res . setHeader ( 'Content-Type' , 'application/octet-stream' ) ;
res . setHeader ( 'Content-Disposition' , 'attachment' ) ;
res . end ( ` Hello world ` ) ;
} ) ;
2021-12-06 20:25:24 +03:00
const page = await browser . newPage ( ) ;
2021-10-14 20:41:03 +03:00
await page . setContent ( ` <a href=" ${ server . PREFIX } /download">download</a> ` ) ;
const [ download ] = await Promise . all ( [
page . waitForEvent ( 'download' ) ,
page . click ( 'a' )
] ) ;
if ( mode === 'service' ) {
const error = await download . path ( ) . catch ( e = > e ) ;
expect ( error . message ) . toContain ( 'Path is not available when connecting remotely. Use saveAs() to save a local copy.' ) ;
}
const userPath = testInfo . outputPath ( 'download.txt' ) ;
await download . saveAs ( userPath ) ;
expect ( fs . existsSync ( userPath ) ) . toBeTruthy ( ) ;
expect ( fs . readFileSync ( userPath ) . toString ( ) ) . toBe ( 'Hello world' ) ;
await page . close ( ) ;
} ) ;
2021-09-30 18:50:13 +03:00
async function assertDownloadToPDF ( download : Download , filePath : string ) {
expect ( download . suggestedFilename ( ) ) . toBe ( path . basename ( filePath ) ) ;
const stream = await download . createReadStream ( ) ;
const data = await new Promise < Buffer > ( ( fulfill , reject ) = > {
const bufs = [ ] ;
stream . on ( 'data' , d = > bufs . push ( d ) ) ;
stream . on ( 'error' , reject ) ;
stream . on ( 'end' , ( ) = > fulfill ( Buffer . concat ( bufs ) ) ) ;
} ) ;
expect ( download . url ( ) . endsWith ( '/' + path . basename ( filePath ) ) ) . toBeTruthy ( ) ;
const expectedPrefix = '%PDF' ;
for ( let i = 0 ; i < expectedPrefix . length ; i ++ )
expect ( data [ i ] ) . toBe ( expectedPrefix . charCodeAt ( i ) ) ;
assertBuffer ( data , fs . readFileSync ( filePath ) ) ;
}
async function assertBuffer ( expected : Buffer , actual : Buffer ) {
expect ( expected . byteLength ) . toBe ( actual . byteLength ) ;
for ( let i = 0 ; i < expected . byteLength ; i ++ )
expect ( expected [ i ] ) . toBe ( actual [ i ] ) ;
}