1# testdriver.js Automation 2 3testdriver.js provides a means to automate tests that cannot be 4written purely using web platform APIs. Outside of automation 5contexts, it allows human operators to provide expected input 6manually (for operations which may be described in simple terms). 7 8It is currently supported only for [testharness.js](testharness) 9tests. 10 11## API 12 13testdriver.js exposes its API through the `test_driver` variable in 14the global scope. 15 16### Actions 17Usage: 18``` 19let actions = new test_driver.Actions() 20 .action1() 21 .action2(); 22actions.send() 23``` 24 25Test authors are encouraged to use the builder API to generate the 26sequence of actions. The builder API can be accessed via the `new 27test_driver.Actions()` object, and actions are defined in 28[testdriver-actions.js](https://github.com/web-platform-tests/wpt/blob/master/resources/testdriver-actions.js) 29 30The `actions.send()` function causes the sequence of actions to be 31sent to the browser. It is based on the [WebDriver 32API](https://w3c.github.io/webdriver/#actions). The action can be a 33keyboard action, a pointer action or a pause. It returns a promise 34that resolves after the actions have been sent, or rejects if an error 35was thrown. 36 37 38Example: 39 40```js 41let text_box = document.getElementById("text"); 42 43let actions = new test_driver.Actions() 44 .pointerMove(0, 0, {origin: text_box}) 45 .pointerDown() 46 .pointerUp() 47 .addTick() 48 .keyDown("p") 49 .keyUp("p"); 50 51actions.send(); 52``` 53 54Calling into `send()` is going to dispatch the action sequence (via 55`test_driver.action_sequence`) and also returns a promise which should 56be handled however is appropriate in the test. The other functions in 57the `Actions()` object are going to modify the state of the object by 58adding a new action in the sequence and returning the same object. So 59the functions can be easily chained, as shown in the example 60above. Here is a list of helper functions in the `Actions` class: 61 62``` 63pointerDown: Create a pointerDown event for the current default pointer source 64pointerUp: Create a pointerUp event for the current default pointer source 65pointerMove: Create a move event for the current default pointer source 66keyDown: Create a keyDown event for the current default key source 67keyUp: Create a keyUp event for the current default key source 68pause: Add a pause to the current tick 69addTick: Insert a new actions tick 70setPointer: Set the current default pointer source (By detault the pointerType is mouse) 71addPointer: Add a new pointer input source with the given name 72setKeyboard: Set the current default key source 73addKeyboard: Add a new key input source with the given name 74``` 75 76This works with elements in other frames/windows as long as they are 77same-origin with the test, and the test does not depend on the 78window.name property remaining unset on the target window. 79 80### bless 81 82Usage: `test_driver.bless(intent, action)` 83 * _intent_: a string describing the motivation for this invocation 84 * _action_: an optional function 85 86This function simulates [activation][activation], allowing tests to 87perform privileged operations that require user interaction. For 88example, sandboxed iframes with 89`allow-top-navigation-by-user-activation` may only navigate their 90parent's browsing context under these circumstances. The _intent_ 91string is presented to human operators when the test is not run in 92automation. 93 94This method returns a promise which is resolved with the result of 95invoking the _action_ function. If no such function is provided, the 96promise is resolved with the value `undefined`. 97 98Example: 99 100```js 101var mediaElement = document.createElement('video'); 102 103test_driver.bless('initiate media playback', function () { 104 mediaElement.play(); 105}); 106``` 107 108### click 109 110Usage: `test_driver.click(element)` 111 * _element_: a DOM Element object 112 113This function causes a click to occur on the target element (an 114`Element` object), potentially scrolling the document to make it 115possible to click it. It returns a promise that resolves after the 116click has occurred or rejects if the element cannot be clicked (for 117example, it is obscured by an element on top of it). 118 119This works with elements in other frames/windows as long as they are 120same-origin with the test, and the test does not depend on the 121window.name property remaining unset on the target window. 122 123Note that if the element to be clicked does not have a unique ID, the 124document must not have any DOM mutations made between the function 125being called and the promise settling. 126 127## delete_all_cookies 128 129Usage: `test_driver.delete_all_cookies(context=null)` 130 * _context_: an optional WindowProxy for the browsing context in which to 131 perform the call. 132 133This function deletes all cookies for the current browsing context. 134 135### send_keys 136 137Usage: `test_driver.send_keys(element, keys)` 138 * _element_: a DOM Element object 139 * _keys_: string to send to the element 140 141This function causes the string _keys_ to be sent to the target 142element (an `Element` object), potentially scrolling the document to 143make it possible to send keys. It returns a promise that resolves 144after the keys have been sent, or rejects if the keys cannot be sent 145to the element. 146 147This works with elements in other frames/windows as long as they are 148same-origin with the test, and the test does not depend on the 149window.name property remaining unset on the target window. 150 151Note that if the element that the keys need to be sent to does not have 152a unique ID, the document must not have any DOM mutations made 153between the function being called and the promise settling. 154 155To send special keys, one must send the respective key's codepoint. Since this uses the WebDriver protocol, you can find a [list for code points to special keys in the spec](https://w3c.github.io/webdriver/#keyboard-actions). 156For example, to send the tab key you would send "\uE004". 157 158[activation]: https://html.spec.whatwg.org/multipage/interaction.html#activation 159 160### set_permission 161 162Usage: `test_driver.set_permission(descriptor, state, one_realm=false, context=null)` 163 * _descriptor_: a 164 [PermissionDescriptor](https://w3c.github.io/permissions/#dictdef-permissiondescriptor) 165 or derived object 166 * _state_: a 167 [PermissionState](https://w3c.github.io/permissions/#enumdef-permissionstate) 168 value 169 * _one_realm_: a boolean that indicates whether the permission settings 170 apply to only one realm 171 * context: a WindowProxy for the browsing context in which to perform the call 172 173This function causes permission requests and queries for the status of a 174certain permission type (e.g. "push", or "background-fetch") to always 175return _state_. It returns a promise that resolves after the permission has 176been set to be overridden with _state_. 177 178Example: 179 180``` js 181await test_driver.set_permission({ name: "background-fetch" }, "denied"); 182await test_driver.set_permission({ name: "push", userVisibleOnly: true }, "granted", true); 183``` 184 185## Using testdriver in Other Browsing Contexts 186 187Testdriver can be used in browsing contexts (i.e. windows or frames) 188from which it's possible to get a reference to the top-level test 189context. There are two basic approaches depending on whether the 190context in which testdriver is used is same-origin with the test 191context, or different origin. 192 193For same-origin contexts, the context can be passed directly into the 194testdriver API calls. For functions that take an element argument this 195is done implicitly using the owner document of the element. For 196functions that don't take an element, this is done via an explicit 197context argument, which takes a WindowProxy object. 198 199Example: 200``` 201let win = window.open("example.html") 202win.onload = () => { 203 await test_driver.set_permission({ name: "background-fetch" }, "denied", win); 204} 205``` 206 207For the actions API, the context can be set using the `setContext` 208method on the builder: 209 210``` 211let actions = new test_driver.Actions() 212 .setContext(frames[0]) 213 .keyDown("p") 214 .keyUp("p"); 215actions.send(); 216``` 217 218Note that if an action uses an element reference, the context will be 219derived from that element, and must match any explictly set 220context. Using elements in multiple contexts in a single action chain 221is not supported. 222 223 224For cross-origin cases, passing in the context id doesn't work because 225of limitations in the WebDriver protocol used to implement testdriver 226in a cross-browser fashion. Instead one may include the testdriver 227scripts directly in the relevant document, and use the 228`set_test_context` API to specify the browsing context containing 229testharness.js. Commands are then sent via postMessage to the test 230context. For convenience there is also a `message_test` function that 231can be used to send arbitary messages to the test window. For example, 232in an auxillary browsing context: 233 234 235``` 236testdriver.set_test_context(window.opener) 237await testdriver.click(document.getElementsByTagName("button")[0]) 238testdriver.message_test("click complete") 239``` 240 241The requirement to have a handle to the test window does mean it's 242currently not possible to write tests where such handles can't be 243obtained e.g. in the case of `rel=noopener`. 244