Bug 2000801 - [wdspec] Add Mozilla-specific tests for chrome window handles. r=frontend-codestyle-reviewers,jdescottes,mossop

Differential Revision: https://phabricator.services.mozilla.com/D273541
This commit is contained in:
Henrik Skupin
2025-12-01 20:46:23 +00:00
committed by hskupin@mozilla.com
parent bac2666086
commit 6e586279cd
20 changed files with 490 additions and 11 deletions
+3 -1
View File
@@ -102,5 +102,7 @@ toolkit/components/pdfjs/content/web/viewer.css
toolkit/components/pdfjs/content/web/viewer-geckoview.css
build/pgo/blueprint/**/*.css
# Ignore web-platform tests as they are not necessarily under our control.
# Ignore web-platform tests as they are not necessarily
# under our control or we don't want to modify at this point:
testing/web-platform/mozilla/
testing/web-platform/tests/
@@ -0,0 +1,3 @@
[chrome.py]
disabled:
if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762066
@@ -0,0 +1,3 @@
[chrome.py]
disabled:
if os == "android": https://bugzilla.mozilla.org/show_bug.cgi?id=1762066
@@ -0,0 +1,35 @@
import pytest
from support.context import using_context
from tests.classic.execute_async_script import execute_async_script
from tests.support.asserts import assert_success
from webdriver.client import WebFrame, WebWindow
@pytest.mark.parametrize(
"expression, expected_type",
[
("window.frames[0]", WebFrame),
("window", WebWindow),
],
ids=["frame", "window"],
)
@pytest.mark.allow_system_access
def test_web_reference(
session, expression, default_chrome_handler, new_chrome_window, expected_type
):
chrome_url = f"{default_chrome_handler}test.xhtml"
new_window = new_chrome_window(chrome_url)
with using_context(session, "chrome"):
session.window_handle = new_window.id
assert session.url == chrome_url
result = execute_async_script(session, f"arguments[0]({expression})")
reference = assert_success(result)
assert isinstance(reference, expected_type)
if isinstance(reference, WebWindow):
assert reference.id in session.handles
else:
assert reference.id not in session.handles
@@ -1,19 +1,10 @@
import pytest
from tests.classic.execute_async_script import execute_async_script
from tests.support.asserts import assert_success
from tests.support.sync import Poll
from webdriver.error import NoSuchAlertException
def execute_async_script(session, script, args=None):
if args is None:
args = []
body = {"script": script, "args": args}
return session.transport.send(
"POST", "/session/{session_id}/execute/async".format(**vars(session)), body
)
@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
def test_no_abort_by_user_prompt_in_other_tab(session, inline, dialog_type):
original_handle = session.window_handle
@@ -0,0 +1,35 @@
import pytest
from support.context import using_context
from tests.classic.execute_script import execute_script
from tests.support.asserts import assert_success
from webdriver.client import WebFrame, WebWindow
@pytest.mark.parametrize(
"expression, expected_type",
[
("window.frames[0]", WebFrame),
("window", WebWindow),
],
ids=["frame", "window"],
)
@pytest.mark.allow_system_access
def test_web_reference(
session, expression, default_chrome_handler, new_chrome_window, expected_type
):
chrome_url = f"{default_chrome_handler}test.xhtml"
new_window = new_chrome_window(chrome_url)
with using_context(session, "chrome"):
session.window_handle = new_window.id
assert session.url == chrome_url
result = execute_script(session, f"return {expression}")
reference = assert_success(result)
assert isinstance(reference, expected_type)
if isinstance(reference, WebWindow):
assert reference.id in session.handles
else:
assert reference.id not in session.handles
@@ -0,0 +1,6 @@
def switch_to_window(session, handle):
return session.transport.send(
"POST",
"session/{session_id}/window".format(**vars(session)),
{"handle": handle},
)
@@ -0,0 +1,31 @@
import pytest
from support.context import using_context
from tests.support.asserts import assert_error, assert_success
from . import switch_to_window
@pytest.mark.allow_system_access
def test_no_such_window(session):
with using_context(session, "chrome"):
response = switch_to_window(session, "foo")
assert_error(response, "no such window")
@pytest.mark.allow_system_access
def test_chrome_window(session):
session.new_window(type_hint="window")
with using_context(session, "chrome"):
current_handle = session.window_handle
chrome_handles = session.handles
chrome_handles.remove(current_handle)
assert len(chrome_handles)
response = switch_to_window(session, chrome_handles[0])
assert_success(response)
assert session.window_handle == chrome_handles[0]
assert session.window_handle != current_handle
@@ -0,0 +1,13 @@
.flex-column {
display: flex;
flex-direction: column;
}
.dialog-box {
min-height: 500px;
min-width: 300px;
}
.input-moz-box-flex {
-moz-box-flex: 1;
}
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<?csp default-src chrome:; ?>
<!-- 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/. -->
<!-- Test file for a XUL window by using a XHTML document instead. -->
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
id="remote-window"
windowtype="remote:xhtml"
>
<head>
<title>Test Window</title>
<link rel="stylesheet" href="chrome://global/skin/global.css" />
<link rel="stylesheet" href="chrome://marionette-chrome/content/style.css" />
</head>
<body>
<div id="types" class="flex-column">
<input id="textInput" size="6" value="test" label="input" />
<input type="checkbox" id="testBox" label="box" />
<button id="button" class="foo" label="button" />
<button id="options-button" label="options" />
</div>
<div id="frames" class="flex-column">
<iframe
id="iframe"
src="chrome://marionette-chrome/content/test_iframe.xhtml"
/>
<iframe
id="iframe-nested"
src="chrome://marionette-chrome/content/test_nested_iframe.xhtml"
/>
</div>
</body>
</html>
@@ -0,0 +1,7 @@
<!-- 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/. -->
<!ENTITY testDialog.title "Test Dialog">
<!ENTITY settings.label "Settings">
@@ -0,0 +1,7 @@
# 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/.
testDialog.title=Test Dialog
settings.label=Settings
@@ -0,0 +1,43 @@
<?xml version="1.0"?>
<?csp default-src chrome:; ?>
<!-- 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/. -->
<!-- Test file for a XUL dialog. -->
<!DOCTYPE testdialog [ <!ENTITY % dialogDTD SYSTEM "chrome://marionette-chrome/content/test_dialog.dtd">
%dialogDTD; ]>
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="&testDialog.title;"
>
<html:link rel="stylesheet" href="chrome://global/skin/global.css" />
<html:link rel="stylesheet" href="chrome://marionette-chrome/content/style.css" />
<dialog id="testDialog" buttons="accept,cancel">
<vbox flex="1" class="dialog-box">
<label>&settings.label;</label>
<separator class="thin" />
<richlistbox id="test-list" flex="1">
<richlistitem id="item-choose" orient="horizontal" selected="true">
<label id="choose-label" value="First Entry" flex="1" />
<button id="choose-button" label="Choose..." />
</richlistitem>
</richlistbox>
<separator class="thin" />
<checkbox id="check-box" label="Test Mode 2" />
<hbox align="center">
<label id="text-box-label" control="text-box">Name:</label>
<input
xmlns="http://www.w3.org/1999/xhtml"
id="text-box"
class="input-moz-box-flex"
/>
</hbox>
</vbox>
</dialog>
</window>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<?csp default-src 'none' ?>
<!-- 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/. -->
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
id="remote-test-iframe"
windowtype="remote:test-xhtml"
>
<head>
<title>iframe</title>
<link rel="stylesheet" href="chrome://global/skin/global.css" />
<link rel="stylesheet" href="chrome://marionette-chrome/content/style.css" />
</head>
<body>
<div id="types" class="flex-column">
<input id="textInput" size="6" value="test" label="input" />
<input type="checkbox" id="testBox" label="box" />
</div>
</body>
</html>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<?csp default-src chrome:; ?>
<!-- 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/. -->
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
id="remote-test-nested-iframe"
windowtype="remote:test-xhtml"
>
<head>
<title>nested iframe</title>
<link rel="stylesheet" href="chrome://global/skin/global.css" />
</head>
<body>
<iframe
id="iframe"
src="chrome://marionette-chrome/content/test_iframe.xhtml"
/>
</body>
</html>
@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<?csp default-src chrome:; ?>
<!-- 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/. -->
<!-- Test file for a XUL window. -->
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
id="remote-window"
title="Test Window"
windowtype="remote:xul"
>
<linkset>
<html:link rel="stylesheet" href="chrome://global/skin/global.css" />
</linkset>
<popupset id="options-popupset">
<menupopup id="options-menupopup" position="before_end">
<menuitem id="option-enabled" type="checkbox" label="enabled" />
<menuitem
id="option-hidden"
type="checkbox"
label="hidden"
hidden="true"
/>
<menuitem
id="option-disabled"
type="checkbox"
label="disabled"
disabled="true"
/>
</menupopup>
</popupset>
<vbox id="types">
<html:input id="textInput" size="6" value="test" label="input" />
<checkbox id="testBox" label="box" />
<button id="button" class="foo" label="button" />
<button id="options-button" popup="options-menupopup" label="options" />
</vbox>
<vbox id="frames">
<html:iframe
id="iframe"
src="chrome://marionette-chrome/content/test_iframe.xhtml"
/>
<html:iframe
id="iframe-nested"
src="chrome://marionette-chrome/content/test_nested_iframe.xhtml"
/>
</vbox>
</window>
@@ -0,0 +1,65 @@
import contextlib
from support.context import using_context
def register_chrome_handler(session, manifest_path, entries):
with using_context(session, "chrome"):
return session.execute_script(
"""
const { FileUtils } = ChromeUtils.importESModule(
"resource://gre/modules/FileUtils.sys.mjs"
);
const [manifestPath, entries] = arguments;
const manifest = new FileUtils.File(manifestPath);
const rootURI = Services.io.newFileURI(manifest.parent);
const manifestURI = Services.io.newURI(manifest.leafName, null, rootURI);
const handle = Cc["@mozilla.org/addons/addon-manager-startup;1"]
.getService(Ci.amIAddonManagerStartup)
.registerChrome(manifestURI, entries);
const id = Services.uuid.generateUUID().toString().slice(1, -1);
if (globalThis.chromeProtocolHandles) {
globalThis.chromeProtocolHandles.set(id, handle);
}
return id;
""",
args=[manifest_path, entries],
)
def unregister_chrome_handler(session, id):
with using_context(session, "chrome"):
return session.execute_script(
"""
const [id] = arguments;
if (globalThis.chromeProtocolHandles) {
if (!globalThis.chromeProtocolHandles.has(id)) {
throw new Error(
`Id ${id} is not a known chrome protocol handler`
);
}
const handle = globalThis.chromeProtocolHandles.get(id);
globalThis.chromeProtocolHandles.delete(id);
handle.destruct();
}
""",
args=[id],
)
@contextlib.contextmanager
def using_chrome_handler(session, manifest_path, entries):
id = register_chrome_handler(session, manifest_path, entries)
try:
yield
finally:
unregister_chrome_handler(session, id)
@@ -1,7 +1,11 @@
import os
import pytest
import pytest_asyncio
from tests.support.helpers import deep_update
from .chrome_handler import using_chrome_handler
from .context import using_context
from .helpers import (
Browser,
Geckodriver,
@@ -137,11 +141,94 @@ def default_capabilities(request):
return capabilities
@pytest.fixture
def default_chrome_handler(current_session):
manifest_path = os.path.join(
os.path.abspath(os.path.dirname(__file__)), "chrome-assets", "chrome.manifest"
)
entries = [["content", "marionette-chrome", "chrome/"]]
with using_chrome_handler(current_session, manifest_path, entries):
yield "chrome://marionette-chrome/content/"
@pytest.fixture
def default_preferences(profile_folder):
return read_user_preferences(profile_folder)
@pytest.fixture
def new_chrome_window(current_session):
opened_chrome_windows = []
def _new_chrome_window(url, focus=True):
# Bug 1944570: Replace with BiDi once scripts can be evaluated
# in the parent process.
with using_context(current_session, "chrome"):
new_window = current_session.execute_async_script(
"""
const { NavigableManager } = ChromeUtils.importESModule(
"chrome://remote/content/shared/NavigableManager.sys.mjs"
);
let [url, focus, resolve] = arguments;
function waitForEvent(target, type, args) {
return new Promise(resolve => {
let params = Object.assign({once: true}, args);
target.addEventListener(type, event => {
dump(`** Received DOM event ${event.type} for ${event.target}\n`);
resolve();
}, params);
});
}
function waitForFocus(win) {
return Promise.all([
waitForEvent(win, "activate"),
waitForEvent(win, "focus", {capture: true}),
]);
}
(async function() {
// Open a window, wait for it to receive focus
let win = window.openDialog(url, null, "chrome,centerscreen");
let focused = waitForFocus(win);
win.focus();
await focused;
// The new window shouldn't get focused. As such set the
// focus back to the opening window.
if (!focus && Services.focus.activeWindow != window) {
let focused = waitForFocus(window);
window.focus();
await focused;
}
resolve(win);
})();
""",
args=[url, focus],
)
# Append opened chrome window to automatic closing on teardown
opened_chrome_windows.append(new_window)
return new_window
yield _new_chrome_window
with using_context(current_session, "chrome"):
for win in opened_chrome_windows:
try:
current_session.window_handle = win.id
current_session.execute_script("arguments[0].close()", args=[win])
except Exception:
pass
current_session.window_handle = current_session.handles[0]
@pytest.fixture(name="create_custom_profile")
def fixture_create_custom_profile(default_preferences, profile_folder):
profile = None