From e70f98ddb93e460777e5bc93a19fea68efab904c Mon Sep 17 00:00:00 2001
From: AlexChung1995 <alex.chung@mail.mcgill.ca>
Date: Tue, 31 Oct 2017 21:47:52 -0700
Subject: [PATCH] feat(Page.select): return selected options from Page.select
 (#1099)

This patch teaches Page.select to return an array of actually selected options.
If no option is selected, an empty array will be returned.
---
 docs/api.md  |  4 ++--
 lib/Page.js  | 15 ++++++--------
 test/test.js | 58 +++++++++++++++++++++++++++++++++++++---------------
 3 files changed, 50 insertions(+), 27 deletions(-)

diff --git a/docs/api.md b/docs/api.md
index 23b70720b8d..5f54e0f2869 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -929,10 +929,10 @@ Shortcut for [page.mainFrame().executionContext().queryObjects(prototypeHandle)]
 #### page.select(selector, ...values)
 - `selector` <[string]> A [selector] to query page for
 - `...values` <...[string]> Values of options to select. If the `<select>` has the `multiple` attribute, all values are considered, otherwise only the first one is taken into account.
-- returns: <[Promise]>
+- returns: <[Promise]<[Array]<[string]>>> Returns an array of option values that have been successfully selected.
 
 Triggers a `change` and `input` event once all the provided options have been selected.
-If there's no `<select>` element matching `selector`, the method throws an error.
+If there's no `<select>` element matching `selector`, the method throws an error. 
 
 ```js
 page.select('select#colors', 'blue'); // single selection
diff --git a/lib/Page.js b/lib/Page.js
index cfb2dd2e0c8..6f56f9f9ad4 100644
--- a/lib/Page.js
+++ b/lib/Page.js
@@ -779,22 +779,19 @@ class Page extends EventEmitter {
   /**
    * @param {string} selector
    * @param {!Array<string>} values
+   * @return {!Promise<!Array<string>>}
    */
   async select(selector, ...values) {
-    await this.$eval(selector, (element, values) => {
+    return await this.$eval(selector, (element, values) => {
       if (element.nodeName.toLowerCase() !== 'select')
         throw new Error('Element is not a <select> element.');
-
       const options = Array.from(element.options);
-
-      if (element.multiple) {
-        for (const option of options)
-          option.selected = values.includes(option.value);
-      } else {
-        element.value = values.shift();
-      }
+      element.value = undefined;
+      for (const option of options)
+        option.selected = values.includes(option.value);
       element.dispatchEvent(new Event('input', { 'bubbles': true }));
       element.dispatchEvent(new Event('change', { 'bubbles': true }));
+      return options.filter(option => option.selected).map(option => option.value);
     }, values);
   }
 
diff --git a/test/test.js b/test/test.js
index 3b0bde150d4..5f98ee573db 100644
--- a/test/test.js
+++ b/test/test.js
@@ -2712,27 +2712,53 @@ describe('Page', function() {
       expect(await page.evaluate(() => result.onBubblingChange)).toEqual(['blue']);
     }));
 
-    it('should work with no options', SX(async function() {
-      await page.goto(PREFIX + '/input/select.html');
-      await page.evaluate(() => makeEmpty());
-      await page.select('select', '42');
-      expect(await page.evaluate(() => result.onInput)).toEqual([]);
-      expect(await page.evaluate(() => result.onChange)).toEqual([]);
-    }));
-
-    it('should not select a non-existent option', SX(async function() {
-      await page.goto(PREFIX + '/input/select.html');
-      await page.select('select', '42');
-      expect(await page.evaluate(() => result.onInput)).toEqual([]);
-      expect(await page.evaluate(() => result.onChange)).toEqual([]);
-    }));
-
-    it('should throw', SX(async function() {
+    it('should throw when element is not a <select>', SX(async function() {
       let error = null;
       await page.goto(PREFIX + '/input/select.html');
       await page.select('body', '').catch(e => error = e);
       expect(error.message).toContain('Element is not a <select> element.');
     }));
+
+    it('should return [] on no matched values', SX(async function() {
+      await page.goto(PREFIX + '/input/select.html');
+      const result = await page.select('select','42','abc');
+      expect(result).toEqual([]);
+    }));
+
+    it('should return an array of matched values', SX(async function() {
+      await page.goto(PREFIX + '/input/select.html');
+      await page.evaluate(() => makeMultiple());
+      const result = await page.select('select','blue','black','magenta');
+      expect(result.reduce((accumulator,current) => ['blue', 'black', 'magenta'].includes(current) && accumulator, true)).toEqual(true);
+    }));
+
+    it('should return an array of one element when multiple is not set', SX(async function() {
+      await page.goto(PREFIX + '/input/select.html');
+      const result = await page.select('select','42','blue','black','magenta');
+      expect(result.length).toEqual(1);
+    }));
+
+    it('should return [] on no values',SX(async function() {
+      await page.goto(PREFIX + '/input/select.html');
+      const result = await page.select('select');
+      expect(result).toEqual([]);
+    }));
+
+    it('should deselect all options when passed no values for a multiple select',SX(async function() {
+      await page.goto(PREFIX + '/input/select.html');
+      await page.evaluate(() => makeMultiple());
+      await page.select('select','blue','black','magenta');
+      await page.select('select');
+      expect(await page.$eval('select', select => Array.from(select.options).every(option => !option.selected))).toEqual(true);
+    }));
+
+    it('should deselect all options when passed no values for a select without multiple',SX(async function() {
+      await page.goto(PREFIX + '/input/select.html');
+      await page.select('select','blue','black','magenta');
+      await page.select('select');
+      expect(await page.$eval('select', select => Array.from(select.options).every(option => !option.selected))).toEqual(true);
+    }));
+
   });
 
   describe('Tracing', function() {