feat: add page convenience methods for textContent and getAttribute (#2235)

This patch adds:
- `page.innerText()` / `frame.innerText()`
- `page.innerHTML()` / `frame.innerHTML()`
- `page.textContent()` / `frame.textContent()`
- `page.getAttribute()` / `frame.getAttribute()`

Fixes #2143
This commit is contained in:
Andrey Lushnikov 2020-05-18 17:58:23 -07:00 коммит произвёл GitHub
Родитель 359cb3a740
Коммит f24696be62
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 126 добавлений и 5 удалений

Просмотреть файл

@ -719,10 +719,13 @@ page.removeListener('request', logRequest);
- [page.focus(selector[, options])](#pagefocusselector-options) - [page.focus(selector[, options])](#pagefocusselector-options)
- [page.frame(options)](#pageframeoptions) - [page.frame(options)](#pageframeoptions)
- [page.frames()](#pageframes) - [page.frames()](#pageframes)
- [page.getAttribute(selector, name[, options])](#pagegetattributeselector-name-options)
- [page.goBack([options])](#pagegobackoptions) - [page.goBack([options])](#pagegobackoptions)
- [page.goForward([options])](#pagegoforwardoptions) - [page.goForward([options])](#pagegoforwardoptions)
- [page.goto(url[, options])](#pagegotourl-options) - [page.goto(url[, options])](#pagegotourl-options)
- [page.hover(selector[, options])](#pagehoverselector-options) - [page.hover(selector[, options])](#pagehoverselector-options)
- [page.innerHTML(selector[, options])](#pageinnerhtmlselector-options)
- [page.innerText(selector[, options])](#pageinnertextselector-options)
- [page.isClosed()](#pageisclosed) - [page.isClosed()](#pageisclosed)
- [page.keyboard](#pagekeyboard) - [page.keyboard](#pagekeyboard)
- [page.mainFrame()](#pagemainframe) - [page.mainFrame()](#pagemainframe)
@ -740,6 +743,7 @@ page.removeListener('request', logRequest);
- [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders) - [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders)
- [page.setInputFiles(selector, files[, options])](#pagesetinputfilesselector-files-options) - [page.setInputFiles(selector, files[, options])](#pagesetinputfilesselector-files-options)
- [page.setViewportSize(viewportSize)](#pagesetviewportsizeviewportsize) - [page.setViewportSize(viewportSize)](#pagesetviewportsizeviewportsize)
- [page.textContent(selector[, options])](#pagetextcontentselector-options)
- [page.title()](#pagetitle) - [page.title()](#pagetitle)
- [page.type(selector, text[, options])](#pagetypeselector-text-options) - [page.type(selector, text[, options])](#pagetypeselector-text-options)
- [page.uncheck(selector, [options])](#pageuncheckselector-options) - [page.uncheck(selector, [options])](#pageuncheckselector-options)
@ -1349,6 +1353,15 @@ Returns frame matching the specified criteria. Either `name` or `url` must be sp
#### page.frames() #### page.frames()
- returns: <[Array]<[Frame]>> An array of all frames attached to the page. - returns: <[Array]<[Frame]>> An array of all frames attached to the page.
#### page.getAttribute(selector, name[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `name` <[string]> Attribute name to get the value for.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<null|[string]>>
Returns element attribute value.
#### page.goBack([options]) #### page.goBack([options])
- `options` <[Object]> Navigation parameters which might have the following properties: - `options` <[Object]> Navigation parameters which might have the following properties:
- `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultNavigationTimeout(timeout)](#browsercontextsetdefaultnavigationtimeouttimeout), [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout), [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultNavigationTimeout(timeout)](#browsercontextsetdefaultnavigationtimeouttimeout), [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout), [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
@ -1419,6 +1432,22 @@ If there's no element matching `selector`, the method waits until a matching ele
Shortcut for [page.mainFrame().hover(selector[, options])](#framehoverselector-options). Shortcut for [page.mainFrame().hover(selector[, options])](#framehoverselector-options).
#### page.innerHTML(selector[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<[string]>>
Resolves to the `element.innerHTML`.
#### page.innerText(selector[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<[string]>>
Resolves to the `element.innerText`.
#### page.isClosed() #### page.isClosed()
- returns: <[boolean]> - returns: <[boolean]>
@ -1703,11 +1732,22 @@ await page.setViewportSize({
await page.goto('https://example.com'); await page.goto('https://example.com');
``` ```
#### page.textContent(selector[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<null|[string]>>
Resolves to the `element.textContent`.
#### page.title() #### page.title()
- returns: <[Promise]<[string]>> The page's title. - returns: <[Promise]<[string]>> The page's title.
Shortcut for [page.mainFrame().title()](#frametitle). Shortcut for [page.mainFrame().title()](#frametitle).
#### page.type(selector, text[, options]) #### page.type(selector, text[, options])
- `selector` <[string]> A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](#working-with-selectors) for more details. - `selector` <[string]> A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](#working-with-selectors) for more details.
- `text` <[string]> A text to type into a focused element. - `text` <[string]> A text to type into a focused element.
@ -1991,8 +2031,11 @@ console.log(text);
- [frame.fill(selector, value[, options])](#framefillselector-value-options) - [frame.fill(selector, value[, options])](#framefillselector-value-options)
- [frame.focus(selector[, options])](#framefocusselector-options) - [frame.focus(selector[, options])](#framefocusselector-options)
- [frame.frameElement()](#frameframeelement) - [frame.frameElement()](#frameframeelement)
- [frame.getAttribute(selector, name[, options])](#framegetattributeselector-name-options)
- [frame.goto(url[, options])](#framegotourl-options) - [frame.goto(url[, options])](#framegotourl-options)
- [frame.hover(selector[, options])](#framehoverselector-options) - [frame.hover(selector[, options])](#framehoverselector-options)
- [frame.innerHTML(selector[, options])](#frameinnerhtmlselector-options)
- [frame.innerText(selector[, options])](#frameinnertextselector-options)
- [frame.isDetached()](#frameisdetached) - [frame.isDetached()](#frameisdetached)
- [frame.name()](#framename) - [frame.name()](#framename)
- [frame.parentFrame()](#frameparentframe) - [frame.parentFrame()](#frameparentframe)
@ -2000,6 +2043,7 @@ console.log(text);
- [frame.selectOption(selector, values[, options])](#frameselectoptionselector-values-options) - [frame.selectOption(selector, values[, options])](#frameselectoptionselector-values-options)
- [frame.setContent(html[, options])](#framesetcontenthtml-options) - [frame.setContent(html[, options])](#framesetcontenthtml-options)
- [frame.setInputFiles(selector, files[, options])](#framesetinputfilesselector-files-options) - [frame.setInputFiles(selector, files[, options])](#framesetinputfilesselector-files-options)
- [frame.textContent(selector[, options])](#frametextcontentselector-options)
- [frame.title()](#frametitle) - [frame.title()](#frametitle)
- [frame.type(selector, text[, options])](#frametypeselector-text-options) - [frame.type(selector, text[, options])](#frametypeselector-text-options)
- [frame.uncheck(selector, [options])](#frameuncheckselector-options) - [frame.uncheck(selector, [options])](#frameuncheckselector-options)
@ -2269,6 +2313,15 @@ const contentFrame = await frameElement.contentFrame();
console.log(frame === contentFrame); // -> true console.log(frame === contentFrame); // -> true
``` ```
#### frame.getAttribute(selector, name[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `name` <[string]> Attribute name to get the value for.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<null|[string]>>
Returns element attribute value.
#### frame.goto(url[, options]) #### frame.goto(url[, options])
- `url` <[string]> URL to navigate frame to. The url should include scheme, e.g. `https://`. - `url` <[string]> URL to navigate frame to. The url should include scheme, e.g. `https://`.
- `options` <[Object]> Navigation parameters which might have the following properties: - `options` <[Object]> Navigation parameters which might have the following properties:
@ -2312,6 +2365,22 @@ console.log(frame === contentFrame); // -> true
This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element. This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element.
If there's no element matching `selector`, the method waits until a matching element appears in the DOM. If the element is detached during the actionability checks, the action is retried. If there's no element matching `selector`, the method waits until a matching element appears in the DOM. If the element is detached during the actionability checks, the action is retried.
#### frame.innerHTML(selector[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<[string]>>
Resolves to the `element.innerHTML`.
#### frame.innerText(selector[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<[string]>>
Resolves to the `element.innerText`.
#### frame.isDetached() #### frame.isDetached()
- returns: <[boolean]> - returns: <[boolean]>
@ -2400,6 +2469,15 @@ This method expects `selector` to point to an [input element](https://developer.
Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they are resolved relative to the [current working directory](https://nodejs.org/api/process.html#process_process_cwd). For empty array, clears the selected files. Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they are resolved relative to the [current working directory](https://nodejs.org/api/process.html#process_process_cwd). For empty array, clears the selected files.
#### frame.textContent(selector[, options])
- `selector` <[string]> A selector to search for an element. If there are multiple elements satisfying the selector, the first will be picked. See [working with selectors](#working-with-selectors) for more details.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<null|[string]>>
Resolves to the `element.textContent`.
#### frame.title() #### frame.title()
- returns: <[Promise]<[string]>> The page's title. - returns: <[Promise]<[string]>> The page's title.
@ -2776,7 +2854,7 @@ Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
#### elementHandle.getAttribute(name) #### elementHandle.getAttribute(name)
- `name` <[string]> Attribute name to get the value for. - `name` <[string]> Attribute name to get the value for.
- returns: <[Promise]<null|[string]>> Resolves to the attribute value. - returns: <[Promise]<null|[string]>>
Returns element attribute value. Returns element attribute value.
@ -2798,10 +2876,10 @@ This method scrolls element into view if needed, and then uses [page.mouse](#pag
If the element is detached from DOM, the method throws an error. If the element is detached from DOM, the method throws an error.
#### elementHandle.innerHTML() #### elementHandle.innerHTML()
- returns: <[Promise]<null|[string]>> Resolves to the `element.innerHTML`. - returns: <[Promise]<[string]>> Resolves to the `element.innerHTML`.
#### elementHandle.innerText() #### elementHandle.innerText()
- returns: <[Promise]<null|[string]>> Resolves to the `element.innerText`. - returns: <[Promise]<[string]>> Resolves to the `element.innerText`.
#### elementHandle.ownerFrame() #### elementHandle.ownerFrame()
- returns: <[Promise]<?[Frame]>> Returns the frame containing the given element. - returns: <[Promise]<?[Frame]>> Returns the frame containing the given element.

Просмотреть файл

@ -146,16 +146,18 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return this._evaluateInUtility(({node}) => node.textContent, {}); return this._evaluateInUtility(({node}) => node.textContent, {});
} }
async innerText(): Promise<string | null> { async innerText(): Promise<string> {
return this._evaluateInUtility(({node}) => { return this._evaluateInUtility(({node}) => {
if (node.nodeType !== Node.ELEMENT_NODE) if (node.nodeType !== Node.ELEMENT_NODE)
throw new Error('Not an element'); throw new Error('Not an element');
if (node.namespaceURI !== 'http://www.w3.org/1999/xhtml')
throw new Error('Not an HTMLElement');
const element = node as unknown as HTMLElement; const element = node as unknown as HTMLElement;
return element.innerText; return element.innerText;
}, {}); }, {});
} }
async innerHTML(): Promise<string | null> { async innerHTML(): Promise<string> {
return this._evaluateInUtility(({node}) => { return this._evaluateInUtility(({node}) => {
if (node.nodeType !== Node.ELEMENT_NODE) if (node.nodeType !== Node.ELEMENT_NODE)
throw new Error('Not an element'); throw new Error('Not an element');

Просмотреть файл

@ -738,6 +738,26 @@ export class Frame {
(handle, deadline) => handle.focus()); (handle, deadline) => handle.focus());
} }
async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> {
return await this._retryWithSelectorIfNotConnected('textContent', selector, options,
(handle, deadline) => handle.textContent());
}
async innerText(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
return await this._retryWithSelectorIfNotConnected('innerText', selector, options,
(handle, deadline) => handle.innerText());
}
async innerHTML(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
return await this._retryWithSelectorIfNotConnected('innerHTML', selector, options,
(handle, deadline) => handle.innerHTML());
}
async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
return await this._retryWithSelectorIfNotConnected('getAttribute', selector, options,
(handle, deadline) => handle.getAttribute(name) as Promise<string>);
}
async hover(selector: string, options: dom.PointerActionOptions & types.PointerActionWaitOptions = {}) { async hover(selector: string, options: dom.PointerActionOptions & types.PointerActionWaitOptions = {}) {
await this._retryWithSelectorIfNotConnected('hover', selector, options, await this._retryWithSelectorIfNotConnected('hover', selector, options,
(handle, deadline) => handle.hover(helper.optionsWithUpdatedTimeout(options, deadline))); (handle, deadline) => handle.hover(helper.optionsWithUpdatedTimeout(options, deadline)));

Просмотреть файл

@ -456,6 +456,22 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
return this.mainFrame().focus(selector, options); return this.mainFrame().focus(selector, options);
} }
async textContent(selector: string, options?: types.TimeoutOptions): Promise<null|string> {
return this.mainFrame().textContent(selector, options);
}
async innerText(selector: string, options?: types.TimeoutOptions): Promise<string> {
return this.mainFrame().innerText(selector, options);
}
async innerHTML(selector: string, options?: types.TimeoutOptions): Promise<string> {
return this.mainFrame().innerHTML(selector, options);
}
async getAttribute(selector: string, name: string, options?: types.TimeoutOptions): Promise<string | null> {
return this.mainFrame().getAttribute(selector, name, options);
}
async hover(selector: string, options?: dom.PointerActionOptions & types.PointerActionWaitOptions) { async hover(selector: string, options?: dom.PointerActionOptions & types.PointerActionWaitOptions) {
return this.mainFrame().hover(selector, options); return this.mainFrame().hover(selector, options);
} }

Просмотреть файл

@ -366,20 +366,24 @@ describe('ElementHandle convenience API', function() {
await page.goto(`${server.PREFIX}/dom.html`); await page.goto(`${server.PREFIX}/dom.html`);
const handle = await page.$('#outer'); const handle = await page.$('#outer');
expect(await handle.getAttribute('name')).toBe('value'); expect(await handle.getAttribute('name')).toBe('value');
expect(await page.getAttribute('#outer', 'name')).toBe('value');
}); });
it('innerHTML should work', async({page, server}) => { it('innerHTML should work', async({page, server}) => {
await page.goto(`${server.PREFIX}/dom.html`); await page.goto(`${server.PREFIX}/dom.html`);
const handle = await page.$('#outer'); const handle = await page.$('#outer');
expect(await handle.innerHTML()).toBe('<div id="inner">Text,\nmore text</div>'); expect(await handle.innerHTML()).toBe('<div id="inner">Text,\nmore text</div>');
expect(await page.innerHTML('#outer')).toBe('<div id="inner">Text,\nmore text</div>');
}); });
it('innerText should work', async({page, server}) => { it('innerText should work', async({page, server}) => {
await page.goto(`${server.PREFIX}/dom.html`); await page.goto(`${server.PREFIX}/dom.html`);
const handle = await page.$('#inner'); const handle = await page.$('#inner');
expect(await handle.innerText()).toBe('Text, more text'); expect(await handle.innerText()).toBe('Text, more text');
expect(await page.innerText('#inner')).toBe('Text, more text');
}); });
it('textContent should work', async({page, server}) => { it('textContent should work', async({page, server}) => {
await page.goto(`${server.PREFIX}/dom.html`); await page.goto(`${server.PREFIX}/dom.html`);
const handle = await page.$('#inner'); const handle = await page.$('#inner');
expect(await handle.textContent()).toBe('Text,\nmore text'); expect(await handle.textContent()).toBe('Text,\nmore text');
expect(await page.textContent('#inner')).toBe('Text,\nmore text');
}); });
}); });

Просмотреть файл

@ -1203,3 +1203,4 @@ describe('Page api coverage', function() {
expect(await frame.evaluate(() => document.querySelector('textarea').value)).toBe('a'); expect(await frame.evaluate(() => document.querySelector('textarea').value)).toBe('a');
}); });
}); });