1
+ /**
2
+ * Copyright 2018 Google Inc. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @author ebidel@ (Eric Bidelman)
17
+ */
18
+
19
+ /**
20
+ * Shows how to click a file download link and verify that that file gets
21
+ * downloaded to the local filesystem in the expected location.
22
+ * Note: this approach only works in headful Chrome.
23
+ *
24
+ * Install:
25
+ * npm i puppeteer
26
+ * Run:
27
+ * node verify_download.js
28
+ */
29
+
30
+ const puppeteer = require ( 'puppeteer' ) ;
31
+ const fs = require ( 'fs' ) ;
32
+ const path = require ( 'path' ) ;
33
+ const os = require ( 'os' ) ;
34
+
35
+ const DOWNLOADS_FOLDER = `${ os . homedir ( ) } /Downloads` ;
36
+
37
+ /**
38
+ * From @xprudhomme.
39
+ * Check if file exists, watching containing directory meanwhile.
40
+ * Resolve if the file exists, or if the file is created before the timeout
41
+ * occurs.
42
+ * @param {string } filePath
43
+ * @param {integer } timeout
44
+ */
45
+ function checkFileExists ( filePath , timeout = 15000 ) {
46
+ return new Promise ( ( resolve , reject ) => {
47
+ const dir = path . dirname ( filePath ) ;
48
+ const basename = path . basename ( filePath ) ;
49
+
50
+ const watcher = fs . watch ( dir , ( eventType , filename ) => {
51
+ if ( eventType === 'rename' && filename === basename ) {
52
+ clearTimeout ( timer ) ;
53
+ watcher . close ( ) ;
54
+ resolve ( basename ) ;
55
+ }
56
+ } ) ;
57
+
58
+ const timer = setTimeout ( ( ) => {
59
+ watcher . close ( ) ;
60
+ reject ( new Error ( ' [checkFileExists] File does not exist, and was not created during the timeout delay.' ) ) ;
61
+ } , timeout ) ;
62
+
63
+ fs . access ( filePath , fs . constants . R_OK , err => {
64
+ if ( ! err ) {
65
+ clearTimeout ( timer ) ;
66
+ watcher . close ( ) ;
67
+ resolve ( basename ) ;
68
+ }
69
+ } ) ;
70
+ } ) ;
71
+ }
72
+
73
+ /**
74
+ * @param {!Browser } browser
75
+ * @param {string } url The URL of the download file to wait for.
76
+ * @returns {!Promise<!Object> } Metadata about the latest file in Download Manager.
77
+ */
78
+ async function waitForFileToDownload ( browser , url ) {
79
+ const downloadPage = await browser . newPage ( ) ;
80
+ // Note: navigating to this page only works in headful chrome.
81
+ await downloadPage . goto ( 'chrome://downloads/' ) ;
82
+
83
+ // Wait for our download to show up in the list by matching on its url.
84
+ const jsHandle = await downloadPage . waitForFunction ( downloadUrl => {
85
+ const manager = document . querySelector ( 'downloads-manager' ) ;
86
+ const downloads = manager . items_ . length ;
87
+ const lastDownload = manager . items_ [ 0 ] ;
88
+ if ( downloads && lastDownload . url === downloadUrl &&
89
+ lastDownload . state === 'COMPLETE' ) {
90
+ return manager . items_ [ 0 ] ;
91
+ }
92
+ } , { polling : 100 } , url ) ;
93
+
94
+ const fileMeta = await jsHandle . jsonValue ( ) ;
95
+
96
+ await downloadPage . close ( ) ;
97
+
98
+ return fileMeta ;
99
+ }
100
+
101
+ /**
102
+ * @param {!Browser } browser
103
+ * @param {string } url The url of the page to navigate to.
104
+ * @param {string } text The link with this text to find and click on the page.
105
+ * @returns {!Promise<?string> } The download resource's url.
106
+ */
107
+ async function clickDownloadLink ( browser , url , text ) {
108
+ const page = await browser . newPage ( ) ;
109
+ await page . goto ( url , { waitUntil : 'networkidle2' } ) ;
110
+
111
+ const downloadUrl = await page . evaluate ( ( text ) => {
112
+ const link = document . evaluate ( `//a[text()="${ text } "]` , document ,
113
+ null , XPathResult . FIRST_ORDERED_NODE_TYPE , null ) . singleNodeValue ;
114
+ if ( link ) {
115
+ link . click ( ) ;
116
+ return link . href ;
117
+ }
118
+ return null ;
119
+ } , text ) ;
120
+
121
+ await page . close ( ) ;
122
+
123
+ return downloadUrl ;
124
+ }
125
+
126
+ ( async ( ) => {
127
+
128
+ const browser = await puppeteer . launch ( {
129
+ headless : false ,
130
+ // dumpio: true,
131
+ } ) ;
132
+
133
+ // TODO: setDownloadBehavior would be a good approach, as we could check
134
+ // that the file shows up in the location specified by downloadPath. Howeverm
135
+ // that arg doesn't currently work.
136
+ // const client = await page.target().createCDPSession();
137
+ // await client.send('Page.setDownloadBehavior', {
138
+ // behavior: 'allow',
139
+ // downloadPath: path.resolve(__dirname, 'downloads'),
140
+ // });
141
+
142
+ // await client.detach();
143
+
144
+ // 1. navigate to a page with a bunch links to download.
145
+ // 2. click the "Short Selling (csv)" link on the page. The browser force downloads the file.
146
+ const url = 'https://www.nseindia.com/products/content/equities/equities/homepage_eq.htm' ;
147
+ const downloadUrl = await clickDownloadLink ( browser , url , 'Short Selling (csv)' ) ;
148
+
149
+ if ( ! downloadUrl ) {
150
+ console . error ( 'Did not find download link!' ) ;
151
+ return ;
152
+ }
153
+
154
+ // 3. Open chrome:downloads and wait for the file to be downloaded.
155
+ const fileMeta = await waitForFileToDownload ( browser , downloadUrl ) ;
156
+
157
+ console . log ( `"${ fileMeta . file_name } " was downloaded` ) ;
158
+
159
+ // 4. Optionally check that the file really ends up in the expected location
160
+ // on the filesystem.
161
+ const exists = await checkFileExists ( `${ DOWNLOADS_FOLDER } /${ fileMeta . file_name } ` ) ;
162
+ console . assert ( exists , `${ fileMeta . file_name } was not downloaded to correct location.` ) ;
163
+
164
+ await browser . close ( ) ;
165
+
166
+ } ) ( ) ;
0 commit comments