1/** 2 * Copyright 2017 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 17import path from 'path'; 18import expect from 'expect'; 19import { 20 getTestState, 21 setupTestBrowserHooks, 22 setupTestPageAndContextHooks, 23 describeFailsFirefox, 24} from './mocha-utils'; // eslint-disable-line import/extensions 25 26const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt'); 27 28describe('input tests', function () { 29 setupTestBrowserHooks(); 30 setupTestPageAndContextHooks(); 31 32 describeFailsFirefox('input', function () { 33 it('should upload the file', async () => { 34 const { page, server } = getTestState(); 35 36 await page.goto(server.PREFIX + '/input/fileupload.html'); 37 const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD); 38 const input = await page.$('input'); 39 await page.evaluate((e: HTMLElement) => { 40 globalThis._inputEvents = []; 41 e.addEventListener('change', (ev) => 42 globalThis._inputEvents.push(ev.type) 43 ); 44 e.addEventListener('input', (ev) => 45 globalThis._inputEvents.push(ev.type) 46 ); 47 }, input); 48 await input.uploadFile(filePath); 49 expect( 50 await page.evaluate((e: HTMLInputElement) => e.files[0].name, input) 51 ).toBe('file-to-upload.txt'); 52 expect( 53 await page.evaluate((e: HTMLInputElement) => e.files[0].type, input) 54 ).toBe('text/plain'); 55 expect(await page.evaluate(() => globalThis._inputEvents)).toEqual([ 56 'input', 57 'change', 58 ]); 59 expect( 60 await page.evaluate((e: HTMLInputElement) => { 61 const reader = new FileReader(); 62 const promise = new Promise((fulfill) => (reader.onload = fulfill)); 63 reader.readAsText(e.files[0]); 64 return promise.then(() => reader.result); 65 }, input) 66 ).toBe('contents of the file'); 67 }); 68 }); 69 70 describeFailsFirefox('Page.waitForFileChooser', function () { 71 it('should work when file input is attached to DOM', async () => { 72 const { page } = getTestState(); 73 74 await page.setContent(`<input type=file>`); 75 const [chooser] = await Promise.all([ 76 page.waitForFileChooser(), 77 page.click('input'), 78 ]); 79 expect(chooser).toBeTruthy(); 80 }); 81 it('should work when file input is not attached to DOM', async () => { 82 const { page } = getTestState(); 83 84 const [chooser] = await Promise.all([ 85 page.waitForFileChooser(), 86 page.evaluate(() => { 87 const el = document.createElement('input'); 88 el.type = 'file'; 89 el.click(); 90 }), 91 ]); 92 expect(chooser).toBeTruthy(); 93 }); 94 it('should respect timeout', async () => { 95 const { page, puppeteer } = getTestState(); 96 97 let error = null; 98 await page 99 .waitForFileChooser({ timeout: 1 }) 100 .catch((error_) => (error = error_)); 101 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 102 }); 103 it('should respect default timeout when there is no custom timeout', async () => { 104 const { page, puppeteer } = getTestState(); 105 106 page.setDefaultTimeout(1); 107 let error = null; 108 await page.waitForFileChooser().catch((error_) => (error = error_)); 109 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 110 }); 111 it('should prioritize exact timeout over default timeout', async () => { 112 const { page, puppeteer } = getTestState(); 113 114 page.setDefaultTimeout(0); 115 let error = null; 116 await page 117 .waitForFileChooser({ timeout: 1 }) 118 .catch((error_) => (error = error_)); 119 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 120 }); 121 it('should work with no timeout', async () => { 122 const { page } = getTestState(); 123 124 const [chooser] = await Promise.all([ 125 page.waitForFileChooser({ timeout: 0 }), 126 page.evaluate(() => 127 setTimeout(() => { 128 const el = document.createElement('input'); 129 el.type = 'file'; 130 el.click(); 131 }, 50) 132 ), 133 ]); 134 expect(chooser).toBeTruthy(); 135 }); 136 it('should return the same file chooser when there are many watchdogs simultaneously', async () => { 137 const { page } = getTestState(); 138 139 await page.setContent(`<input type=file>`); 140 const [fileChooser1, fileChooser2] = await Promise.all([ 141 page.waitForFileChooser(), 142 page.waitForFileChooser(), 143 page.$eval('input', (input: HTMLInputElement) => input.click()), 144 ]); 145 expect(fileChooser1 === fileChooser2).toBe(true); 146 }); 147 }); 148 149 describeFailsFirefox('FileChooser.accept', function () { 150 it('should accept single file', async () => { 151 const { page } = getTestState(); 152 153 await page.setContent( 154 `<input type=file oninput='javascript:console.timeStamp()'>` 155 ); 156 const [chooser] = await Promise.all([ 157 page.waitForFileChooser(), 158 page.click('input'), 159 ]); 160 await Promise.all([ 161 chooser.accept([FILE_TO_UPLOAD]), 162 new Promise((x) => page.once('metrics', x)), 163 ]); 164 expect( 165 await page.$eval( 166 'input', 167 (input: HTMLInputElement) => input.files.length 168 ) 169 ).toBe(1); 170 expect( 171 await page.$eval( 172 'input', 173 (input: HTMLInputElement) => input.files[0].name 174 ) 175 ).toBe('file-to-upload.txt'); 176 }); 177 it('should be able to read selected file', async () => { 178 const { page } = getTestState(); 179 180 await page.setContent(`<input type=file>`); 181 page 182 .waitForFileChooser() 183 .then((chooser) => chooser.accept([FILE_TO_UPLOAD])); 184 expect( 185 await page.$eval('input', async (picker: HTMLInputElement) => { 186 picker.click(); 187 await new Promise((x) => (picker.oninput = x)); 188 const reader = new FileReader(); 189 const promise = new Promise((fulfill) => (reader.onload = fulfill)); 190 reader.readAsText(picker.files[0]); 191 return promise.then(() => reader.result); 192 }) 193 ).toBe('contents of the file'); 194 }); 195 it('should be able to reset selected files with empty file list', async () => { 196 const { page } = getTestState(); 197 198 await page.setContent(`<input type=file>`); 199 page 200 .waitForFileChooser() 201 .then((chooser) => chooser.accept([FILE_TO_UPLOAD])); 202 expect( 203 await page.$eval('input', async (picker: HTMLInputElement) => { 204 picker.click(); 205 await new Promise((x) => (picker.oninput = x)); 206 return picker.files.length; 207 }) 208 ).toBe(1); 209 page.waitForFileChooser().then((chooser) => chooser.accept([])); 210 expect( 211 await page.$eval('input', async (picker: HTMLInputElement) => { 212 picker.click(); 213 await new Promise((x) => (picker.oninput = x)); 214 return picker.files.length; 215 }) 216 ).toBe(0); 217 }); 218 it('should not accept multiple files for single-file input', async () => { 219 const { page } = getTestState(); 220 221 await page.setContent(`<input type=file>`); 222 const [chooser] = await Promise.all([ 223 page.waitForFileChooser(), 224 page.click('input'), 225 ]); 226 let error = null; 227 await chooser 228 .accept([ 229 path.relative( 230 process.cwd(), 231 __dirname + '/assets/file-to-upload.txt' 232 ), 233 path.relative(process.cwd(), __dirname + '/assets/pptr.png'), 234 ]) 235 .catch((error_) => (error = error_)); 236 expect(error).not.toBe(null); 237 }); 238 it('should fail for non-existent files', async () => { 239 const { page } = getTestState(); 240 241 await page.setContent(`<input type=file>`); 242 const [chooser] = await Promise.all([ 243 page.waitForFileChooser(), 244 page.click('input'), 245 ]); 246 let error = null; 247 await chooser 248 .accept(['file-does-not-exist.txt']) 249 .catch((error_) => (error = error_)); 250 expect(error).not.toBe(null); 251 }); 252 it('should fail when accepting file chooser twice', async () => { 253 const { page } = getTestState(); 254 255 await page.setContent(`<input type=file>`); 256 const [fileChooser] = await Promise.all([ 257 page.waitForFileChooser(), 258 page.$eval('input', (input: HTMLInputElement) => input.click()), 259 ]); 260 await fileChooser.accept([]); 261 let error = null; 262 await fileChooser.accept([]).catch((error_) => (error = error_)); 263 expect(error.message).toBe( 264 'Cannot accept FileChooser which is already handled!' 265 ); 266 }); 267 }); 268 269 describeFailsFirefox('FileChooser.cancel', function () { 270 it('should cancel dialog', async () => { 271 const { page } = getTestState(); 272 273 // Consider file chooser canceled if we can summon another one. 274 // There's no reliable way in WebPlatform to see that FileChooser was 275 // canceled. 276 await page.setContent(`<input type=file>`); 277 const [fileChooser1] = await Promise.all([ 278 page.waitForFileChooser(), 279 page.$eval('input', (input: HTMLInputElement) => input.click()), 280 ]); 281 await fileChooser1.cancel(); 282 // If this resolves, than we successfully canceled file chooser. 283 await Promise.all([ 284 page.waitForFileChooser(), 285 page.$eval('input', (input: HTMLInputElement) => input.click()), 286 ]); 287 }); 288 it('should fail when canceling file chooser twice', async () => { 289 const { page } = getTestState(); 290 291 await page.setContent(`<input type=file>`); 292 const [fileChooser] = await Promise.all([ 293 page.waitForFileChooser(), 294 page.$eval('input', (input: HTMLInputElement) => input.click()), 295 ]); 296 await fileChooser.cancel(); 297 let error = null; 298 299 try { 300 fileChooser.cancel(); 301 } catch (error_) { 302 error = error_; 303 } 304 305 expect(error.message).toBe( 306 'Cannot cancel FileChooser which is already handled!' 307 ); 308 }); 309 }); 310 311 describeFailsFirefox('FileChooser.isMultiple', () => { 312 it('should work for single file pick', async () => { 313 const { page } = getTestState(); 314 315 await page.setContent(`<input type=file>`); 316 const [chooser] = await Promise.all([ 317 page.waitForFileChooser(), 318 page.click('input'), 319 ]); 320 expect(chooser.isMultiple()).toBe(false); 321 }); 322 it('should work for "multiple"', async () => { 323 const { page } = getTestState(); 324 325 await page.setContent(`<input multiple type=file>`); 326 const [chooser] = await Promise.all([ 327 page.waitForFileChooser(), 328 page.click('input'), 329 ]); 330 expect(chooser.isMultiple()).toBe(true); 331 }); 332 it('should work for "webkitdirectory"', async () => { 333 const { page } = getTestState(); 334 335 await page.setContent(`<input multiple webkitdirectory type=file>`); 336 const [chooser] = await Promise.all([ 337 page.waitForFileChooser(), 338 page.click('input'), 339 ]); 340 expect(chooser.isMultiple()).toBe(true); 341 }); 342 }); 343}); 344