mirror of
https://forge.fsky.io/oneflux/omegafox.git
synced 2026-02-10 20:12:06 -08:00
352 lines
13 KiB
Python
352 lines
13 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from playwright.async_api import Browser, Error, Page, Selectors
|
|
|
|
from .utils import Utils
|
|
|
|
|
|
async def test_selectors_register_should_work(
|
|
selectors: Selectors, browser: Browser, browser_name: str
|
|
) -> None:
|
|
tag_selector = """
|
|
{
|
|
create(root, target) {
|
|
return target.nodeName;
|
|
},
|
|
query(root, selector) {
|
|
return root.querySelector(selector);
|
|
},
|
|
queryAll(root, selector) {
|
|
return Array.from(root.querySelectorAll(selector));
|
|
}
|
|
}"""
|
|
|
|
selector_name = f"tag_{browser_name}"
|
|
selector2_name = f"tag2_{browser_name}"
|
|
|
|
# Register one engine before creating context.
|
|
await selectors.register(selector_name, tag_selector)
|
|
|
|
context = await browser.new_context()
|
|
# Register another engine after creating context.
|
|
await selectors.register(selector2_name, tag_selector)
|
|
|
|
page = await context.new_page()
|
|
await page.set_content("<div><span></span></div><div></div>")
|
|
|
|
assert await page.eval_on_selector(f"{selector_name}=DIV", "e => e.nodeName") == "DIV"
|
|
assert await page.eval_on_selector(f"{selector_name}=SPAN", "e => e.nodeName") == "SPAN"
|
|
assert await page.eval_on_selector_all(f"{selector_name}=DIV", "es => es.length") == 2
|
|
|
|
assert await page.eval_on_selector(f"{selector2_name}=DIV", "e => e.nodeName") == "DIV"
|
|
assert await page.eval_on_selector(f"{selector2_name}=SPAN", "e => e.nodeName") == "SPAN"
|
|
assert await page.eval_on_selector_all(f"{selector2_name}=DIV", "es => es.length") == 2
|
|
|
|
# Selector names are case-sensitive.
|
|
with pytest.raises(Error) as exc:
|
|
await page.query_selector("tAG=DIV")
|
|
assert 'Unknown engine "tAG" while parsing selector tAG=DIV' in exc.value.message
|
|
|
|
await context.close()
|
|
|
|
|
|
async def test_selectors_register_should_work_with_path(
|
|
selectors: Selectors, page: Page, utils: Utils, assetdir: Path
|
|
) -> None:
|
|
await utils.register_selector_engine(
|
|
selectors, "foo", path=assetdir / "sectionselectorengine.js"
|
|
)
|
|
await page.set_content("<section></section>")
|
|
assert await page.eval_on_selector("foo=whatever", "e => e.nodeName") == "SECTION"
|
|
|
|
|
|
async def test_selectors_register_should_work_in_main_and_isolated_world(
|
|
selectors: Selectors, page: Page, utils: Utils
|
|
) -> None:
|
|
dummy_selector_script = """{
|
|
create(root, target) { },
|
|
query(root, selector) {
|
|
return window.__answer;
|
|
},
|
|
queryAll(root, selector) {
|
|
return window['__answer'] ? [window['__answer'], document.body, document.documentElement] : [];
|
|
}
|
|
}"""
|
|
|
|
await utils.register_selector_engine(selectors, "main", dummy_selector_script)
|
|
await utils.register_selector_engine(
|
|
selectors, "isolated", dummy_selector_script, content_script=True
|
|
)
|
|
await page.set_content("<div><span><section></section></span></div>")
|
|
await page.evaluate('() => window.__answer = document.querySelector("span")')
|
|
# Works in main if asked.
|
|
assert await page.eval_on_selector("main=ignored", "e => e.nodeName") == "SPAN"
|
|
assert await page.eval_on_selector("css=div >> main=ignored", "e => e.nodeName") == "SPAN"
|
|
assert await page.eval_on_selector_all("main=ignored", "es => window.__answer !== undefined")
|
|
assert await page.eval_on_selector_all("main=ignored", "es => es.filter(e => e).length") == 3
|
|
# Works in isolated by default.
|
|
assert await page.query_selector("isolated=ignored") is None
|
|
assert await page.query_selector("css=div >> isolated=ignored") is None
|
|
# $$eval always works in main, to avoid adopting nodes one by one.
|
|
assert await page.eval_on_selector_all(
|
|
"isolated=ignored", "es => window.__answer !== undefined"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all("isolated=ignored", "es => es.filter(e => e).length") == 3
|
|
)
|
|
# At least one engine in main forces all to be in main.
|
|
assert (
|
|
await page.eval_on_selector("main=ignored >> isolated=ignored", "e => e.nodeName") == "SPAN"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector("isolated=ignored >> main=ignored", "e => e.nodeName") == "SPAN"
|
|
)
|
|
# Can be chained to css.
|
|
assert (
|
|
await page.eval_on_selector("main=ignored >> css=section", "e => e.nodeName") == "SECTION"
|
|
)
|
|
|
|
|
|
async def test_selectors_register_should_handle_errors(
|
|
selectors: Selectors, page: Page, utils: Utils
|
|
) -> None:
|
|
with pytest.raises(Error) as exc:
|
|
await page.query_selector("neverregister=ignored")
|
|
assert (
|
|
'Unknown engine "neverregister" while parsing selector neverregister=ignored'
|
|
in exc.value.message
|
|
)
|
|
|
|
dummy_selector_engine_script = """{
|
|
create(root, target) {
|
|
return target.nodeName;
|
|
},
|
|
query(root, selector) {
|
|
return root.querySelector('dummy');
|
|
},
|
|
queryAll(root, selector) {
|
|
return Array.from(root.query_selector_all('dummy'));
|
|
}
|
|
}"""
|
|
|
|
with pytest.raises(Error) as exc:
|
|
await selectors.register("$", dummy_selector_engine_script)
|
|
assert (
|
|
exc.value.message
|
|
== "Selectors.register: Selector engine name may only contain [a-zA-Z0-9_] characters"
|
|
)
|
|
|
|
# Selector names are case-sensitive.
|
|
await utils.register_selector_engine(selectors, "dummy", dummy_selector_engine_script)
|
|
await utils.register_selector_engine(selectors, "duMMy", dummy_selector_engine_script)
|
|
|
|
with pytest.raises(Error) as exc:
|
|
await selectors.register("dummy", dummy_selector_engine_script)
|
|
assert (
|
|
exc.value.message
|
|
== 'Selectors.register: "dummy" selector engine has been already registered'
|
|
)
|
|
|
|
with pytest.raises(Error) as exc:
|
|
await selectors.register("css", dummy_selector_engine_script)
|
|
assert exc.value.message == 'Selectors.register: "css" is a predefined selector engine'
|
|
|
|
|
|
async def test_should_work_with_layout_selectors(page: Page) -> None:
|
|
# +--+ +--+
|
|
# | 1| | 2|
|
|
# +--+ ++-++
|
|
# | 3| | 4|
|
|
# +-------+ ++-++
|
|
# | 0 | | 5|
|
|
# | +--+ +--+--+
|
|
# | | 6| | 7|
|
|
# | +--+ +--+
|
|
# | |
|
|
# O-------+
|
|
# +--+
|
|
# | 8|
|
|
# +--++--+
|
|
# | 9|
|
|
# +--+
|
|
|
|
boxes = [
|
|
# x, y, width, height
|
|
[0, 0, 150, 150],
|
|
[100, 200, 50, 50],
|
|
[200, 200, 50, 50],
|
|
[100, 150, 50, 50],
|
|
[201, 150, 50, 50],
|
|
[200, 100, 50, 50],
|
|
[50, 50, 50, 50],
|
|
[150, 50, 50, 50],
|
|
[150, -51, 50, 50],
|
|
[201, -101, 50, 50],
|
|
]
|
|
await page.set_content(
|
|
'<container style="width: 500px; height: 500px; position: relative;"></container>'
|
|
)
|
|
await page.eval_on_selector(
|
|
"container",
|
|
"""(container, boxes) => {
|
|
for (let i = 0; i < boxes.length; i++) {
|
|
const div = document.createElement('div');
|
|
div.style.position = 'absolute';
|
|
div.style.overflow = 'hidden';
|
|
div.style.boxSizing = 'border-box';
|
|
div.style.border = '1px solid black';
|
|
div.id = 'id' + i;
|
|
div.textContent = 'id' + i;
|
|
const box = boxes[i];
|
|
div.style.left = box[0] + 'px';
|
|
// Note that top is a flipped y coordinate.
|
|
div.style.top = (250 - box[1] - box[3]) + 'px';
|
|
div.style.width = box[2] + 'px';
|
|
div.style.height = box[3] + 'px';
|
|
container.appendChild(div);
|
|
const span = document.createElement('span');
|
|
span.textContent = '' + i;
|
|
div.appendChild(span);
|
|
}
|
|
}""",
|
|
boxes,
|
|
)
|
|
|
|
assert await page.eval_on_selector("div:right-of(#id6)", "e => e.id") == "id7"
|
|
assert await page.eval_on_selector("div:right-of(#id1)", "e => e.id") == "id2"
|
|
assert await page.eval_on_selector("div:right-of(#id3)", "e => e.id") == "id4"
|
|
assert await page.query_selector("div:right-of(#id4)") is None
|
|
assert await page.eval_on_selector("div:right-of(#id0)", "e => e.id") == "id7"
|
|
assert await page.eval_on_selector("div:right-of(#id8)", "e => e.id") == "id9"
|
|
assert (
|
|
await page.eval_on_selector_all("div:right-of(#id3)", "els => els.map(e => e.id).join(',')")
|
|
== "id4,id2,id5,id7,id8,id9"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:right-of(#id3, 50)", "els => els.map(e => e.id).join(',')"
|
|
)
|
|
== "id2,id5,id7,id8"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:right-of(#id3, 49)", "els => els.map(e => e.id).join(',')"
|
|
)
|
|
== "id7,id8"
|
|
)
|
|
|
|
assert await page.eval_on_selector("div:left-of(#id2)", "e => e.id") == "id1"
|
|
assert await page.query_selector("div:left-of(#id0)") is None
|
|
assert await page.eval_on_selector("div:left-of(#id5)", "e => e.id") == "id0"
|
|
assert await page.eval_on_selector("div:left-of(#id9)", "e => e.id") == "id8"
|
|
assert await page.eval_on_selector("div:left-of(#id4)", "e => e.id") == "id3"
|
|
assert (
|
|
await page.eval_on_selector_all("div:left-of(#id5)", "els => els.map(e => e.id).join(',')")
|
|
== "id0,id7,id3,id1,id6,id8"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:left-of(#id5, 3)", "els => els.map(e => e.id).join(',')"
|
|
)
|
|
== "id7,id8"
|
|
)
|
|
|
|
assert await page.eval_on_selector("div:above(#id0)", "e => e.id") == "id3"
|
|
assert await page.eval_on_selector("div:above(#id5)", "e => e.id") == "id4"
|
|
assert await page.eval_on_selector("div:above(#id7)", "e => e.id") == "id5"
|
|
assert await page.eval_on_selector("div:above(#id8)", "e => e.id") == "id0"
|
|
assert await page.eval_on_selector("div:above(#id9)", "e => e.id") == "id8"
|
|
assert await page.query_selector("div:above(#id2)") is None
|
|
assert (
|
|
await page.eval_on_selector_all("div:above(#id5)", "els => els.map(e => e.id).join(',')")
|
|
== "id4,id2,id3,id1"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:above(#id5, 20)", "els => els.map(e => e.id).join(',')"
|
|
)
|
|
== "id4,id3"
|
|
)
|
|
|
|
assert await page.eval_on_selector("div:below(#id4)", "e => e.id") == "id5"
|
|
assert await page.eval_on_selector("div:below(#id3)", "e => e.id") == "id0"
|
|
assert await page.eval_on_selector("div:below(#id2)", "e => e.id") == "id4"
|
|
assert await page.eval_on_selector("div:below(#id6)", "e => e.id") == "id8"
|
|
assert await page.eval_on_selector("div:below(#id7)", "e => e.id") == "id8"
|
|
assert await page.eval_on_selector("div:below(#id8)", "e => e.id") == "id9"
|
|
assert await page.query_selector("div:below(#id9)") is None
|
|
assert (
|
|
await page.eval_on_selector_all("div:below(#id3)", "els => els.map(e => e.id).join(',')")
|
|
== "id0,id5,id6,id7,id8,id9"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:below(#id3, 105)", "els => els.map(e => e.id).join(',')"
|
|
)
|
|
== "id0,id5,id6,id7"
|
|
)
|
|
|
|
assert await page.eval_on_selector("div:near(#id0)", "e => e.id") == "id3"
|
|
assert (
|
|
await page.eval_on_selector_all("div:near(#id7)", "els => els.map(e => e.id).join(',')")
|
|
== "id0,id5,id3,id6"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all("div:near(#id0)", "els => els.map(e => e.id).join(',')")
|
|
== "id3,id6,id7,id8,id1,id5"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all("div:near(#id6)", "els => els.map(e => e.id).join(',')")
|
|
== "id0,id3,id7"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all("div:near(#id6, 10)", "els => els.map(e => e.id).join(',')")
|
|
== "id0"
|
|
)
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:near(#id0, 100)", "els => els.map(e => e.id).join(',')"
|
|
)
|
|
== "id3,id6,id7,id8,id1,id5,id4,id2"
|
|
)
|
|
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:below(#id5):above(#id8)", "els => els.map(e => e.id).join(',')"
|
|
)
|
|
== "id7,id6"
|
|
)
|
|
assert await page.eval_on_selector("div:below(#id5):above(#id8)", "e => e.id") == "id7"
|
|
|
|
assert (
|
|
await page.eval_on_selector_all(
|
|
"div:right-of(#id0) + div:above(#id8)",
|
|
"els => els.map(e => e.id).join(',')",
|
|
)
|
|
== "id5,id6,id3"
|
|
)
|
|
|
|
with pytest.raises(Error) as exc_info:
|
|
await page.query_selector(":near(50)")
|
|
assert (
|
|
'"near" engine expects a selector list and optional maximum distance in pixels'
|
|
in exc_info.value.message
|
|
)
|
|
with pytest.raises(Error) as exc_info:
|
|
await page.query_selector('left-of="div"')
|
|
assert '"left-of" selector cannot be first' in exc_info.value.message
|