Skip to content

Commit

Permalink
[api] Introduce Page.select method (#779)
Browse files Browse the repository at this point in the history
This patch adds `Page.select` method to select
values in a `select` tag.
  • Loading branch information
alixaxel authored and aslushnikov committed Sep 25, 2017
1 parent acdb588 commit 45f2640
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
14 changes: 14 additions & 0 deletions docs/api.md
Expand Up @@ -60,6 +60,7 @@
+ [page.press(key[, options])](#pagepresskey-options)
+ [page.reload(options)](#pagereloadoptions)
+ [page.screenshot([options])](#pagescreenshotoptions)
+ [page.select(selector, ...values)](#pageselectselector-values)
+ [page.setContent(html)](#pagesetcontenthtml)
+ [page.setCookie(...cookies)](#pagesetcookiecookies)
+ [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders)
Expand Down Expand Up @@ -725,6 +726,19 @@ Shortcut for [`keyboard.down`](#keyboarddownkey-options) and [`keyboard.up`](#ke
- `omitBackground` <[boolean]> Hides default white background and allows capturing screenshots with transparency. Defaults to `false`.
- returns: <[Promise]<[Buffer]>> Promise which resolves to buffer with captured screenshot

#### 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]>

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.

```js
page.select('select#colors', 'blue'); // single selection
page.select('select#colors', 'red', 'green', 'blue'); // multiple selections
```

#### page.setContent(html)
- `html` <[string]> HTML markup to assign to the page.
- returns: <[Promise]>
Expand Down
23 changes: 23 additions & 0 deletions lib/Page.js
Expand Up @@ -693,6 +693,29 @@ class Page extends EventEmitter {
await handle.dispose();
}

/**
* @param {string} selector
* @param {!Array<string>} values
*/
async select(selector, ...values) {
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.dispatchEvent(new Event('change'));
element.dispatchEvent(new Event('input'));
}, values);
}

/**
* @param {string} text
* @param {{delay: (number|undefined)}=} options
Expand Down
55 changes: 55 additions & 0 deletions test/assets/input/select.html
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<title>Selection Test</title>
</head>
<body>
<select>
<option value="black">Black</option>
<option value="blue">Blue</option>
<option value="brown">Brown</option>
<option value="cyan">Cyan</option>
<option value="gray">Gray</option>
<option value="green">Green</option>
<option value="indigo">Indigo</option>
<option value="magenta">Magenta</option>
<option value="orange">Orange</option>
<option value="pink">Pink</option>
<option value="purple">Purple</option>
<option value="red">Red</option>
<option value="violet">Violet</option>
<option value="white">White</option>
<option value="yellow">Yellow</option>
</select>
<script>
window.result = {
onInput: null,
onChange: null,
};

let select = document.querySelector('select');

function makeEmpty() {
for (let i = select.options.length - 1; i >= 0; --i) {
select.remove(i);
}
}

function makeMultiple() {
select.setAttribute('multiple', true);
}

select.addEventListener('input', () => {
result.onInput = Array.from(select.querySelectorAll('option:checked')).map((option) => {
return option.value;
});
}, false);

select.addEventListener('change', () => {
result.onChange = Array.from(select.querySelectorAll('option:checked')).map((option) => {
return option.value;
});
}, false);
</script>
</body>
</html>
44 changes: 44 additions & 0 deletions test/test.js
Expand Up @@ -1177,6 +1177,7 @@ describe('Page', function() {
expect(element).toBe(null);
}));
});

describe('Page.$$', function() {
it('should query existing elements', SX(async function() {
await page.setContent('<div>A</div><br/><div>B</div>');
Expand Down Expand Up @@ -1530,6 +1531,7 @@ describe('Page', function() {
expect(await page.evaluate(() => navigator.userAgent)).toContain('Safari');
}));
});

describe('Page.setExtraHTTPHeaders', function() {
it('should work', SX(async function() {
await page.setExtraHTTPHeaders({
Expand All @@ -1551,6 +1553,7 @@ describe('Page', function() {
expect(error.message).toBe('Expected value of header "foo" to be String, but "number" is found.');
}));
});

describe('Page.authenticate', function() {
it('should work', SX(async function() {
server.setAuth('/empty.html', 'user', 'pass');
Expand Down Expand Up @@ -1588,6 +1591,7 @@ describe('Page', function() {
expect(response.status).toBe(401);
}));
});

describe('Page.setContent', function() {
const expectedOutput = '<html><head></head><body><div>hello</div></body></html>';
it('should work', SX(async function() {
Expand All @@ -1609,6 +1613,7 @@ describe('Page', function() {
expect(result).toBe(`${doctype}${expectedOutput}`);
}));
});

describe('Network Events', function() {
it('Page.Events.Request', SX(async function() {
const requests = [];
Expand Down Expand Up @@ -2021,6 +2026,45 @@ describe('Page', function() {
}));
});

describe('Page.select', function() {
it('should select single option', SX(async function() {
await page.goto(PREFIX + '/input/select.html');
await page.select('select', 'blue');
expect(await page.evaluate(() => result.onInput)).toEqual(['blue']);
expect(await page.evaluate(() => result.onChange)).toEqual(['blue']);
}));

it('should select multiple options', SX(async function() {
await page.goto(PREFIX + '/input/select.html');
await page.evaluate(() => makeMultiple());
await page.select('select', 'blue', 'green', 'red');
expect(await page.evaluate(() => result.onInput)).toEqual(['blue', 'green', 'red']);
expect(await page.evaluate(() => result.onChange)).toEqual(['blue', 'green', 'red']);
}));

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() {
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.');
}));
});

describe('Tracing', function() {
const outputFile = path.join(__dirname, 'assets', 'trace.json');
afterEach(function() {
Expand Down

0 comments on commit 45f2640

Please sign in to comment.