The Servodriver test harness
Servodriver is a test harness built on top of the wptrunner framework and a WebDriver server.
It is not yet enabled by default, but can be run with the --product servodriver
argument appended to any test-wpt
command.
Servodriver is made of three main components: the Python test harness that orchestrates the browser, the web server inside Servo that implements the WebDriver specification, and the scripts that are loaded inside the test pages.
The wptrunner harness
Wptrunner is the harness that uses an executor
and browser
combination to launch a supported product.
The browser defines Python classes to instantiate and methods to invoke for supported configurations, as well as arguments to pass to the Servo binary when launching it.
It delegates all WebDriver-specific logic to the base WebDriverBrowser class, which is common to all browsers that rely on WebDriver.
The Servodriver executor defines Servo-specific test initialization, result processing, and inter-test state management. For example, Servo defines WebDriver extension methods for managing preferences, and these are invoked between tests to ensure that each test runs with our intended configuration.
Our Servodriver executor delegates a lot of logic to the common WebDriverTestharnessExecutor. This executor is responsible for executing the testdriver.js harness and enables the Python harness to retrieve test results from the browser.
Servo’s WebDriver implementation
There are three main components to this implementation: the server handler, the script handler, and the input handler.
Server handler
When wptrunner connects to the WebDriver server in the new Servo instance, it creates a new session.
This session persists state between WebDriver API calls.
API calls that interact with the browser are routed through the constellation as ConstellationMsg::WebDriverCommand, and those that need to interact directly with content inside a document are part of the WebDriverScriptCommand enum.
Many of these APIs require a synchronous response, via an IpcSender
channel that Servo’s WebDriver server uses to wait for a response.
However, cases like navigation and executing async scripts involve additional synchronization.
Navigation
When a navigation is requested, the constellation receives an IpcSender that it stores. When a navigation completes, the constellation checks if the pipeline matches the navigation from WebDriver, and notifies the WebDriver server using the original channel.
Async scripts
When an async script execution is initiated, the WebDriver specification provides a way for scripts to communicate a result to the server. The script is invoked as an anonymous function with an additional function argument that sends the response to the server.
Script handler
When a WebDriver message is received that targets a specific browsing context, it is handled by the ScriptThread that contains the active document. The logic for processing each command lives in webdriver_handlers.rs. Any command that returns a value derived from web content must serialize JS values as values that the WebDriver server can convert into API value types.
We expose two web-accessible methods for communicating async results to the WebDriver server: Window.webdriverCallback
and Window.webdriverTimeout
.
Input handler
When the WebDriver server receives input action commands (e.g. pointer or mouse inputs), it creates an action sequence and progressively dispatches them to the compositor via the constellation. The compositor then handles these events the same way as any input events received from the embedder (modulo known bugs).
The test page scripts
The testdriver harness works by incorporating all of these elements together.
The executor opens a new window and invokes an async script that sets the testdriver callback to Window.webdriverCallback
(arguments[arguments.length - 1]
).
This window creates a message queue, which is read by the executor harness.
Subsequently, each test that runs is opened as a new window; these windows can then use opener.postMessage
to communicate with the original window, which allows the testdriver APIs to send messages that represent WebDriver API calls.
Debugging hints
Always start with the following RUST_LOG:
RUST_LOG=webdriver_server,webdriver,script::webdriver_handlers,constellation
This usually makes it clear if the expected API calls are being processed.
When debugging input-related problems, add JS debugging lines that log the getBoundingClientRect()
of whatever element is being clicked, then compare the coordinates received by the compositor when clicking manually vs the coordinates received from the WebDriver server.