mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
fix: waitForSelector should work for pseudo classes (#12545)
Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com>
This commit is contained in:
parent
80bbd76626
commit
0b2999f7b1
@ -9,6 +9,7 @@ import type {Protocol} from 'devtools-protocol';
|
|||||||
import type {Frame} from '../api/Frame.js';
|
import type {Frame} from '../api/Frame.js';
|
||||||
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
|
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
|
||||||
import {LazyArg} from '../common/LazyArg.js';
|
import {LazyArg} from '../common/LazyArg.js';
|
||||||
|
import {PollingOptions} from '../common/QueryHandler.js';
|
||||||
import type {
|
import type {
|
||||||
AwaitableIterable,
|
AwaitableIterable,
|
||||||
ElementFor,
|
ElementFor,
|
||||||
@ -534,13 +535,12 @@ export abstract class ElementHandle<
|
|||||||
selector: Selector,
|
selector: Selector,
|
||||||
options: WaitForSelectorOptions = {}
|
options: WaitForSelectorOptions = {}
|
||||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||||
const {updatedSelector, QueryHandler} =
|
const {updatedSelector, QueryHandler, selectorHasPseudoClasses} =
|
||||||
getQueryHandlerAndSelector(selector);
|
getQueryHandlerAndSelector(selector);
|
||||||
return (await QueryHandler.waitFor(
|
return (await QueryHandler.waitFor(this, updatedSelector, {
|
||||||
this,
|
polling: selectorHasPseudoClasses ? PollingOptions.RAF : undefined,
|
||||||
updatedSelector,
|
...options,
|
||||||
options
|
})) as ElementHandle<NodeFor<Selector>> | null;
|
||||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async #checkVisibility(visibility: boolean): Promise<boolean> {
|
async #checkVisibility(visibility: boolean): Promise<boolean> {
|
||||||
|
@ -18,6 +18,7 @@ import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher.js';
|
|||||||
import {EventEmitter, type EventType} from '../common/EventEmitter.js';
|
import {EventEmitter, type EventType} from '../common/EventEmitter.js';
|
||||||
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
|
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
|
||||||
import {transposeIterableHandle} from '../common/HandleIterator.js';
|
import {transposeIterableHandle} from '../common/HandleIterator.js';
|
||||||
|
import {PollingOptions} from '../common/QueryHandler.js';
|
||||||
import type {
|
import type {
|
||||||
Awaitable,
|
Awaitable,
|
||||||
EvaluateFunc,
|
EvaluateFunc,
|
||||||
@ -716,13 +717,12 @@ export abstract class Frame extends EventEmitter<FrameEvents> {
|
|||||||
selector: Selector,
|
selector: Selector,
|
||||||
options: WaitForSelectorOptions = {}
|
options: WaitForSelectorOptions = {}
|
||||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||||
const {updatedSelector, QueryHandler} =
|
const {updatedSelector, QueryHandler, selectorHasPseudoClasses} =
|
||||||
getQueryHandlerAndSelector(selector);
|
getQueryHandlerAndSelector(selector);
|
||||||
return (await QueryHandler.waitFor(
|
return (await QueryHandler.waitFor(this, updatedSelector, {
|
||||||
this,
|
polling: selectorHasPseudoClasses ? PollingOptions.RAF : undefined,
|
||||||
updatedSelector,
|
...options,
|
||||||
options
|
})) as ElementHandle<NodeFor<Selector>> | null;
|
||||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +29,7 @@ const QUERY_SEPARATORS = ['=', '/'];
|
|||||||
*/
|
*/
|
||||||
export function getQueryHandlerAndSelector(selector: string): {
|
export function getQueryHandlerAndSelector(selector: string): {
|
||||||
updatedSelector: string;
|
updatedSelector: string;
|
||||||
|
selectorHasPseudoClasses: boolean;
|
||||||
QueryHandler: typeof QueryHandler;
|
QueryHandler: typeof QueryHandler;
|
||||||
} {
|
} {
|
||||||
for (const handlerMap of [
|
for (const handlerMap of [
|
||||||
@ -42,20 +43,26 @@ export function getQueryHandlerAndSelector(selector: string): {
|
|||||||
const prefix = `${name}${separator}`;
|
const prefix = `${name}${separator}`;
|
||||||
if (selector.startsWith(prefix)) {
|
if (selector.startsWith(prefix)) {
|
||||||
selector = selector.slice(prefix.length);
|
selector = selector.slice(prefix.length);
|
||||||
return {updatedSelector: selector, QueryHandler};
|
return {
|
||||||
|
updatedSelector: selector,
|
||||||
|
selectorHasPseudoClasses: false,
|
||||||
|
QueryHandler,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [pSelector, isPureCSS] = parsePSelectors(selector);
|
const [pSelector, isPureCSS, hasPseudoClasses] = parsePSelectors(selector);
|
||||||
if (isPureCSS) {
|
if (isPureCSS) {
|
||||||
return {
|
return {
|
||||||
updatedSelector: selector,
|
updatedSelector: selector,
|
||||||
|
selectorHasPseudoClasses: hasPseudoClasses,
|
||||||
QueryHandler: CSSQueryHandler,
|
QueryHandler: CSSQueryHandler,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
updatedSelector: JSON.stringify(pSelector),
|
updatedSelector: JSON.stringify(pSelector),
|
||||||
|
selectorHasPseudoClasses: hasPseudoClasses,
|
||||||
QueryHandler: PQueryHandler,
|
QueryHandler: PQueryHandler,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -37,11 +37,16 @@ const unquote = (text: string): string => {
|
|||||||
*/
|
*/
|
||||||
export function parsePSelectors(
|
export function parsePSelectors(
|
||||||
selector: string
|
selector: string
|
||||||
): [selector: ComplexPSelectorList, isPureCSS: boolean] {
|
): [
|
||||||
|
selector: ComplexPSelectorList,
|
||||||
|
isPureCSS: boolean,
|
||||||
|
hasPseudoClasses: boolean,
|
||||||
|
] {
|
||||||
let isPureCSS = true;
|
let isPureCSS = true;
|
||||||
|
let hasPseudoClasses = false;
|
||||||
const tokens = tokenize(selector);
|
const tokens = tokenize(selector);
|
||||||
if (tokens.length === 0) {
|
if (tokens.length === 0) {
|
||||||
return [[], isPureCSS];
|
return [[], isPureCSS, hasPseudoClasses];
|
||||||
}
|
}
|
||||||
let compoundSelector: CompoundPSelector = [];
|
let compoundSelector: CompoundPSelector = [];
|
||||||
let complexSelector: ComplexPSelector = [compoundSelector];
|
let complexSelector: ComplexPSelector = [compoundSelector];
|
||||||
@ -87,6 +92,9 @@ export function parsePSelectors(
|
|||||||
value: unquote(token.argument ?? ''),
|
value: unquote(token.argument ?? ''),
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
|
case 'pseudo-class':
|
||||||
|
hasPseudoClasses = true;
|
||||||
|
continue;
|
||||||
case 'comma':
|
case 'comma':
|
||||||
if (storage.length) {
|
if (storage.length) {
|
||||||
compoundSelector.push(stringify(storage));
|
compoundSelector.push(stringify(storage));
|
||||||
@ -102,5 +110,5 @@ export function parsePSelectors(
|
|||||||
if (storage.length) {
|
if (storage.length) {
|
||||||
compoundSelector.push(stringify(storage));
|
compoundSelector.push(stringify(storage));
|
||||||
}
|
}
|
||||||
return [selectors, isPureCSS];
|
return [selectors, isPureCSS, hasPseudoClasses];
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,14 @@ export type QuerySelector = (
|
|||||||
PuppeteerUtil: PuppeteerUtil
|
PuppeteerUtil: PuppeteerUtil
|
||||||
) => Awaitable<Node | null>;
|
) => Awaitable<Node | null>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const enum PollingOptions {
|
||||||
|
RAF = 'raf',
|
||||||
|
MUTATION = 'mutation',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -139,7 +147,9 @@ export class QueryHandler {
|
|||||||
static async waitFor(
|
static async waitFor(
|
||||||
elementOrFrame: ElementHandle<Node> | Frame,
|
elementOrFrame: ElementHandle<Node> | Frame,
|
||||||
selector: string,
|
selector: string,
|
||||||
options: WaitForSelectorOptions
|
options: WaitForSelectorOptions & {
|
||||||
|
polling?: PollingOptions;
|
||||||
|
}
|
||||||
): Promise<ElementHandle<Node> | null> {
|
): Promise<ElementHandle<Node> | null> {
|
||||||
let frame!: Frame;
|
let frame!: Frame;
|
||||||
using element = await (async () => {
|
using element = await (async () => {
|
||||||
@ -152,6 +162,9 @@ export class QueryHandler {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
const {visible = false, hidden = false, timeout, signal} = options;
|
const {visible = false, hidden = false, timeout, signal} = options;
|
||||||
|
const polling =
|
||||||
|
options.polling ??
|
||||||
|
(visible || hidden ? PollingOptions.RAF : PollingOptions.MUTATION);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
@ -169,7 +182,7 @@ export class QueryHandler {
|
|||||||
return PuppeteerUtil.checkVisibility(node, visible);
|
return PuppeteerUtil.checkVisibility(node, visible);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
polling: visible || hidden ? 'raf' : 'mutation',
|
polling,
|
||||||
root: element,
|
root: element,
|
||||||
timeout,
|
timeout,
|
||||||
signal,
|
signal,
|
||||||
|
@ -407,6 +407,19 @@ describe('waittask specs', function () {
|
|||||||
await watchdog;
|
await watchdog;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work for selector with a pseudo class', async () => {
|
||||||
|
const {page, server} = await getTestState();
|
||||||
|
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const watchdog = page.waitForSelector('input:focus');
|
||||||
|
await expect(
|
||||||
|
Promise.race([watchdog, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await page.setContent(`<input></input>`);
|
||||||
|
await page.click('input');
|
||||||
|
await watchdog;
|
||||||
|
});
|
||||||
|
|
||||||
it('Page.waitForSelector is shortcut for main frame', async () => {
|
it('Page.waitForSelector is shortcut for main frame', async () => {
|
||||||
const {page, server} = await getTestState();
|
const {page, server} = await getTestState();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user