зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1459801 [wpt PR 10882] - Documentation/Tutorial for adding a command to testdriver, a=testonly
Automatic update from web-platform-testsMerge pull request #10882 from kereliuk/d/testdriver-tut Documentation/Tutorial for adding a command to testdriver -- wpt-commits: 0bdaaf9c1622ca49eb140381af1ece6d8001c934 wpt-pr: 10882
This commit is contained in:
Родитель
71a4acf3f8
Коммит
b08df65e0f
|
@ -272686,6 +272686,11 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"docs/_writing-tests/testdriver-tutorial.md": [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"docs/_writing-tests/testdriver.md": [
|
"docs/_writing-tests/testdriver.md": [
|
||||||
[
|
[
|
||||||
{}
|
{}
|
||||||
|
@ -556658,6 +556663,10 @@
|
||||||
"62747b6d0328445778050f3e4d6ec46dbdc3a18c",
|
"62747b6d0328445778050f3e4d6ec46dbdc3a18c",
|
||||||
"support"
|
"support"
|
||||||
],
|
],
|
||||||
|
"docs/_writing-tests/testdriver-tutorial.md": [
|
||||||
|
"f98be71f37dad3a469de5f4b55b99b8392255476",
|
||||||
|
"support"
|
||||||
|
],
|
||||||
"docs/_writing-tests/testdriver.md": [
|
"docs/_writing-tests/testdriver.md": [
|
||||||
"2ffb3871fb13e6acd3171205bf80517dca06bcfe",
|
"2ffb3871fb13e6acd3171205bf80517dca06bcfe",
|
||||||
"support"
|
"support"
|
||||||
|
|
|
@ -0,0 +1,317 @@
|
||||||
|
# Adding new commands to testdriver.js
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
We assume the following in this writeup:
|
||||||
|
- You know what web-platform-tests is and you have a working checkout and can run tests
|
||||||
|
- You know what WebDriver or Selenium is
|
||||||
|
- Familiarity with JavaScript and Python
|
||||||
|
|
||||||
|
## Introduction!
|
||||||
|
|
||||||
|
Let's implement window resizing. We can do this via the [Set Window Rect](https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-window-rect) command in WebDriver.
|
||||||
|
|
||||||
|
First, we need to think of what the API will look like a little. We will be using Selenium and Marionette for this, so we can look and see that they take in x, y coordinates, width and height integers.
|
||||||
|
|
||||||
|
The first part of this will be browser agnostic, but later we will need to implement a specific layer for each browser (here we will do Firefox and Chrome).
|
||||||
|
|
||||||
|
## Code!
|
||||||
|
|
||||||
|
### [resources/testdriver.js](resources/testdriver.js)
|
||||||
|
|
||||||
|
This is the main entry point the tests get. Here we need to add a function to the `test_driver` object that will call the `test_driver_internal` object.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.test_driver = {
|
||||||
|
|
||||||
|
// other commands...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers browser window to be resized and relocated
|
||||||
|
*
|
||||||
|
* This matches the behaviour of the {@link
|
||||||
|
* https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-window-rect|WebDriver
|
||||||
|
* Set Window Rect command}.
|
||||||
|
*
|
||||||
|
* @param {Integer} x - The x coordinate of the top left of the window
|
||||||
|
* @param {Integer} y - The x coordinate of the top left of the window
|
||||||
|
* @param {Integer} width - The width of the window
|
||||||
|
* @param {Integer} height - The width of the window
|
||||||
|
* @returns {Promise} fulfilled after window rect is set occurs, or rejected in
|
||||||
|
* the cases the WebDriver command errors
|
||||||
|
*/
|
||||||
|
set_window_rect: function(x, y, width, height) {
|
||||||
|
return window.test_driver_internal.set_element_rect(x, y, width, height);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the same file, lets add to the internal object. ( do we need to do this?) (make sure to do this if the internal call has different arguments than the external call, especially if it calls multiple internal calls)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.test_driver_internal = {
|
||||||
|
|
||||||
|
// other commands...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers browser window to be resized and relocated
|
||||||
|
*
|
||||||
|
* This matches the behaviour of the {@link
|
||||||
|
* https://w3c.github.io/webdriver/webdriver-spec.html#dfn-set-window-rect|WebDriver
|
||||||
|
* Set Window Rect command}.
|
||||||
|
*
|
||||||
|
* @param {Integer} x - The x coordinate of the top left of the window
|
||||||
|
* @param {Integer} y - The x coordinate of the top left of the window
|
||||||
|
* @param {Integer} width - The width of the window
|
||||||
|
* @param {Integer} height - The width of the window
|
||||||
|
* @returns {Promise} fulfilled after window rect is set occurs, or rejected in
|
||||||
|
* the cases the WebDriver command errors
|
||||||
|
*/
|
||||||
|
set_window_rect: function(x, y, width, height) {
|
||||||
|
return Promise.reject(new Error("unimplemented"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
We will leave this unimplemented and override it in another file. Lets do that now!
|
||||||
|
|
||||||
|
### [wptrunner/wptrunner/testdriver-extra.js](tools/wptrunner/wptrunner/testdriver-extra.js)
|
||||||
|
|
||||||
|
This will be the default function called when invoking the test driver commands (sometimes it is overridden by testdriver-vendor.js, but this is outside the scope of this writeup).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.test_driver_internal.set_element_rect = function(x, y, width, height) {
|
||||||
|
const pending_promise = new Promise(function(resolve, reject) {
|
||||||
|
pending_resolve = resolve;
|
||||||
|
pending_reject = reject;
|
||||||
|
});
|
||||||
|
window.opener.postMessage(
|
||||||
|
{"type": "action", "action": "set_window_rect", "x": x, "y": y, "width": width, "height": height}, "*");
|
||||||
|
return pending_promise;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
The main thing here is the `postMessage` argument. The first argument is an object with properties
|
||||||
|
- `type`: this always has to be the string `"action"`
|
||||||
|
- `action`: the name of the testdriver command this defines (in this case, `set_window_rect`)
|
||||||
|
- any other things you want to pass to the next point of execution (in this case, the x, y coordinates and the width and height)
|
||||||
|
|
||||||
|
<!-- The pending promise needs to be there as it is resolved when the window recieves a completion message from the executor. -->
|
||||||
|
The pending promise is out of scope of this function and is resolved when the window recieves a completion message from the executor.
|
||||||
|
This happens here in the same file:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let pending_resolve = null;
|
||||||
|
let pending_reject = null;
|
||||||
|
window.addEventListener("message", function(event) {
|
||||||
|
const data = event.data;
|
||||||
|
|
||||||
|
if (typeof data !== "object" && data !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type !== "testdriver-complete") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.status === "success") {
|
||||||
|
pending_resolve();
|
||||||
|
} else {
|
||||||
|
pending_reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
One limitation this introduces is that only one testdriver call can be made at one time since the `pending_resolve` and `pending_reject` variables are in an outer scope.
|
||||||
|
|
||||||
|
Next, this is passed to the executor and protocol in wptrunner. Time to switch to Python!
|
||||||
|
|
||||||
|
[tools/wptrunner/wptrunner/executors/protocol.py](tools/wptrunner/wptrunner/executors/protocol.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SetWindowRectProtocolPart(ProtocolPart):
|
||||||
|
"""Protocol part for resizing and changing location of window"""
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
name = "set_window_rect"
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def set_window_rect(self, x, y, width, height):
|
||||||
|
"""Change the window rect
|
||||||
|
|
||||||
|
:param x: The x coordinate of the top left of the window.
|
||||||
|
:param y: The y coordinate of the top left of the window.
|
||||||
|
:param width: The width of the window.
|
||||||
|
:param height: The height of the window."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
Next we change the base executor.
|
||||||
|
|
||||||
|
[tools/wptrunner/wptrunner/executors/base.py](tools/wptrunner/wptrunner/executors/base.py)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CallbackHandler(object):
|
||||||
|
"""Handle callbacks from testdriver-using tests.
|
||||||
|
|
||||||
|
The default implementation here makes sense for things that are roughly like
|
||||||
|
WebDriver. Things that are more different to WebDriver may need to create a
|
||||||
|
fully custom implementation."""
|
||||||
|
|
||||||
|
def __init__(self, logger, protocol, test_window):
|
||||||
|
self.protocol = protocol
|
||||||
|
self.test_window = test_window
|
||||||
|
self.logger = logger
|
||||||
|
self.callbacks = {
|
||||||
|
"action": self.process_action,
|
||||||
|
"complete": self.process_complete
|
||||||
|
}
|
||||||
|
|
||||||
|
self.actions = {
|
||||||
|
"click": ClickAction(self.logger, self.protocol),
|
||||||
|
"send_keys": SendKeysAction(self.logger, self.protocol),
|
||||||
|
{other actions},
|
||||||
|
"set_window_rect": SetWindowRectAction(self.logger, self.protocol) # add this!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SetWindowRectAction(object):
|
||||||
|
def __init__(self, logger, protocol):
|
||||||
|
self.logger = logger
|
||||||
|
self.protocol = protocol
|
||||||
|
|
||||||
|
def __call__(self, payload):
|
||||||
|
x, y, width, height = payload["x"], payload["y"], payload["width"], payload["height"]
|
||||||
|
self.logger.debug("Setting window rect to be: x=%s, y=%s, width=%s, height=%s"
|
||||||
|
.format(x, y, width, height))
|
||||||
|
self.protocol.set_window_rect.set_window_rect(x, y, width, height)
|
||||||
|
```
|
||||||
|
|
||||||
|
Don't forget to write docs in ```testdriver.md```.
|
||||||
|
Now we write the browser specific implementations.
|
||||||
|
|
||||||
|
### Chrome
|
||||||
|
|
||||||
|
We will use [executorselenium](tools/wptrunner/wptrunner/executors/executorselenium.py) and use the Selenium API (in the future there are plans to use the WebDriver API directly).
|
||||||
|
|
||||||
|
There isn't too much work to do here, we just need to define a subclass of the protocol part we defined earlier.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SeleniumSetWindowRectProtocolPart(SetWindowRectProtocolPart):
|
||||||
|
def setup(self):
|
||||||
|
self.webdriver = self.parent.webdriver
|
||||||
|
|
||||||
|
def set_window_rect(self, x, y, width, height):
|
||||||
|
return self.webdriver.set_window_rect(x, y, width, height)
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to import the protocol part too!
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .protocol import (BaseProtocolPart,
|
||||||
|
TestharnessProtocolPart,
|
||||||
|
Protocol,
|
||||||
|
SelectorProtocolPart,
|
||||||
|
ClickProtocolPart,
|
||||||
|
SendKeysProtocolPart,
|
||||||
|
{... other protocol parts}
|
||||||
|
SetWindowRectProtocolPart, # add this!
|
||||||
|
TestDriverProtocolPart)
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we have the setup method which just redefines the webdriver object at this level. The important part is the `set_window_rect` function (and it's important it is named that since we called it that earlier). This will be call the Selenium API for [set window rect](http://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webdriver.WebDriver.set_window_rect) (`self.webdriver` is a Selenium WebDriver instance here).
|
||||||
|
|
||||||
|
Finally, we just need to tell the SeleniumProtocol to implement this part.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SeleniumProtocol(Protocol):
|
||||||
|
implements = [SeleniumBaseProtocolPart,
|
||||||
|
SeleniumTestharnessProtocolPart,
|
||||||
|
SeleniumSelectorProtocolPart,
|
||||||
|
SeleniumClickProtocolPart,
|
||||||
|
SeleniumSendKeysProtocolPart,
|
||||||
|
{... other protocol parts}
|
||||||
|
SeleniumSetWindowRectProtocolPart,
|
||||||
|
SeleniumTestDriverProtocolPart]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Firefox
|
||||||
|
We use the [set window rect](http://marionette-client.readthedocs.io/en/master/reference.html#marionette_driver.marionette.Marionette.set_window_rect) Marionette command.
|
||||||
|
|
||||||
|
We will use [executormarionette](tools/wptrunner/wptrunner/executors/executormarionette.py) and use the Marionette Python API.
|
||||||
|
|
||||||
|
We have little actual work to do here! We just need to define a subclass of the protocol part we defined earlier.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MarionetteSetWindowRectProtocolPart(SetWindowRectProtocolPart):
|
||||||
|
def setup(self):
|
||||||
|
self.marionette = self.parent.marionette
|
||||||
|
|
||||||
|
def set_window_rect(self, x, y, width, height):
|
||||||
|
return self.marionette.set_window_rect(x, y, width, height)
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to import the protocol part too!
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .protocol import (BaseProtocolPart,
|
||||||
|
TestharnessProtocolPart,
|
||||||
|
Protocol,
|
||||||
|
SelectorProtocolPart,
|
||||||
|
ClickProtocolPart,
|
||||||
|
SendKeysProtocolPart,
|
||||||
|
{... other protocol parts}
|
||||||
|
SetWindowRectProtocolPart, # add this!
|
||||||
|
TestDriverProtocolPart)
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we have the setup method which just redefines the webdriver object at this level. The important part is the `set_window_rect` function (and it's important it is named that since we called it that earlier). This will be call the Marionette API for [set window rect](http://marionette-client.readthedocs.io/en/master/reference.html#marionette_driver.marionette.Marionette.set_window_rect) (`self.marionette` is a marionette instance here).
|
||||||
|
|
||||||
|
Finally, we just need to tell the SeleniumProtocol to implement this part.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MarionetteProtocol(Protocol):
|
||||||
|
implements = [MarionetteBaseProtocolPart,
|
||||||
|
MarionetteTestharnessProtocolPart,
|
||||||
|
MarionettePrefsProtocolPart,
|
||||||
|
MarionetteStorageProtocolPart,
|
||||||
|
MarionetteSelectorProtocolPart,
|
||||||
|
MarionetteClickProtocolPart,
|
||||||
|
MarionetteSendKeysProtocolPart,
|
||||||
|
{... other protocol parts}
|
||||||
|
MarionetteSetWindowRectProtocolPart # add this
|
||||||
|
MarionetteTestDriverProtocolPart]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other Browsers
|
||||||
|
|
||||||
|
Other browsers may also use executorselenium (such as safari), or a completely new executor (such as servo). For these, you must change the executor in the same way as we did with chrome and firefox.
|
||||||
|
|
||||||
|
### Write an infra test
|
||||||
|
|
||||||
|
Make sure to add a test to `infrastructure/testdriver` :)
|
||||||
|
|
||||||
|
Here is some template code!
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>TestDriver set window rect method</title>
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script src="/resources/testdriver.js"></script>
|
||||||
|
<script src="/resources/testdriver-vendor.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
promise_test(async t => {
|
||||||
|
await test_driver.set_window_rect(100, 100, 100, 100);
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
### What about testdriver-vendor.js?
|
||||||
|
|
||||||
|
The file [testdriver-vendor.js](resources/testdriver-vendor.js) is the equivalent to testdriver-extra.js above, except is
|
||||||
|
run instead of testdriver-extra.js in browser specific test environments. For example, in [Chromium LayoutTests](https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/?q=LayoutTests&sq=package:chromium&dr).
|
||||||
|
|
||||||
|
### What if I need to return a value from my testdriver API?
|
||||||
|
|
||||||
|
We currently don't have this capability, but it is coming soon and will be documented. The bug is [here](https://github.com/w3c/web-platform-tests/issues/10716)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче