Skip to content

Commit

Permalink
fix!: remove root from WaitForSelectorOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
jrandolf committed Aug 25, 2022
1 parent 498fbf9 commit e8e6fc8
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 116 deletions.
87 changes: 59 additions & 28 deletions src/common/AriaQueryHandler.ts
Expand Up @@ -18,11 +18,8 @@ import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {
IsolatedWorld,
PageBinding,
WaitForSelectorOptions,
} from './IsolatedWorld.js';
import {Frame} from './Frame.js';
import {MAIN_WORLD, PageBinding, PUPPETEER_WORLD} from './IsolatedWorld.js';
import {InternalQueryHandler} from './QueryHandler.js';

async function queryAXTree(
Expand Down Expand Up @@ -89,52 +86,86 @@ function parseAriaSelector(selector: string): ARIAQueryOption {
return queryOptions;
}

const queryOne = async (
element: ElementHandle<Node>,
selector: string
): Promise<ElementHandle<Node> | null> => {
const exeCtx = element.executionContext();
const queryOneId = async (element: ElementHandle<Node>, selector: string) => {
const {name, role} = parseAriaSelector(selector);
const res = await queryAXTree(exeCtx._client, element, name, role);
const res = await queryAXTree(element.client, element, name, role);
if (!res[0] || !res[0].backendDOMNodeId) {
return null;
}
return (await exeCtx._world!.adoptBackendNode(
res[0].backendDOMNodeId
return res[0].backendDOMNodeId;
};

const queryOne: InternalQueryHandler['queryOne'] = async (
element,
selector
) => {
const id = await queryOneId(element, selector);
if (!id) {
return null;
}
return (await element.frame.worlds[MAIN_WORLD].adoptBackendNode(
id
)) as ElementHandle<Node>;
};

const waitFor = async (
isolatedWorld: IsolatedWorld,
selector: string,
options: WaitForSelectorOptions
): Promise<ElementHandle<Element> | null> => {
const waitFor: InternalQueryHandler['waitFor'] = async (
elementOrFrame,
selector,
options
) => {
let frame: Frame;
let element: ElementHandle<Node> | undefined;
if (elementOrFrame instanceof Frame) {
frame = elementOrFrame;
} else {
frame = elementOrFrame.frame;
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(elementOrFrame);
}
const binding: PageBinding = {
name: 'ariaQuerySelector',
pptrFunction: async (selector: string) => {
const root = options.root || (await isolatedWorld.document());
const element = await queryOne(root, selector);
return element;
const id = await queryOneId(
element || (await frame.worlds[PUPPETEER_WORLD].document()),
selector
);
if (!id) {
return null;
}
return (await frame.worlds[PUPPETEER_WORLD].adoptBackendNode(
id
)) as ElementHandle<Node>;
},
};
return (await isolatedWorld._waitForSelectorInPage(
const result = await frame.worlds[PUPPETEER_WORLD]._waitForSelectorInPage(
(_: Element, selector: string) => {
return (
globalThis as unknown as {
ariaQuerySelector(selector: string): void;
ariaQuerySelector(selector: string): Node | null;
}
).ariaQuerySelector(selector);
},
element,
selector,
options,
binding
)) as ElementHandle<Element> | null;
);
if (element) {
await element.dispose();
}
if (!result) {
return null;
}
if (!(result instanceof ElementHandle)) {
await result.dispose();
return null;
}
return result.frame.worlds[MAIN_WORLD].transferHandle(result);
};

const queryAll = async (
element: ElementHandle<Node>,
selector: string
): Promise<Array<ElementHandle<Node>>> => {
const queryAll: InternalQueryHandler['queryAll'] = async (
element,
selector
) => {
const exeCtx = element.executionContext();
const {name, role} = parseAriaSelector(selector);
const res = await queryAXTree(exeCtx._client, element, name, role);
Expand Down
34 changes: 10 additions & 24 deletions src/common/ElementHandle.ts
Expand Up @@ -3,11 +3,7 @@ import {assert} from '../util/assert.js';
import {ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js';
import {
MAIN_WORLD,
PUPPETEER_WORLD,
WaitForSelectorOptions,
} from './IsolatedWorld.js';
import {WaitForSelectorOptions} from './IsolatedWorld.js';
import {
BoundingBox,
BoxModel,
Expand Down Expand Up @@ -310,26 +306,16 @@ export class ElementHandle<
*/
async waitForSelector<Selector extends string>(
selector: Selector,
options: Exclude<WaitForSelectorOptions, 'root'> = {}
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> {
const frame = this.#frame;
const adoptedRoot = await frame.worlds[PUPPETEER_WORLD].adoptHandle(this);
const handle = await frame.worlds[PUPPETEER_WORLD].waitForSelector(
selector,
{
...options,
root: adoptedRoot,
}
);
await adoptedRoot.dispose();
if (!handle) {
return null;
}
const result = (await frame.worlds[MAIN_WORLD].adoptHandle(
handle
)) as ElementHandle<NodeFor<Selector>>;
await handle.dispose();
return result;
const {updatedSelector, queryHandler} =
getQueryHandlerAndSelector(selector);
assert(queryHandler.waitFor, 'Query handler does not support waiting');
return (await queryHandler.waitFor(
this,
updatedSelector,
options
)) as ElementHandle<NodeFor<Selector>> | null;
}

/**
Expand Down
20 changes: 9 additions & 11 deletions src/common/Frame.ts
@@ -1,4 +1,5 @@
import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
Expand All @@ -15,6 +16,7 @@ import {
} from './IsolatedWorld.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {Page} from './Page.js';
import {getQueryHandlerAndSelector} from './QueryHandler.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';

/**
Expand Down Expand Up @@ -579,18 +581,14 @@ export class Frame {
selector: Selector,
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> {
const handle = await this.worlds[PUPPETEER_WORLD].waitForSelector(
selector,
const {updatedSelector, queryHandler} =
getQueryHandlerAndSelector(selector);
assert(queryHandler.waitFor, 'Query handler does not support waiting');
return (await queryHandler.waitFor(
this,
updatedSelector,
options
);
if (!handle) {
return null;
}
const mainHandle = (await this.worlds[MAIN_WORLD].adoptHandle(
handle
)) as ElementHandle<NodeFor<Selector>>;
await handle.dispose();
return mainHandle;
)) as ElementHandle<NodeFor<Selector>> | null;
}

/**
Expand Down
65 changes: 23 additions & 42 deletions src/common/IsolatedWorld.ts
Expand Up @@ -16,16 +16,19 @@

import {Protocol} from 'devtools-protocol';
import {assert} from '../util/assert.js';
import {
createDeferredPromise,
DeferredPromise,
} from '../util/DeferredPromise.js';
import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {TimeoutError} from './Errors.js';
import {ExecutionContext} from './ExecutionContext.js';
import {FrameManager} from './FrameManager.js';
import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js';
import {MouseButton} from './Input.js';
import {JSHandle} from './JSHandle.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {getQueryHandlerAndSelector} from './QueryHandler.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {
Expand All @@ -37,10 +40,6 @@ import {
makePredicateString,
pageBindingInitString,
} from './util.js';
import {
createDeferredPromise,
DeferredPromise,
} from '../util/DeferredPromise.js';

// predicateQueryHandler and checkWaitForOptions are declared here so that
// TypeScript knows about them when used in the predicate function below.
Expand Down Expand Up @@ -80,10 +79,6 @@ export interface WaitForSelectorOptions {
* @defaultValue `30000` (30 seconds)
*/
timeout?: number;
/**
* @deprecated Do not use. Use the {@link ElementHandle.waitForSelector}
*/
root?: ElementHandle<Node>;
}

/**
Expand Down Expand Up @@ -122,7 +117,7 @@ export interface IsolatedWorldChart {
*/
export class IsolatedWorld {
#frame: Frame;
#documentPromise: Promise<ElementHandle<Document>> | null = null;
#document?: ElementHandle<Document>;
#contextPromise: DeferredPromise<ExecutionContext> = createDeferredPromise();
#detached = false;

Expand Down Expand Up @@ -169,7 +164,7 @@ export class IsolatedWorld {
}

clearContext(): void {
this.#documentPromise = null;
this.#document = undefined;
this.#contextPromise = createDeferredPromise();
}

Expand Down Expand Up @@ -248,15 +243,14 @@ export class IsolatedWorld {
}

async document(): Promise<ElementHandle<Document>> {
if (this.#documentPromise) {
return this.#documentPromise;
if (this.#document) {
return this.#document;
}
this.#documentPromise = this.executionContext().then(async context => {
return await context.evaluateHandle(() => {
return document;
});
const context = await this.executionContext();
this.#document = await context.evaluateHandle(() => {
return document;
});
return this.#documentPromise;
return this.#document;
}

async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
Expand Down Expand Up @@ -294,20 +288,6 @@ export class IsolatedWorld {
return document.$$eval(selector, pageFunction, ...args);
}

async waitForSelector<Selector extends string>(
selector: Selector,
options: WaitForSelectorOptions
): Promise<ElementHandle<NodeFor<Selector>> | null> {
const {updatedSelector, queryHandler} =
getQueryHandlerAndSelector(selector);
assert(queryHandler.waitFor, 'Query handler does not support waiting');
return (await queryHandler.waitFor(
this,
updatedSelector,
options
)) as ElementHandle<NodeFor<Selector>> | null;
}

async content(): Promise<string> {
return await this.evaluate(() => {
let retVal = '';
Expand Down Expand Up @@ -707,10 +687,11 @@ export class IsolatedWorld {

async _waitForSelectorInPage(
queryOne: Function,
root: ElementHandle<Node> | undefined,
selector: string,
options: WaitForSelectorOptions,
binding?: PageBinding
): Promise<ElementHandle<Node> | null> {
): Promise<JSHandle<unknown> | null> {
const {
visible: waitForVisible = false,
hidden: waitForHidden = false,
Expand Down Expand Up @@ -738,16 +719,10 @@ export class IsolatedWorld {
timeout,
args: [selector, waitForVisible, waitForHidden],
binding,
root: options.root,
root,
};
const waitTask = new WaitTask(waitTaskOptions);
const jsHandle = await waitTask.promise;
const elementHandle = jsHandle.asElement();
if (!elementHandle) {
await jsHandle.dispose();
return null;
}
return elementHandle;
return waitTask.promise;
}

waitForFunction(
Expand Down Expand Up @@ -798,6 +773,12 @@ export class IsolatedWorld {
});
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
}

async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
const result = await this.adoptHandle(handle);
await handle.dispose();
return result;
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/common/Page.ts
Expand Up @@ -3396,7 +3396,7 @@ export class Page extends EventEmitter {
*/
async waitForSelector<Selector extends string>(
selector: Selector,
options: Exclude<WaitForSelectorOptions, 'root'> = {}
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> {
return await this.mainFrame().waitForSelector(selector, options);
}
Expand Down

0 comments on commit e8e6fc8

Please sign in to comment.