# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
import asyncio import contextlib import time from base64 import b64decode from io import BytesIO from urllib.parse import quote
import pytest import webdriver from PIL import Image from webdriver.bidi.error import InvalidArgumentException, NoSuchFrameException from webdriver.bidi.modules.script import ContextTarget
async def on_event(event, data):
val = data if checkFn:
val = await checkFn(event, data) if val isNone: return for remover in remove_listeners:
remover()
await self.unsubscribe(events)
future.set_result(val)
for event in events:
remove_listeners.append(
self.session.bidi_session.add_event_listener(event, on_event)
)
await self.subscribe(events) return await asyncio.wait_for(future, timeout=timeout)
async def get_iframe_by_url(self, url): def check_children(children): for child in children: if"url"in child and url in child["url"]: return child for child in children: if"children"in child:
frame = check_children(child["children"]) if frame: return frame returnNone
tree = await self.session.bidi_session.browsing_context.get_tree() for top in tree:
frame = check_children(top["children"]) if frame isnotNone: return frame
returnNone
async def is_iframe(self, context): def check_children(children): for child in children: if"context"in child and child["context"] == context: returnTrue if"children"in child: return check_children(child["children"]) returnFalse
for top in await self.session.bidi_session.browsing_context.get_tree(): if check_children(top["children"]): returnTrue returnFalse
async def wait_for_iframe_loaded(self, url, timeout=None):
async def wait_for_url(_, data): if url in data["url"] and await self.is_iframe(data["context"]): return data["context"] returnNone
async def navigate(self, url, timeout=90, no_skip=False, **kwargs): try: return await asyncio.wait_for(
asyncio.ensure_future(self._navigate(url, **kwargs)), timeout=timeout
) except asyncio.exceptions.TimeoutError as t: if no_skip: raise t return
pytest.skip( "%s: Timed out navigating to site after %s seconds. Please try again later."
% (self.request.fspath.basename, timeout)
) except webdriver.bidi.error.UnknownErrorException as e: if no_skip: raise e return
s = str(e) if"Address rejected"in s:
pytest.skip( "%s: Site not responding. Please try again later."
% self.request.fspath.basename
) return elif"NS_ERROR_REDIRECT_LOOP"in s:
pytest.skip( "%s: Site is stuck in a redirect loop. Please try again later."
% self.request.fspath.basename
) return raise e
async def _navigate(self, url, wait="complete", await_console_message=None): if self.session.test_config.get("use_pbm") or self.session.test_config.get( "use_strict_etp"
):
print("waiting for content blocker...")
self.wait_for_content_blocker() if await_console_message isnotNone:
console_message = await self.promise_console_message_listener(
await_console_message
) if wait == "load":
page_load = await self.promise_readystate_listener("load", url=url) try:
await self.session.bidi_session.browsing_context.navigate(
context=(await self.top_context())["context"],
url=url,
wait=wait if wait != "load"elseNone,
) except webdriver.bidi.error.UnknownErrorException as u:
m = str(u) if ( "NS_BINDING_ABORTED"notin m and"NS_ERROR_ABORT"notin m and"NS_ERROR_WONT_HANDLE_CONTENT"notin m
): raise u if wait == "load":
await page_load if await_console_message isnotNone:
await console_message
def remove_listeners(): for listener_remover in listener_removers: try:
listener_remover() except Exception: pass
async def on_event(method, data):
print("on_event", method, data)
val = None if check_fn isnotNone:
val = check_fn(method, data) if val isNone: return
future.set_result(val)
for event in events:
r = self.session.bidi_session.add_event_listener(event, on_event)
listener_removers.append(r)
async def promise_navigation_begins(self, url=None, **kwargs): def check(method, data): if url isNone: return data if"url"in data and url in data["url"]: return data
async def promise_console_message_listener(self, msg, **kwargs): def check(method, data): if"text"in data: if msg in data["text"]: return data if"args"in data and len(data["args"]): for arg in data["args"]: if"value"in arg and msg in arg["value"]: return data
async def find_frame_context_by_url(self, url): def find_in(arr, url): for context in arr: if url in context["url"]: return context for context in arr:
found = find_in(context["children"], url) if found: return found
async def await_xpath(
self, xpath, all=False, timeout=10, poll=0.25, is_displayed=False
):
all = "true"if all else"false" return await self.client.session.bidi_session.script.evaluate(
expression=self.timed_js(
timeout,
poll, """
var ret = [];
var r, res = document.evaluate(`{xpath}`, document, null, 4); while (r = res.iterateNext()) {
ret.push(r);
}
resolve({all} ? ret : ret[0]); """,
),
target=self.target,
await_promise=True,
)
def wrap_script_args(self, args): if args isNone: return args
out = [] for arg in args: if arg isNone:
out.append({"type": "undefined"}) continue elif isinstance(arg, webdriver.client.WebElement):
out.append({"sharedId": arg.id}) continue
t = type(arg) if t is int or t is float:
out.append({"type": "number", "value": arg}) elif t is bool:
out.append({"type": "boolean", "value": arg}) elif t is str:
out.append({"type": "string", "value": arg}) else: if"type"in arg:
out.push(arg) continue raise ValueError(f"Unhandled argument type: {t}") return out
class PreloadScript: def __init__(self, client, script, target):
self.client = client
self.script = script if type(target) is list:
self.target = target[0] else:
self.target = target
def _do_is_displayed_check(self, ele, is_displayed): if ele isNone: returnNone
if type(ele) in [list, tuple]: return [x for x in ele if self._do_is_displayed_check(x, is_displayed)]
if is_displayed isFalseand ele and self.is_displayed(ele): returnNone if is_displayed isTrueand ele andnot self.is_displayed(ele): returnNone return ele
exc = None while time.time() < t0 + timeout: for i, finder in enumerate(finders): try:
result = finder.find(self, **kwargs) if result and ( not condition or self.session.execute_script(condition, [result])
):
found[i] = result return found except webdriver.error.NoSuchElementException as e:
exc = e
time.sleep(delay) raise exc if exc isnotNoneelse webdriver.error.NoSuchElementException return found
async def dom_ready(self, timeout=None): if timeout isNone:
timeout = 20
def is_float_cleared(self, elem1, elem2): return self.session.execute_script( """return (function(a, b) {
// Ensure that a is placed under b (andnot to its right) return a?.offsetTop >= b?.offsetTop + b?.offsetHeight &&
a?.offsetLeft < b?.offsetLeft + b?.offsetWidth;
}(arguments[0], arguments[1]));""",
elem1,
elem2,
)
def try_closing_popups(self, popup_close_button_finders, timeout=None):
left_to_try = list(popup_close_button_finders)
closed_one = False
num_intercepted = 0 while len(left_to_try):
finder = left_to_try.pop(0) try: if self.try_closing_popup(finder, timeout=timeout):
closed_one = True
num_intercepted = 0 except webdriver.error.ElementClickInterceptedException as e: # If more than one popup is visible at the same time, we will # get this exception for all but the topmost one. So we re-try # removing the others again after the topmost one is dismissed, # until we've removed them all.
num_intercepted += 1 if num_intercepted == len(left_to_try): raise e
left_to_try.append(finder) return closed_one
def click(
self, element, force=False, popups=None, popups_timeout=None, button=None
):
tries = 0 whileTrue:
self.scroll_into_view(element) try: if button:
self.mouse.pointer_move(0, 0, origin=element).pointer_down(
button
).pointer_up(button).perform() else:
element.click() return except webdriver.error.ElementClickInterceptedException as c: if force:
self.clear_covering_elements(element) elifnot popups ornot self.try_closing_popups(
popups, timeout=popups_timeout
): raise c except webdriver.error.WebDriverException as e: ifnot"could not be scrolled into view"in str(e): raise e
tries += 1 if tries == 5: raise e
time.sleep(0.5)
whileTrue:
consent, blocked, play = self.await_first_element_of(
[
CONSENT,
BLOCKED,
PLAY,
],
is_displayed=True,
timeout=30,
) ifnot consent: break
consent.click()
self.await_element_hidden(CONSENT) continue if shouldPass: assert play else: assert blocked
async def test_entrata_banner_hidden(self, url, iframe_css=None): # some sites take a while to load, but they always have the browser # warning popup, it just isn't shown until the page finishes loading.
await self.navigate(url, wait="none") if iframe_css:
frame = self.await_css(iframe_css)
self.switch_frame(frame)
self.await_css("#browser-warning-popup", timeout=120) try:
self.await_css("#browser-warning-popup", is_displayed=True, timeout=2) returnFalse except webdriver.error.NoSuchElementException: returnTrue
def test_future_plc_trending_scrollbar(self, shouldFail=False):
trending_list = self.await_css(".trending__list") ifnot trending_list: raise ValueError("trending list is still where expected")
# First confirm that the scrollbar is the color the site specifies.
css_var_colors = self.execute_script( """
// first, force a scrollbar, as the content on each site might
// not always be wide enough to force a scrollbar to appear.
const list = arguments[0];
list.style.overflow = "scroll hidden !important";
const computedStyle = getComputedStyle(list); return [
computedStyle.getPropertyValue('--trending-scrollbar-color'),
computedStyle.getPropertyValue('--trending-scrollbar-background-color'),
]; """,
trending_list,
) ifnot css_var_colors[0] ornot css_var_colors[1]: raise ValueError("expected CSS vars are still used for scrollbar-color")
[expected, actual] = self.execute_script( """
const [list, cssVarColors] = arguments;
const sbColor = getComputedStyle(list).scrollbarColor;
// scrollbar-color is a two-color value wth no easy way to separate
// them and no way to be sure the value will remain consistent in
// the format "rgb(x, y, z) rgb(x, y, z)". Likewise, the colors the
// site specified in the CSS might be in hex format or any CSS color
// value. So rather than trying to normalize the values ourselves, we
// set the border-color of an element, which is also a two-color CSS
// value, and then also read it back through the computed style, so
// Firefox normalizes both colors the same way for us and lets us
// compare their equivalence as simple strings.
list.style.borderColor = sbColor;
const actual = getComputedStyle(list).borderColor;
list.style.borderColor = cssVarColors.join(" ");
const expected = getComputedStyle(list).borderColor; return [expected, actual]; """,
trending_list,
css_var_colors,
) if shouldFail: assert expected != actual, "scrollbar is not the correct color" else: assert expected == actual, "scrollbar is the correct color"
# Also check that the scrollbar does not cover any text (it may not # actually cover any text even without the intervention, so we skip # checking that case). To find out, we color the scrollbar the same as # the trending list's background, and compare screenshots of the # list with and without the scrollbar. This way if no text is covered, # the screenshots will not differ. ifnot shouldFail:
self.execute_script( """
const list = arguments[0];
const bgc = getComputedStyle(list).backgroundColor;
list.style.scrollbarColor = `${bgc} ${bgc}`; """,
trending_list,
)
with_scrollbar = trending_list.screenshot()
self.execute_script( """
arguments[0].style.scrollbarWidth = "none"; """,
trending_list,
)
without_scrollbar = trending_list.screenshot() assert (
with_scrollbar == without_scrollbar
), "scrollbar does not cover any text"
def test_for_fastclick(self, element): # FastClick cancels touchend, breaking default actions on Fenix. # It instead fires a mousedown or click, which we can detect.
self.execute_script( """
const sel = arguments[0];
window.fastclicked = false;
const evt = sel.nodeName === "SELECT" ? "mousedown" : "click";
document.addEventListener(evt, e => { if (e.target === sel && !e.isTrusted) {
window.fastclicked = true;
}
}, true);
sel.style.position = "absolute";
sel.style.zIndex = 2147483647; """,
element,
)
self.scroll_into_view(element)
self.clear_covering_elements(element) # tap a few times in case the site's other code interferes
self.touch.click(element=element).perform()
self.touch.click(element=element).perform()
self.touch.click(element=element).perform() return self.execute_script("return window.fastclicked")
def is_displayed(self, element): if element isNone: returnFalse
return self.session.execute_script( """
const e = arguments[0],
s = window.getComputedStyle(e),
v = s.visibility === "visible",
o = Math.abs(parseFloat(s.opacity)); return e.getClientRects().length > 0 && v && (isNaN(o) || o === 1.0); """,
args=[element],
)
def is_one_solid_color(self, element, max_fuzz=8): # max_fuzz is needed as screenshots can have slight color bleeding/fringing
shotb64 = element.screenshot()
shot = Image.open(BytesIO(b64decode(shotb64))).convert("RGB") for min, max in shot.getextrema(): if max - min > max_fuzz: returnFalse returnTrue
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.