diff --git a/docs/api.md b/docs/api.md index 6a04e7df1c..2bc64ca19f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1632,7 +1632,7 @@ Page routes take precedence over browser context routes (set up with [browserCon #### page.selectOption(selector, values[, options]) - `selector` <[string]> A selector to query page for. See [working with selectors](#working-with-selectors) for more details. -- `values` <[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>> Options to select. If the `` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. - `value` <[string]> Matches by `option.value`. - `label` <[string]> Matches by `option.label`. - `index` <[number]> Matches by the index. @@ -2421,7 +2421,7 @@ Shortcuts such as `key: "Control+o"` or `key: "Control+Shift+T"` are supported a #### frame.selectOption(selector, values[, options]) - `selector` <[string]> A selector to query frame for. See [working with selectors](#working-with-selectors) for more details. -- `values` <[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>> Options to select. If the `` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. - `value` <[string]> Matches by `option.value`. - `label` <[string]> Matches by `option.label`. - `index` <[number]> Matches by the index. @@ -2926,7 +2926,7 @@ Throws when ```elementHandle``` does not point to an element [connected](https:/ > **NOTE** If javascript is disabled, element is scrolled into view even when already completely visible. #### elementHandle.selectOption(values[, options]) -- `values` <[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>> Options to select. If the `` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. - `value` <[string]> Matches by `option.value`. - `label` <[string]> Matches by `option.label`. - `index` <[number]> Matches by the index. diff --git a/src/dom.ts b/src/dom.ts index f67c1e1def..925e46c91b 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -344,18 +344,21 @@ export class ElementHandle extends js.JSHandle { return this._retryPointerAction(progress, point => this._page.mouse.dblclick(point.x, point.y, options), options); } - async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise { + async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { return runAbortableTask(progress => this._selectOption(progress, values, options), this._page._logger, this._page._timeoutSettings.timeout(options)); } - async _selectOption(progress: Progress, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions): Promise { + async _selectOption(progress: Progress, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions): Promise { let vals: string[] | ElementHandle[] | types.SelectOption[]; - if (!Array.isArray(values)) + if (values === null) + vals = []; + else if (!Array.isArray(values)) vals = [ values ] as (string[] | ElementHandle[] | types.SelectOption[]); else vals = values; const selectOptions = (vals as any).map((value: any) => typeof value === 'object' ? value : { value }); for (const option of selectOptions) { + assert(option !== null, 'Value items must not be null'); if (option instanceof ElementHandle) continue; if (option.value !== undefined) diff --git a/src/frames.ts b/src/frames.ts index 82a6a6ebde..e9924c787e 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -765,7 +765,7 @@ export class Frame { await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._hover(progress, options)); } - async selectOption(selector: string, values: string | dom.ElementHandle | types.SelectOption | string[] | dom.ElementHandle[] | types.SelectOption[], options: types.NavigatingActionWaitOptions = {}): Promise { + async selectOption(selector: string, values: string | dom.ElementHandle | types.SelectOption | string[] | dom.ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { return this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._selectOption(progress, values, options)); } diff --git a/src/page.ts b/src/page.ts index c451590609..9ca36dd010 100644 --- a/src/page.ts +++ b/src/page.ts @@ -481,7 +481,7 @@ export class Page extends ExtendedEventEmitter { return this.mainFrame().hover(selector, options); } - async selectOption(selector: string, values: string | dom.ElementHandle | types.SelectOption | string[] | dom.ElementHandle[] | types.SelectOption[], options?: types.NavigatingActionWaitOptions): Promise { + async selectOption(selector: string, values: string | dom.ElementHandle | types.SelectOption | string[] | dom.ElementHandle[] | types.SelectOption[] | null, options?: types.NavigatingActionWaitOptions): Promise { return this.mainFrame().selectOption(selector, values, options); } diff --git a/test/page.spec.js b/test/page.spec.js index 663cf937da..b66be4f8dd 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -953,6 +953,21 @@ describe('Page.selectOption', function() { const result = await page.selectOption('select', []); expect(result).toEqual([]); }); + it('should not allow null items',async({page, server}) => { + await page.goto(server.PREFIX + '/input/select.html'); + await page.evaluate(() => makeMultiple()); + let error = null + await page.selectOption('select', ['blue', null, 'black','magenta']).catch(e => error = e); + expect(error.message).toContain('Value items must not be null'); + }); + it('should unselect with null',async({page, server}) => { + await page.goto(server.PREFIX + '/input/select.html'); + await page.evaluate(() => makeMultiple()); + const result = await page.selectOption('select', ['blue', 'black','magenta']); + expect(result.reduce((accumulator,current) => ['blue', 'black', 'magenta'].includes(current) && accumulator, true)).toEqual(true); + await page.selectOption('select', null); + 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 multiple select',async({page, server}) => { await page.goto(server.PREFIX + '/input/select.html'); await page.evaluate(() => makeMultiple());