2018-11-21 02:45:37 +00:00
|
|
|
/**
|
2024-01-03 10:11:33 +00:00
|
|
|
* @license
|
|
|
|
* Copyright 2018 Google Inc.
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
2018-11-21 02:45:37 +00:00
|
|
|
*/
|
2020-06-23 05:18:46 +00:00
|
|
|
import expect from 'expect';
|
2022-10-10 14:01:09 +00:00
|
|
|
import {Puppeteer} from 'puppeteer';
|
2023-02-14 21:31:30 +00:00
|
|
|
import type {CustomQueryHandler} from 'puppeteer-core/internal/common/CustomQueryHandler.js';
|
2023-02-15 23:09:31 +00:00
|
|
|
|
2023-07-03 12:01:29 +00:00
|
|
|
import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';
|
2018-11-21 02:45:37 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
describe('querySelector', function () {
|
2023-07-03 12:01:29 +00:00
|
|
|
setupTestBrowserHooks();
|
|
|
|
|
2020-06-12 13:55:51 +00:00
|
|
|
describe('Page.$eval', function () {
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should work', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2018-11-21 02:45:37 +00:00
|
|
|
|
|
|
|
await page.setContent('<section id="testAttribute">43543</section>');
|
2022-06-22 13:25:44 +00:00
|
|
|
const idAttribute = await page.$eval('section', e => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return e.id;
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(idAttribute).toBe('testAttribute');
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should accept arguments', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent('<section>hello</section>');
|
2020-05-07 10:54:55 +00:00
|
|
|
const text = await page.$eval(
|
|
|
|
'section',
|
2022-06-15 10:09:22 +00:00
|
|
|
(e, suffix) => {
|
|
|
|
return e.textContent! + suffix;
|
|
|
|
},
|
2020-05-07 10:54:55 +00:00
|
|
|
' world!'
|
|
|
|
);
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(text).toBe('hello world!');
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should accept ElementHandles as arguments', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent('<section>hello</section><div> world</div>');
|
2023-08-30 10:02:59 +00:00
|
|
|
using divHandle = (await page.$('div'))!;
|
2020-05-07 10:54:55 +00:00
|
|
|
const text = await page.$eval(
|
|
|
|
'section',
|
2022-06-15 10:09:22 +00:00
|
|
|
(e, div) => {
|
|
|
|
return e.textContent! + (div as HTMLElement).textContent!;
|
|
|
|
},
|
2020-05-07 10:54:55 +00:00
|
|
|
divHandle
|
|
|
|
);
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(text).toBe('hello world');
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should throw error if no element is found', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2022-06-15 10:09:22 +00:00
|
|
|
let error!: Error;
|
2020-05-07 10:54:55 +00:00
|
|
|
await page
|
2022-06-22 13:25:44 +00:00
|
|
|
.$eval('section', e => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return e.id;
|
|
|
|
})
|
2022-06-22 13:25:44 +00:00
|
|
|
.catch(error_ => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return (error = error_);
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
expect(error.message).toContain(
|
|
|
|
'failed to find element matching selector "section"'
|
|
|
|
);
|
2018-11-21 02:45:37 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-09-23 14:02:22 +00:00
|
|
|
// 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>.
|
2020-06-12 13:55:51 +00:00
|
|
|
describe('Page.$$eval', function () {
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should work', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<div>hello</div><div>beautiful</div><div>world!</div>'
|
|
|
|
);
|
2022-06-22 13:25:44 +00:00
|
|
|
const divsCount = await page.$$eval('div', divs => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return divs.length;
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(divsCount).toBe(3);
|
|
|
|
});
|
2020-09-23 14:02:22 +00:00
|
|
|
it('should accept extra arguments', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<div>hello</div><div>beautiful</div><div>world!</div>'
|
|
|
|
);
|
|
|
|
const divsCountPlus5 = await page.$$eval(
|
|
|
|
'div',
|
2022-06-15 10:09:22 +00:00
|
|
|
(divs, two, three) => {
|
|
|
|
return divs.length + (two as number) + (three as number);
|
|
|
|
},
|
2020-09-23 14:02:22 +00:00
|
|
|
2,
|
|
|
|
3
|
|
|
|
);
|
|
|
|
expect(divsCountPlus5).toBe(8);
|
|
|
|
});
|
|
|
|
it('should accept ElementHandles as arguments', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<section>2</section><section>2</section><section>1</section><div>3</div>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using divHandle = (await page.$('div'))!;
|
2020-09-23 14:02:22 +00:00
|
|
|
const sum = await page.$$eval(
|
|
|
|
'section',
|
2022-06-15 10:09:22 +00:00
|
|
|
(sections, div) => {
|
|
|
|
return (
|
|
|
|
sections.reduce((acc, section) => {
|
|
|
|
return acc + Number(section.textContent);
|
|
|
|
}, 0) + Number((div as HTMLElement).textContent)
|
|
|
|
);
|
|
|
|
},
|
2020-09-23 14:02:22 +00:00
|
|
|
divHandle
|
|
|
|
);
|
|
|
|
expect(sum).toBe(8);
|
|
|
|
});
|
2023-03-24 08:35:45 +00:00
|
|
|
it('should handle many elements', async function () {
|
|
|
|
this.timeout(25_000);
|
|
|
|
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
await page.evaluate(
|
|
|
|
`
|
|
|
|
for (var i = 0; i <= 1000; i++) {
|
|
|
|
const section = document.createElement('section');
|
|
|
|
section.textContent = i;
|
|
|
|
document.body.appendChild(section);
|
|
|
|
}
|
|
|
|
`
|
|
|
|
);
|
2022-06-22 13:25:44 +00:00
|
|
|
const sum = await page.$$eval('section', sections => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return sections.reduce((acc, section) => {
|
|
|
|
return acc + Number(section.textContent);
|
|
|
|
}, 0);
|
|
|
|
});
|
2020-09-23 14:02:22 +00:00
|
|
|
expect(sum).toBe(500500);
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
});
|
|
|
|
|
2020-06-12 13:55:51 +00:00
|
|
|
describe('Page.$', function () {
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should query existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent('<section>test</section>');
|
2023-08-30 10:02:59 +00:00
|
|
|
using element = (await page.$('section'))!;
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(element).toBeTruthy();
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should return null for non-existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2023-08-30 10:02:59 +00:00
|
|
|
using element = (await page.$('non-existing-element'))!;
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(element).toBe(null);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
describe('Page.$$', function () {
|
2020-06-12 13:55:51 +00:00
|
|
|
it('should query existing elements', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent('<div>A</div><br/><div>B</div>');
|
|
|
|
const elements = await page.$$('div');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements).toHaveLength(2);
|
2022-06-22 13:25:44 +00:00
|
|
|
const promises = elements.map(element => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return page.evaluate((e: HTMLElement) => {
|
|
|
|
return e.textContent;
|
|
|
|
}, element);
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(await Promise.all(promises)).toEqual(['A', 'B']);
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should return empty array if nothing is found', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page, server} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.goto(server.EMPTY_PAGE);
|
|
|
|
const elements = await page.$$('div');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements).toHaveLength(0);
|
2018-11-21 02:45:37 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-08-04 13:45:21 +00:00
|
|
|
describe('Page.$x', function () {
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should query existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent('<section>test</section>');
|
|
|
|
const elements = await page.$x('/html/body/section');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements[0]).toBeTruthy();
|
|
|
|
expect(elements).toHaveLength(1);
|
2018-11-21 02:45:37 +00:00
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should return empty array for non-existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
const element = await page.$x('/html/body/non-existing-element');
|
|
|
|
expect(element).toEqual([]);
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should return multiple elements', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent('<div></div><div></div>');
|
|
|
|
const elements = await page.$x('/html/body/div');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements).toHaveLength(2);
|
2018-11-21 02:45:37 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
describe('ElementHandle.$', function () {
|
|
|
|
it('should query existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page, server} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.goto(server.PREFIX + '/playground.html');
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div class="second"><div class="inner">A</div></div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
|
|
|
using second = (await html.$('.second'))!;
|
|
|
|
using inner = await second.$('.inner');
|
2022-06-24 06:40:08 +00:00
|
|
|
const content = await page.evaluate(e => {
|
|
|
|
return e?.textContent;
|
2022-06-15 10:09:22 +00:00
|
|
|
}, inner);
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(content).toBe('A');
|
|
|
|
});
|
|
|
|
|
2020-06-12 13:55:51 +00:00
|
|
|
it('should return null for non-existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div class="second"><div class="inner">B</div></div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
|
|
|
using second = await html.$('.third');
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(second).toBe(null);
|
|
|
|
});
|
|
|
|
});
|
2020-06-12 13:55:51 +00:00
|
|
|
describe('ElementHandle.$eval', function () {
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should work', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using tweet = (await page.$('.tweet'))!;
|
2022-06-22 13:25:44 +00:00
|
|
|
const content = await tweet.$eval('.like', node => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return (node as HTMLElement).innerText;
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(content).toBe('100');
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should retrieve content from subtree', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
const htmlContent =
|
|
|
|
'<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-child-div</div></div>';
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent(htmlContent);
|
2023-08-30 10:02:59 +00:00
|
|
|
using elementHandle = (await page.$('#myId'))!;
|
2022-06-22 13:25:44 +00:00
|
|
|
const content = await elementHandle.$eval('.a', node => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return (node as HTMLElement).innerText;
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(content).toBe('a-child-div');
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should throw in case of missing selector', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
const htmlContent =
|
|
|
|
'<div class="a">not-a-child-div</div><div id="myId"></div>';
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent(htmlContent);
|
2023-08-30 10:02:59 +00:00
|
|
|
using elementHandle = (await page.$('#myId'))!;
|
2020-05-07 10:54:55 +00:00
|
|
|
const errorMessage = await elementHandle
|
2022-06-22 13:25:44 +00:00
|
|
|
.$eval('.a', node => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return (node as HTMLElement).innerText;
|
|
|
|
})
|
2022-06-22 13:25:44 +00:00
|
|
|
.catch(error => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return error.message;
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
expect(errorMessage).toBe(
|
|
|
|
`Error: failed to find element matching selector ".a"`
|
|
|
|
);
|
2018-11-21 02:45:37 +00:00
|
|
|
});
|
|
|
|
});
|
2020-06-12 13:55:51 +00:00
|
|
|
describe('ElementHandle.$$eval', function () {
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should work', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using tweet = (await page.$('.tweet'))!;
|
2022-06-22 13:25:44 +00:00
|
|
|
const content = await tweet.$$eval('.like', nodes => {
|
|
|
|
return (nodes as HTMLElement[]).map(n => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return n.innerText;
|
|
|
|
});
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(content).toEqual(['100', '10']);
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should retrieve content from subtree', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
const htmlContent =
|
|
|
|
'<div class="a">not-a-child-div</div><div id="myId"><div class="a">a1-child-div</div><div class="a">a2-child-div</div></div>';
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent(htmlContent);
|
2023-08-30 10:02:59 +00:00
|
|
|
using elementHandle = (await page.$('#myId'))!;
|
2022-06-22 13:25:44 +00:00
|
|
|
const content = await elementHandle.$$eval('.a', nodes => {
|
|
|
|
return (nodes as HTMLElement[]).map(n => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return n.innerText;
|
|
|
|
});
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(content).toEqual(['a1-child-div', 'a2-child-div']);
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should not throw in case of missing selector', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
const htmlContent =
|
|
|
|
'<div class="a">not-a-child-div</div><div id="myId"></div>';
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.setContent(htmlContent);
|
2023-08-30 10:02:59 +00:00
|
|
|
using elementHandle = (await page.$('#myId'))!;
|
2022-06-22 13:25:44 +00:00
|
|
|
const nodesLength = await elementHandle.$$eval('.a', nodes => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return nodes.length;
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(nodesLength).toBe(0);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-06-12 13:55:51 +00:00
|
|
|
describe('ElementHandle.$$', function () {
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should query existing elements', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div>A</div><br/><div>B</div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
2018-11-21 02:45:37 +00:00
|
|
|
const elements = await html.$$('div');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements).toHaveLength(2);
|
2022-06-22 13:25:44 +00:00
|
|
|
const promises = elements.map(element => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return page.evaluate((e: HTMLElement) => {
|
|
|
|
return e.textContent;
|
|
|
|
}, element);
|
|
|
|
});
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(await Promise.all(promises)).toEqual(['A', 'B']);
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
it('should return empty array for non-existing elements', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><span>A</span><br/><span>B</span></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
2018-11-21 02:45:37 +00:00
|
|
|
const elements = await html.$$('div');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements).toHaveLength(0);
|
2018-11-21 02:45:37 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
describe('ElementHandle.$x', function () {
|
|
|
|
it('should query existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page, server} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2018-11-21 02:45:37 +00:00
|
|
|
await page.goto(server.PREFIX + '/playground.html');
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div class="second"><div class="inner">A</div></div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
2018-11-21 02:45:37 +00:00
|
|
|
const second = await html.$x(`./body/div[contains(@class, 'second')]`);
|
2022-06-15 10:09:22 +00:00
|
|
|
const inner = await second[0]!.$x(`./div[contains(@class, 'inner')]`);
|
feat!: type inference for evaluation types (#8547)
This PR greatly improves the types within Puppeteer:
- **Almost everything** is auto-deduced.
- Parameters don't need to be specified in the function. They are deduced from the spread.
- Return types don't need to be specified. They are deduced from the function. (More on this below)
- Selections based on tag names correctly deduce element type, similar to TypeScript's mechanism for `getElementByTagName`.
- [**BREAKING CHANGE**] We've removed the ability to declare return types in type arguments for the following reasons:
1. Setting them will indubitably break auto-deduction.
2. You can just use `as ...` in TypeScript to coerce the correct type (given it makes sense).
- [**BREAKING CHANGE**] `waitFor` is officially gone.
To migrate to these changes, there are only four things you may need to change:
- If you set a return type using the `ReturnType` type parameter, remove it and use `as ...` and `HandleFor` (if necessary).
⛔ `evaluate<ReturnType>(a: number, b: number) => {...}, a, b)`
✅ `(await evaluate(a, b) => {...}, a, b)) as ReturnType`
⛔ `evaluateHandle<ReturnType>(a: number, b: number) => {...}, a, b)`
✅ `(await evaluateHandle(a, b) => {...}, a, b)) as HandleFor<ReturnType>`
- If you set any type parameters in the *parameters* of an evaluation function, remove them.
⛔ `evaluate(a: number, b: number) => {...}, a, b)`
✅ `evaluate(a, b) => {...}, a, b)`
- If you set any type parameters in the method's declaration, remove them.
⛔ `evaluate<(a: number, b: number) => void>((a, b) => {...}, a, b)`
✅ `evaluate(a, b) => {...}, a, b)`
2022-06-23 09:29:46 +00:00
|
|
|
const content = await page.evaluate(e => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return e.textContent;
|
|
|
|
}, inner[0]!);
|
2018-11-21 02:45:37 +00:00
|
|
|
expect(content).toBe('A');
|
|
|
|
});
|
|
|
|
|
2020-06-12 13:55:51 +00:00
|
|
|
it('should return null for non-existing element', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-04-09 05:56:25 +00:00
|
|
|
|
2020-05-07 10:54:55 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div class="second"><div class="inner">B</div></div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
2018-11-21 02:45:37 +00:00
|
|
|
const second = await html.$x(`/div[contains(@class, 'third')]`);
|
|
|
|
expect(second).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
2020-09-23 14:02:22 +00:00
|
|
|
|
|
|
|
// 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 = {
|
2022-06-15 10:09:22 +00:00
|
|
|
queryAll: (element, selector) => {
|
2022-07-06 07:05:37 +00:00
|
|
|
return [...(element as Element).querySelectorAll(selector)];
|
2022-06-15 10:09:22 +00:00
|
|
|
},
|
2020-09-23 14:02:22 +00:00
|
|
|
};
|
|
|
|
before(() => {
|
2022-10-10 14:01:09 +00:00
|
|
|
Puppeteer.registerCustomQueryHandler('allArray', handler);
|
2020-10-23 14:28:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should have registered handler', async () => {
|
|
|
|
expect(
|
2022-10-10 14:01:09 +00:00
|
|
|
Puppeteer.customQueryHandlerNames().includes('allArray')
|
2020-10-23 14:28:38 +00:00
|
|
|
).toBeTruthy();
|
2020-09-23 14:02:22 +00:00
|
|
|
});
|
|
|
|
it('$$ should query existing elements', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
|
|
|
|
await page.setContent(
|
|
|
|
'<html><body><div>A</div><br/><div>B</div></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
2020-09-23 14:02:22 +00:00
|
|
|
const elements = await html.$$('allArray/div');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements).toHaveLength(2);
|
2022-06-22 13:25:44 +00:00
|
|
|
const promises = elements.map(element => {
|
feat!: type inference for evaluation types (#8547)
This PR greatly improves the types within Puppeteer:
- **Almost everything** is auto-deduced.
- Parameters don't need to be specified in the function. They are deduced from the spread.
- Return types don't need to be specified. They are deduced from the function. (More on this below)
- Selections based on tag names correctly deduce element type, similar to TypeScript's mechanism for `getElementByTagName`.
- [**BREAKING CHANGE**] We've removed the ability to declare return types in type arguments for the following reasons:
1. Setting them will indubitably break auto-deduction.
2. You can just use `as ...` in TypeScript to coerce the correct type (given it makes sense).
- [**BREAKING CHANGE**] `waitFor` is officially gone.
To migrate to these changes, there are only four things you may need to change:
- If you set a return type using the `ReturnType` type parameter, remove it and use `as ...` and `HandleFor` (if necessary).
⛔ `evaluate<ReturnType>(a: number, b: number) => {...}, a, b)`
✅ `(await evaluate(a, b) => {...}, a, b)) as ReturnType`
⛔ `evaluateHandle<ReturnType>(a: number, b: number) => {...}, a, b)`
✅ `(await evaluateHandle(a, b) => {...}, a, b)) as HandleFor<ReturnType>`
- If you set any type parameters in the *parameters* of an evaluation function, remove them.
⛔ `evaluate(a: number, b: number) => {...}, a, b)`
✅ `evaluate(a, b) => {...}, a, b)`
- If you set any type parameters in the method's declaration, remove them.
⛔ `evaluate<(a: number, b: number) => void>((a, b) => {...}, a, b)`
✅ `evaluate(a, b) => {...}, a, b)`
2022-06-23 09:29:46 +00:00
|
|
|
return page.evaluate(e => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return e.textContent;
|
|
|
|
}, element);
|
|
|
|
});
|
2020-09-23 14:02:22 +00:00
|
|
|
expect(await Promise.all(promises)).toEqual(['A', 'B']);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('$$ should return empty array for non-existing elements', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
|
|
|
|
await page.setContent(
|
|
|
|
'<html><body><span>A</span><br/><span>B</span></body></html>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using html = (await page.$('html'))!;
|
2020-09-23 14:02:22 +00:00
|
|
|
const elements = await html.$$('allArray/div');
|
2023-04-25 13:02:25 +00:00
|
|
|
expect(elements).toHaveLength(0);
|
2020-09-23 14:02:22 +00:00
|
|
|
});
|
|
|
|
it('$$eval should work', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
|
|
|
|
await page.setContent(
|
|
|
|
'<div>hello</div><div>beautiful</div><div>world!</div>'
|
|
|
|
);
|
2022-06-22 13:25:44 +00:00
|
|
|
const divsCount = await page.$$eval('allArray/div', divs => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return divs.length;
|
|
|
|
});
|
2020-09-23 14:02:22 +00:00
|
|
|
expect(divsCount).toBe(3);
|
|
|
|
});
|
|
|
|
it('$$eval should accept extra arguments', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<div>hello</div><div>beautiful</div><div>world!</div>'
|
|
|
|
);
|
|
|
|
const divsCountPlus5 = await page.$$eval(
|
|
|
|
'allArray/div',
|
2022-06-15 10:09:22 +00:00
|
|
|
(divs, two, three) => {
|
|
|
|
return divs.length + (two as number) + (three as number);
|
|
|
|
},
|
2020-09-23 14:02:22 +00:00
|
|
|
2,
|
|
|
|
3
|
|
|
|
);
|
|
|
|
expect(divsCountPlus5).toBe(8);
|
|
|
|
});
|
|
|
|
it('$$eval should accept ElementHandles as arguments', async () => {
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
await page.setContent(
|
|
|
|
'<section>2</section><section>2</section><section>1</section><div>3</div>'
|
|
|
|
);
|
2023-08-30 10:02:59 +00:00
|
|
|
using divHandle = (await page.$('div'))!;
|
2020-09-23 14:02:22 +00:00
|
|
|
const sum = await page.$$eval(
|
|
|
|
'allArray/section',
|
2022-06-15 10:09:22 +00:00
|
|
|
(sections, div) => {
|
|
|
|
return (
|
|
|
|
sections.reduce((acc, section) => {
|
|
|
|
return acc + Number(section.textContent);
|
|
|
|
}, 0) + Number((div as HTMLElement).textContent)
|
|
|
|
);
|
|
|
|
},
|
2020-09-23 14:02:22 +00:00
|
|
|
divHandle
|
|
|
|
);
|
|
|
|
expect(sum).toBe(8);
|
|
|
|
});
|
2023-03-24 08:35:45 +00:00
|
|
|
it('$$eval should handle many elements', async function () {
|
|
|
|
this.timeout(25_000);
|
|
|
|
|
2023-06-21 19:41:09 +00:00
|
|
|
const {page} = await getTestState();
|
2020-09-23 14:02:22 +00:00
|
|
|
await page.evaluate(
|
|
|
|
`
|
|
|
|
for (var i = 0; i <= 1000; i++) {
|
|
|
|
const section = document.createElement('section');
|
|
|
|
section.textContent = i;
|
|
|
|
document.body.appendChild(section);
|
|
|
|
}
|
|
|
|
`
|
|
|
|
);
|
2022-06-22 13:25:44 +00:00
|
|
|
const sum = await page.$$eval('allArray/section', sections => {
|
2022-06-15 10:09:22 +00:00
|
|
|
return sections.reduce((acc, section) => {
|
|
|
|
return acc + Number(section.textContent);
|
|
|
|
}, 0);
|
|
|
|
});
|
2020-09-23 14:02:22 +00:00
|
|
|
expect(sum).toBe(500500);
|
|
|
|
});
|
|
|
|
});
|
2020-04-09 05:56:25 +00:00
|
|
|
});
|