Skip to content

Commit 0a88259

Browse files
committed
[testdriver] Add test_driver.create_window()
Add a testdriver method matching the WebDriver classic New Window command that returns the new window's handle. This gives tests a top-level browsing context that is created as if the user opened it. This differs from the behavior of window.open. Needed to test the session-history branch of HTML's script closable check. My assumption is that RFC exemption 127 applies in this case.
1 parent 2745551 commit 0a88259

9 files changed

Lines changed: 95 additions & 0 deletions

File tree

docs/writing-tests/testdriver.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ the global scope.
9090

9191
### Window State ###
9292
```eval_rst
93+
.. js:autofunction:: test_driver.create_window
9394
.. js:autofunction:: test_driver.minimize_window
9495
.. js:autofunction:: test_driver.set_window_rect
9596
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<title>TestDriver create_window method</title>
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
<script src="/resources/testdriver.js"></script>
7+
<script src="/resources/testdriver-vendor.js"></script>
8+
9+
<script>
10+
promise_test(async () => {
11+
const handle = await test_driver.create_window();
12+
assert_equals(typeof handle, "string", "handle expected to be a string");
13+
assert_not_equals(handle, "", "handle should not be an empty string");
14+
}, "create_window returns a window handle");
15+
16+
promise_test(async () => {
17+
const first = await test_driver.create_window();
18+
const second = await test_driver.create_window();
19+
assert_not_equals(first, second, "each call should create a distinct window");
20+
}, "create_window returns a distinct handle for each created window");
21+
22+
// Created windows are deliberately left open.
23+
// testdriver has no window close API.
24+
// wptrunner closes leftover windows between tests.
25+
</script>

resources/testdriver.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,33 @@
14221422
return window.test_driver_internal.freeze();
14231423
},
14241424

1425+
/**
1426+
* Creates a new top-level browsing context, as if the user requested
1427+
* a new tab or window from the browser.
1428+
*
1429+
* Matches the behaviour of the `New Window
1430+
* <https://www.w3.org/TR/webdriver/#new-window>`_
1431+
* WebDriver command.
1432+
*
1433+
* The new window is opened with `about:blank`,
1434+
* the test does not get a ``WindowProxy`` for it,
1435+
* and the returned WebDriver window handle is its only identifier.
1436+
*
1437+
* @param {String} type - Type hint for the new browsing context,
1438+
* either "tab" or "window" or null for the
1439+
* implementation default.
1440+
* @param {WindowProxy} context - Browsing context in which
1441+
* to run the call, or null to use the current
1442+
* browsing context.
1443+
*
1444+
* @returns {Promise} fulfilled with the WebDriver window handle
1445+
* (a string) of the new browsing context, or
1446+
* rejected if the WebDriver command errors.
1447+
*/
1448+
create_window: function(type=null, context=null) {
1449+
return window.test_driver_internal.create_window(type, context);
1450+
},
1451+
14251452
/**
14261453
* Minimizes the browser window.
14271454
*
@@ -2618,6 +2645,10 @@
26182645
throw new Error("freeze() is not implemented by testdriver-vendor.js");
26192646
},
26202647

2648+
async create_window(type=null, context=null) {
2649+
throw new Error("create_window() is not implemented by testdriver-vendor.js");
2650+
},
2651+
26212652
async minimize_window(context=null) {
26222653
throw new Error("minimize_window() is not implemented by testdriver-vendor.js");
26232654
},

tools/wptrunner/wptrunner/executors/actions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,17 @@ def __init__(self, logger, protocol):
153153
def __call__(self, payload):
154154
return self.protocol.window.get_rect()
155155

156+
class CreateWindowAction:
157+
name = "create_window"
158+
159+
def __init__(self, logger, protocol):
160+
self.logger = logger
161+
self.protocol = protocol
162+
163+
def __call__(self, payload):
164+
type = payload["type"]
165+
return self.protocol.window.create(type)
166+
156167
class ActionSequenceAction:
157168
name = "action_sequence"
158169

@@ -633,6 +644,7 @@ def __call__(self, payload):
633644
MinimizeWindowAction,
634645
SetWindowRectAction,
635646
GetWindowRectAction,
647+
CreateWindowAction,
636648
ActionSequenceAction,
637649
GenerateTestReportAction,
638650
SetPermissionAction,

tools/wptrunner/wptrunner/executors/executormarionette.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,12 @@ class MarionetteWindowProtocolPart(WindowProtocolPart):
468468
def setup(self):
469469
self.marionette = self.parent.marionette
470470

471+
def create(self, type_hint=None):
472+
# Unlike the New Window command, WebDriver:NewWindow rejects a null
473+
# type, and the client always sends the key so the default is
474+
# applied here. focus=False retains focus on the test window.
475+
return self.marionette.open(type=type_hint or "tab", focus=False)["handle"]
476+
471477
def minimize(self):
472478
return self.marionette.minimize_window()
473479

tools/wptrunner/wptrunner/executors/executorselenium.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ class SeleniumWindowProtocolPart(WindowProtocolPart):
216216
def setup(self):
217217
self.webdriver = self.parent.webdriver
218218

219+
def create(self, type_hint=None):
220+
raise NotImplementedError()
221+
219222
def minimize(self):
220223
self.previous_rect = self.webdriver.window.rect
221224
self.logger.info("Minimizing")

tools/wptrunner/wptrunner/executors/executorwebdriver.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,10 @@ class WebDriverWindowProtocolPart(WindowProtocolPart):
613613
def setup(self):
614614
self.webdriver = self.parent.webdriver
615615

616+
def create(self, type_hint=None):
617+
self.logger.debug("Creating new window")
618+
return self.webdriver.new_window(type_hint=type_hint)
619+
616620
def minimize(self):
617621
self.logger.debug("Minimizing")
618622
return self.webdriver.window.minimize()

tools/wptrunner/wptrunner/executors/protocol.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,15 @@ class WindowProtocolPart(ProtocolPart):
749749

750750
name = "window"
751751

752+
@abstractmethod
753+
def create(self, type_hint=None):
754+
"""Create a new top-level browsing context without switching to it.
755+
756+
:param type_hint: Optional hint, either "tab" or "window", for the
757+
type of top-level browsing context to create.
758+
:returns: A handle string identifying the new top-level browsing context."""
759+
pass
760+
752761
@abstractmethod
753762
def set_rect(self, rect):
754763
"""Restores the window to the given rect."""

tools/wptrunner/wptrunner/testdriver-extra.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,10 @@
542542
return create_context_action("get_named_cookie", context, {name});
543543
};
544544

545+
window.test_driver_internal.create_window = function(type=null, context=null) {
546+
return create_context_action("create_window", context, {type});
547+
};
548+
545549
window.test_driver_internal.minimize_window = function(context=null) {
546550
return create_context_action("minimize_window", context, {});
547551
};

0 commit comments

Comments
 (0)