mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(a11y-query): introduce internal handlers (#6437)
This commit changes the internal representation of query handlers to contain Puppeteer-level code instead of page functions. The interface `CustomQueryHandler` is introduced for user-defined query handlers. When a `CustomQueryHandler` is registered using `registerCustomQueryHandler` a corresponding Puppeteer-level handler is created through `makeQueryHandler` by wrapping the page functions as appropriate. The internal query handlers (defined by the interface `QueryHandler`) contain two new functions: `waitFor` and `queryAllArray`. - `waitFor` allows page-based handlers to make use of the `WaitTask`-backed implementation in `DOMWorld`, whereas purely Puppeteer-based handlers can define an alternative approach instead. - `queryAllArray` is similar to `queryAll` but with a slightly different interface; it returns a `JSHandle` to an array with the results as opposed to an array of `ElementHandle`. It is used by `$$eval`. After this change, we can introduce built-in query handlers that are not executed in the page context (#6307).
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
setupTestBrowserHooks,
|
||||
setupTestPageAndContextHooks,
|
||||
} from './mocha-utils'; // eslint-disable-line import/extensions
|
||||
import { CustomQueryHandler } from '../lib/cjs/puppeteer/common/QueryHandler.js';
|
||||
|
||||
describe('querySelector', function () {
|
||||
setupTestBrowserHooks();
|
||||
@@ -67,6 +68,9 @@ describe('querySelector', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// The tests for $$eval are repeated later in this file in the test group 'QueryAll'.
|
||||
// This is done to also test a query handler where QueryAll returns an Element[]
|
||||
// as opposed to NodeListOf<Element>.
|
||||
describe('Page.$$eval', function () {
|
||||
it('should work', async () => {
|
||||
const { page } = getTestState();
|
||||
@@ -77,6 +81,52 @@ describe('querySelector', function () {
|
||||
const divsCount = await page.$$eval('div', (divs) => divs.length);
|
||||
expect(divsCount).toBe(3);
|
||||
});
|
||||
it('should accept extra arguments', async () => {
|
||||
const { page } = getTestState();
|
||||
await page.setContent(
|
||||
'<div>hello</div><div>beautiful</div><div>world!</div>'
|
||||
);
|
||||
const divsCountPlus5 = await page.$$eval(
|
||||
'div',
|
||||
(divs, two: number, three: number) => divs.length + two + three,
|
||||
2,
|
||||
3
|
||||
);
|
||||
expect(divsCountPlus5).toBe(8);
|
||||
});
|
||||
it('should accept ElementHandles as arguments', async () => {
|
||||
const { page } = getTestState();
|
||||
await page.setContent(
|
||||
'<section>2</section><section>2</section><section>1</section><div>3</div>'
|
||||
);
|
||||
const divHandle = await page.$('div');
|
||||
const sum = await page.$$eval(
|
||||
'section',
|
||||
(sections, div: HTMLElement) =>
|
||||
sections.reduce(
|
||||
(acc, section) => acc + Number(section.textContent),
|
||||
0
|
||||
) + Number(div.textContent),
|
||||
divHandle
|
||||
);
|
||||
expect(sum).toBe(8);
|
||||
});
|
||||
it('should handle many elements', async () => {
|
||||
const { page } = getTestState();
|
||||
await page.evaluate(
|
||||
`
|
||||
for (var i = 0; i <= 1000; i++) {
|
||||
const section = document.createElement('section');
|
||||
section.textContent = i;
|
||||
document.body.appendChild(section);
|
||||
}
|
||||
`
|
||||
);
|
||||
const sum = await page.$$eval('section', (sections) =>
|
||||
sections.reduce((acc, section) => acc + Number(section.textContent), 0)
|
||||
);
|
||||
expect(sum).toBe(500500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page.$', function () {
|
||||
@@ -312,4 +362,100 @@ describe('querySelector', function () {
|
||||
expect(second).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// This is the same tests for `$$eval` and `$$` as above, but with a queryAll
|
||||
// handler that returns an array instead of a list of nodes.
|
||||
describe('QueryAll', function () {
|
||||
const handler: CustomQueryHandler = {
|
||||
queryAll: (element: Element, selector: string) =>
|
||||
Array.from(element.querySelectorAll(selector)),
|
||||
};
|
||||
before(() => {
|
||||
const { puppeteer } = getTestState();
|
||||
puppeteer.__experimental_registerCustomQueryHandler('allArray', handler);
|
||||
});
|
||||
it('$$ should query existing elements', async () => {
|
||||
const { page } = getTestState();
|
||||
|
||||
await page.setContent(
|
||||
'<html><body><div>A</div><br/><div>B</div></body></html>'
|
||||
);
|
||||
const html = await page.$('html');
|
||||
const elements = await html.$$('allArray/div');
|
||||
expect(elements.length).toBe(2);
|
||||
const promises = elements.map((element) =>
|
||||
page.evaluate((e: HTMLElement) => e.textContent, element)
|
||||
);
|
||||
expect(await Promise.all(promises)).toEqual(['A', 'B']);
|
||||
});
|
||||
|
||||
it('$$ should return empty array for non-existing elements', async () => {
|
||||
const { page } = getTestState();
|
||||
|
||||
await page.setContent(
|
||||
'<html><body><span>A</span><br/><span>B</span></body></html>'
|
||||
);
|
||||
const html = await page.$('html');
|
||||
const elements = await html.$$('allArray/div');
|
||||
expect(elements.length).toBe(0);
|
||||
});
|
||||
it('$$eval should work', async () => {
|
||||
const { page } = getTestState();
|
||||
|
||||
await page.setContent(
|
||||
'<div>hello</div><div>beautiful</div><div>world!</div>'
|
||||
);
|
||||
const divsCount = await page.$$eval(
|
||||
'allArray/div',
|
||||
(divs) => divs.length
|
||||
);
|
||||
expect(divsCount).toBe(3);
|
||||
});
|
||||
it('$$eval should accept extra arguments', async () => {
|
||||
const { page } = getTestState();
|
||||
await page.setContent(
|
||||
'<div>hello</div><div>beautiful</div><div>world!</div>'
|
||||
);
|
||||
const divsCountPlus5 = await page.$$eval(
|
||||
'allArray/div',
|
||||
(divs, two: number, three: number) => divs.length + two + three,
|
||||
2,
|
||||
3
|
||||
);
|
||||
expect(divsCountPlus5).toBe(8);
|
||||
});
|
||||
it('$$eval should accept ElementHandles as arguments', async () => {
|
||||
const { page } = getTestState();
|
||||
await page.setContent(
|
||||
'<section>2</section><section>2</section><section>1</section><div>3</div>'
|
||||
);
|
||||
const divHandle = await page.$('div');
|
||||
const sum = await page.$$eval(
|
||||
'allArray/section',
|
||||
(sections, div: HTMLElement) =>
|
||||
sections.reduce(
|
||||
(acc, section) => acc + Number(section.textContent),
|
||||
0
|
||||
) + Number(div.textContent),
|
||||
divHandle
|
||||
);
|
||||
expect(sum).toBe(8);
|
||||
});
|
||||
it('$$eval should handle many elements', async () => {
|
||||
const { page } = getTestState();
|
||||
await page.evaluate(
|
||||
`
|
||||
for (var i = 0; i <= 1000; i++) {
|
||||
const section = document.createElement('section');
|
||||
section.textContent = i;
|
||||
document.body.appendChild(section);
|
||||
}
|
||||
`
|
||||
);
|
||||
const sum = await page.$$eval('allArray/section', (sections) =>
|
||||
sections.reduce((acc, section) => acc + Number(section.textContent), 0)
|
||||
);
|
||||
expect(sum).toBe(500500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user