Skip to content

Commit

Permalink
feat: support maxNodeCount in locators (#2040)
Browse files Browse the repository at this point in the history
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: sadym-chromium <sadym-chromium@users.noreply.github.com>
  • Loading branch information
sadym-chromium and sadym-chromium authored Mar 20, 2024
1 parent 8c41582 commit ba68a85
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 89 deletions.
104 changes: 69 additions & 35 deletions src/bidiMapper/domains/context/BrowsingContextImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1011,11 +1011,6 @@ export class BrowsingContextImpl {
async locateNodes(
params: BrowsingContext.LocateNodesParameters
): Promise<BrowsingContext.LocateNodesResult> {
// TODO: test sandboxing.
if (params.maxNodeCount !== undefined) {
// TODO: implement.
throw new UnsupportedOperationException(`maxNodeCount is not supported`);
}
if (params.serializationOptions !== undefined) {
// TODO: implement.
throw new UnsupportedOperationException(
Expand All @@ -1029,43 +1024,69 @@ export class BrowsingContextImpl {
);
}

return await this.#locateNodesByLocator(this.#defaultRealm, params.locator);
// TODO: create a dedicated sandbox instead of `#defaultRealm`.
return await this.#locateNodesByLocator(
this.#defaultRealm,
params.locator,
params.maxNodeCount
);
}

#getLocatorDelegate(locator: BrowsingContext.Locator): {
#getLocatorDelegate(
locator: BrowsingContext.Locator,
maxNodeCount?: number
): {
functionDeclaration: string;
argumentsLocalValues: Script.LocalValue[];
} {
switch (locator.type) {
case 'css':
return {
functionDeclaration: String((cssSelector: string) => {
const results = document.querySelectorAll(cssSelector);
const array = [];
for (const item of results) {
array.push(item);
functionDeclaration: String(
(cssSelector: string, maxNodeCount: number) => {
const results = document.querySelectorAll(cssSelector);
const returnedNodes = [];
for (const item of results) {
returnedNodes.push(item);
}
return maxNodeCount === 0
? returnedNodes
: returnedNodes.slice(0, maxNodeCount);
}
return array;
}),
argumentsLocalValues: [{type: 'string', value: locator.value}],
),
argumentsLocalValues: [
// `cssSelector`
{type: 'string', value: locator.value},
// `maxNodeCount` with `0` means no limit.
{type: 'number', value: maxNodeCount ?? 0},
],
};
case 'xpath':
return {
functionDeclaration: String((xPathSelector: string) => {
// https://w3c.github.io/webdriver-bidi/#locate-nodes-using-xpath
const evaluator = new XPathEvaluator();
const expression = evaluator.createExpression(xPathSelector);
const xPathResult = expression.evaluate(
document,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
);
const result = [];
for (let i = 0; i < xPathResult.snapshotLength; i++) {
result.push(xPathResult.snapshotItem(i));
functionDeclaration: String(
(xPathSelector: string, maxNodeCount: number) => {
// https://w3c.github.io/webdriver-bidi/#locate-nodes-using-xpath
const evaluator = new XPathEvaluator();
const expression = evaluator.createExpression(xPathSelector);
const xPathResult = expression.evaluate(
document,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
);
const returnedNodes = [];
for (let i = 0; i < xPathResult.snapshotLength; i++) {
returnedNodes.push(xPathResult.snapshotItem(i));
}
return maxNodeCount === 0
? returnedNodes
: returnedNodes.slice(0, maxNodeCount);
}
return result;
}),
argumentsLocalValues: [{type: 'string', value: locator.value}],
),
argumentsLocalValues: [
// `xPathSelector`
{type: 'string', value: locator.value},
// `maxNodeCount` with `0` means no limit.
{type: 'number', value: maxNodeCount ?? 0},
],
};
case 'innerText':
// https://w3c.github.io/webdriver-bidi/#locate-nodes-using-inner-text
Expand All @@ -1076,8 +1097,15 @@ export class BrowsingContextImpl {
}
return {
functionDeclaration: String(
(selector: string, fullMatch: boolean, ignoreCase: boolean) => {
const searchText = ignoreCase ? selector.toUpperCase() : selector;
(
innerTextSelector: string,
fullMatch: boolean,
ignoreCase: boolean,
maxNodeCount: number
) => {
const searchText = ignoreCase
? innerTextSelector.toUpperCase()
: innerTextSelector;
const locateNodesUsingInnerText = (element: HTMLElement) => {
const returnedNodes: HTMLElement[] = [];
const nodeInnerText = ignoreCase
Expand Down Expand Up @@ -1114,30 +1142,36 @@ export class BrowsingContextImpl {
returnedNodes.push(...childNodeMatches);
}
}
return returnedNodes;
// TODO: stop search early if `maxNodeCount` is reached.
return maxNodeCount === 0
? returnedNodes
: returnedNodes.slice(0, maxNodeCount);
};
// TODO: add maxDepth.
// TODO: provide proper start node.
// TODO: add maxNodeCount.
return locateNodesUsingInnerText(document.body);
}
),
argumentsLocalValues: [
// `innerTextSelector`
{type: 'string', value: locator.value},
// `fullMatch` with default `true`.
{type: 'boolean', value: locator.matchType !== 'partial'},
// `ignoreCase` with default `false`.
{type: 'boolean', value: locator.ignoreCase === true},
// `maxNodeCount` with `0` means no limit.
{type: 'number', value: maxNodeCount ?? 0},
],
};
}
}

async #locateNodesByLocator(
realm: Realm,
locator: BrowsingContext.Locator
locator: BrowsingContext.Locator,
maxNodeCount?: number
): Promise<BrowsingContext.LocateNodesResult> {
const locatorDelegate = this.#getLocatorDelegate(locator);
const locatorDelegate = this.#getLocatorDelegate(locator, maxNodeCount);
const locatorResult = await realm.callFunction(
locatorDelegate.functionDeclaration,
{type: 'undefined'},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
[max_node_count.py]
[test_find_by_locator_limit_return_count[css_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[xpath_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[inner_text_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[css_multiple\]]
expected: FAIL

[test_find_by_locator_limit_return_count[xpath_multiple\]]
expected: FAIL

[test_find_by_locator_limit_return_count[inner_text_multiple\]]
expected: FAIL

[test_several_context_nodes]
expected: FAIL
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
[max_node_count.py]
[test_find_by_locator_limit_return_count[css_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[xpath_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[inner_text_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[css_multiple\]]
expected: FAIL

[test_find_by_locator_limit_return_count[xpath_multiple\]]
expected: FAIL

[test_find_by_locator_limit_return_count[inner_text_multiple\]]
expected: FAIL

[test_several_context_nodes]
expected: FAIL
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
[max_node_count.py]
[test_find_by_locator_limit_return_count[css_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[xpath_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[inner_text_single\]]
expected: FAIL

[test_find_by_locator_limit_return_count[css_multiple\]]
expected: FAIL

[test_find_by_locator_limit_return_count[xpath_multiple\]]
expected: FAIL

[test_find_by_locator_limit_return_count[inner_text_multiple\]]
expected: FAIL

[test_several_context_nodes]
expected: FAIL

0 comments on commit ba68a85

Please sign in to comment.