зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1729274 [wpt PR 30357] - Update WAVE test runner, a=testonly
Automatic update from web-platform-tests Update WAVE test runner (#30357) -- wpt-commits: e5c7faa6e8062dbf57eae6f58e99483cd2a4ec4d wpt-pr: 30357
This commit is contained in:
Родитель
e80bd061fe
Коммит
3ab7df3afe
|
@ -0,0 +1,3 @@
|
|||
!www/lib
|
||||
!export/lib
|
||||
!export/css
|
|
@ -37,9 +37,13 @@
|
|||
"automatic": 60000,
|
||||
"manual": 300000
|
||||
},
|
||||
"enable_results_import": false,
|
||||
"enable_import_results": false,
|
||||
"web_root": "/_wave",
|
||||
"persisting_interval": 20,
|
||||
"api_titles": []
|
||||
"api_titles": [],
|
||||
"enable_read_sessions": false,
|
||||
"event_cache_duration": 60000,
|
||||
"enable_test_type_selection": false,
|
||||
"enable_test_file_selection": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
from io import open
|
||||
|
||||
from tools.wpt import wpt
|
||||
|
||||
|
@ -46,15 +47,24 @@ def load(configuration_file_path):
|
|||
configuration["hostname"] = configuration.get(
|
||||
"browser_host", default_configuration["browser_host"])
|
||||
|
||||
configuration["import_enabled"] = configuration.get(
|
||||
configuration["import_results_enabled"] = configuration.get(
|
||||
"wave", default_configuration["wave"]).get(
|
||||
"enable_results_import",
|
||||
default_configuration["wave"]["enable_results_import"])
|
||||
"enable_import_results",
|
||||
default_configuration["wave"]["enable_import_results"])
|
||||
|
||||
configuration["read_sessions_enabled"] = configuration.get(
|
||||
"wave", default_configuration["wave"]).get(
|
||||
"enable_read_sessions",
|
||||
default_configuration["wave"]["enable_read_sessions"])
|
||||
|
||||
configuration["persisting_interval"] = configuration.get(
|
||||
"wave", default_configuration["wave"]).get(
|
||||
"persisting_interval", default_configuration["wave"]["persisting_interval"])
|
||||
|
||||
configuration["event_cache_duration"] = configuration.get(
|
||||
"wave", default_configuration["wave"]).get(
|
||||
"event_cache_duration", default_configuration["wave"]["event_cache_duration"])
|
||||
|
||||
configuration["tests_directory_path"] = os.getcwd()
|
||||
|
||||
configuration["manifest_file_path"] = os.path.join(
|
||||
|
@ -64,6 +74,14 @@ def load(configuration_file_path):
|
|||
"wave", default_configuration["wave"]).get(
|
||||
"api_titles", default_configuration["wave"]["api_titles"])
|
||||
|
||||
configuration["enable_test_type_selection"] = configuration.get(
|
||||
"wave", default_configuration["wave"]).get(
|
||||
"enable_test_type_selection", default_configuration["wave"]["enable_test_type_selection"])
|
||||
|
||||
configuration["enable_test_file_selection"] = configuration.get(
|
||||
"wave", default_configuration["wave"]).get(
|
||||
"enable_test_file_selection", default_configuration["wave"]["enable_test_file_selection"])
|
||||
|
||||
return configuration
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class Device(object):
|
||||
def __init__(self, token, user_agent, name, last_active):
|
||||
self.token = token
|
||||
self.user_agent = user_agent
|
||||
self.name = name
|
||||
self.last_active = last_active
|
|
@ -0,0 +1,8 @@
|
|||
class EventListener(object):
|
||||
def __init__(self, dispatcher_token):
|
||||
super(EventListener, self).__init__()
|
||||
self.dispatcher_token = dispatcher_token
|
||||
self.token = None
|
||||
|
||||
def send_message(self, message):
|
||||
raise Exception("Client.send_message(message) not implemented!")
|
|
@ -0,0 +1,11 @@
|
|||
from .event_listener import EventListener
|
||||
|
||||
class HttpPollingEventListener(EventListener):
|
||||
def __init__(self, dispatcher_token, event):
|
||||
super(HttpPollingEventListener, self).__init__(dispatcher_token)
|
||||
self.event = event
|
||||
self.message = None
|
||||
|
||||
def send_message(self, message):
|
||||
self.message = message
|
||||
self.event.set()
|
|
@ -12,7 +12,7 @@ class Session(object):
|
|||
def __init__(
|
||||
self,
|
||||
token=None,
|
||||
types=None,
|
||||
test_types=None,
|
||||
user_agent=None,
|
||||
labels=None,
|
||||
tests=None,
|
||||
|
@ -23,35 +23,30 @@ class Session(object):
|
|||
test_state=None,
|
||||
last_completed_test=None,
|
||||
recent_completed_count=None,
|
||||
date_created=None,
|
||||
date_started=None,
|
||||
date_finished=None,
|
||||
is_public=None,
|
||||
reference_tokens=None,
|
||||
browser=None,
|
||||
webhook_urls=None,
|
||||
expiration_date=None,
|
||||
type=None,
|
||||
malfunctioning_tests=None
|
||||
):
|
||||
if token is None:
|
||||
token = ""
|
||||
self.token = token
|
||||
if types is None:
|
||||
types = [AUTOMATIC, MANUAL]
|
||||
self.types = types
|
||||
if test_types is None:
|
||||
test_types = [AUTOMATIC, MANUAL]
|
||||
self.test_types = test_types
|
||||
if user_agent is None:
|
||||
user_agent = ""
|
||||
self.user_agent = user_agent
|
||||
if labels is None:
|
||||
labels = []
|
||||
self.labels = labels
|
||||
if tests is None:
|
||||
tests = {}
|
||||
self.tests = tests
|
||||
if pending_tests is None:
|
||||
pending_tests = {}
|
||||
self.pending_tests = pending_tests
|
||||
if running_tests is None:
|
||||
running_tests = {}
|
||||
self.running_tests = running_tests
|
||||
if timeouts is None:
|
||||
timeouts = {}
|
||||
|
@ -59,13 +54,12 @@ class Session(object):
|
|||
if status is None:
|
||||
status = UNKNOWN
|
||||
self.status = status
|
||||
if test_state is None:
|
||||
test_state = {}
|
||||
self.test_state = test_state
|
||||
self.last_completed_test = last_completed_test
|
||||
if recent_completed_count is None:
|
||||
recent_completed_count = 0
|
||||
self.recent_completed_count = recent_completed_count
|
||||
self.date_created = date_created
|
||||
self.date_started = date_started
|
||||
self.date_finished = date_finished
|
||||
if is_public is None:
|
||||
|
@ -75,10 +69,8 @@ class Session(object):
|
|||
reference_tokens = []
|
||||
self.reference_tokens = reference_tokens
|
||||
self.browser = browser
|
||||
if webhook_urls is None:
|
||||
webhook_urls = []
|
||||
self.webhook_urls = webhook_urls
|
||||
self.expiration_date = expiration_date
|
||||
self.type = type
|
||||
if malfunctioning_tests is None:
|
||||
malfunctioning_tests = []
|
||||
self.malfunctioning_tests = malfunctioning_tests
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
# WAVE Test Suite Documentation
|
||||
# WAVE Test Runner Documentation
|
||||
|
||||
- [REST API](./rest-api/README.md)
|
||||
- [Usage Guide](./usage/usage.md)
|
||||
As part of the [WAVE project](https://cta.tech/Resources/Standards/WAVE-Project)
|
||||
the WAVE Test Runner was implemented to run tests that confirm proper implementation
|
||||
of specified features. The code base is used in different subprojects, each of which
|
||||
may have different requirements and scopes, so some features and screenshots in
|
||||
this documentation may use specific contexts.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Configuration](./config.md): How to configure the test runner
|
||||
- [REST API](./rest-api/README.md): Documentation of endpoints, parameters and payloads
|
||||
- [Guides](./rest-api/guides/README.md): How to use certain API mechanisms
|
||||
- [Usage Guide](./usage/usage.md): General usage guide
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
# Configuration - [WAVE Test Runner](./README.md)
|
||||
|
||||
Using a configuration file, the WAVE Test Runner can be configured to be more
|
||||
functional in different use cases. This document lists all configuration
|
||||
parameters and what they are used for.
|
||||
|
||||
## Contents
|
||||
|
||||
1. [Location and structure](#1-location-and-structure)
|
||||
2. [Parameters](#2-parameters)
|
||||
1. [Results directory](#21-results-directory)
|
||||
2. [Test Timeouts](#22-test-timeouts)
|
||||
3. [Enable import of results](#23-enable-import-of-results)
|
||||
4. [Web namespace](#24-web-namespace)
|
||||
5. [Persisting interval](#25-persisting-interval)
|
||||
6. [API titles](#26-api-titles)
|
||||
7. [Enable listing all sessions](#27-enable-listing-all-sessions)
|
||||
8. [Event caching duration](#28-event-caching-duration)
|
||||
9. [Enable test type selection](#29-enable-test-type-selection)
|
||||
|
||||
## 1. Location and structure
|
||||
|
||||
Configuration parameters are defined in a JSON file called `config.json` in
|
||||
the project root of the WPT runner. This configuration file is also used by
|
||||
the WPT runner, so any WAVE Test Runner related configuration parameters are
|
||||
wrapped inside a `wave` object.
|
||||
|
||||
```
|
||||
<PRJ_ROOT>/config.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"wave": {
|
||||
"results": "./results"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All the default values are stored in a configuration file inside the wave
|
||||
directory:
|
||||
|
||||
```
|
||||
<PRJ_ROOT>/tools/wave/config.default.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"wave": {
|
||||
"results": "./results",
|
||||
"timeouts": {
|
||||
"automatic": 60000,
|
||||
"manual": 300000
|
||||
},
|
||||
"enable_import_results": false,
|
||||
"web_root": "/_wave",
|
||||
"persisting_interval": 20,
|
||||
"api_titles": [],
|
||||
"enable_read_sessions": false,
|
||||
"event_cache_duration": 60000
|
||||
}
|
||||
}
|
||||
```
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
## 2. Parameters
|
||||
|
||||
### 2.1 Results directory
|
||||
|
||||
The results parameter sets where results and session information are stored.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": "<String>"
|
||||
}
|
||||
```
|
||||
|
||||
- **results**: Path to the results directory. Can be absolute, or relative to
|
||||
the project root.
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": "./results"
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.2 Test Timeouts
|
||||
|
||||
The test timeouts set the default test timeout for different test types.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"timeouts": {
|
||||
"automatic": "<Number>",
|
||||
"manual": "<Number>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **timeouts**: Holds the key value pairs for different types of tests
|
||||
- **automatic**: Default time to wait for automatic tests in milliseconds.
|
||||
- **manual**: Default time to wait for manual tests in milliseconds.
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"timeouts": {
|
||||
"automatic": 600000,
|
||||
"manual": 300000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.3 Enable import of results
|
||||
|
||||
This parameter enables the capability to import session results from other
|
||||
WAVE Test Runner instances into the current one.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"enable_import_results": "<Boolean>"
|
||||
}
|
||||
```
|
||||
|
||||
- **enable_import_results**: Sets whether or not to enable the [REST API endpoint to import results](./rest-api/results-api/import.md)
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"enable_import_results": "false"
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.4 Web namespace
|
||||
|
||||
All static resources and REST API endpoints are accessible under a
|
||||
configurable namespace. This namespace can be set using the `web_root`
|
||||
parameter.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"web_root": "<String>"
|
||||
}
|
||||
```
|
||||
|
||||
- **web_root**: The namespace to use
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"web_root": "/_wave"
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.5 Persisting interval
|
||||
|
||||
The persisting interval specifies how many tests have to be completed until
|
||||
all session information is updated in the results directory.
|
||||
|
||||
For example, if set to 5, then every 5 completed tests the `info.json` in the
|
||||
results directory is updated with the current state of the session. When
|
||||
restarting the server, this state is used to reconstruct all sessions testing
|
||||
state.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"persisting_interval": "<Number>"
|
||||
}
|
||||
```
|
||||
|
||||
- **persisting_interval**: The number of tests to execute until the persisted
|
||||
session information gets updated
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"persisting_interval": 20
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.6 API titles
|
||||
|
||||
The API titles are used to display a more human readible representation of an
|
||||
API that tests are available for. Using the parameter it is possible to assign
|
||||
a name to an API subdirectory.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_titles": [
|
||||
{
|
||||
"title": "<String>",
|
||||
"path": "<String>"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **api_titles**: An array of titles assigned to paths
|
||||
- **title**: The displayed title of the API in the UI
|
||||
- **path**: The path relative to the project root of the tested API
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_titles": []
|
||||
}
|
||||
```
|
||||
|
||||
**Example**:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_titles": [
|
||||
{
|
||||
"title": "WebGL",
|
||||
"path": "/webgl"
|
||||
},
|
||||
{
|
||||
"title": "WebRTC Extensions",
|
||||
"path": "/webrtc-extensions"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.7 Enable listing all sessions
|
||||
|
||||
This parameter enables the [REST API endpoint to list all available sessions](./rest-api/sessions-api/read_sessions.md).
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"enable_read_sessions": "<Boolean>"
|
||||
}
|
||||
```
|
||||
|
||||
- **enable_import_results**: Sets whether or not to enable the REST API endpoint read all sessions
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"enable_read_sessions": "false"
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.8 Event caching duration
|
||||
|
||||
This parameters specifies how long events are hold in the cache. Depending on
|
||||
how fast clients are able to evaluate events, this value may be changed
|
||||
accordingly.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_cache_duration": "<Number>"
|
||||
}
|
||||
```
|
||||
|
||||
- **event_cache_duration**: The duration events are hold in the cache in milliseconds
|
||||
|
||||
**Default**:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_cache_duration": 60000
|
||||
}
|
||||
```
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
||||
|
||||
### 2.9 Enable test type selection
|
||||
|
||||
Sets display of test type configuration UI elements.
|
||||
|
||||
**Parameters**:
|
||||
|
||||
```json
|
||||
{
|
||||
"enable_test_type_selection": "<Boolean>"
|
||||
}
|
||||
```
|
||||
|
||||
- **enable_test_type_selection**: Whether or not test type UI controls are displayed
|
||||
|
||||
**Default**:
|
||||
|
||||
False
|
||||
|
||||
[🠑 top](#configuration---wave-test-runner)
|
|
@ -1,26 +1,31 @@
|
|||
# REST API - [WAVE Test Suite](../README.md)
|
||||
# REST API - [WAVE Test Runner](../README.md)
|
||||
|
||||
The REST API allows the WAVE server to be integrated into other systems. Every
|
||||
call must be preceded with a namespace or web root, which is omitted in this
|
||||
documentation. The default web root is `/_wave`, which can be changed in the
|
||||
The REST API allows the WAVE server to be integrated into other systems. Every
|
||||
call must be preceded with a namespace or web root, which is omitted in this
|
||||
documentation. The default web root is `/_wave`, which can be changed in the
|
||||
config.json using the keyword `web_root`.
|
||||
|
||||
Additional [REST API Guides](./guides/README.md) can help to understand how to
|
||||
use these endpoints in context.
|
||||
|
||||
## Sessions API <a name="sessions-api"></a>
|
||||
|
||||
| Name | Description |
|
||||
| ---------------------------------------------- | ---------------------------------------------------- |
|
||||
| [`create`](./sessions-api/create.md) | Creates a new test session. |
|
||||
| [`read`](./sessions-api/read.md) | Reads a sessions configuration. |
|
||||
| [`read public`](./sessions-api/read-public.md) | Reads all public sessions tokens. |
|
||||
| [`update`](./sessions-api/update.md) | Updates a session configuration. |
|
||||
| [`delete`](./sessions-api/delete.md) | Deletes a test session. |
|
||||
| [`status`](./sessions-api/status.md) | Reads the status and progress of a session. |
|
||||
| [`start`](./sessions-api/control.md#start) | Starts a test session. |
|
||||
| [`stop`](./sessions-api/control.md#stop) | Stops a test session. |
|
||||
| [`pause`](./sessions-api/control.md#pause) | Pauses a test session. |
|
||||
| [`find`](./sessions-api/find.md) | Finds a session token by providing a token fragment. |
|
||||
| [`labels`](./sessions-api/labels.md) | Attach labels to sessions for organization purposes. |
|
||||
| [`events`](./sessions-api/events.md) | Register for sessions specific events. |
|
||||
| Name | Description |
|
||||
| -------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| [`create`](./sessions-api/create.md) | Creates a new test session. |
|
||||
| [`read session`](./sessions-api/read.md) | Reads a sessions configuration. |
|
||||
| [`read sessions`](./sessions-api/read_sessions.md) | Reads all session tokens, expandable with configs and statuses |
|
||||
| [`read public`](./sessions-api/read-public.md) | Reads all public sessions tokens. |
|
||||
| [`update`](./sessions-api/update.md) | Updates a session configuration. |
|
||||
| [`delete`](./sessions-api/delete.md) | Deletes a test session. |
|
||||
| [`status`](./sessions-api/status.md) | Reads the status and progress of a session. |
|
||||
| [`start`](./sessions-api/control.md#start) | Starts a test session. |
|
||||
| [`stop`](./sessions-api/control.md#stop) | Stops a test session. |
|
||||
| [`pause`](./sessions-api/control.md#pause) | Pauses a test session. |
|
||||
| [`find`](./sessions-api/find.md) | Finds a session token by providing a token fragment. |
|
||||
| [`labels`](./sessions-api/labels.md) | Attach labels to sessions for organization purposes. |
|
||||
| [`listen events`](./sessions-api/events.md) | Register for sessions specific events. |
|
||||
| [`push events`](./sessions-api/events.md) | Push session specific events. |
|
||||
|
||||
## Tests API <a name="tests-api"></a>
|
||||
|
||||
|
@ -36,19 +41,36 @@ config.json using the keyword `web_root`.
|
|||
|
||||
## Results API <a name="results-api"></a>
|
||||
|
||||
| Name | Description |
|
||||
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| [`create`](./results-api/create.md) | Create a new test result for a test in a session. |
|
||||
| [`read`](./results-api/read.md) | Read all test results of a session. |
|
||||
| [`read compact`](./results-api/read-compact.md) | Read the number of passed, failed, timed out and not run tests of a session. |
|
||||
| [`config`](./results-api/config.md) | Read what features of the results API are enabled. |
|
||||
| [`import`](./results-api/import.md) | Import session results. |
|
||||
| [`import enabled`](./results-api/import.md#2-import-enabled) | Check whether or not the import feature is enabled. |
|
||||
| [`download`](./results-api/download.md#1-download) | Download all session results to import into other WMATS instance. |
|
||||
| [`download api`](./results-api/download.md#2-download-api) | Download all results of an API. |
|
||||
| [`download all apis`](./results-api/download.md#3-download-all-apis) | Download all results of all APIs. |
|
||||
| [`view report`](./results-api/download.md#4-download-report) | View the WPT report of an API of a session. |
|
||||
| [`view multi report`](./results-api/download.md#5-download-multi-report) | View the WPT report of an API of multiple sessions. |
|
||||
| [`download overview`](./results-api/download.md#6-download-overview) | Download an overview of results of all APIs of a session. |
|
||||
| [`view report`](./results-api/view.md#1-view-report) | Read an url to a hosted version of a WPT report for an API of a session. |
|
||||
| [`view multi report`](./results-api/view.md#2-view-multi-report) | Read an url to a hosted version of a WPT report for an API of multiple session. |
|
||||
| Name | Description |
|
||||
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`create`](./results-api/create.md) | Create a new test result for a test in a session. |
|
||||
| [`read`](./results-api/read.md) | Read all test results of a session. |
|
||||
| [`read compact`](./results-api/read-compact.md) | Read the number of passed, failed, timed out and not run tests of a session. |
|
||||
| [`import session`](./results-api/import.md#1-import-session) | Import session results. |
|
||||
| [`import api results`](./results-api/import.md#2-import-api-results) | Import results of a specific API into existing session. |
|
||||
| [`download`](./results-api/download.md#1-download) | Download all session results to import into other WMATS instance. |
|
||||
| [`download api`](./results-api/download.md#2-download-api) | Download all results of an API. |
|
||||
| [`download all apis`](./results-api/download.md#3-download-all-apis) | Download all results of all APIs. |
|
||||
| [`view report`](./results-api/download.md#4-download-report) | View the WPT report of an API of a session. |
|
||||
| [`view multi report`](./results-api/download.md#5-download-multi-report) | View the WPT report of an API of multiple sessions. |
|
||||
| [`download overview`](./results-api/download.md#6-download-overview) | Download an overview of results of all APIs of a session. |
|
||||
| [`view report`](./results-api/view.md#1-view-report) | Read an url to a hosted version of a WPT report for an API of a session. |
|
||||
| [`view multi report`](./results-api/view.md#2-view-multi-report) | Read an url to a hosted version of a WPT report for an API of multiple session. |
|
||||
|
||||
## Devices API <a name="devices-api"></a>
|
||||
|
||||
| Name | Description |
|
||||
| -------------------------------------------------------------------- | -------------------------------------- |
|
||||
| [`create`](./devices-api/create.md) | Registers a new device. |
|
||||
| [`read device`](./devices-api/read-device.md) | Read information of a specific device. |
|
||||
| [`read devices`](./devices-api/read-devices.md) | Read a list of all available devices. |
|
||||
| [`register event listener`](./devices-api/register.md) | Register for a device specific event. |
|
||||
| [`send event`](./devices-api/send-event.md) | Sends a device specific event. |
|
||||
| [`register global event listener`](./devices-api/register-global.md) | Register for a global device event. |
|
||||
| [`send global event`](./devices-api/send-global-event.md) | Sends a global device event. |
|
||||
|
||||
## General API <a name="general-api"></a>
|
||||
|
||||
| Name | Description |
|
||||
| ----------------------------------- | ---------------------------------------------------- |
|
||||
| [`status`](./general-api/status.md) | Returns information on how the server is configured. |
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# `create` - [Devices API](../README.md#devices-api)
|
||||
|
||||
The `create` method of the devices API registers a new devices to remotely
|
||||
start test sessions on. The device will automatically be unregistered if its
|
||||
not registering for an [event](./register.md) for more than a minute.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
`POST /api/devices`
|
||||
|
||||
## Response Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "<String>"
|
||||
}
|
||||
```
|
||||
|
||||
- **token** specifies the handle to reference the registered device by.
|
||||
|
||||
## Example
|
||||
|
||||
**Request:**
|
||||
|
||||
`POST /api/devices`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "e5f0b92e-8309-11ea-a1b1-0021ccd76152"
|
||||
}
|
||||
```
|
|
@ -0,0 +1,37 @@
|
|||
# Device Event Types - [Devices API](../README.md#devices-api)
|
||||
|
||||
Device events are events that are triggered by actions related to devices.
|
||||
This document specifies what possible events can occur and what they mean.
|
||||
|
||||
## Device specific <a name="device-specific"></a>
|
||||
|
||||
Device specific events are always related to a specific device, referenced by
|
||||
its token.
|
||||
|
||||
### Start session
|
||||
|
||||
**Type identifier**: `start_session`
|
||||
**Payload**:
|
||||
```json
|
||||
{
|
||||
"session_token": "<String>"
|
||||
}
|
||||
```
|
||||
**Description**: Triggered by a companion device, this event starts a
|
||||
pre-configured session on the registered device.
|
||||
|
||||
## Global <a name="global"></a>
|
||||
|
||||
Global device events have no special relation to any device.
|
||||
|
||||
### Device added
|
||||
|
||||
**Type identifier**: `device_added`
|
||||
**Payload**: Same as response of [`read device`](./read-device.md) method.
|
||||
**Description**: This event is triggered once a new device registers.
|
||||
|
||||
### Device removed
|
||||
|
||||
**Type identifier**: `device_removed`
|
||||
**Payload**: Same as response of [`read device`](./read-device.md) method.
|
||||
**Description**: This event is triggered once a device unregisters.
|
|
@ -0,0 +1,41 @@
|
|||
# `read device` - [Devices API](../README.md#devices-api)
|
||||
|
||||
The `read device` method of the devices API fetches available information regarding a
|
||||
specific device.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
`GET /api/devices/<device_token>`
|
||||
|
||||
## Response Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "<String>",
|
||||
"user_agent": "<String>",
|
||||
"last_active": "<String>",
|
||||
"name": "<String>"
|
||||
}
|
||||
```
|
||||
|
||||
- **token** is the unique identifier of the device.
|
||||
- **user_agent** is the user agent of the request the device was registered with.
|
||||
- **last_active** defines the point in time the device was last active. Expressed as ISO 8601 date and time format.
|
||||
- **name** the name the device was assign based on its user agent.
|
||||
|
||||
## Example
|
||||
|
||||
**Request:**
|
||||
|
||||
`GET /api/devices/1d9f5d30-830f-11ea-8dcb-0021ccd76152`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "1d9f5d30-830f-11ea-8dcb-0021ccd76152",
|
||||
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36",
|
||||
"last_active": 1587391153295,
|
||||
"name": "Chrome 81.0.4044"
|
||||
}
|
||||
```
|
|
@ -0,0 +1,47 @@
|
|||
# `read devices` - [Devices API](../README.md#devices-api)
|
||||
|
||||
The `read devices` method of the devices API fetches a list of all registered
|
||||
devices.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
`GET /api/devices`
|
||||
|
||||
## Response Payload
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"token": "<String>",
|
||||
"user_agent": "<String>",
|
||||
"last_active": "<String>",
|
||||
"name": "<String>"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
- **token** is the unique identifier of the device.
|
||||
- **user_agent** is the user agent of the request the device was registered with.
|
||||
- **last_active** defines the point in time the device was last active. Expressed as ISO 8601 date and time format.
|
||||
- **name** the name the device was assign based on its user agent.
|
||||
|
||||
## Example
|
||||
|
||||
**Request:**
|
||||
|
||||
`GET /api/devices`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"token": "1d9f5d30-830f-11ea-8dcb-0021ccd76152",
|
||||
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36",
|
||||
"last_active": 1587391153295,
|
||||
"name": "Chrome 81.0.4044"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
|
@ -0,0 +1,54 @@
|
|||
# `register global event listener` - [Devices API](../README.md#devices-api)
|
||||
|
||||
The `register global event listener` method of the devices API notifies a
|
||||
registered listener upon global device events. It uses HTTP long polling in
|
||||
send the event to this listener in real time, so upon receiving an event, the
|
||||
connection has to be reestablished by the client to receive further events.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
`GET /api/devices/events`
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Desciption | Example |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| `device_token` | The token of the device which performed the request. (Optional) Lets the server know the registered device is still active. | `device_token=7dafeec0-c351-11e9-84c5-3d1ede2e7d2e` |
|
||||
|
||||
## Response Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<String>",
|
||||
"data": "<Any>"
|
||||
}
|
||||
```
|
||||
|
||||
- **type** defines what type of event has been triggered.
|
||||
- **data** contains the event specific payload.
|
||||
|
||||
## Event Types
|
||||
|
||||
See [global events](./event-types.md#global)
|
||||
|
||||
## Example
|
||||
|
||||
**Request:**
|
||||
|
||||
`GET /api/devices/events`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "device_added",
|
||||
"data": {
|
||||
"token": "1d9f5d30-830f-11ea-8dcb-0021ccd76152",
|
||||
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36",
|
||||
"last_active": 1587391153295,
|
||||
"name": "Chrome 81.0.4044"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# `register event listener` - [Devices API](../README.md#devices-api)
|
||||
|
||||
The `register event listener` method of the devices API notifies a registered
|
||||
listener upon device specific events. It uses HTTP long polling in send the
|
||||
event to this listener in real time, so upon receiving an event, the
|
||||
connection has to be reestablished by the client to receive further events.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
`GET /api/devices/<device_token>/events`
|
||||
|
||||
## Query Parameters
|
||||
|
||||
| Parameter | Desciption | Example |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
||||
| `device_token` | The token of the device which performed the request. (Optional) Lets the server know the registered device is still active. | `device_token=7dafeec0-c351-11e9-84c5-3d1ede2e7d2e` |
|
||||
|
||||
## Response Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<String>",
|
||||
"data": "<Any>"
|
||||
}
|
||||
```
|
||||
|
||||
- **type** defines what type of event has been triggered.
|
||||
- **data** contains the event specific payload.
|
||||
|
||||
## Event Types
|
||||
|
||||
### Start session
|
||||
|
||||
See [device specific events](./event-types.md#device-specific)
|
||||
|
||||
## Example
|
||||
|
||||
**Request:**
|
||||
|
||||
`GET /api/devices/1d9f5d30-830f-11ea-8dcb-0021ccd76152/events`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "start_session",
|
||||
"data": {
|
||||
"session_token": "974c84e0-c35d-11e9-8f8d-47bb5bb0037d"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# `send event` - [Devices API](../README.md#devices-api)
|
||||
|
||||
The `send event` method of the devices API enables sending an event to
|
||||
listeners of specific devices events.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
`POST /api/devices/<device_token>/events`
|
||||
|
||||
## Request Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<String>",
|
||||
"data": "<Any>"
|
||||
}
|
||||
```
|
||||
|
||||
- **type** defines what type of event has been triggered.
|
||||
- **data** contains the event specific payload.
|
||||
|
||||
## Event Types
|
||||
|
||||
See [device specific events](./event-types.md#device-specific)
|
||||
|
||||
## Example
|
||||
|
||||
**Request:**
|
||||
|
||||
`POST /api/devices/1d9f5d30-830f-11ea-8dcb-0021ccd76152/events`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "start_session",
|
||||
"data": {
|
||||
"session_token": "974c84e0-c35d-11e9-8f8d-47bb5bb0037d"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
`200 OK`
|
|
@ -0,0 +1,46 @@
|
|||
# `send global event` - [Devices API](../README.md#devices-api)
|
||||
|
||||
The `send global event` method of the devices API enables sending an event to
|
||||
listeners of global device events.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
`POST /api/devices/events`
|
||||
|
||||
## Request Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<String>",
|
||||
"data": "<Any>"
|
||||
}
|
||||
```
|
||||
|
||||
- **type** defines what type of event has been triggered.
|
||||
- **data** contains the event specific payload.
|
||||
|
||||
## Event Types
|
||||
|
||||
See [global events](./event-types.md#global)
|
||||
|
||||
## Example
|
||||
|
||||
**Request:**
|
||||
|
||||
`POST /api/devices/1d9f5d30-830f-11ea-8dcb-0021ccd76152/events`
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "device_added",
|
||||
"data": {
|
||||
"token": "1d9f5d30-830f-11ea-8dcb-0021ccd76152",
|
||||
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36",
|
||||
"last_active": 1587391153295,
|
||||
"name": "Chrome 81.0.4044"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
`200 OK`
|
|
@ -0,0 +1,41 @@
|
|||
# `status` - [General API](../README.md#general-api)
|
||||
|
||||
The `status` method is used to ensure the server is reachable and to determine
|
||||
what features of different server APIs are enabled.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
```
|
||||
GET /api/status
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"version_string": "String",
|
||||
"import_results_enabled": "Boolean",
|
||||
"reports_enabled": "Boolean",
|
||||
"read_sessions_enabled": "Boolean"
|
||||
}
|
||||
```
|
||||
|
||||
- **version_string**: The version of the server.
|
||||
- **import_results_enabled**: If true the [`import result`](../results-api/import.md) endpoint is available
|
||||
- **reports_enabled**: If true the server will generate reports for completed APIs in a given test session.
|
||||
- **read_sessions_enabled**: If true it is possible to list all sessions using the [`read sessions`](../sessions-api/read_sessions.md) endpoint of the sessions API
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
GET /api/status
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"version_string": "v2.0.0",
|
||||
"import_results_enabled": false,
|
||||
"reports_enabled": true,
|
||||
"read_sessions_enabled": false
|
||||
}
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
# REST API Guides - [WAVE Test Runner](../../README.md)
|
||||
|
||||
In addition to the [REST API documentation](../README.md), these guide shall
|
||||
provide a better understanding on how to properly use the different endpoints.
|
||||
|
||||
[Starting sessions on a DUT using the devices API](./session-start-devices-api.md):
|
||||
How to register a DUT and start a pre-configured session on it.
|
||||
|
||||
[Sending and receiving session events](./session-events.md):
|
||||
How to register for session events and push events to other listeners.
|
|
@ -0,0 +1,52 @@
|
|||
# Sending and receiving session events
|
||||
|
||||
The session event endpoints allow to listen for events related to a specific
|
||||
session and to send new events to all registered listeners.
|
||||
|
||||
See all [REST API Guides](./README.md).
|
||||
|
||||
## Register for session specific events
|
||||
|
||||
To receive events of a session, simply perform a GET request to the desired
|
||||
sessions event endpoint. For example, if we want to receive any events that
|
||||
are related to the session with token `6fdbd1a0-c339-11e9-b775-6d49dd567772`:
|
||||
|
||||
```
|
||||
GET /_wave/api/sessions/6fdbd1a0-c339-11e9-b775-6d49dd567772/events
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "status",
|
||||
"data": "paused"
|
||||
}
|
||||
```
|
||||
|
||||
As this endpoint makes use of the HTTP long polling, you will not immediately
|
||||
receive a response. The connection stays open either until an event gets
|
||||
triggered, in which case the server will respond with that events data, or
|
||||
there is no event within the timeout, which will return an empty response.
|
||||
|
||||
With one request only one event can be received. To get any further events,
|
||||
additional requests are necessary. To not miss any events, it is important to
|
||||
perform the next request immediately after receiving a response.
|
||||
|
||||
## Sending events
|
||||
|
||||
To create a new event, simply send a POST request containing the event data to
|
||||
the desired sessions event endpoint. For example, if you want to trigger a new
|
||||
event for a session with token `6fdbd1a0-c339-11e9-b775-6d49dd567772`:
|
||||
|
||||
```
|
||||
POST /_wave/api/sessions/6fdbd1a0-c339-11e9-b775-6d49dd567772/events
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "status",
|
||||
"data": "paused"
|
||||
}
|
||||
```
|
||||
|
||||
This will cause any client, that currently has a connection open as described
|
||||
in the preceding section, to receive the specified event.
|
|
@ -0,0 +1,60 @@
|
|||
# Starting sessions on a DUT using the devices API
|
||||
|
||||
See all [REST API Guides](./README.md).
|
||||
|
||||
## Connecting the DUT
|
||||
|
||||
To start a session on a DUT using the devices API, first register the DUT at
|
||||
the test runner.
|
||||
|
||||
```
|
||||
POST /api/devices
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "fa3fb226-98ef-11ea-a21d-0021ccd76152"
|
||||
}
|
||||
```
|
||||
|
||||
Using the device token, you can listen for any events related to the device.
|
||||
|
||||
```
|
||||
GET /api/devices/fa3fb226-98ef-11ea-a21d-0021ccd76152/events
|
||||
```
|
||||
|
||||
Once an event occurs, the response to this call will contain the event data.
|
||||
If no event occurs until the request times out, you have to perfom another call.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "start_session",
|
||||
"data": {
|
||||
"session_token": "98ed4b8e-98ed-11ea-9de7-0021ccd76152"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Using this data you can start the session and get the URL to the next test to
|
||||
open.
|
||||
|
||||
## Triggering the session start
|
||||
|
||||
Once a device is registered and waits for events, you can use the device's
|
||||
event channel to push an event to start a session on it.
|
||||
|
||||
```
|
||||
POST /api/devices/fa3fb226-98ef-11ea-a21d-0021ccd76152/events
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "start_session",
|
||||
"data": {
|
||||
"session_token": "98ed4b8e-98ed-11ea-9de7-0021ccd76152"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The session related to the provided token can be a newly created one or may
|
||||
already be running.
|
|
@ -7,7 +7,7 @@ the [`create`](./create.md) method of the results API.
|
|||
## 1. `download`
|
||||
|
||||
Downloads all results of a session as ZIP, which other instances of the WMAS
|
||||
Test Suite can import.
|
||||
Test Runner can import.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
# Import results - [Results API](../README.md#results-api)
|
||||
|
||||
If enabled, the WMAS Test Suite can import results exported by any arbitrary other instance.
|
||||
If enabled, the WMAS Test Runner can import results exported by any arbitrary other instance.
|
||||
|
||||
## 1. `import`
|
||||
## 1. Import session
|
||||
|
||||
Import a session's results from a ZIP file.
|
||||
Upload results and create a new, finished session
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`POST /api/results/import`
|
||||
```
|
||||
POST /api/results/import
|
||||
```
|
||||
|
||||
### HTTP Response
|
||||
#### HTTP Response
|
||||
|
||||
If successful, the server responds with the token of the imported session:
|
||||
|
||||
|
@ -28,18 +30,37 @@ However, if an error occured, the server responds the error message:
|
|||
}
|
||||
```
|
||||
|
||||
## 2. `import enabled`
|
||||
## 2. Import API results
|
||||
|
||||
To check whether or not the import features is enabled, the `import enabled` method returns the state as JSON.
|
||||
Upload a results json file and overwrite results of a specific API.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/results/import`
|
||||
```
|
||||
POST /api/results/<session_token>/<api_name>/json
|
||||
```
|
||||
|
||||
### Response
|
||||
### File structure
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": "Boolean"
|
||||
"results": [
|
||||
{
|
||||
"test": "String",
|
||||
"status": "Enum['OK', 'ERROR', 'TIMEOUT', 'NOT_RUN']",
|
||||
"message": "String",
|
||||
"subtests": [
|
||||
{
|
||||
"name": "String",
|
||||
"status": "Enum['PASS', 'FAIL', 'TIMEOUT', 'NOT_RUN']",
|
||||
"message": "String"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Response
|
||||
|
||||
If successful, the server responds with status code 200.
|
||||
|
|
|
@ -21,12 +21,13 @@ The `create` method of the sessions API creates a new session. If provided with
|
|||
"<test_path>": "Integer"
|
||||
},
|
||||
"reference_tokens": "Array<String>",
|
||||
"labels": "Array<String>"
|
||||
"labels": "Array<String>",
|
||||
"type": "String"
|
||||
}
|
||||
```
|
||||
|
||||
- **tests** specifies the tests of the session:
|
||||
- **include** specifies what tests should be selected from all available tests. Can be a path to a test file or directory.
|
||||
- **include** specifies what tests should be selected from all available tests. Can be a path to a test file or directory. Provided query parameters will be added to all matching test urls.
|
||||
- **exclude** specifies what tests should be removed from the included tests. Can be a path to a test file or directory.
|
||||
- **types** what types of tests should be included. Possible values:
|
||||
- **automatic** tests are tests that execute without user interaction.
|
||||
|
@ -37,6 +38,7 @@ The `create` method of the sessions API creates a new session. If provided with
|
|||
- **custom test paths**: Set the timeout for a test file or directory by putting the path with all dots removed as the key.
|
||||
- **reference_tokens** specifies a set of completed sessions that is used to filter out all tests that have not passed in all those sessions from the session that is going to be created.
|
||||
- **labels** specifies the initial set of labels for the session.
|
||||
- **type** specifies the session type to trigger type specific behaviour like different control pages.
|
||||
|
||||
### Default
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Session Event Types - [Sessions API](../README.md#sessions-api)
|
||||
|
||||
Session events are events that are triggered by actions related to sessions.
|
||||
The [`event`](./events.md) functions of the sessions API make use of these events.
|
||||
|
||||
## Status change
|
||||
|
||||
**Type identifier**: `status`
|
||||
**Payload**: `"<String>"`
|
||||
Possible Values: `paused`, `running`, `completed`, `aborted`
|
||||
**Description**: Triggered once the status of the session changes.
|
||||
|
||||
## Resume
|
||||
|
||||
**Type identifier**: `resume`
|
||||
**Payload**: `"<String>"`
|
||||
Contains the token of the session to resume.
|
||||
**Description**: Triggered when a specific session is supposed to be resumed.
|
||||
This will discard the current session and continue executing the session with
|
||||
the provided token.
|
||||
|
||||
## Test Completed
|
||||
|
||||
**Type identifier**: `test_completed`
|
||||
**Payload**: `"<String>"`
|
||||
Contains the test case that completed.
|
||||
**Description**: Triggered when the test runner received a result for a test.
|
|
@ -1,12 +1,84 @@
|
|||
# `events` - [Sessions API](../README.md#sessions-api)
|
||||
|
||||
Session events can be used to send messages related to a specific session for
|
||||
others to receive. This can include status updates or action that running
|
||||
session react on.
|
||||
|
||||
For possible events see [Session Event Types](./event-types.md)
|
||||
|
||||
## 1. `listen events`
|
||||
|
||||
Listen for session specific events by registering on the `events` endpoint using HTTP long polling.
|
||||
|
||||
## HTTP Request
|
||||
### HTTP Request
|
||||
|
||||
`GET /api/sessions/<token>/events`
|
||||
```
|
||||
GET /api/sessions/<token>/events
|
||||
```
|
||||
|
||||
## Response Payload
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Desciption | Default | Example |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -------------- |
|
||||
| `last_event` | The number of the last received event. All events that are newer than `last_event` are returned immediately. If there are no newer events, connection stays open until a new event is triggered. | None | `last_event=5` |
|
||||
|
||||
#### Response Payload
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "String",
|
||||
"data": "String",
|
||||
"number": "Number"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
- **type**: the type of event that occurred.
|
||||
- **data**: the actual payload of the event
|
||||
- **number**: the number of the event
|
||||
|
||||
#### Example
|
||||
|
||||
```
|
||||
GET /api/sessions/6fdbd1a0-c339-11e9-b775-6d49dd567772/events?last_event=8
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "status",
|
||||
"data": "paused",
|
||||
"number": 9
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"data": "running",
|
||||
"number": 10
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"data": "paused",
|
||||
"number": 11
|
||||
},
|
||||
{
|
||||
"type": "status",
|
||||
"data": "running",
|
||||
"number": 12
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 2. `push events`
|
||||
|
||||
Push session specific events for any registered listeners to receive.
|
||||
|
||||
### HTTP Request
|
||||
|
||||
```
|
||||
POST /api/sessions/<token>/events
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -18,13 +90,11 @@ Listen for session specific events by registering on the `events` endpoint using
|
|||
- **type**: the type of event that occurred.
|
||||
- **data**: the actual payload of the event
|
||||
|
||||
## Example
|
||||
#### Example
|
||||
|
||||
**Request**
|
||||
|
||||
`GET /api/sessions/6fdbd1a0-c339-11e9-b775-6d49dd567772/events`
|
||||
|
||||
**Response**
|
||||
```
|
||||
POST /api/sessions/6fdbd1a0-c339-11e9-b775-6d49dd567772/events
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# `read` - [Sessions API](../README.md#sessions-api)
|
||||
# `read session` - [Sessions API](../README.md#sessions-api)
|
||||
|
||||
The `read` method of the sessions API fetches the configuration of a session, including values that can not be set by the user, but are created by the server upon creation.
|
||||
|
||||
|
@ -27,7 +27,9 @@ The `read` method of the sessions API fetches the configuration of a session, in
|
|||
"name": "String",
|
||||
"version": "String"
|
||||
},
|
||||
"is_public": "Boolean"
|
||||
"is_public": "Boolean",
|
||||
"date_created": "String",
|
||||
"labels": "Array<String>"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -48,6 +50,8 @@ The `read` method of the sessions API fetches the configuration of a session, in
|
|||
- **name**: The name of the browser.
|
||||
- **version**: The version numbers of the browser.
|
||||
- **is_public** defines whether or not the session is listed when fetching the list of public session using [`read public`](./read-public.md).
|
||||
- **date_created**: The date the session was created in ISO 8601 format.
|
||||
- **labels**: A list of the sessions labels.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -79,6 +83,8 @@ The `read` method of the sessions API fetches the configuration of a session, in
|
|||
"name": "Chromium",
|
||||
"version": "76"
|
||||
},
|
||||
"is_public": "false"
|
||||
"is_public": "false",
|
||||
"date_created": "2020-05-25T11:37:07",
|
||||
"labels": ["labelA", "labelB"]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
# `read sessions` - [Sessions API](../README.md#sessions-api)
|
||||
|
||||
The `read sessions` method of the sessions API fetches a list of all available
|
||||
session's token with the option expand the returned data by all retrieved
|
||||
tokens corresponding session configurations or statuses.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
```
|
||||
GET /api/sessions
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --------- | ---------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `index` | At what index of all session to start the returned list. | `0` |
|
||||
| `count` | How many entries to return starting from `index`. | `10` |
|
||||
| `expand` | Comma separated list of relations from `_links`. Includes additional data in the `_embedded` object. | none |
|
||||
|
||||
### Response Payload
|
||||
|
||||
```
|
||||
200 OK
|
||||
Content-Type: application/json+hal
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"_links": {
|
||||
"<relation>": {
|
||||
"href": "String"
|
||||
}
|
||||
...
|
||||
},
|
||||
"_embedded": {
|
||||
"<relation>": "Array<Any>"
|
||||
...
|
||||
},
|
||||
"items": "Array<String>"
|
||||
}
|
||||
```
|
||||
|
||||
- **\_links** contains URLs to related data. For more, see [HAL Specfication](https://tools.ietf.org/html/draft-kelly-json-hal).
|
||||
- **\_embedded** additional content specified by `expand` query paramater. For more, see [HAL Specfication](https://tools.ietf.org/html/draft-kelly-json-hal).
|
||||
- **items** contains the returned list of session tokens.
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
GET /api/sessions?index=0&count=3&expand=status
|
||||
```
|
||||
|
||||
```
|
||||
200 OK
|
||||
Content-Type: application/json+hal
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"_links": {
|
||||
"first": {
|
||||
"href": "/_wave/api/sessions?index=0&count=3"
|
||||
},
|
||||
"last": {
|
||||
"href": "/_wave/api/sessions?index=39&count=3"
|
||||
},
|
||||
"self": {
|
||||
"href": "/_wave/api/sessions?index=0&count=3"
|
||||
},
|
||||
"next": {
|
||||
"href": "/_wave/api/sessions?index=3&count=3"
|
||||
},
|
||||
"configuration": {
|
||||
"href": "/_wave/api/sessions/{token}",
|
||||
"templated": true
|
||||
},
|
||||
"status": {
|
||||
"href": "/_wave/api/sessions/{token}/status",
|
||||
"templated": true
|
||||
}
|
||||
},
|
||||
"items": [
|
||||
"13f80c84-9046-11ea-9c80-0021ccd76152",
|
||||
"34db08e4-903b-11ea-89ce-0021ccd76152",
|
||||
"a355f846-9465-11ea-ae9e-0021ccd76152"
|
||||
],
|
||||
"_embedded": {
|
||||
"status": [
|
||||
{
|
||||
"status": "completed",
|
||||
"expiration_date": null,
|
||||
"labels": [],
|
||||
"date_finished": 1588844145897.157,
|
||||
"token": "13f80c84-9046-11ea-9c80-0021ccd76152",
|
||||
"date_created": null,
|
||||
"date_started": 1588844127000,
|
||||
"type": "wmas"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"expiration_date": null,
|
||||
"labels": [],
|
||||
"date_finished": 1588839822768.9568,
|
||||
"token": "34db08e4-903b-11ea-89ce-0021ccd76152",
|
||||
"date_created": null,
|
||||
"date_started": 1588839522000,
|
||||
"type": "wmas"
|
||||
},
|
||||
{
|
||||
"status": "completed",
|
||||
"expiration_date": null,
|
||||
"labels": [],
|
||||
"date_finished": 1589297485065.1802,
|
||||
"token": "a355f846-9465-11ea-ae9e-0021ccd76152",
|
||||
"date_created": null,
|
||||
"date_started": 1589297484000,
|
||||
"type": "wmas"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
|
@ -25,9 +25,9 @@ The `status` method of the results API returns information about a sessions curr
|
|||
- **paused**: The execution of tests in this session is currently paused.
|
||||
- **completed**: All tests files include in this session were executed and have a result.
|
||||
- **aborted**: The session was finished before all tests were executed.
|
||||
- **date_started** contains the time the status changed from `PENDING` to `RUNNING` in unix epoch time milliseconds.
|
||||
- **date_finished** contains the time the status changed to either `COMPLETED` or `ABORTED` in unix epoch time milliseconds.
|
||||
- **expiration_date** contains the time at which the sessions will be deleted
|
||||
- **date_started** contains the time the status changed from `PENDING` to `RUNNING` in ISO 8601.
|
||||
- **date_finished** contains the time the status changed to either `COMPLETED` or `ABORTED` in ISO 8601.
|
||||
- **expiration_date** contains the time at which the sessions will be deleted in ISO 8601.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -41,8 +41,8 @@ The `status` method of the results API returns information about a sessions curr
|
|||
{
|
||||
"token": "d9caaae0-c362-11e9-943f-eedb305f22f6",
|
||||
"status": "running",
|
||||
"date_started": "1567606879230",
|
||||
"date_started": "2019-09-04T14:21:19",
|
||||
"date_finished": null,
|
||||
"expiration_date": "1567607179230"
|
||||
"expiration_date": "2019-09-04T14:26:19"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
# Usage Guide - [WAVE Test Suite](../README.md)
|
||||
# Usage Guide - [WAVE Test Runner](../README.md)
|
||||
|
||||
With WAVE Test Runner v1.0.0 all files and REST API endpoints are served under
|
||||
a configurable namespace, by default `/_wave/`, which will be used in this
|
||||
usage guide.
|
||||
|
||||
In this document the usage is explained using screenshots from the context of
|
||||
the WMAS project. However, practices can be applied to different contexts as well.
|
||||
|
||||
## Contents
|
||||
|
||||
|
@ -26,7 +33,7 @@ Each new session is configured using several parameters before the run starts.
|
|||
Every new session is created from the landing page.
|
||||
It is recommended to create a new session from the device that is tested, as the user agent is part of the displayed information, as well as the browser and version, which gets parsed from it.
|
||||
However, this does not influence the execution of tests or the creation of test results.
|
||||
To create a new session, open the landing page on the URI path `/`.
|
||||
To create a new session, open the landing page on the URI path `/_wave/index.html`.
|
||||
|
||||
![landing_page]
|
||||
|
||||
|
@ -56,7 +63,7 @@ Only tests that have passed the reference test session in all selected browsers
|
|||
The reference browsers represent the status of implementation of all WAVE APIs in modern desktop browsers, at about the time the WAVE specification was published.
|
||||
To start the session press "Start Session", note that the landing page has to stay opened, as the test are going to be execute in the same window.
|
||||
|
||||
[To the top](#usage-guide---wave-test-suite)
|
||||
[To the top](#usage-guide---wave-test-runner)
|
||||
|
||||
## 1.3 Exclude tests
|
||||
|
||||
|
@ -96,11 +103,11 @@ Enter the first eight characters or more into the text field labelled "Session T
|
|||
Click "Add" to confirm.
|
||||
The tests should now appear in the list below.
|
||||
|
||||
[To the top](#usage-guide---wave-test-suite)
|
||||
[To the top](#usage-guide---wave-test-runner)
|
||||
|
||||
# 2. Resuming test sessions
|
||||
|
||||
Certain test cases may cause some devices to crash, which makes the test suite unable to automatically run the next test.
|
||||
Certain test cases may cause some devices to crash, which makes the test runner unable to automatically run the next test.
|
||||
In this case, external interaction is necessary.
|
||||
To alleviate the process of resuming the test session, the are two mechanisms integrated into the web interface that reduce interaction with the device to a minimum.
|
||||
There is also a mechanism that can be useful if a test framework with access to the tested browser is utilized.
|
||||
|
@ -132,9 +139,9 @@ To load the next test of a specific session, simply open the following URL:
|
|||
|
||||
For example:
|
||||
|
||||
`/next.html?token=24fcd360-ef4d-11e9-a95f-d6e1ad4c5fdb`
|
||||
`/_wave/next.html?token=24fcd360-ef4d-11e9-a95f-d6e1ad4c5fdb`
|
||||
|
||||
[To the top](#usage-guide---wave-test-suite)
|
||||
[To the top](#usage-guide---wave-test-runner)
|
||||
|
||||
# 3. Monitoring test sessions
|
||||
|
||||
|
@ -167,18 +174,18 @@ Once all test files of an API have received a result, it is possible to download
|
|||
|
||||
Below the table of API results, there are more options to download the results of the session.
|
||||
The first option downloads the results the same way it is persisted on the serverside, along with some meta data.
|
||||
This form is especially useful if you want to import the session details with the results into other instances of the WAVE Test Suite.
|
||||
This form is especially useful if you want to import the session details with the results into other instances of the WAVE Test Runner.
|
||||
Furthermore, there is the option to download the raw result in JSON format of all finished APIs.
|
||||
This the same JSON you get by clicking on the "JSON" button in the API results column, but of all finished APIs in a ZIP file.
|
||||
Lastly, you can download a static HTML page, similiar to the results view.
|
||||
Finally, at the bottom of the page you can find the list of malfunctioning tests that have been added from the list of last timed-out test files.
|
||||
Remove tests by clicking their corresponding button with the trashcan icon.
|
||||
|
||||
[To the top](#usage-guide---wave-test-suite)
|
||||
[To the top](#usage-guide---wave-test-runner)
|
||||
|
||||
# 4. Managing test sessions
|
||||
|
||||
The overview page provides features that help to manage and organize multiple sessions. You can access it from the URL `/overview.html`.
|
||||
The overview page provides features that help to manage and organize multiple sessions. You can access it from the URL `/_wave/overview.html`.
|
||||
|
||||
![overview_page]
|
||||
|
||||
|
@ -205,7 +212,7 @@ Sort the list of sessions by clicking on the column to filter them by.
|
|||
|
||||
Add one or more tags to the filter to conveniently find the sessions you are looking for. Add labels to session when creating them or in their corresponding results page.
|
||||
|
||||
[To the top](#usage-guide---wave-test-suite)
|
||||
[To the top](#usage-guide---wave-test-runner)
|
||||
|
||||
[landing_page]: ../res/landing_page.jpg "Landing Page"
|
||||
[configuration_page]: ../res/configuration_page_top.jpg "Configuration Page"
|
||||
|
|
|
@ -2,6 +2,7 @@ import json
|
|||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
global logger
|
||||
|
@ -51,3 +52,46 @@ class ApiHandler(object):
|
|||
info = sys.exc_info()
|
||||
traceback.print_tb(info[2])
|
||||
logger.error("{}: {}: {}".format(message, info[0].__name__, info[1].args[0]))
|
||||
|
||||
def create_hal_list(self, items, uris, index, count, total):
|
||||
hal_list = {}
|
||||
links = {}
|
||||
if uris is not None:
|
||||
for relation in uris:
|
||||
if relation == "self":
|
||||
continue
|
||||
uri = uris[relation]
|
||||
templated = "{" in uri
|
||||
links[relation] = {"href": uri, "templated": templated}
|
||||
|
||||
if "self" in uris:
|
||||
self_uri = uris["self"]
|
||||
self_uri += "?index={}&count={}".format(index, count)
|
||||
links["self"] = {"href": self_uri}
|
||||
|
||||
first_uri = uris["self"]
|
||||
first_uri += "?index={}&count={}".format(0, count)
|
||||
links["first"] = {"href": first_uri}
|
||||
|
||||
last_uri = uris["self"]
|
||||
last_uri += "?index={}&count={}".format(total - (total % count), count)
|
||||
links["last"] = {"href": last_uri}
|
||||
|
||||
if index + count <= total:
|
||||
next_index = index + count
|
||||
next_uri = uris["self"]
|
||||
next_uri += "?index={}&count={}".format(next_index, count)
|
||||
links["next"] = {"href": next_uri}
|
||||
|
||||
if index != 0:
|
||||
previous_index = index - count
|
||||
if previous_index < 0:
|
||||
previous_index = 0
|
||||
previous_uri = uris["self"]
|
||||
previous_uri += "?index={}&count={}".format(previous_index, count)
|
||||
links["previous"] = {"href": previous_uri}
|
||||
|
||||
hal_list["_links"] = links
|
||||
hal_list["items"] = items
|
||||
|
||||
return hal_list
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
import json
|
||||
import threading
|
||||
|
||||
from .api_handler import ApiHandler
|
||||
from ...data.http_polling_event_listener import HttpPollingEventListener
|
||||
from ...testing.event_dispatcher import DEVICES
|
||||
from ...utils.serializer import serialize_device
|
||||
from ...testing.devices_manager import DEVICE_TIMEOUT, RECONNECT_TIME
|
||||
from ...data.exceptions.not_found_exception import NotFoundException
|
||||
|
||||
|
||||
class DevicesApiHandler(ApiHandler):
|
||||
def __init__(self, devices_manager, event_dispatcher, web_root):
|
||||
super(DevicesApiHandler, self).__init__(web_root)
|
||||
self._devices_manager = devices_manager
|
||||
self._event_dispatcher = event_dispatcher
|
||||
|
||||
def create_device(self, request, response):
|
||||
try:
|
||||
user_agent = request.headers[b"user-agent"].decode("utf-8")
|
||||
|
||||
device = self._devices_manager.create_device(user_agent)
|
||||
|
||||
self.send_json({"token": device.token}, response)
|
||||
except Exception:
|
||||
self.handle_exception("Failed to create device")
|
||||
response.status = 500
|
||||
|
||||
def read_device(self, request, response):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
|
||||
device = self._devices_manager.read_device(token)
|
||||
|
||||
device_object = serialize_device(device)
|
||||
|
||||
self.send_json(device_object, response)
|
||||
except NotFoundException:
|
||||
self.handle_exception("Failed to read device")
|
||||
response.status = 404
|
||||
except Exception:
|
||||
self.handle_exception("Failed to read device")
|
||||
response.status = 500
|
||||
|
||||
def read_devices(self, request, response):
|
||||
try:
|
||||
devices = self._devices_manager.read_devices()
|
||||
|
||||
device_objects = []
|
||||
for device in devices:
|
||||
device_object = serialize_device(device)
|
||||
device_objects.append(device_object)
|
||||
|
||||
self.send_json(device_objects, response)
|
||||
except Exception:
|
||||
self.handle_exception("Failed to read devices")
|
||||
response.status = 500
|
||||
|
||||
def register_event_listener(self, request, response):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
query = self.parse_query_parameters(request)
|
||||
|
||||
if u"device_token" in query:
|
||||
self._devices_manager.refresh_device(query["device_token"])
|
||||
|
||||
event = threading.Event()
|
||||
timer = threading.Timer(
|
||||
(DEVICE_TIMEOUT - RECONNECT_TIME) / 1000,
|
||||
event.set,
|
||||
[])
|
||||
timer.start()
|
||||
http_polling_event_listener = HttpPollingEventListener(token, event)
|
||||
event_listener_token = self._event_dispatcher.add_event_listener(http_polling_event_listener)
|
||||
|
||||
event.wait()
|
||||
|
||||
message = http_polling_event_listener.message
|
||||
if message is not None:
|
||||
self.send_json(data=message, response=response)
|
||||
self._event_dispatcher.remove_event_listener(event_listener_token)
|
||||
except Exception:
|
||||
self.handle_exception("Failed to register event listener")
|
||||
response.status = 500
|
||||
|
||||
def register_global_event_listener(self, request, response):
|
||||
try:
|
||||
query = self.parse_query_parameters(request)
|
||||
|
||||
if u"device_token" in query:
|
||||
self._devices_manager.refresh_device(query["device_token"])
|
||||
|
||||
event = threading.Event()
|
||||
timer = threading.Timer(
|
||||
(DEVICE_TIMEOUT - RECONNECT_TIME) / 1000,
|
||||
event.set,
|
||||
[])
|
||||
timer.start()
|
||||
http_polling_event_listener = HttpPollingEventListener(DEVICES, event)
|
||||
event_listener_token = self._event_dispatcher.add_event_listener(http_polling_event_listener)
|
||||
|
||||
event.wait()
|
||||
|
||||
message = http_polling_event_listener.message
|
||||
if message is not None:
|
||||
self.send_json(data=message, response=response)
|
||||
self._event_dispatcher.remove_event_listener(event_listener_token)
|
||||
except Exception:
|
||||
self.handle_exception(u"Failed to register global event listener")
|
||||
response.status = 500
|
||||
|
||||
def post_global_event(self, request, response):
|
||||
try:
|
||||
event = {}
|
||||
body = request.body.decode("utf-8")
|
||||
if body != "":
|
||||
event = json.loads(body)
|
||||
|
||||
query = self.parse_query_parameters(request)
|
||||
if u"device_token" in query:
|
||||
self._devices_manager.refresh_device(query["device_token"])
|
||||
|
||||
event_type = None
|
||||
if "type" in event:
|
||||
event_type = event["type"]
|
||||
data = None
|
||||
if "data" in event:
|
||||
data = event["data"]
|
||||
self._devices_manager.post_global_event(event_type, data)
|
||||
|
||||
except Exception:
|
||||
self.handle_exception("Failed to post global event")
|
||||
response.status = 500
|
||||
|
||||
def post_event(self, request, response):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
|
||||
event = {}
|
||||
body = request.body.decode("utf-8")
|
||||
if body != "":
|
||||
event = json.loads(body)
|
||||
|
||||
query = self.parse_query_parameters(request)
|
||||
if u"device_token" in query:
|
||||
self._devices_manager.refresh_device(query["device_token"])
|
||||
|
||||
event_type = None
|
||||
if "type" in event:
|
||||
event_type = event["type"]
|
||||
data = None
|
||||
if "data" in event:
|
||||
data = event["data"]
|
||||
self._devices_manager.post_event(token, event_type, data)
|
||||
|
||||
except Exception:
|
||||
self.handle_exception("Failed to post event")
|
||||
response.status = 500
|
||||
|
||||
def handle_request(self, request, response):
|
||||
method = request.method
|
||||
uri_parts = self.parse_uri(request)
|
||||
|
||||
# /api/devices
|
||||
if len(uri_parts) == 2:
|
||||
if method == u"POST":
|
||||
self.create_device(request, response)
|
||||
return
|
||||
if method == u"GET":
|
||||
self.read_devices(request, response)
|
||||
return
|
||||
|
||||
# /api/devices/<function>
|
||||
if len(uri_parts) == 3:
|
||||
function = uri_parts[2]
|
||||
if method == u"GET":
|
||||
if function == u"events":
|
||||
self.register_global_event_listener(request, response)
|
||||
return
|
||||
self.read_device(request, response)
|
||||
return
|
||||
if method == "POST":
|
||||
if function == "events":
|
||||
self.post_global_event(request, response)
|
||||
return
|
||||
|
||||
# /api/devices/<token>/<function>
|
||||
if len(uri_parts) == 4:
|
||||
function = uri_parts[3]
|
||||
if method == u"GET":
|
||||
if function == u"events":
|
||||
self.register_event_listener(request, response)
|
||||
return
|
||||
if method == "POST":
|
||||
if function == "events":
|
||||
self.post_event(request, response)
|
||||
return
|
|
@ -0,0 +1,77 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .api_handler import ApiHandler
|
||||
|
||||
TOKEN_LENGTH = 36
|
||||
|
||||
|
||||
class GeneralApiHandler(ApiHandler):
|
||||
def __init__(
|
||||
self,
|
||||
web_root,
|
||||
read_sessions_enabled,
|
||||
import_results_enabled,
|
||||
reports_enabled,
|
||||
version_string,
|
||||
test_type_selection_enabled,
|
||||
test_file_selection_enabled
|
||||
):
|
||||
super(GeneralApiHandler, self).__init__(web_root)
|
||||
self.read_sessions_enabled = read_sessions_enabled
|
||||
self.import_results_enabled = import_results_enabled
|
||||
self.reports_enabled = reports_enabled
|
||||
self.version_string = version_string
|
||||
self.test_type_selection_enabled = test_type_selection_enabled
|
||||
self.test_file_selection_enabled = test_file_selection_enabled
|
||||
|
||||
def read_status(self):
|
||||
try:
|
||||
return {
|
||||
"format": "application/json",
|
||||
"data": {
|
||||
"version_string": self.version_string,
|
||||
"read_sessions_enabled": self.read_sessions_enabled,
|
||||
"import_results_enabled": self.import_results_enabled,
|
||||
"reports_enabled": self.reports_enabled,
|
||||
"test_type_selection_enabled": self.test_type_selection_enabled,
|
||||
"test_file_selection_enabled": self.test_file_selection_enabled
|
||||
}
|
||||
}
|
||||
except Exception:
|
||||
self.handle_exception("Failed to read server configuration")
|
||||
return {"status": 500}
|
||||
|
||||
def handle_request(self, request, response):
|
||||
method = request.method
|
||||
uri_parts = self.parse_uri(request)
|
||||
|
||||
result = None
|
||||
# /api/<function>
|
||||
if len(uri_parts) == 2:
|
||||
function = uri_parts[1]
|
||||
if method == "GET":
|
||||
if function == "status":
|
||||
result = self.read_status()
|
||||
|
||||
if result is None:
|
||||
response.status = 404
|
||||
return
|
||||
|
||||
format = None
|
||||
if "format" in result:
|
||||
format = result["format"]
|
||||
if format == "application/json":
|
||||
data = None
|
||||
if "data" in result:
|
||||
data = result["data"]
|
||||
status = 200
|
||||
if "status" in result:
|
||||
status = result["status"]
|
||||
self.send_json(data, response, status)
|
||||
return
|
||||
|
||||
status = 404
|
||||
if "status" in result:
|
||||
status = result["status"]
|
||||
response.status = status
|
|
@ -6,9 +6,10 @@ from ...data.exceptions.invalid_data_exception import InvalidDataException
|
|||
|
||||
|
||||
class ResultsApiHandler(ApiHandler):
|
||||
def __init__(self, results_manager, web_root):
|
||||
def __init__(self, results_manager, session_manager, web_root):
|
||||
super(ResultsApiHandler, self).__init__(web_root)
|
||||
self._results_manager = results_manager
|
||||
self._sessions_manager = session_manager
|
||||
|
||||
def create_result(self, request, response):
|
||||
try:
|
||||
|
@ -31,6 +32,11 @@ class ResultsApiHandler(ApiHandler):
|
|||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
|
||||
session = self._sessions_manager.read_session(token)
|
||||
if session is None:
|
||||
response.status = 404
|
||||
return
|
||||
|
||||
results = self._results_manager.read_results(token)
|
||||
|
||||
self.send_json(response=response, data=results)
|
||||
|
@ -52,19 +58,6 @@ class ResultsApiHandler(ApiHandler):
|
|||
self.handle_exception("Failed to read compact results")
|
||||
response.status = 500
|
||||
|
||||
def read_results_config(self, request, response):
|
||||
try:
|
||||
import_enabled = self._results_manager.is_import_enabled()
|
||||
reports_enabled = self._results_manager.are_reports_enabled()
|
||||
|
||||
self.send_json({
|
||||
"import_enabled": import_enabled,
|
||||
"reports_enabled": reports_enabled
|
||||
}, response)
|
||||
except Exception:
|
||||
self.handle_exception("Failed to read results configuration")
|
||||
response.status = 500
|
||||
|
||||
def read_results_api_wpt_report_url(self, request, response):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
|
@ -112,6 +105,20 @@ class ResultsApiHandler(ApiHandler):
|
|||
self.handle_exception("Failed to download api json")
|
||||
response.status = 500
|
||||
|
||||
def import_results_api_json(self, request, response):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
api = uri_parts[3]
|
||||
blob = request.body
|
||||
|
||||
self._results_manager.import_results_api_json(token, api, blob)
|
||||
|
||||
response.status = 200
|
||||
except Exception:
|
||||
self.handle_exception("Failed to upload api json")
|
||||
response.status = 500
|
||||
|
||||
def download_results_all_api_jsons(self, request, response):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
|
@ -182,12 +189,8 @@ class ResultsApiHandler(ApiHandler):
|
|||
return
|
||||
|
||||
if method == "GET":
|
||||
if uri_parts[2] == "config":
|
||||
self.read_results_config(request, response)
|
||||
return
|
||||
else:
|
||||
self.read_results(request, response)
|
||||
return
|
||||
self.read_results(request, response)
|
||||
return
|
||||
|
||||
# /api/results/<token>/<function>
|
||||
if len(uri_parts) == 4:
|
||||
|
@ -219,5 +222,9 @@ class ResultsApiHandler(ApiHandler):
|
|||
if function == "json":
|
||||
self.download_results_api_json(request, response)
|
||||
return
|
||||
if method == "POST":
|
||||
if function == "json":
|
||||
self.import_results_api_json(request, response)
|
||||
return
|
||||
|
||||
response.status = 404
|
||||
|
|
|
@ -6,121 +6,187 @@ from .api_handler import ApiHandler
|
|||
from ...utils.serializer import serialize_session
|
||||
from ...data.exceptions.not_found_exception import NotFoundException
|
||||
from ...data.exceptions.invalid_data_exception import InvalidDataException
|
||||
from ...data.http_polling_client import HttpPollingClient
|
||||
from ...data.http_polling_event_listener import HttpPollingEventListener
|
||||
|
||||
TOKEN_LENGTH = 36
|
||||
|
||||
|
||||
class SessionsApiHandler(ApiHandler):
|
||||
def __init__(self, sessions_manager, results_manager, event_dispatcher, web_root):
|
||||
def __init__(
|
||||
self,
|
||||
sessions_manager,
|
||||
results_manager,
|
||||
event_dispatcher,
|
||||
web_root,
|
||||
read_sessions_enabled
|
||||
):
|
||||
super(SessionsApiHandler, self).__init__(web_root)
|
||||
self._sessions_manager = sessions_manager
|
||||
self._results_manager = results_manager
|
||||
self._event_dispatcher = event_dispatcher
|
||||
self._read_sessions_enabled = read_sessions_enabled
|
||||
|
||||
def create_session(self, request, response):
|
||||
def create_session(self, body, headers):
|
||||
try:
|
||||
config = {}
|
||||
body = request.body.decode("utf-8")
|
||||
body = body.decode("utf-8")
|
||||
if body != "":
|
||||
config = json.loads(body)
|
||||
tests = {}
|
||||
if "tests" in config:
|
||||
tests = config["tests"]
|
||||
types = None
|
||||
test_types = None
|
||||
if "types" in config:
|
||||
types = config["types"]
|
||||
test_types = config["types"]
|
||||
timeouts = {}
|
||||
if "timeouts" in config:
|
||||
timeouts = config["timeouts"]
|
||||
reference_tokens = []
|
||||
if "reference_tokens" in config:
|
||||
reference_tokens = config["reference_tokens"]
|
||||
webhook_urls = []
|
||||
if "webhook_urls" in config:
|
||||
webhook_urls = config["webhook_urls"]
|
||||
user_agent = request.headers[b"user-agent"].decode("utf-8")
|
||||
user_agent = headers[b"user-agent"].decode("utf-8")
|
||||
labels = []
|
||||
if "labels" in config:
|
||||
labels = config["labels"]
|
||||
expiration_date = None
|
||||
if "expiration_date" in config:
|
||||
expiration_date = config["expiration_date"]
|
||||
type = None
|
||||
if "type" in config:
|
||||
type = config["type"]
|
||||
|
||||
session = self._sessions_manager.create_session(
|
||||
tests,
|
||||
types,
|
||||
test_types,
|
||||
timeouts,
|
||||
reference_tokens,
|
||||
webhook_urls,
|
||||
user_agent,
|
||||
labels,
|
||||
expiration_date
|
||||
expiration_date,
|
||||
type
|
||||
)
|
||||
|
||||
self.send_json({"token": session.token}, response)
|
||||
return {
|
||||
"format": "application/json",
|
||||
"data": {"token": session.token}
|
||||
}
|
||||
|
||||
except InvalidDataException:
|
||||
self.handle_exception("Failed to create session")
|
||||
self.send_json({"error": "Invalid input data!"}, response, 400)
|
||||
return {
|
||||
"format": "application/json",
|
||||
"data": {"error": "Invalid input data!"},
|
||||
"status": 400
|
||||
}
|
||||
|
||||
except Exception:
|
||||
self.handle_exception("Failed to create session")
|
||||
response.status = 500
|
||||
return {"status": 500}
|
||||
|
||||
def read_session(self, request, response):
|
||||
def read_session(self, token):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
|
||||
session = self._sessions_manager.read_session(token)
|
||||
if session is None:
|
||||
response.status = 404
|
||||
return
|
||||
return {"status": 404}
|
||||
|
||||
data = serialize_session(session)
|
||||
|
||||
del data["pending_tests"]
|
||||
del data["running_tests"]
|
||||
del data["malfunctioning_tests"]
|
||||
del data["test_state"]
|
||||
del data["date_started"]
|
||||
del data["date_finished"]
|
||||
del data["status"]
|
||||
|
||||
self.send_json(data, response)
|
||||
return {
|
||||
"format": "application/json",
|
||||
"data": {
|
||||
"token": data["token"],
|
||||
"tests": data["tests"],
|
||||
"types": data["types"],
|
||||
"timeouts": data["timeouts"],
|
||||
"reference_tokens": data["reference_tokens"],
|
||||
"user_agent": data["user_agent"],
|
||||
"browser": data["browser"],
|
||||
"is_public": data["is_public"],
|
||||
"date_created": data["date_created"],
|
||||
"labels": data["labels"]
|
||||
}
|
||||
}
|
||||
except Exception:
|
||||
self.handle_exception("Failed to read session")
|
||||
response.status = 500
|
||||
return {"status": 500}
|
||||
|
||||
def read_session_status(self, request, response):
|
||||
def read_sessions(self, query_parameters, uri_path):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
index = 0
|
||||
if "index" in query_parameters:
|
||||
index = int(query_parameters["index"])
|
||||
count = 10
|
||||
if "count" in query_parameters:
|
||||
count = int(query_parameters["count"])
|
||||
expand = []
|
||||
if "expand" in query_parameters:
|
||||
expand = query_parameters["expand"].split(",")
|
||||
|
||||
session_tokens = self._sessions_manager.read_sessions(index=index, count=count)
|
||||
total_sessions = self._sessions_manager.get_total_sessions()
|
||||
|
||||
embedded = {}
|
||||
|
||||
for relation in expand:
|
||||
if relation == "configuration":
|
||||
configurations = []
|
||||
for token in session_tokens:
|
||||
result = self.read_session(token)
|
||||
if "status" in result and result["status"] != 200:
|
||||
continue
|
||||
configurations.append(result["data"])
|
||||
embedded["configuration"] = configurations
|
||||
|
||||
if relation == "status":
|
||||
statuses = []
|
||||
for token in session_tokens:
|
||||
result = self.read_session_status(token)
|
||||
if "status" in result and result["status"] != 200:
|
||||
continue
|
||||
statuses.append(result["data"])
|
||||
embedded["status"] = statuses
|
||||
|
||||
uris = {
|
||||
"self": uri_path,
|
||||
"configuration": self._web_root + "api/sessions/{token}",
|
||||
"status": self._web_root + "api/sessions/{token}/status"
|
||||
}
|
||||
|
||||
data = self.create_hal_list(session_tokens, uris, index, count, total=total_sessions)
|
||||
|
||||
if len(embedded) > 0:
|
||||
data["_embedded"] = embedded
|
||||
|
||||
return {
|
||||
"format": "application/json",
|
||||
"data": data
|
||||
}
|
||||
except Exception:
|
||||
self.handle_exception("Failed to read session")
|
||||
return {"status": 500}
|
||||
|
||||
def read_session_status(self, token):
|
||||
try:
|
||||
session = self._sessions_manager.read_session_status(token)
|
||||
if session is None:
|
||||
response.status = 404
|
||||
return
|
||||
return {"status": 404}
|
||||
|
||||
data = serialize_session(session)
|
||||
|
||||
del data["tests"]
|
||||
del data["pending_tests"]
|
||||
del data["running_tests"]
|
||||
del data["malfunctioning_tests"]
|
||||
del data["types"]
|
||||
del data["test_state"]
|
||||
del data["last_completed_test"]
|
||||
del data["user_agent"]
|
||||
del data["timeouts"]
|
||||
del data["browser"]
|
||||
del data["is_public"]
|
||||
del data["reference_tokens"]
|
||||
del data["webhook_urls"]
|
||||
|
||||
self.send_json(data, response)
|
||||
return {
|
||||
"format": "application/json",
|
||||
"data": {
|
||||
"token": data["token"],
|
||||
"status": data["status"],
|
||||
"date_started": data["date_started"],
|
||||
"date_finished": data["date_finished"],
|
||||
"expiration_date": data["expiration_date"]
|
||||
}
|
||||
}
|
||||
except Exception:
|
||||
self.handle_exception("Failed to read session status")
|
||||
response.status = 500
|
||||
return {"status": 500}
|
||||
|
||||
def read_public_sessions(self, request, response):
|
||||
try:
|
||||
|
@ -144,26 +210,26 @@ class SessionsApiHandler(ApiHandler):
|
|||
tests = {}
|
||||
if "tests" in config:
|
||||
tests = config["tests"]
|
||||
types = None
|
||||
test_types = None
|
||||
if "types" in config:
|
||||
types = config["types"]
|
||||
test_types = config["types"]
|
||||
timeouts = {}
|
||||
if "timeouts" in config:
|
||||
timeouts = config["timeouts"]
|
||||
reference_tokens = []
|
||||
if "reference_tokens" in config:
|
||||
reference_tokens = config["reference_tokens"]
|
||||
webhook_urls = []
|
||||
if "webhook_urls" in config:
|
||||
webhook_urls = config["webhook_urls"]
|
||||
type = None
|
||||
if "type" in config:
|
||||
type = config["type"]
|
||||
|
||||
self._sessions_manager.update_session_configuration(
|
||||
token,
|
||||
tests,
|
||||
types,
|
||||
test_types,
|
||||
timeouts,
|
||||
reference_tokens,
|
||||
webhook_urls
|
||||
type
|
||||
)
|
||||
except NotFoundException:
|
||||
self.handle_exception("Failed to update session configuration")
|
||||
|
@ -268,27 +334,56 @@ class SessionsApiHandler(ApiHandler):
|
|||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
|
||||
query_parameters = self.parse_query_parameters(request)
|
||||
last_event_number = None
|
||||
if ("last_event" in query_parameters):
|
||||
last_event_number = int(query_parameters["last_event"])
|
||||
|
||||
event = threading.Event()
|
||||
http_polling_client = HttpPollingClient(token, event)
|
||||
self._event_dispatcher.add_session_client(http_polling_client)
|
||||
http_polling_event_listener = HttpPollingEventListener(token, event)
|
||||
event_listener_token = self._event_dispatcher.add_event_listener(http_polling_event_listener, last_event_number)
|
||||
|
||||
event.wait()
|
||||
|
||||
message = http_polling_client.message
|
||||
message = http_polling_event_listener.message
|
||||
self.send_json(data=message, response=response)
|
||||
self._event_dispatcher.remove_event_listener(event_listener_token)
|
||||
except Exception:
|
||||
self.handle_exception("Failed to register event listener")
|
||||
response.status = 500
|
||||
|
||||
def push_event(self, request, response):
|
||||
try:
|
||||
uri_parts = self.parse_uri(request)
|
||||
token = uri_parts[2]
|
||||
message = None
|
||||
body = request.body.decode(u"utf-8")
|
||||
if body != u"":
|
||||
message = json.loads(body)
|
||||
|
||||
self._event_dispatcher.dispatch_event(
|
||||
token,
|
||||
message["type"],
|
||||
message["data"])
|
||||
except Exception:
|
||||
self.handle_exception("Failed to push session event")
|
||||
|
||||
def handle_request(self, request, response):
|
||||
method = request.method
|
||||
uri_parts = self.parse_uri(request)
|
||||
body = request.body
|
||||
headers = request.headers
|
||||
query_parameters = self.parse_query_parameters(request)
|
||||
uri_path = request.url_parts.path
|
||||
|
||||
result = None
|
||||
# /api/sessions
|
||||
if len(uri_parts) == 2:
|
||||
if method == "POST":
|
||||
self.create_session(request, response)
|
||||
return
|
||||
result = self.create_session(body, headers)
|
||||
if method == "GET":
|
||||
if self._read_sessions_enabled:
|
||||
result = self.read_sessions(query_parameters, uri_path)
|
||||
|
||||
# /api/sessions/<token>
|
||||
if len(uri_parts) == 3:
|
||||
|
@ -300,8 +395,7 @@ class SessionsApiHandler(ApiHandler):
|
|||
if len(function) != TOKEN_LENGTH:
|
||||
self.find_session(request, response)
|
||||
return
|
||||
self.read_session(request, response)
|
||||
return
|
||||
result = self.read_session(token=uri_parts[2])
|
||||
if method == "PUT":
|
||||
self.update_session_configuration(request, response)
|
||||
return
|
||||
|
@ -314,8 +408,7 @@ class SessionsApiHandler(ApiHandler):
|
|||
function = uri_parts[3]
|
||||
if method == "GET":
|
||||
if function == "status":
|
||||
self.read_session_status(request, response)
|
||||
return
|
||||
result = self.read_session_status(token=uri_parts[2])
|
||||
if function == "events":
|
||||
self.register_event_listener(request, response)
|
||||
return
|
||||
|
@ -332,9 +425,32 @@ class SessionsApiHandler(ApiHandler):
|
|||
if function == "resume":
|
||||
self.resume_session(request, response)
|
||||
return
|
||||
if function == "events":
|
||||
self.push_event(request, response)
|
||||
return
|
||||
if method == "PUT":
|
||||
if function == "labels":
|
||||
self.update_labels(request, response)
|
||||
return
|
||||
|
||||
response.status = 404
|
||||
if result is None:
|
||||
response.status = 404
|
||||
return
|
||||
|
||||
format = None
|
||||
if "format" in result:
|
||||
format = result["format"]
|
||||
if format == "application/json":
|
||||
data = None
|
||||
if "data" in result:
|
||||
data = result["data"]
|
||||
status = 200
|
||||
if "status" in result:
|
||||
status = result["status"]
|
||||
self.send_json(data, response, status)
|
||||
return
|
||||
|
||||
status = 404
|
||||
if "status" in result:
|
||||
status = result["status"]
|
||||
response.status = status
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
|
||||
from urllib.parse import urlunsplit
|
||||
|
||||
from .api_handler import ApiHandler
|
||||
|
@ -8,6 +9,10 @@ from ...data.session import PAUSED, COMPLETED, ABORTED, PENDING, RUNNING
|
|||
DEFAULT_LAST_COMPLETED_TESTS_COUNT = 5
|
||||
DEFAULT_LAST_COMPLETED_TESTS_STATUS = ["ALL"]
|
||||
|
||||
EXECUTION_MODE_AUTO = "auto"
|
||||
EXECUTION_MODE_MANUAL = "manual"
|
||||
EXECUTION_MODE_PROGRAMMATIC = "programmatic"
|
||||
|
||||
|
||||
class TestsApiHandler(ApiHandler):
|
||||
def __init__(
|
||||
|
@ -103,6 +108,8 @@ class TestsApiHandler(ApiHandler):
|
|||
|
||||
test_timeout = self._tests_manager.get_test_timeout(
|
||||
test=test, session=session)
|
||||
|
||||
test = self._sessions_manager.get_test_path_with_query(test, session)
|
||||
url = self._generate_test_url(
|
||||
test=test,
|
||||
token=token,
|
||||
|
@ -250,11 +257,18 @@ class TestsApiHandler(ApiHandler):
|
|||
protocol = "https"
|
||||
port = self._wpt_ssl_port
|
||||
|
||||
query = "token={}&timeout={}&https_port={}&web_root={}".format(
|
||||
test_query = ""
|
||||
split = test.split("?")
|
||||
if len(split) > 1:
|
||||
test = split[0]
|
||||
test_query = split[1]
|
||||
|
||||
query = "token={}&timeout={}&https_port={}&web_root={}&{}".format(
|
||||
token,
|
||||
test_timeout,
|
||||
self._wpt_ssl_port,
|
||||
self._web_root
|
||||
self._web_root,
|
||||
test_query
|
||||
)
|
||||
|
||||
return self._generate_url(
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
import http.client as httplib
|
||||
import sys
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
|
||||
global logger
|
||||
logger = logging.getLogger("wave-api-handler")
|
||||
|
||||
class HttpHandler(object):
|
||||
def __init__(
|
||||
self,
|
||||
static_handler=None,
|
||||
sessions_api_handler=None,
|
||||
tests_api_handler=None,
|
||||
results_api_handler=None,
|
||||
http_port=None,
|
||||
web_root=None
|
||||
static_handler,
|
||||
sessions_api_handler,
|
||||
tests_api_handler,
|
||||
results_api_handler,
|
||||
devices_api_handler,
|
||||
general_api_handler,
|
||||
http_port,
|
||||
web_root
|
||||
):
|
||||
self.static_handler = static_handler
|
||||
self.sessions_api_handler = sessions_api_handler
|
||||
self.tests_api_handler = tests_api_handler
|
||||
self.results_api_handler = results_api_handler
|
||||
self.general_api_handler = general_api_handler
|
||||
self.devices_api_handler = devices_api_handler
|
||||
self._http_port = http_port
|
||||
self._web_root = web_root
|
||||
|
||||
|
@ -50,6 +58,7 @@ class HttpHandler(object):
|
|||
|
||||
def handle_api(self, request, response):
|
||||
path = self._remove_web_root(request.request_path)
|
||||
path = path.split("?")[0]
|
||||
api_name = path.split("/")[1]
|
||||
|
||||
if api_name is None:
|
||||
|
@ -64,6 +73,11 @@ class HttpHandler(object):
|
|||
if api_name == "results":
|
||||
self.results_api_handler.handle_request(request, response)
|
||||
return
|
||||
if api_name == "devices":
|
||||
self.devices_api_handler.handle_request(request, response)
|
||||
return
|
||||
|
||||
self.general_api_handler.handle_request(request, response)
|
||||
|
||||
def handle_static_file(self, request, response):
|
||||
self.static_handler.handle_request(request, response)
|
||||
|
@ -76,16 +90,19 @@ class HttpHandler(object):
|
|||
|
||||
def _proxy(self, request, response):
|
||||
host = 'localhost'
|
||||
port = str(self._http_port)
|
||||
port = int(self._http_port)
|
||||
uri = request.url_parts.path
|
||||
uri = uri + "?" + request.url_parts.query
|
||||
data = request.raw_input.read(int(request.headers.get('Content-Length', -1)))
|
||||
content_length = request.headers.get('Content-Length')
|
||||
data = ""
|
||||
if content_length is not None:
|
||||
data = request.raw_input.read(int(content_length))
|
||||
method = request.method
|
||||
# HTTPConnection expects values to be a comma separated string instead
|
||||
# of a list of values.
|
||||
|
||||
headers = {}
|
||||
for k, v in request.headers.items():
|
||||
headers[k] = b",".join(list(v))
|
||||
for key in request.headers:
|
||||
value = request.headers[key]
|
||||
headers[key.decode("utf-8")] = value.decode("utf-8")
|
||||
|
||||
try:
|
||||
proxy_connection = httplib.HTTPConnection(host, port)
|
||||
|
@ -96,8 +113,8 @@ class HttpHandler(object):
|
|||
response.status = proxy_response.status
|
||||
|
||||
except IOError:
|
||||
message = "Failed to perform proxy request"
|
||||
info = sys.exc_info()
|
||||
traceback.print_tb(info[2])
|
||||
print("Failed to perform proxy request: " +
|
||||
info[0].__name__ + ": " + str(info[1].args[0]))
|
||||
logger.error("{}: {}: {}".format(message, info[0].__name__, info[1].args[0]))
|
||||
response.status = 500
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from io import open
|
||||
|
||||
|
||||
class StaticHandler(object):
|
||||
|
@ -13,14 +14,21 @@ class StaticHandler(object):
|
|||
file_path = request.request_path
|
||||
|
||||
if self._web_root is not None:
|
||||
if not file_path.startswith(self._web_root):
|
||||
response.status = 404
|
||||
return
|
||||
file_path = file_path[len(self._web_root):]
|
||||
|
||||
if file_path == "." or file_path == "./" or file_path == "":
|
||||
if file_path == "":
|
||||
file_path = "index.html"
|
||||
|
||||
file_path = file_path.split("?")[0]
|
||||
file_path = os.path.join(self.static_dir, file_path)
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
response.status = 404
|
||||
return
|
||||
|
||||
headers = []
|
||||
|
||||
content_types = {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
|
||||
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fs-extra": "^7.0.1"
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
ua-parser==0.10.0
|
||||
ua-parser==0.8.0
|
||||
python-dateutil==2.8.1
|
||||
|
|
|
@ -15,272 +15,270 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* If the query parameter token is available means that the test was loaded by
|
||||
* If the query parameter token is available means that the test was loaded by
|
||||
* the WAVE test runner and the results need to be reported to the server using
|
||||
* the provided token to identify the session associated this token.
|
||||
* the provided token to identify the session associated this token.
|
||||
*/
|
||||
console.log("ARDVAARD")
|
||||
if (location.search && location.search.indexOf("token=") != -1) {
|
||||
var __WAVE__HOSTNAME = location.hostname;
|
||||
var __WAVE__PORT = location.port;
|
||||
var __WAVE__PROTOCOL = location.protocol.replace(/:/, "");
|
||||
var __WAVE__QUERY = location.search;
|
||||
if (!__WAVE__QUERY) __WAVE__QUERY = "?";
|
||||
var match = __WAVE__QUERY.match(/https_port=(\d+)/);
|
||||
var __HTTPS_PORT = parseInt(match && match[1] ? match[1] : 443);
|
||||
match = __WAVE__QUERY.match(/timeout=(\d+)/);
|
||||
var __WAVE__TIMEOUT = parseInt(match && match[1] ? match[1] : 65000);
|
||||
match = __WAVE__QUERY.match(/web_root=(.+)/);
|
||||
var __WAVE__WEB_ROOT = match && match[1] ? match[1] : "/wave/";
|
||||
console.log("\n\n\n\n\n")
|
||||
console.log(match)
|
||||
console.log(__WAVE__WEB_ROOT)
|
||||
match = __WAVE__QUERY.match(/token=([^&]+)/);
|
||||
var __WAVE__TOKEN = match ? match[1] : null;
|
||||
var __WAVE__TEST = location.pathname;
|
||||
var nextUrl = null;
|
||||
var resultSent = false;
|
||||
var screenConsole;
|
||||
var __WAVE__HOSTNAME = location.hostname;
|
||||
var __WAVE__PORT = location.port;
|
||||
var __WAVE__PROTOCOL = location.protocol.replace(/:/, "");
|
||||
var __WAVE__QUERY = location.search;
|
||||
var queryParameters = {};
|
||||
var keysAndValues = location.search.replace("?", "").split("&");
|
||||
for (var i = 0; i < keysAndValues.length; i++) {
|
||||
var key = keysAndValues[i].split("=")[0];
|
||||
var value = keysAndValues[i].split("=")[1];
|
||||
queryParameters[key] = value;
|
||||
}
|
||||
var __HTTPS_PORT = parseInt(queryParameters["https_port"] || 443);
|
||||
var __WAVE__TIMEOUT = parseInt(queryParameters["timeout"] || 65000);
|
||||
var __WAVE__WEB_ROOT = queryParameters["web_root"] || "/_wave/";
|
||||
var __WAVE__TOKEN = queryParameters["token"] || null;
|
||||
var __WAVE__TEST = location.pathname;
|
||||
var nextUrl = null;
|
||||
var resultSent = false;
|
||||
var screenConsole;
|
||||
|
||||
try {
|
||||
var documentRoot = document.body ? document.body : document.documentElement;
|
||||
documentRoot.style["background-color"] = "#FFF";
|
||||
window.open = function () {
|
||||
logToConsole(
|
||||
"window.open() is overridden in testharnessreport.js and has not effect"
|
||||
);
|
||||
var dummyWin = {
|
||||
close: function () {
|
||||
logToConsole(
|
||||
"dummyWindow.close() in testharnessreport.js and has not effect"
|
||||
);
|
||||
}
|
||||
};
|
||||
return dummyWin;
|
||||
try {
|
||||
var documentRoot = document.body ? document.body : document.documentElement;
|
||||
documentRoot.style["background-color"] = "#FFF";
|
||||
window.open = function () {
|
||||
logToConsole(
|
||||
"window.open() is overridden in testharnessreport.js and has not effect"
|
||||
);
|
||||
var dummyWin = {
|
||||
close: function () {
|
||||
logToConsole(
|
||||
"dummyWindow.close() in testharnessreport.js and has not effect"
|
||||
);
|
||||
},
|
||||
};
|
||||
window.close = function () {
|
||||
logToConsole(
|
||||
"window.close() is overridden in testharnessreport.js and has not effect"
|
||||
);
|
||||
return dummyWin;
|
||||
};
|
||||
window.close = function () {
|
||||
logToConsole(
|
||||
"window.close() is overridden in testharnessreport.js and has not effect"
|
||||
);
|
||||
};
|
||||
} catch (err) {}
|
||||
|
||||
setTimeout(function () {
|
||||
loadNext();
|
||||
}, __WAVE__TIMEOUT);
|
||||
|
||||
function logToConsole() {
|
||||
var text = "";
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
text += arguments[i] + " ";
|
||||
}
|
||||
if (console && console.log) {
|
||||
console.log(text);
|
||||
}
|
||||
if (screenConsole) {
|
||||
try {
|
||||
text = text.replace(/ /gm, " ");
|
||||
text = text.replace(/\n/gm, "<br/>");
|
||||
screenConsole.innerHTML += "<br/>" + text;
|
||||
} catch (error) {
|
||||
screenConsole.innerText += "\n" + text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dump_and_report_test_results(tests, status) {
|
||||
var results_element = document.createElement("script");
|
||||
results_element.type = "text/json";
|
||||
results_element.id = "__testharness__results__";
|
||||
var test_results = tests.map(function (x) {
|
||||
return {
|
||||
name: x.name,
|
||||
status: x.status,
|
||||
message: x.message,
|
||||
stack: x.stack,
|
||||
};
|
||||
} catch (err) {}
|
||||
});
|
||||
var data = {
|
||||
test: window.location.href,
|
||||
tests: test_results,
|
||||
status: status.status,
|
||||
message: status.message,
|
||||
stack: status.stack,
|
||||
};
|
||||
results_element.textContent = JSON.stringify(data);
|
||||
|
||||
setTimeout(function () {
|
||||
loadNext();
|
||||
}, __WAVE__TIMEOUT);
|
||||
// To avoid a HierarchyRequestError with XML documents, ensure that 'results_element'
|
||||
// is inserted at a location that results in a valid document.
|
||||
var parent = document.body
|
||||
? document.body // <body> is required in XHTML documents
|
||||
: document.documentElement; // fallback for optional <body> in HTML5, SVG, etc.
|
||||
|
||||
function logToConsole() {
|
||||
var text = "";
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
text += arguments[i] + " ";
|
||||
}
|
||||
if (console && console.log) {
|
||||
console.log(text);
|
||||
}
|
||||
if (screenConsole) {
|
||||
try {
|
||||
text = text.replace(/ /gm, " ");
|
||||
text = text.replace(/\n/gm, "<br/>");
|
||||
screenConsole.innerHTML += "<br/>" + text;
|
||||
} catch (error) {
|
||||
screenConsole.innerText += "\n" + text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dump_and_report_test_results(tests, status) {
|
||||
var results_element = document.createElement("script");
|
||||
results_element.type = "text/json";
|
||||
results_element.id = "__testharness__results__";
|
||||
var test_results = tests.map(function (x) {
|
||||
return {
|
||||
name: x.name,
|
||||
status: x.status,
|
||||
message: x.message,
|
||||
stack: x.stack
|
||||
};
|
||||
});
|
||||
var data = {
|
||||
test: window.location.href,
|
||||
tests: test_results,
|
||||
status: status.status,
|
||||
message: status.message,
|
||||
stack: status.stack
|
||||
};
|
||||
results_element.textContent = JSON.stringify(data);
|
||||
|
||||
// To avoid a HierarchyRequestError with XML documents, ensure that 'results_element'
|
||||
// is inserted at a location that results in a valid document.
|
||||
var parent = document.body ?
|
||||
document.body // <body> is required in XHTML documents
|
||||
:
|
||||
document.documentElement; // fallback for optional <body> in HTML5, SVG, etc.
|
||||
|
||||
parent.appendChild(results_element);
|
||||
parent.appendChild(results_element);
|
||||
|
||||
screenConsole = document.getElementById("console");
|
||||
if (!screenConsole) {
|
||||
screenConsole = document.createElement("div");
|
||||
screenConsole.setAttribute("id", "console");
|
||||
screenConsole.setAttribute("style", "font-family: monospace; padding: 5px");
|
||||
parent.appendChild(screenConsole);
|
||||
window.onerror = logToConsole;
|
||||
}
|
||||
window.onerror = logToConsole;
|
||||
|
||||
finishWptTest(data);
|
||||
}
|
||||
finishWptTest(data);
|
||||
}
|
||||
|
||||
function finishWptTest(data) {
|
||||
logToConsole("Creating result ...");
|
||||
data.test = __WAVE__TEST;
|
||||
createResult(
|
||||
__WAVE__TOKEN,
|
||||
data,
|
||||
function () {
|
||||
logToConsole("Result created.");
|
||||
loadNext();
|
||||
},
|
||||
function () {
|
||||
logToConsole("Failed to create result.");
|
||||
logToConsole("Trying alternative method ...");
|
||||
createResultAlt(__WAVE__TOKEN, data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function loadNext() {
|
||||
logToConsole("Loading next test ...");
|
||||
if (window.gc) {
|
||||
// This is using a chromium/v8 feature, enable it by
|
||||
// --js-flags="expose-gc".
|
||||
// TODO: Replace this with TestUtils.gc() once/if that is available
|
||||
// in browsers:
|
||||
// https://jgraham.github.io/browser-test/#the-testutils-object
|
||||
logToConsole("Forcing garbage collection using window.gc()");
|
||||
window.gc();
|
||||
function finishWptTest(data) {
|
||||
logToConsole("Creating result ...");
|
||||
data.test = __WAVE__TEST;
|
||||
createResult(
|
||||
__WAVE__TOKEN,
|
||||
data,
|
||||
function () {
|
||||
logToConsole("Result created.");
|
||||
loadNext();
|
||||
},
|
||||
function () {
|
||||
logToConsole("Failed to create result.");
|
||||
logToConsole("Trying alternative method ...");
|
||||
createResultAlt(__WAVE__TOKEN, data);
|
||||
}
|
||||
readNextTest(
|
||||
__WAVE__TOKEN,
|
||||
function (url) {
|
||||
logToConsole("Redirecting to " + url);
|
||||
location.href = url;
|
||||
},
|
||||
function () {
|
||||
logToConsole("Could not load next test.");
|
||||
logToConsole("Trying alternative method ...");
|
||||
readNextAlt(__WAVE__TOKEN);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function readNextTest(token, onSuccess, onError) {
|
||||
sendRequest(
|
||||
"GET",
|
||||
"api/tests/" + token + "/next",
|
||||
null,
|
||||
null,
|
||||
function (response) {
|
||||
var jsonObject = JSON.parse(response);
|
||||
onSuccess(jsonObject.next_test);
|
||||
},
|
||||
onError
|
||||
);
|
||||
}
|
||||
|
||||
function readNextAlt(token) {
|
||||
location.href = getWaveUrl("next.html?token=" + token);
|
||||
}
|
||||
|
||||
function createResult(token, result, onSuccess, onError) {
|
||||
sendRequest(
|
||||
"POST",
|
||||
"api/results/" + token, {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
JSON.stringify(result),
|
||||
function () {
|
||||
onSuccess();
|
||||
},
|
||||
onError
|
||||
);
|
||||
}
|
||||
|
||||
function createResultAlt(token, result) {
|
||||
location.href = __WAVE__WEB_ROOT + "submitresult.html" +
|
||||
"?token=" + token +
|
||||
"&result=" + encodeURIComponent(JSON.stringify(result));
|
||||
}
|
||||
|
||||
function sendRequest(method, uri, headers, data, onSuccess, onError) {
|
||||
var url = getWaveUrl(uri);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function () {
|
||||
onSuccess(xhr.response);
|
||||
});
|
||||
xhr.addEventListener("error", function () {
|
||||
if (onError) onError();
|
||||
});
|
||||
logToConsole("Sending", method, 'request to "' + url + '"');
|
||||
xhr.open(method, url, true);
|
||||
if (headers) {
|
||||
for (var header in headers) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
function loadNext() {
|
||||
logToConsole("Loading next test ...");
|
||||
readNextTest(
|
||||
__WAVE__TOKEN,
|
||||
function (url) {
|
||||
logToConsole("Redirecting to " + url);
|
||||
location.href = url;
|
||||
},
|
||||
function () {
|
||||
logToConsole("Could not load next test.");
|
||||
logToConsole("Trying alternative method ...");
|
||||
readNextAlt(__WAVE__TOKEN);
|
||||
}
|
||||
xhr.send(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getWaveUrl(uri) {
|
||||
var url = __WAVE__WEB_ROOT + uri;
|
||||
console.log(url)
|
||||
return url;
|
||||
}
|
||||
function readNextTest(token, onSuccess, onError) {
|
||||
sendRequest(
|
||||
"GET",
|
||||
"api/tests/" + token + "/next",
|
||||
null,
|
||||
null,
|
||||
function (response) {
|
||||
var jsonObject = JSON.parse(response);
|
||||
onSuccess(jsonObject.next_test);
|
||||
},
|
||||
onError
|
||||
);
|
||||
}
|
||||
|
||||
add_completion_callback(dump_and_report_test_results);
|
||||
function readNextAlt(token) {
|
||||
location.href =
|
||||
location.protocol +
|
||||
"//" +
|
||||
location.host +
|
||||
getWaveUrl("next.html?token=" + token);
|
||||
}
|
||||
|
||||
function createResult(token, result, onSuccess, onError) {
|
||||
sendRequest(
|
||||
"POST",
|
||||
"api/results/" + token,
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
JSON.stringify(result),
|
||||
function () {
|
||||
onSuccess();
|
||||
},
|
||||
onError
|
||||
);
|
||||
}
|
||||
|
||||
function createResultAlt(token, result) {
|
||||
location.href =
|
||||
__WAVE__WEB_ROOT +
|
||||
"submitresult.html" +
|
||||
"?token=" +
|
||||
token +
|
||||
"&result=" +
|
||||
encodeURIComponent(JSON.stringify(result));
|
||||
}
|
||||
|
||||
function sendRequest(method, uri, headers, data, onSuccess, onError) {
|
||||
var url = getWaveUrl(uri);
|
||||
url = location.protocol + "//" + location.host + url;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function () {
|
||||
onSuccess(xhr.response);
|
||||
});
|
||||
xhr.addEventListener("error", function () {
|
||||
if (onError) onError();
|
||||
});
|
||||
logToConsole("Sending", method, 'request to "' + url + '"');
|
||||
xhr.open(method, url, true);
|
||||
if (headers) {
|
||||
for (var header in headers) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
}
|
||||
xhr.send(data);
|
||||
}
|
||||
|
||||
function getWaveUrl(uri) {
|
||||
var url = __WAVE__WEB_ROOT + uri;
|
||||
return url;
|
||||
}
|
||||
|
||||
add_completion_callback(dump_and_report_test_results);
|
||||
} else {
|
||||
function dump_test_results(tests, status) {
|
||||
var results_element = document.createElement("script");
|
||||
results_element.type = "text/json";
|
||||
results_element.id = "__testharness__results__";
|
||||
var test_results = tests.map(function (x) {
|
||||
return {
|
||||
name: x.name,
|
||||
status: x.status,
|
||||
message: x.message,
|
||||
stack: x.stack
|
||||
}
|
||||
});
|
||||
var data = {
|
||||
test: window.location.href,
|
||||
tests: test_results,
|
||||
status: status.status,
|
||||
message: status.message,
|
||||
stack: status.stack
|
||||
function dump_test_results(tests, status) {
|
||||
var results_element = document.createElement("script");
|
||||
results_element.type = "text/json";
|
||||
results_element.id = "__testharness__results__";
|
||||
var test_results = tests.map(function (x) {
|
||||
return {
|
||||
name: x.name,
|
||||
status: x.status,
|
||||
message: x.message,
|
||||
stack: x.stack,
|
||||
};
|
||||
results_element.textContent = JSON.stringify(data);
|
||||
});
|
||||
var data = {
|
||||
test: window.location.href,
|
||||
tests: test_results,
|
||||
status: status.status,
|
||||
message: status.message,
|
||||
stack: status.stack,
|
||||
};
|
||||
results_element.textContent = JSON.stringify(data);
|
||||
|
||||
// To avoid a HierarchyRequestError with XML documents, ensure that 'results_element'
|
||||
// is inserted at a location that results in a valid document.
|
||||
var parent = document.body
|
||||
? document.body // <body> is required in XHTML documents
|
||||
: document.documentElement; // fallback for optional <body> in HTML5, SVG, etc.
|
||||
// To avoid a HierarchyRequestError with XML documents, ensure that 'results_element'
|
||||
// is inserted at a location that results in a valid document.
|
||||
var parent = document.body
|
||||
? document.body // <body> is required in XHTML documents
|
||||
: document.documentElement; // fallback for optional <body> in HTML5, SVG, etc.
|
||||
|
||||
parent.appendChild(results_element);
|
||||
}
|
||||
parent.appendChild(results_element);
|
||||
}
|
||||
|
||||
add_completion_callback(dump_test_results);
|
||||
add_completion_callback(dump_test_results);
|
||||
|
||||
/* If the parent window has a testharness_properties object,
|
||||
* we use this to provide the test settings. This is used by the
|
||||
* default in-browser runner to configure the timeout and the
|
||||
* rendering of results
|
||||
*/
|
||||
try {
|
||||
if (window.opener && "testharness_properties" in window.opener) {
|
||||
/* If we pass the testharness_properties object as-is here without
|
||||
* JSON stringifying and reparsing it, IE fails & emits the message
|
||||
* "Could not complete the operation due to error 80700019".
|
||||
*/
|
||||
setup(JSON.parse(JSON.stringify(window.opener.testharness_properties)));
|
||||
}
|
||||
} catch (e) {}
|
||||
// vim: set expandtab shiftwidth=4 tabstop=4:
|
||||
/* If the parent window has a testharness_properties object,
|
||||
* we use this to provide the test settings. This is used by the
|
||||
* default in-browser runner to configure the timeout and the
|
||||
* rendering of results
|
||||
*/
|
||||
try {
|
||||
if (window.opener && "testharness_properties" in window.opener) {
|
||||
/* If we pass the testharness_properties object as-is here without
|
||||
* JSON stringifying and reparsing it, IE fails & emits the message
|
||||
* "Could not complete the operation due to error 80700019".
|
||||
*/
|
||||
setup(JSON.parse(JSON.stringify(window.opener.testharness_properties)));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"id": "37be8ec4-7855-4554-867e-7a5d2a4f99e6",
|
||||
"name": "WAVE Local",
|
||||
"values": [
|
||||
{
|
||||
"key": "host",
|
||||
"value": "web-platform.test",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "port",
|
||||
"value": "8000",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "protocol",
|
||||
"value": "http",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "web_root",
|
||||
"value": "_wave",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "device_timeout",
|
||||
"value": "60000",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"_postman_variable_scope": "environment",
|
||||
"_postman_exported_at": "2020-05-25T12:12:37.098Z",
|
||||
"_postman_exported_using": "Postman/7.25.0"
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,117 @@
|
|||
import time
|
||||
import uuid
|
||||
|
||||
from threading import Timer
|
||||
|
||||
from ..data.device import Device
|
||||
from ..testing.event_dispatcher import DEVICES, DEVICE_ADDED_EVENT, DEVICE_REMOVED_EVENT
|
||||
from ..utils.user_agent_parser import parse_user_agent
|
||||
from ..utils.serializer import serialize_device
|
||||
from ..data.exceptions.not_found_exception import NotFoundException
|
||||
|
||||
|
||||
DEVICE_TIMEOUT = 60000 # 60sec
|
||||
RECONNECT_TIME = 5000 # 5sec
|
||||
|
||||
class DevicesManager(object):
|
||||
def initialize(self, event_dispatcher):
|
||||
self.devices = {}
|
||||
self._event_dispatcher = event_dispatcher
|
||||
self._timer = None
|
||||
|
||||
def create_device(self, user_agent):
|
||||
browser = parse_user_agent(user_agent)
|
||||
name = "{} {}".format(browser["name"], browser["version"])
|
||||
token = str(uuid.uuid1())
|
||||
last_active = int(time.time() * 1000)
|
||||
|
||||
device = Device(token, user_agent, name, last_active)
|
||||
|
||||
self._event_dispatcher.dispatch_event(
|
||||
DEVICES,
|
||||
DEVICE_ADDED_EVENT,
|
||||
serialize_device(device))
|
||||
self.add_to_cache(device)
|
||||
|
||||
self._set_timer(DEVICE_TIMEOUT)
|
||||
|
||||
return device
|
||||
|
||||
def read_device(self, token):
|
||||
if token not in self.devices:
|
||||
raise NotFoundException("Could not find device '{}'".format(token))
|
||||
return self.devices[token]
|
||||
|
||||
def read_devices(self):
|
||||
devices = []
|
||||
for key in self.devices:
|
||||
devices.append(self.devices[key])
|
||||
|
||||
return devices
|
||||
|
||||
def update_device(self, device):
|
||||
if device.token not in self.devices:
|
||||
return
|
||||
self.devices[device.token] = device
|
||||
|
||||
def delete_device(self, token):
|
||||
if token not in self.devices:
|
||||
return
|
||||
device = self.devices[token]
|
||||
del self.devices[token]
|
||||
self._event_dispatcher.dispatch_event(
|
||||
DEVICES,
|
||||
DEVICE_REMOVED_EVENT,
|
||||
serialize_device(device))
|
||||
|
||||
def refresh_device(self, token):
|
||||
if token not in self.devices:
|
||||
return
|
||||
device = self.devices[token]
|
||||
device.last_active = int(time.time() * 1000)
|
||||
self.update_device(device)
|
||||
|
||||
def post_event(self, handle, event_type, data):
|
||||
if event_type is None:
|
||||
return
|
||||
self._event_dispatcher.dispatch_event(handle, event_type, data)
|
||||
|
||||
def post_global_event(self, event_type, data):
|
||||
self.post_event(DEVICES, event_type, data)
|
||||
|
||||
def _set_timer(self, timeout):
|
||||
if self._timer is not None:
|
||||
return
|
||||
|
||||
def handle_timeout(self):
|
||||
self._timer = None
|
||||
now = int(time.time() * 1000)
|
||||
timed_out_devices = []
|
||||
for token in self.devices:
|
||||
device = self.devices[token]
|
||||
if now - device.last_active < DEVICE_TIMEOUT:
|
||||
continue
|
||||
timed_out_devices.append(token)
|
||||
|
||||
for token in timed_out_devices:
|
||||
self.delete_device(token)
|
||||
|
||||
oldest_active_time = None
|
||||
for token in self.devices:
|
||||
device = self.devices[token]
|
||||
if oldest_active_time is None:
|
||||
oldest_active_time = device.last_active
|
||||
else:
|
||||
if oldest_active_time > device.last_active:
|
||||
oldest_active_time = device.last_active
|
||||
if oldest_active_time is not None:
|
||||
self._set_timer(now - oldest_active_time)
|
||||
|
||||
self._timer = Timer(timeout / 1000.0, handle_timeout, [self])
|
||||
self._timer.start()
|
||||
|
||||
def add_to_cache(self, device):
|
||||
if device.token in self.devices:
|
||||
return
|
||||
|
||||
self.devices[device.token] = device
|
|
@ -1,39 +1,146 @@
|
|||
import uuid
|
||||
import time
|
||||
from threading import Timer
|
||||
|
||||
|
||||
STATUS_EVENT = "status"
|
||||
RESUME_EVENT = "resume"
|
||||
TEST_COMPLETED_EVENT = "test_completed"
|
||||
|
||||
DEVICES = "devices"
|
||||
DEVICE_ADDED_EVENT = "device_added"
|
||||
DEVICE_REMOVED_EVENT = "device_removed"
|
||||
|
||||
class EventDispatcher(object):
|
||||
def __init__(self):
|
||||
self._clients = {}
|
||||
def __init__(self, event_cache_duration):
|
||||
self._listeners = {}
|
||||
self._events = {}
|
||||
self._current_events = {}
|
||||
self._cache_duration = event_cache_duration
|
||||
self._cache_timeout = None
|
||||
|
||||
def add_session_client(self, client):
|
||||
token = client.session_token
|
||||
if token not in self._clients:
|
||||
self._clients[token] = []
|
||||
self._clients[token].append(client)
|
||||
def add_event_listener(self, listener, last_event_number=None):
|
||||
token = listener.dispatcher_token
|
||||
|
||||
def remove_session_client(self, client_to_delete):
|
||||
if client_to_delete is None:
|
||||
return
|
||||
token = client_to_delete.session_token
|
||||
if token not in self._clients:
|
||||
if last_event_number is not None \
|
||||
and token in self._current_events \
|
||||
and self._current_events[token] > last_event_number:
|
||||
diff_events = self._get_diff_events(token, last_event_number)
|
||||
if len(diff_events) > 0:
|
||||
listener.send_message(diff_events)
|
||||
return
|
||||
|
||||
if token not in self._listeners:
|
||||
self._listeners[token] = []
|
||||
self._listeners[token].append(listener)
|
||||
listener.token = str(uuid.uuid1())
|
||||
return listener.token
|
||||
|
||||
def remove_event_listener(self, listener_token):
|
||||
if listener_token is None:
|
||||
return
|
||||
|
||||
for client in self._clients[token]:
|
||||
if client.session_token == client_to_delete.session_token:
|
||||
self._clients.remove(client)
|
||||
break
|
||||
if len(self._clients[token]) == 0:
|
||||
del self._clients[token]
|
||||
for dispatcher_token in self._listeners:
|
||||
for listener in self._listeners[dispatcher_token]:
|
||||
if listener.token == listener_token:
|
||||
self._listeners[dispatcher_token].remove(listener)
|
||||
if len(self._listeners[dispatcher_token]) == 0:
|
||||
del self._listeners[dispatcher_token]
|
||||
return
|
||||
|
||||
def dispatch_event(self, token, event_type, data):
|
||||
if token not in self._clients:
|
||||
def dispatch_event(self, dispatcher_token, event_type, data=None):
|
||||
if dispatcher_token not in self._current_events:
|
||||
self._current_events[dispatcher_token] = -1
|
||||
|
||||
if dispatcher_token not in self._events:
|
||||
self._events[dispatcher_token] = []
|
||||
|
||||
self._add_to_cache(dispatcher_token, event_type, data)
|
||||
self._set_cache_timer()
|
||||
|
||||
if dispatcher_token not in self._listeners:
|
||||
return
|
||||
|
||||
event = {
|
||||
"type": event_type,
|
||||
"data": data
|
||||
"data": data,
|
||||
"number": self._current_events[dispatcher_token]
|
||||
}
|
||||
|
||||
for client in self._clients[token]:
|
||||
client.send_message(event)
|
||||
for listener in self._listeners[dispatcher_token]:
|
||||
listener.send_message([event])
|
||||
|
||||
def _get_diff_events(self, dispatcher_token, last_event_number):
|
||||
token = dispatcher_token
|
||||
diff_events = []
|
||||
if token not in self._events:
|
||||
return diff_events
|
||||
for event in self._events[token]:
|
||||
if event["number"] <= last_event_number:
|
||||
continue
|
||||
diff_events.append({
|
||||
"type": event["type"],
|
||||
"data": event["data"],
|
||||
"number": event["number"]
|
||||
})
|
||||
return diff_events
|
||||
|
||||
def _set_cache_timer(self):
|
||||
if self._cache_timeout is not None:
|
||||
return
|
||||
|
||||
events = self._read_cached_events()
|
||||
if len(events) == 0:
|
||||
return
|
||||
|
||||
next_event = events[0]
|
||||
for event in events:
|
||||
if next_event["expiration_date"] > event["expiration_date"]:
|
||||
next_event = event
|
||||
|
||||
timeout = next_event["expiration_date"] / 1000.0 - time.time()
|
||||
if timeout < 0:
|
||||
timeout = 0
|
||||
|
||||
def handle_timeout(self):
|
||||
self._delete_expired_events()
|
||||
self._cache_timeout = None
|
||||
self._set_cache_timer()
|
||||
|
||||
self._cache_timeout = Timer(timeout, handle_timeout, [self])
|
||||
self._cache_timeout.start()
|
||||
|
||||
def _delete_expired_events(self):
|
||||
events = self._read_cached_events()
|
||||
now = int(time.time() * 1000)
|
||||
|
||||
for event in events:
|
||||
if event["expiration_date"] < now:
|
||||
self._remove_from_cache(event)
|
||||
|
||||
def _add_to_cache(self, dispatcher_token, event_type, data):
|
||||
self._current_events[dispatcher_token] += 1
|
||||
current_event_number = self._current_events[dispatcher_token]
|
||||
event = {
|
||||
"type": event_type,
|
||||
"data": data,
|
||||
"number": current_event_number,
|
||||
"expiration_date": int(time.time() * 1000) + self._cache_duration
|
||||
}
|
||||
self._events[dispatcher_token].append(event)
|
||||
|
||||
def _remove_from_cache(self, event):
|
||||
for dispatcher_token in self._events:
|
||||
for cached_event in self._events[dispatcher_token]:
|
||||
if cached_event is not event:
|
||||
continue
|
||||
self._events[dispatcher_token].remove(cached_event)
|
||||
if len(self._events[dispatcher_token]) == 0:
|
||||
del self._events[dispatcher_token]
|
||||
return
|
||||
|
||||
def _read_cached_events(self):
|
||||
events = []
|
||||
for dispatcher_token in self._events:
|
||||
events = events + self._events[dispatcher_token]
|
||||
return events
|
||||
|
|
|
@ -5,6 +5,7 @@ import json
|
|||
import hashlib
|
||||
import zipfile
|
||||
import time
|
||||
from threading import Timer
|
||||
|
||||
from ..utils.user_agent_parser import parse_user_agent, abbreviate_browser_name
|
||||
from ..utils.serializer import serialize_session
|
||||
|
@ -17,6 +18,9 @@ from .wpt_report import generate_report, generate_multi_report
|
|||
from ..data.session import COMPLETED
|
||||
|
||||
WAVE_SRC_DIR = "./tools/wave"
|
||||
RESULTS_FILE_REGEX = r"^\w\w\d\d\d?\.json$"
|
||||
RESULTS_FILE_PATTERN = re.compile(RESULTS_FILE_REGEX)
|
||||
SESSION_RESULTS_TIMEOUT = 60*30 # 30min
|
||||
|
||||
|
||||
class ResultsManager(object):
|
||||
|
@ -25,17 +29,18 @@ class ResultsManager(object):
|
|||
results_directory_path,
|
||||
sessions_manager,
|
||||
tests_manager,
|
||||
import_enabled,
|
||||
import_results_enabled,
|
||||
reports_enabled,
|
||||
persisting_interval
|
||||
):
|
||||
self._results_directory_path = results_directory_path
|
||||
self._sessions_manager = sessions_manager
|
||||
self._tests_manager = tests_manager
|
||||
self._import_enabled = import_enabled
|
||||
self._import_results_enabled = import_results_enabled
|
||||
self._reports_enabled = reports_enabled
|
||||
self._results = {}
|
||||
self._persisting_interval = persisting_interval
|
||||
self._timeouts = {}
|
||||
|
||||
def create_result(self, token, data):
|
||||
result = self.prepare_result(data)
|
||||
|
@ -82,10 +87,10 @@ class ResultsManager(object):
|
|||
if filter_path is not None:
|
||||
filter_api = next((p for p in filter_path.split("/")
|
||||
if p is not None), None)
|
||||
cached_results = self._read_from_cache(token)
|
||||
persisted_results = self.load_results(token)
|
||||
results = self._combine_results_by_api(cached_results,
|
||||
persisted_results)
|
||||
results = self._read_from_cache(token)
|
||||
if results == []:
|
||||
results = self.load_results(token)
|
||||
self._set_session_cache(token, results)
|
||||
|
||||
filtered_results = {}
|
||||
|
||||
|
@ -209,6 +214,7 @@ class ResultsManager(object):
|
|||
if test in failed_tests[api]:
|
||||
continue
|
||||
failed_tests[api].append(test)
|
||||
return passed_tests
|
||||
|
||||
def read_results_wpt_report_uri(self, token, api):
|
||||
api_directory = os.path.join(self._results_directory_path, token, api)
|
||||
|
@ -244,8 +250,7 @@ class ResultsManager(object):
|
|||
return
|
||||
for api in list(self._results[token].keys())[:]:
|
||||
self.save_api_results(token, api)
|
||||
self.create_info_file(session)
|
||||
self._clear_cache_api(token, api)
|
||||
self.create_info_file(session)
|
||||
session.recent_completed_count = 0
|
||||
self._sessions_manager.update_session(session)
|
||||
|
||||
|
@ -282,22 +287,28 @@ class ResultsManager(object):
|
|||
if api not in self._results[token]:
|
||||
self._results[token][api] = []
|
||||
self._results[token][api].append(result)
|
||||
self._set_timeout(token)
|
||||
|
||||
def _set_session_cache(self, token, results):
|
||||
if token is None:
|
||||
return
|
||||
self._results[token] = results
|
||||
self._set_timeout(token)
|
||||
|
||||
def _read_from_cache(self, token):
|
||||
if token is None:
|
||||
return []
|
||||
if token not in self._results:
|
||||
return []
|
||||
self._set_timeout(token)
|
||||
return self._results[token]
|
||||
|
||||
def _clear_cache_api(self, token, api):
|
||||
def _clear_session_cache(self, token):
|
||||
if token is None:
|
||||
return
|
||||
if token not in self._results:
|
||||
return
|
||||
if api not in self._results[token]:
|
||||
return
|
||||
del self._results[token][api]
|
||||
del self._results[token]
|
||||
|
||||
def _combine_results_by_api(self, result_a, result_b):
|
||||
combined_result = {}
|
||||
|
@ -534,7 +545,7 @@ class ResultsManager(object):
|
|||
zip.write(file_path, file_name, zipfile.ZIP_DEFLATED)
|
||||
zip.close()
|
||||
|
||||
with open(zip_file_name, "rb") as file:
|
||||
with open(zip_file_name, "r") as file:
|
||||
blob = file.read()
|
||||
os.remove(zip_file_name)
|
||||
|
||||
|
@ -575,8 +586,8 @@ class ResultsManager(object):
|
|||
|
||||
return blob
|
||||
|
||||
def is_import_enabled(self):
|
||||
return self._import_enabled
|
||||
def is_import_results_enabled(self):
|
||||
return self._import_results_enabled
|
||||
|
||||
def are_reports_enabled(self):
|
||||
return self._reports_enabled
|
||||
|
@ -592,7 +603,7 @@ class ResultsManager(object):
|
|||
return deserialize_session(info)
|
||||
|
||||
def import_results(self, blob):
|
||||
if not self.is_import_enabled:
|
||||
if not self.is_import_results_enabled:
|
||||
raise PermissionDeniedException()
|
||||
tmp_file_name = "{}.zip".format(str(time.time()))
|
||||
|
||||
|
@ -614,9 +625,35 @@ class ResultsManager(object):
|
|||
os.makedirs(destination_path)
|
||||
zip.extractall(destination_path)
|
||||
self.remove_tmp_files()
|
||||
self.load_results()
|
||||
self.load_results(token)
|
||||
return token
|
||||
|
||||
def import_results_api_json(self, token, api, blob):
|
||||
if not self.is_import_results_enabled:
|
||||
raise PermissionDeniedException()
|
||||
destination_path = os.path.join(self._results_directory_path, token, api)
|
||||
files = os.listdir(destination_path)
|
||||
file_name = ""
|
||||
for file in files:
|
||||
if RESULTS_FILE_PATTERN.match(file):
|
||||
file_name = file
|
||||
break
|
||||
destination_file_path = os.path.join(destination_path, file_name)
|
||||
with open(destination_file_path, "wb") as file:
|
||||
file.write(blob)
|
||||
|
||||
self.generate_report(token, api)
|
||||
|
||||
session = self._sessions_manager.read_session(token)
|
||||
if session is None:
|
||||
raise NotFoundException()
|
||||
|
||||
results = self.load_results(token)
|
||||
test_state = self.parse_test_state(results)
|
||||
session.test_state = test_state
|
||||
|
||||
self._sessions_manager.update_session(session)
|
||||
|
||||
def remove_tmp_files(self):
|
||||
files = os.listdir(".")
|
||||
|
||||
|
@ -624,3 +661,12 @@ class ResultsManager(object):
|
|||
if re.match(r"\d{10}\.\d{2}\.zip", file) is None:
|
||||
continue
|
||||
os.remove(file)
|
||||
|
||||
def _set_timeout(self, token):
|
||||
if token in self._timeouts:
|
||||
self._timeouts[token].cancel()
|
||||
|
||||
def handler(self, token):
|
||||
self._clear_session_cache(token)
|
||||
|
||||
self._timeouts[token] = Timer(SESSION_RESULTS_TIMEOUT, handler, [self, token])
|
||||
|
|
|
@ -2,6 +2,7 @@ import uuid
|
|||
import time
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
|
||||
from threading import Timer
|
||||
|
||||
|
@ -25,7 +26,8 @@ class SessionsManager(object):
|
|||
event_dispatcher,
|
||||
tests_manager,
|
||||
results_directory,
|
||||
results_manager):
|
||||
results_manager,
|
||||
configuration):
|
||||
self._test_loader = test_loader
|
||||
self._sessions = {}
|
||||
self._expiration_timeout = None
|
||||
|
@ -33,17 +35,18 @@ class SessionsManager(object):
|
|||
self._tests_manager = tests_manager
|
||||
self._results_directory = results_directory
|
||||
self._results_manager = results_manager
|
||||
self._configuration = configuration
|
||||
|
||||
def create_session(
|
||||
self,
|
||||
tests=None,
|
||||
types=None,
|
||||
test_types=None,
|
||||
timeouts=None,
|
||||
reference_tokens=None,
|
||||
webhook_urls=None,
|
||||
user_agent=None,
|
||||
labels=None,
|
||||
expiration_date=None
|
||||
expiration_date=None,
|
||||
type=None
|
||||
):
|
||||
if tests is None:
|
||||
tests = {}
|
||||
|
@ -51,8 +54,6 @@ class SessionsManager(object):
|
|||
timeouts = {}
|
||||
if reference_tokens is None:
|
||||
reference_tokens = []
|
||||
if webhook_urls is None:
|
||||
webhook_urls = []
|
||||
if user_agent is None:
|
||||
user_agent = ""
|
||||
if labels is None:
|
||||
|
@ -63,19 +64,19 @@ class SessionsManager(object):
|
|||
if "exclude" not in tests:
|
||||
tests["exclude"] = []
|
||||
if "automatic" not in timeouts:
|
||||
timeouts["automatic"] = DEFAULT_TEST_AUTOMATIC_TIMEOUT
|
||||
timeouts["automatic"] = self._configuration["timeouts"]["automatic"]
|
||||
if "manual" not in timeouts:
|
||||
timeouts["manual"] = DEFAULT_TEST_MANUAL_TIMEOUT
|
||||
if types is None:
|
||||
types = DEFAULT_TEST_TYPES
|
||||
timeouts["manual"] = self._configuration["timeouts"]["manual"]
|
||||
if test_types is None:
|
||||
test_types = DEFAULT_TEST_TYPES
|
||||
|
||||
for type in types:
|
||||
if type != "automatic" and type != "manual":
|
||||
raise InvalidDataException("Unknown type '{}'".format(type))
|
||||
for test_type in test_types:
|
||||
if test_type != "automatic" and test_type != "manual":
|
||||
raise InvalidDataException("Unknown type '{}'".format(test_type))
|
||||
|
||||
token = str(uuid.uuid1())
|
||||
pending_tests = self._test_loader.get_tests(
|
||||
types,
|
||||
test_types,
|
||||
include_list=tests["include"],
|
||||
exclude_list=tests["exclude"],
|
||||
reference_tokens=reference_tokens)
|
||||
|
@ -96,21 +97,24 @@ class SessionsManager(object):
|
|||
"total": test_files_count[api],
|
||||
"complete": 0}
|
||||
|
||||
date_created = int(time.time() * 1000)
|
||||
|
||||
session = Session(
|
||||
token=token,
|
||||
tests=tests,
|
||||
user_agent=user_agent,
|
||||
browser=browser,
|
||||
types=types,
|
||||
test_types=test_types,
|
||||
timeouts=timeouts,
|
||||
pending_tests=pending_tests,
|
||||
running_tests={},
|
||||
test_state=test_state,
|
||||
status=PENDING,
|
||||
reference_tokens=reference_tokens,
|
||||
webhook_urls=webhook_urls,
|
||||
labels=labels,
|
||||
expiration_date=expiration_date
|
||||
type=type,
|
||||
expiration_date=expiration_date,
|
||||
date_created=date_created
|
||||
)
|
||||
|
||||
self._push_to_cache(session)
|
||||
|
@ -124,11 +128,27 @@ class SessionsManager(object):
|
|||
return None
|
||||
session = self._read_from_cache(token)
|
||||
if session is None or session.test_state is None:
|
||||
print("loading session from file system")
|
||||
session = self.load_session(token)
|
||||
if session is not None:
|
||||
self._push_to_cache(session)
|
||||
return session
|
||||
|
||||
def read_sessions(self, index=None, count=None):
|
||||
if index is None:
|
||||
index = 0
|
||||
if count is None:
|
||||
count = 10
|
||||
self.load_all_sessions_info()
|
||||
sessions = []
|
||||
for it_index, token in enumerate(self._sessions):
|
||||
if it_index < index:
|
||||
continue
|
||||
if len(sessions) == count:
|
||||
break
|
||||
sessions.append(token)
|
||||
return sessions
|
||||
|
||||
def read_session_status(self, token):
|
||||
if token is None:
|
||||
return None
|
||||
|
@ -158,7 +178,7 @@ class SessionsManager(object):
|
|||
self._push_to_cache(session)
|
||||
|
||||
def update_session_configuration(
|
||||
self, token, tests, types, timeouts, reference_tokens, webhook_urls
|
||||
self, token, tests, test_types, timeouts, reference_tokens, type
|
||||
):
|
||||
session = self.read_session(token)
|
||||
if session is None:
|
||||
|
@ -173,14 +193,14 @@ class SessionsManager(object):
|
|||
tests["exclude"] = session.tests["exclude"]
|
||||
if reference_tokens is None:
|
||||
reference_tokens = session.reference_tokens
|
||||
if types is None:
|
||||
types = session.types
|
||||
if test_types is None:
|
||||
test_types = session.test_types
|
||||
|
||||
pending_tests = self._test_loader.get_tests(
|
||||
include_list=tests["include"],
|
||||
exclude_list=tests["exclude"],
|
||||
reference_tokens=reference_tokens,
|
||||
types=types
|
||||
test_types=test_types
|
||||
)
|
||||
session.pending_tests = pending_tests
|
||||
session.tests = tests
|
||||
|
@ -198,8 +218,8 @@ class SessionsManager(object):
|
|||
}
|
||||
session.test_state = test_state
|
||||
|
||||
if types is not None:
|
||||
session.types = types
|
||||
if test_types is not None:
|
||||
session.test_types = test_types
|
||||
if timeouts is not None:
|
||||
if AUTOMATIC not in timeouts:
|
||||
timeouts[AUTOMATIC] = session.timeouts[AUTOMATIC]
|
||||
|
@ -208,8 +228,8 @@ class SessionsManager(object):
|
|||
session.timeouts = timeouts
|
||||
if reference_tokens is not None:
|
||||
session.reference_tokens = reference_tokens
|
||||
if webhook_urls is not None:
|
||||
session.webhook_urls = webhook_urls
|
||||
if type is not None:
|
||||
session.type = type
|
||||
|
||||
self._push_to_cache(session)
|
||||
return session
|
||||
|
@ -306,7 +326,7 @@ class SessionsManager(object):
|
|||
if self._expiration_timeout is not None:
|
||||
self._expiration_timeout.cancel()
|
||||
|
||||
timeout = next_session.expiration_date / 1000.0 - int(time.time())
|
||||
timeout = next_session.expiration_date / 1000 - time.time()
|
||||
if timeout < 0:
|
||||
timeout = 0
|
||||
|
||||
|
@ -319,10 +339,10 @@ class SessionsManager(object):
|
|||
|
||||
def _delete_expired_sessions(self):
|
||||
expiring_sessions = self._read_expiring_sessions()
|
||||
now = int(time.time())
|
||||
now = int(time.time() * 1000)
|
||||
|
||||
for session in expiring_sessions:
|
||||
if session.expiration_date / 1000.0 < now:
|
||||
if session.expiration_date < now:
|
||||
self.delete_session(session.token)
|
||||
|
||||
def _read_expiring_sessions(self):
|
||||
|
@ -344,7 +364,7 @@ class SessionsManager(object):
|
|||
return
|
||||
|
||||
if session.status == PENDING:
|
||||
session.date_started = int(time.time()) * 1000
|
||||
session.date_started = int(time.time() * 1000)
|
||||
session.expiration_date = None
|
||||
|
||||
session.status = RUNNING
|
||||
|
@ -374,7 +394,7 @@ class SessionsManager(object):
|
|||
if session.status == ABORTED or session.status == COMPLETED:
|
||||
return
|
||||
session.status = ABORTED
|
||||
session.date_finished = time.time() * 1000
|
||||
session.date_finished = int(time.time() * 1000)
|
||||
self.update_session(session)
|
||||
self._event_dispatcher.dispatch_event(
|
||||
token,
|
||||
|
@ -398,7 +418,7 @@ class SessionsManager(object):
|
|||
if session.status == COMPLETED or session.status == ABORTED:
|
||||
return
|
||||
session.status = COMPLETED
|
||||
session.date_finished = time.time() * 1000
|
||||
session.date_finished = int(time.time() * 1000)
|
||||
self.update_session(session)
|
||||
self._event_dispatcher.dispatch_event(
|
||||
token,
|
||||
|
@ -427,6 +447,20 @@ class SessionsManager(object):
|
|||
return api not in session.pending_tests \
|
||||
and api not in session.running_tests
|
||||
|
||||
def get_test_path_with_query(self, test, session):
|
||||
query_string = ""
|
||||
include_list = session.tests["include"]
|
||||
for include_test in include_list:
|
||||
split = include_test.split("?")
|
||||
query = ""
|
||||
if len(split) > 1:
|
||||
include_test = split[0]
|
||||
query = split[1]
|
||||
pattern = re.compile("^" + include_test)
|
||||
if pattern.match(test) is not None:
|
||||
query_string += query + "&"
|
||||
return "{}?{}".format(test, query_string)
|
||||
|
||||
def find_token(self, fragment):
|
||||
if len(fragment) < 8:
|
||||
return None
|
||||
|
@ -437,3 +471,6 @@ class SessionsManager(object):
|
|||
if len(tokens) != 1:
|
||||
return None
|
||||
return tokens[0]
|
||||
|
||||
def get_total_sessions(self):
|
||||
return len(self._sessions)
|
||||
|
|
|
@ -70,7 +70,7 @@ class TestLoader(object):
|
|||
paths.append(test)
|
||||
continue
|
||||
if test.endswith(".js"):
|
||||
for element in tests[test]:
|
||||
for element in tests[test][1:]:
|
||||
paths.append(element[0])
|
||||
continue
|
||||
return paths
|
||||
|
@ -98,6 +98,7 @@ class TestLoader(object):
|
|||
if include_list is not None and len(include_list) > 0:
|
||||
is_valid = False
|
||||
for include_test in include_list:
|
||||
include_test = include_test.split("?")[0]
|
||||
pattern = re.compile("^" + include_test)
|
||||
if pattern.match(test_path) is not None:
|
||||
is_valid = True
|
||||
|
@ -109,6 +110,7 @@ class TestLoader(object):
|
|||
if exclude_list is not None and len(exclude_list) > 0:
|
||||
is_valid = True
|
||||
for exclude_test in exclude_list:
|
||||
exclude_test = exclude_test.split("?")[0]
|
||||
pattern = re.compile("^" + exclude_test)
|
||||
if pattern.match(test_path) is not None:
|
||||
is_valid = False
|
||||
|
@ -136,13 +138,13 @@ class TestLoader(object):
|
|||
|
||||
def get_tests(
|
||||
self,
|
||||
types=None,
|
||||
test_types=None,
|
||||
include_list=None,
|
||||
exclude_list=None,
|
||||
reference_tokens=None
|
||||
):
|
||||
if types is None:
|
||||
types = [AUTOMATIC, MANUAL]
|
||||
if test_types is None:
|
||||
test_types = [AUTOMATIC, MANUAL]
|
||||
if include_list is None:
|
||||
include_list = []
|
||||
if exclude_list is None:
|
||||
|
@ -155,7 +157,7 @@ class TestLoader(object):
|
|||
reference_results = self._results_manager.read_common_passed_tests(
|
||||
reference_tokens)
|
||||
|
||||
for test_type in types:
|
||||
for test_type in test_types:
|
||||
if test_type not in TEST_TYPES:
|
||||
continue
|
||||
for api in self._tests[test_type]:
|
||||
|
@ -164,7 +166,8 @@ class TestLoader(object):
|
|||
include_list):
|
||||
continue
|
||||
if reference_results is not None and \
|
||||
test_path not in reference_results[api]:
|
||||
(api not in reference_results or
|
||||
(api in reference_results and test_path not in reference_results[api])):
|
||||
continue
|
||||
if api not in loaded_tests:
|
||||
loaded_tests[api] = []
|
||||
|
|
|
@ -6,32 +6,6 @@ from .event_dispatcher import TEST_COMPLETED_EVENT
|
|||
from ..data.exceptions.not_found_exception import NotFoundException
|
||||
from ..data.session import COMPLETED, ABORTED
|
||||
|
||||
# Helper class used to adapt a specific compare function to a Python 3
|
||||
# key function.
|
||||
class CmpWrapper:
|
||||
def __init__(self, test_manager, compare_function, obj):
|
||||
self.obj = obj
|
||||
self.test_manager = test_manager
|
||||
self.compare_function = compare_function
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.compare_function(self.test_manager, self.obj, other.obj) < 0
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.compare_function(self.test_manager, self.obj, other.obj) > 0
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.compare_function(self.test_manager, self.obj, other.obj) == 0
|
||||
|
||||
def __le__(self, other):
|
||||
return self.compare_function(self.test_manager, self.obj, other.obj) <= 0
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.compare_function(self.test_manager, self.obj, other.obj) >= 0
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.compare_function(self.test_manager, self.obj, other.obj) != 0
|
||||
|
||||
|
||||
class TestsManager(object):
|
||||
def initialize(
|
||||
|
@ -115,6 +89,8 @@ class TestsManager(object):
|
|||
if potential_result["test"] == test:
|
||||
result = potential_result
|
||||
break
|
||||
if result is None:
|
||||
break
|
||||
|
||||
if result["status"] == "ERROR":
|
||||
if len(tests["fail"]) < count:
|
||||
|
@ -144,29 +120,35 @@ class TestsManager(object):
|
|||
for test in tests[api]:
|
||||
sorted_tests.append(test)
|
||||
|
||||
def compare(tests_manager, test_a, test_b):
|
||||
micro_test_list = {}
|
||||
api_a = ""
|
||||
for part in test_a.split("/"):
|
||||
if part != "":
|
||||
api_a = part
|
||||
break
|
||||
api_b = ""
|
||||
for part in test_b.split("/"):
|
||||
if part != "":
|
||||
api_b = part
|
||||
break
|
||||
if api_a == api_b:
|
||||
micro_test_list[api_a] = [test_a, test_b]
|
||||
else:
|
||||
micro_test_list[api_a] = [test_a]
|
||||
micro_test_list[api_b] = [test_b]
|
||||
next_test = tests_manager._get_next_test_from_list(micro_test_list)
|
||||
if next_test == test_a:
|
||||
return -1
|
||||
return 1
|
||||
class compare(object):
|
||||
def __init__(self, tests_manager, test):
|
||||
self.test = test
|
||||
self.tests_manager = tests_manager
|
||||
|
||||
return sorted(sorted_tests, key=lambda x:CmpWrapper(self, compare, x))
|
||||
def __lt__(self, test_b):
|
||||
test_a = self.test
|
||||
test_b = test_b.test
|
||||
micro_test_list = {}
|
||||
api_a = ""
|
||||
for part in test_a.split("/"):
|
||||
if part != "":
|
||||
api_a = part
|
||||
break
|
||||
api_b = ""
|
||||
for part in test_b.split("/"):
|
||||
if part != "":
|
||||
api_b = part
|
||||
break
|
||||
if api_a == api_b:
|
||||
micro_test_list[api_a] = [test_a, test_b]
|
||||
else:
|
||||
micro_test_list[api_a] = [test_a]
|
||||
micro_test_list[api_b] = [test_b]
|
||||
next_test = self.tests_manager._get_next_test_from_list(micro_test_list)
|
||||
return next_test == test_b
|
||||
|
||||
sorted_tests.sort(key=lambda test: compare(self, test))
|
||||
return sorted_tests
|
||||
|
||||
def _get_next_test_from_list(self, tests):
|
||||
test = None
|
||||
|
@ -180,7 +162,7 @@ class TestsManager(object):
|
|||
apis.sort(key=lambda api: api.lower())
|
||||
|
||||
for api in apis:
|
||||
tests[api].sort(key=lambda api: api.replace("/", "").lower())
|
||||
tests[api].sort(key=lambda test: test.replace("/", "").lower())
|
||||
|
||||
while test is None:
|
||||
if len(apis) <= current_api:
|
||||
|
@ -332,7 +314,7 @@ class TestsManager(object):
|
|||
)
|
||||
|
||||
self._event_dispatcher.dispatch_event(
|
||||
token=session.token,
|
||||
dispatcher_token=session.token,
|
||||
event_type=TEST_COMPLETED_EVENT,
|
||||
data=test
|
||||
)
|
||||
|
@ -377,7 +359,7 @@ class TestsManager(object):
|
|||
|
||||
def load_tests(self, session):
|
||||
pending_tests = self._test_loader.get_tests(
|
||||
session.types,
|
||||
session.test_types,
|
||||
include_list=session.tests["include"],
|
||||
exclude_list=session.tests["exclude"],
|
||||
reference_tokens=session.reference_tokens
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"ports": {
|
||||
"http": [8080, "auto"],
|
||||
"https": [8483]
|
||||
}
|
||||
}
|
|
@ -7,15 +7,13 @@ import time
|
|||
from urllib.request import urlopen
|
||||
from urllib.error import URLError
|
||||
|
||||
import pytest
|
||||
|
||||
from tools.wpt import wpt
|
||||
|
||||
def is_port_8000_in_use():
|
||||
def is_port_8080_in_use():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.bind(("127.0.0.1", 8000))
|
||||
except OSError as e:
|
||||
s.bind(("127.0.0.1", 8080))
|
||||
except socket.error as e:
|
||||
if e.errno == errno.EADDRINUSE:
|
||||
return True
|
||||
else:
|
||||
|
@ -25,25 +23,28 @@ def is_port_8000_in_use():
|
|||
return False
|
||||
|
||||
def test_serve():
|
||||
if is_port_8000_in_use():
|
||||
pytest.skip("WAVE Test Runner failed: Port 8000 already in use.")
|
||||
if is_port_8080_in_use():
|
||||
assert False, "WAVE Test Runner failed: Port 8080 already in use."
|
||||
|
||||
p = subprocess.Popen([os.path.join(wpt.localpaths.repo_root, "wpt"), "serve-wave"],
|
||||
preexec_fn=os.setsid)
|
||||
p = subprocess.Popen([os.path.join(wpt.localpaths.repo_root, "wpt"),
|
||||
"serve-wave",
|
||||
"--config",
|
||||
os.path.join(wpt.localpaths.repo_root, "tools/wave/tests/config.json")],
|
||||
preexec_fn=os.setsid)
|
||||
|
||||
start = time.time()
|
||||
try:
|
||||
while True:
|
||||
if p.poll() is not None:
|
||||
assert False, "WAVE Test Runner failed: Server not running."
|
||||
if time.time() - start > 6 * 60:
|
||||
assert False, "WAVE Test Runner failed: Server did not start responding within 6m."
|
||||
if time.time() - start > 60:
|
||||
assert False, "WAVE Test Runner failed: Server did not start responding within 60s."
|
||||
try:
|
||||
resp = urlopen("http://web-platform.test:8000/_wave/api/sessions/public")
|
||||
resp = urlopen("http://web-platform.test:8080/_wave/api/sessions/public")
|
||||
print(resp)
|
||||
except URLError:
|
||||
print("Server not responding, waiting another 10s.")
|
||||
time.sleep(10)
|
||||
print("URLError")
|
||||
time.sleep(1)
|
||||
else:
|
||||
assert resp.code == 200
|
||||
break
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
from ..data.session import Session, UNKNOWN
|
||||
from datetime import datetime
|
||||
import dateutil.parser
|
||||
from dateutil.tz import tzutc
|
||||
|
||||
|
||||
def deserialize_sessions(session_dicts):
|
||||
|
@ -19,9 +24,9 @@ def deserialize_session(session_dict):
|
|||
if "path" in session_dict:
|
||||
test_paths = session_dict["path"].split(", ")
|
||||
tests["include"] = tests["include"] + test_paths
|
||||
types = []
|
||||
test_types = []
|
||||
if "types" in session_dict:
|
||||
types = session_dict["types"]
|
||||
test_types = session_dict["types"]
|
||||
user_agent = ""
|
||||
if "user_agent" in session_dict:
|
||||
user_agent = session_dict["user_agent"]
|
||||
|
@ -46,12 +51,18 @@ def deserialize_session(session_dict):
|
|||
last_completed_test = None
|
||||
if "last_completed_test" in session_dict:
|
||||
last_completed_test = session_dict["last_completed_test"]
|
||||
date_created = None
|
||||
if "date_created" in session_dict:
|
||||
date_created = session_dict["date_created"]
|
||||
date_created = iso_to_millis(date_created)
|
||||
date_started = None
|
||||
if "date_started" in session_dict:
|
||||
date_started = session_dict["date_started"]
|
||||
date_started = iso_to_millis(date_started)
|
||||
date_finished = None
|
||||
if "date_finished" in session_dict:
|
||||
date_finished = session_dict["date_finished"]
|
||||
date_finished = iso_to_millis(date_finished)
|
||||
is_public = False
|
||||
if "is_public" in session_dict:
|
||||
is_public = session_dict["is_public"]
|
||||
|
@ -61,12 +72,13 @@ def deserialize_session(session_dict):
|
|||
browser = None
|
||||
if "browser" in session_dict:
|
||||
browser = session_dict["browser"]
|
||||
webhook_urls = []
|
||||
if "webhook_urls" in session_dict:
|
||||
webhook_urls = session_dict["webhook_urls"]
|
||||
expiration_date = None
|
||||
if "expiration_date" in session_dict:
|
||||
expiration_date = session_dict["expiration_date"]
|
||||
expiration_date = iso_to_millis(expiration_date)
|
||||
type = None
|
||||
if "type" in session_dict:
|
||||
type = session_dict["type"]
|
||||
malfunctioning_tests = []
|
||||
if "malfunctioning_tests" in session_dict:
|
||||
malfunctioning_tests = session_dict["malfunctioning_tests"]
|
||||
|
@ -74,7 +86,7 @@ def deserialize_session(session_dict):
|
|||
return Session(
|
||||
token=token,
|
||||
tests=tests,
|
||||
types=types,
|
||||
test_types=test_types,
|
||||
user_agent=user_agent,
|
||||
labels=labels,
|
||||
timeouts=timeouts,
|
||||
|
@ -83,12 +95,24 @@ def deserialize_session(session_dict):
|
|||
status=status,
|
||||
test_state=test_state,
|
||||
last_completed_test=last_completed_test,
|
||||
date_created=date_created,
|
||||
date_started=date_started,
|
||||
date_finished=date_finished,
|
||||
is_public=is_public,
|
||||
reference_tokens=reference_tokens,
|
||||
browser=browser,
|
||||
webhook_urls=webhook_urls,
|
||||
expiration_date=expiration_date,
|
||||
type=type,
|
||||
malfunctioning_tests=malfunctioning_tests
|
||||
)
|
||||
|
||||
def iso_to_millis(iso_string):
|
||||
if iso_string is None:
|
||||
return None
|
||||
try:
|
||||
date = dateutil.parser.isoparse(iso_string)
|
||||
date = date.replace(tzinfo=tzutc())
|
||||
epoch = datetime.utcfromtimestamp(0).replace(tzinfo=tzutc())
|
||||
return int((date - epoch).total_seconds() * 1000)
|
||||
except Exception:
|
||||
return iso_string
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from datetime import datetime
|
||||
|
||||
|
||||
def serialize_session(session):
|
||||
return {
|
||||
"token": session.token,
|
||||
"types": session.types,
|
||||
"types": session.test_types,
|
||||
"user_agent": session.user_agent,
|
||||
"labels": session.labels,
|
||||
"timeouts": session.timeouts,
|
||||
|
@ -12,11 +15,31 @@ def serialize_session(session):
|
|||
"running_tests": session.running_tests,
|
||||
"status": session.status,
|
||||
"browser": session.browser,
|
||||
"date_started": session.date_started,
|
||||
"date_finished": session.date_finished,
|
||||
"date_created": millis_to_iso(session.date_created),
|
||||
"date_started": millis_to_iso(session.date_started),
|
||||
"date_finished": millis_to_iso(session.date_finished),
|
||||
"is_public": session.is_public,
|
||||
"reference_tokens": session.reference_tokens,
|
||||
"webhook_urls": session.webhook_urls,
|
||||
"expiration_date": session.expiration_date,
|
||||
"expiration_date": millis_to_iso(session.expiration_date),
|
||||
"type": session.type,
|
||||
"malfunctioning_tests": session.malfunctioning_tests
|
||||
}
|
||||
|
||||
def serialize_sessions(sessions):
|
||||
serialized_sessions = []
|
||||
for session in sessions:
|
||||
serialized_sessions.append(serialize_session(session))
|
||||
return serialized_sessions
|
||||
|
||||
def serialize_device(device):
|
||||
return {
|
||||
"token": device.token,
|
||||
"user_agent": device.user_agent,
|
||||
"name": device.name,
|
||||
"last_active": millis_to_iso(device.last_active)
|
||||
}
|
||||
|
||||
def millis_to_iso(millis):
|
||||
if millis is None:
|
||||
return None
|
||||
return datetime.utcfromtimestamp(millis/1000.0).isoformat() + "+00:00"
|
||||
|
|
|
@ -7,14 +7,19 @@ from .network.http_handler import HttpHandler
|
|||
from .network.api.sessions_api_handler import SessionsApiHandler
|
||||
from .network.api.tests_api_handler import TestsApiHandler
|
||||
from .network.api.results_api_handler import ResultsApiHandler
|
||||
from .network.api.devices_api_handler import DevicesApiHandler
|
||||
from .network.api.general_api_handler import GeneralApiHandler
|
||||
from .network.static_handler import StaticHandler
|
||||
|
||||
from .testing.sessions_manager import SessionsManager
|
||||
from .testing.results_manager import ResultsManager
|
||||
from .testing.tests_manager import TestsManager
|
||||
from .testing.devices_manager import DevicesManager
|
||||
from .testing.test_loader import TestLoader
|
||||
from .testing.event_dispatcher import EventDispatcher
|
||||
|
||||
VERSION_STRING = "v3.3.0"
|
||||
|
||||
|
||||
class WaveServer(object):
|
||||
def initialize(self,
|
||||
|
@ -35,10 +40,13 @@ class WaveServer(object):
|
|||
configuration = configuration_loader.load(configuration_file_path)
|
||||
|
||||
# Initialize Managers
|
||||
event_dispatcher = EventDispatcher()
|
||||
event_dispatcher = EventDispatcher(
|
||||
event_cache_duration=configuration["event_cache_duration"]
|
||||
)
|
||||
sessions_manager = SessionsManager()
|
||||
results_manager = ResultsManager()
|
||||
tests_manager = TestsManager()
|
||||
devices_manager = DevicesManager()
|
||||
test_loader = TestLoader()
|
||||
|
||||
sessions_manager.initialize(
|
||||
|
@ -46,14 +54,15 @@ class WaveServer(object):
|
|||
event_dispatcher=event_dispatcher,
|
||||
tests_manager=tests_manager,
|
||||
results_directory=configuration["results_directory_path"],
|
||||
results_manager=results_manager
|
||||
results_manager=results_manager,
|
||||
configuration=configuration
|
||||
)
|
||||
|
||||
results_manager.initialize(
|
||||
results_directory_path=configuration["results_directory_path"],
|
||||
sessions_manager=sessions_manager,
|
||||
tests_manager=tests_manager,
|
||||
import_enabled=configuration["import_enabled"],
|
||||
import_results_enabled=configuration["import_results_enabled"],
|
||||
reports_enabled=reports_enabled,
|
||||
persisting_interval=configuration["persisting_interval"]
|
||||
)
|
||||
|
@ -65,6 +74,8 @@ class WaveServer(object):
|
|||
event_dispatcher=event_dispatcher
|
||||
)
|
||||
|
||||
devices_manager.initialize(event_dispatcher)
|
||||
|
||||
exclude_list_file_path = os.path.abspath("./excluded.json")
|
||||
include_list_file_path = os.path.abspath("./included.json")
|
||||
test_loader.initialize(
|
||||
|
@ -86,7 +97,8 @@ class WaveServer(object):
|
|||
sessions_manager=sessions_manager,
|
||||
results_manager=results_manager,
|
||||
event_dispatcher=event_dispatcher,
|
||||
web_root=configuration["web_root"]
|
||||
web_root=configuration["web_root"],
|
||||
read_sessions_enabled=configuration["read_sessions_enabled"]
|
||||
)
|
||||
tests_api_handler = TestsApiHandler(
|
||||
tests_manager=tests_manager,
|
||||
|
@ -97,9 +109,25 @@ class WaveServer(object):
|
|||
web_root=configuration["web_root"],
|
||||
test_loader=test_loader
|
||||
)
|
||||
devices_api_handler = DevicesApiHandler(
|
||||
devices_manager=devices_manager,
|
||||
event_dispatcher=event_dispatcher,
|
||||
web_root=configuration["web_root"]
|
||||
)
|
||||
results_api_handler = ResultsApiHandler(
|
||||
results_manager,
|
||||
web_root=configuration["web_root"])
|
||||
sessions_manager,
|
||||
web_root=configuration["web_root"]
|
||||
)
|
||||
general_api_handler = GeneralApiHandler(
|
||||
web_root=configuration["web_root"],
|
||||
read_sessions_enabled=configuration["read_sessions_enabled"],
|
||||
import_results_enabled=configuration["import_results_enabled"],
|
||||
reports_enabled=reports_enabled,
|
||||
version_string=VERSION_STRING,
|
||||
test_type_selection_enabled=configuration["enable_test_type_selection"],
|
||||
test_file_selection_enabled=configuration["enable_test_file_selection"]
|
||||
)
|
||||
|
||||
# Initialize HTTP server
|
||||
http_handler = HttpHandler(
|
||||
|
@ -107,6 +135,8 @@ class WaveServer(object):
|
|||
sessions_api_handler=sessions_api_handler,
|
||||
tests_api_handler=tests_api_handler,
|
||||
results_api_handler=results_api_handler,
|
||||
devices_api_handler=devices_api_handler,
|
||||
general_api_handler=general_api_handler,
|
||||
http_port=configuration["wpt_port"],
|
||||
web_root=configuration["web_root"]
|
||||
)
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
tokens = tokens ? tokens.split(",") : [];
|
||||
const refTokens = reftokens ? reftokens.split(",") : [];
|
||||
if (tokens) {
|
||||
WaveService.readResultsConfig(function(config) {
|
||||
WaveService.readStatus(function(config) {
|
||||
comparisonUi.state.reportsEnabled = config.reportsEnabled;
|
||||
comparisonUi.render();
|
||||
});
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -26,7 +26,7 @@
|
|||
.pagination-ellipsis, .tabs {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<h1 class="title is-spaced">
|
||||
WAVE WMAS Test Suite
|
||||
</h1>
|
||||
<p class="subtitle">
|
||||
<p class="subtitle">
|
||||
<a href="https://github.com/w3c/webmediaapi/">GitHub</a> -
|
||||
</p>
|
||||
</div>
|
||||
|
@ -32,23 +32,29 @@
|
|||
<h2 class="title is-5">
|
||||
New test session
|
||||
</h2>
|
||||
<div class="columns is-vcentered" style="margin-top: 20px">
|
||||
<div class="columns is-vcentered" style="margin-top: 20px;">
|
||||
<div class="column is-narrow">
|
||||
<div
|
||||
id="qr-code"
|
||||
style="width: 256px; height: 256px; padding: 5px; border: 1px gray solid; border-radius: 3px;"
|
||||
style="
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
padding: 5px;
|
||||
border: 1px gray solid;
|
||||
border-radius: 3px;
|
||||
"
|
||||
></div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<table style="margin-bottom: 1.5em">
|
||||
<table style="margin-bottom: 1.5em;">
|
||||
<tr>
|
||||
<td class="has-text-weight-bold" style="padding-right: 1rem">
|
||||
<td class="has-text-weight-bold" style="padding-right: 1rem;">
|
||||
Token:
|
||||
</td>
|
||||
<td id="session-token" class="is-family-monospace"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="has-text-weight-bold" style="padding-right: 1rem">
|
||||
<td class="has-text-weight-bold" style="padding-right: 1rem;">
|
||||
Expires:
|
||||
</td>
|
||||
<td id="expiary-date"></td>
|
||||
|
@ -87,7 +93,7 @@
|
|||
</h2>
|
||||
<article
|
||||
id="unknown_token_error"
|
||||
style="max-width: 30em; display: none"
|
||||
style="max-width: 30em; display: none;"
|
||||
class="message is-danger"
|
||||
>
|
||||
<div class="message-body">
|
||||
|
@ -112,148 +118,146 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
var selectedTabbable = -1;
|
||||
<script>
|
||||
var selectedTabbable = -1;
|
||||
|
||||
function removeClass(element, className) {
|
||||
var elementClass = element.className;
|
||||
var index = elementClass.indexOf(className);
|
||||
if (index !== -1) {
|
||||
element.className = elementClass.replace(className, "");
|
||||
}
|
||||
}
|
||||
function removeClass(element, className) {
|
||||
var elementClass = element.className;
|
||||
var index = elementClass.indexOf(className);
|
||||
if (index !== -1) {
|
||||
element.className = elementClass.replace(className, "");
|
||||
}
|
||||
}
|
||||
|
||||
function addClass(element, className) {
|
||||
element.className += " " + className;
|
||||
}
|
||||
function addClass(element, className) {
|
||||
element.className += " " + className;
|
||||
}
|
||||
|
||||
function skipFocus(steps) {
|
||||
var tabbables = document.getElementsByClassName("tabbable");
|
||||
if (selectedTabbable === -1) {
|
||||
selectedTabbable = 0;
|
||||
} else {
|
||||
removeClass(tabbables[selectedTabbable], "focused");
|
||||
selectedTabbable += steps;
|
||||
}
|
||||
function skipFocus(steps) {
|
||||
var tabbables = document.getElementsByClassName("tabbable");
|
||||
if (selectedTabbable === -1) {
|
||||
selectedTabbable = 0;
|
||||
} else {
|
||||
removeClass(tabbables[selectedTabbable], "focused");
|
||||
selectedTabbable += steps;
|
||||
}
|
||||
|
||||
if (selectedTabbable >= tabbables.length) {
|
||||
selectedTabbable = 0;
|
||||
}
|
||||
if (selectedTabbable >= tabbables.length) {
|
||||
selectedTabbable = 0;
|
||||
}
|
||||
|
||||
if (selectedTabbable < 0) {
|
||||
selectedTabbable = tabbables.length - 1;
|
||||
}
|
||||
if (selectedTabbable < 0) {
|
||||
selectedTabbable = tabbables.length - 1;
|
||||
}
|
||||
|
||||
tabbables[selectedTabbable].focus();
|
||||
addClass(tabbables[selectedTabbable], "focused");
|
||||
}
|
||||
tabbables[selectedTabbable].focus();
|
||||
addClass(tabbables[selectedTabbable], "focused");
|
||||
}
|
||||
|
||||
function focusNext() {
|
||||
skipFocus(1);
|
||||
}
|
||||
function focusNext() {
|
||||
skipFocus(1);
|
||||
}
|
||||
|
||||
function focusPrevious() {
|
||||
skipFocus(-1);
|
||||
}
|
||||
function focusPrevious() {
|
||||
skipFocus(-1);
|
||||
}
|
||||
|
||||
// Resume
|
||||
var resumeToken = "";
|
||||
var cookies = document.cookie.split(";");
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = cookies[i];
|
||||
if (cookie.split("=")[0].replace(/ /g, "") === "resume_token") {
|
||||
resumeToken = cookie.split("=")[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resumeToken) resumeToken = "";
|
||||
// Resume
|
||||
var resumeToken = "";
|
||||
var cookies = document.cookie.split(";");
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = cookies[i];
|
||||
if (cookie.split("=")[0].replace(/ /g, "") === "resume_token") {
|
||||
resumeToken = cookie.split("=")[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resumeToken) resumeToken = "";
|
||||
|
||||
var resumeButton = document.getElementById("resume-button");
|
||||
var resumeButton = document.getElementById("resume-button");
|
||||
|
||||
var tokenText = document.getElementById("resume_token");
|
||||
if (resumeToken) {
|
||||
tokenText.innerText = "Last session: " + resumeToken;
|
||||
} else {
|
||||
tokenText.innerText = "No recent session.";
|
||||
resumeButton.setAttribute("disabled", "");
|
||||
}
|
||||
var unknownTokenError = document.getElementById("unknown_token_error");
|
||||
resumeButton.onclick = function(event) {
|
||||
location.href = WEB_ROOT + "next.html?token=" + resumeToken;
|
||||
};
|
||||
var tokenText = document.getElementById("resume_token");
|
||||
if (resumeToken) {
|
||||
tokenText.innerText = "Last session: " + resumeToken;
|
||||
} else {
|
||||
tokenText.innerText = "No recent session.";
|
||||
resumeButton.setAttribute("disabled", "");
|
||||
}
|
||||
var unknownTokenError = document.getElementById("unknown_token_error");
|
||||
resumeButton.onclick = function (event) {
|
||||
location.href = WEB_ROOT + "next.html?token=" + resumeToken;
|
||||
};
|
||||
|
||||
resumeButton.onkeydown = function(event) {
|
||||
var charCode =
|
||||
typeof event.which === "number" ? event.which : event.keyCode;
|
||||
if (ACTION_KEYS.indexOf(charCode) === -1) return;
|
||||
location.href = WEB_ROOT + "next.html?token=" + resumeToken;
|
||||
};
|
||||
resumeButton.onkeydown = function (event) {
|
||||
var charCode =
|
||||
typeof event.which === "number" ? event.which : event.keyCode;
|
||||
if (ACTION_KEYS.indexOf(charCode) === -1) return;
|
||||
location.href = WEB_ROOT + "next.html?token=" + resumeToken;
|
||||
};
|
||||
|
||||
document.onkeydown = function(event) {
|
||||
event = event || window.event;
|
||||
var charCode =
|
||||
typeof event.which === "number" ? event.which : event.keyCode;
|
||||
document.onkeydown = function (event) {
|
||||
event = event || window.event;
|
||||
var charCode =
|
||||
typeof event.which === "number" ? event.which : event.keyCode;
|
||||
|
||||
if (ACTION_KEYS.indexOf(charCode) !== -1) {
|
||||
event.preventDefault();
|
||||
var tabbables = document.getElementsByClassName("tabbable");
|
||||
var element = tabbables[selectedTabbable];
|
||||
if (!element) return;
|
||||
if (element.type === "checkbox") {
|
||||
element.checked = !element.checked;
|
||||
} else {
|
||||
element.click();
|
||||
}
|
||||
}
|
||||
if (ACTION_KEYS.indexOf(charCode) !== -1) {
|
||||
event.preventDefault();
|
||||
var tabbables = document.getElementsByClassName("tabbable");
|
||||
var element = tabbables[selectedTabbable];
|
||||
if (!element) return;
|
||||
if (element.type === "checkbox") {
|
||||
element.checked = !element.checked;
|
||||
} else {
|
||||
element.click();
|
||||
}
|
||||
}
|
||||
|
||||
if (PREV_KEYS.indexOf(charCode) !== -1) {
|
||||
focusPrevious();
|
||||
}
|
||||
if (PREV_KEYS.indexOf(charCode) !== -1) {
|
||||
focusPrevious();
|
||||
}
|
||||
|
||||
if (NEXT_KEYS.indexOf(charCode) !== -1) {
|
||||
focusNext();
|
||||
}
|
||||
};
|
||||
if (NEXT_KEYS.indexOf(charCode) !== -1) {
|
||||
focusNext();
|
||||
}
|
||||
};
|
||||
|
||||
var lifeTime = 30 * 60 * 1000; // 30min
|
||||
WaveService.createSession(
|
||||
{ expirationDate: new Date().getTime() + lifeTime },
|
||||
function(token) {
|
||||
var sessionTokenElement = document.getElementById("session-token");
|
||||
sessionTokenElement.innerText = token;
|
||||
var lifeTime = 30 * 60 * 1000; // 30min
|
||||
WaveService.createSession(
|
||||
{ expirationDate: new Date().getTime() + lifeTime },
|
||||
function (token) {
|
||||
var sessionTokenElement = document.getElementById("session-token");
|
||||
sessionTokenElement.innerText = token;
|
||||
|
||||
WaveService.readSession(token, function(config) {
|
||||
var expiaryDateElement = document.getElementById("expiary-date");
|
||||
expiaryDateElement.innerText = new Date(
|
||||
config.expirationDate
|
||||
).toLocaleString();
|
||||
});
|
||||
WaveService.readSessionStatus(token, function (config) {
|
||||
var expiaryDateElement = document.getElementById("expiary-date");
|
||||
expiaryDateElement.innerText = config.expirationDate.toLocaleString();
|
||||
});
|
||||
|
||||
var configurationUrl =
|
||||
location.origin +
|
||||
WEB_ROOT + "configuration.html?token=" +
|
||||
token +
|
||||
"&resume=" +
|
||||
resumeToken;
|
||||
new QRCode(document.getElementById("qr-code"), configurationUrl);
|
||||
document.getElementById("configure-button").onclick = function() {
|
||||
window.open(configurationUrl, "_blank");
|
||||
};
|
||||
var configurationUrl =
|
||||
location.origin +
|
||||
WEB_ROOT +
|
||||
"configuration.html?token=" +
|
||||
token +
|
||||
"&resume=" +
|
||||
resumeToken;
|
||||
new QRCode(document.getElementById("qr-code"), configurationUrl);
|
||||
document.getElementById("configure-button").onclick = function () {
|
||||
window.open(configurationUrl, "_blank");
|
||||
};
|
||||
|
||||
WaveService.connectHttpPolling(token);
|
||||
WaveService.onMessage(function(message) {
|
||||
if (message.type === "resume") {
|
||||
var resumeToken = message.data;
|
||||
location.href = WEB_ROOT + "next.html?token=" + resumeToken;
|
||||
}
|
||||
WaveService.addSessionEventListener(token, function (message) {
|
||||
if (message.type === "resume") {
|
||||
var resumeToken = message.data;
|
||||
location.href = WEB_ROOT + "next.html?token=" + resumeToken;
|
||||
}
|
||||
|
||||
if (message.type !== "status") return;
|
||||
if (message.data === "pending") return;
|
||||
location.href = WEB_ROOT + "next.html?token=" + token;
|
||||
});
|
||||
}
|
||||
);
|
||||
document.getElementById("resume-button").focus();
|
||||
</script>
|
||||
if (message.type !== "status") return;
|
||||
if (message.data === "pending") return;
|
||||
location.href = WEB_ROOT + "next.html?token=" + token;
|
||||
});
|
||||
}
|
||||
);
|
||||
document.getElementById("resume-button").focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,12 @@
|
|||
var QueryParser = {};
|
||||
|
||||
QueryParser.parseQuery = function () {
|
||||
var queryParameters = {};
|
||||
var keysAndValues = location.search.replace("?", "").split("&");
|
||||
for (var i = 0; i < keysAndValues.length; i++) {
|
||||
var key = keysAndValues[i].split("=")[0];
|
||||
var value = keysAndValues[i].split("=")[1];
|
||||
queryParameters[key] = value;
|
||||
}
|
||||
return queryParameters;
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
function ScreenConsole(element) {
|
||||
this._element = element;
|
||||
}
|
||||
|
||||
ScreenConsole.prototype.log = function () {
|
||||
var text = "";
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
text += arguments[i] + " ";
|
||||
}
|
||||
console.log(text);
|
||||
this._element.innerText += text + "\n";
|
||||
};
|
||||
|
||||
ScreenConsole.prototype.clear = function () {
|
||||
this._element.innerText = "";
|
||||
};
|
|
@ -68,6 +68,9 @@ const UI = {
|
|||
case "checked":
|
||||
if (value) element.setAttribute("checked", true);
|
||||
return;
|
||||
case "indeterminate":
|
||||
element.indeterminate = value;
|
||||
return;
|
||||
}
|
||||
});
|
||||
return element;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -2,42 +2,30 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="lib/wave-service.js"></script>
|
||||
<script src="lib/screen-console.js"></script>
|
||||
<script src="lib/query-parser.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="console" style="font-family: monospace; padding: 5px"></div>
|
||||
<p id="console" style="font-family: monospace;"></p>
|
||||
<script>
|
||||
var screenConsole = document.getElementById("console");
|
||||
var log = function() {
|
||||
var text = "";
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
text += arguments[i] + " ";
|
||||
}
|
||||
console.log(text);
|
||||
text = text.replace(/ /gm, " ");
|
||||
text = text.replace(/\n/gm, "<br/>");
|
||||
screenConsole.innerHTML += "<br/>" + text;
|
||||
};
|
||||
|
||||
var HOSTNAME = location.hostname;
|
||||
var PORT = location.port;
|
||||
var PROTOCOL = location.protocol.replace(/:/, "");
|
||||
var QUERY = location.search.replace(/\?/, "");
|
||||
var match = QUERY.match(/token=([^&]+)/);
|
||||
var TOKEN = match ? match[1] : null;
|
||||
var consoleElement = document.getElementById("console");
|
||||
var screenConsole = new ScreenConsole(consoleElement);
|
||||
var queryParameters = QueryParser.parseQuery();
|
||||
var TOKEN = queryParameters["token"];
|
||||
if (TOKEN)
|
||||
document.cookie =
|
||||
"resume_token=" + TOKEN + "; expires=Fri, 31 Dec 9999 23:59:59 GMT";
|
||||
|
||||
log("Loading next test ...");
|
||||
screenConsole.log("Loading next test ...");
|
||||
|
||||
WaveService.readNextTest(
|
||||
TOKEN,
|
||||
function(url) {
|
||||
log("Redirecting to " + url);
|
||||
function (url) {
|
||||
screenConsole.log("Redirecting to " + url);
|
||||
location.href = url;
|
||||
},
|
||||
function() {
|
||||
log("Connection failed.");
|
||||
function () {
|
||||
screenConsole.log("Connection failed.");
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -128,8 +128,8 @@
|
|||
})
|
||||
);
|
||||
});
|
||||
WaveService.readResultsConfig(function(config) {
|
||||
resultsUi.state.importResultsEnabled = config.importEnabled;
|
||||
WaveService.readStatus(function(config) {
|
||||
resultsUi.state.importResultsEnabled = config.importResultsEnabled;
|
||||
resultsUi.state.reportsEnabled = config.reportsEnabled;
|
||||
resultsUi.renderManageSessions();
|
||||
});
|
||||
|
|
|
@ -122,8 +122,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
WaveService.connectHttpPolling(TOKEN);
|
||||
WaveService.onMessage(function(message) {
|
||||
WaveService.addSessionEventListener(TOKEN, function(message) {
|
||||
if (message.type !== "status") return;
|
||||
if (message.data !== "running") return;
|
||||
WaveService.readNextTest(TOKEN, function(url) {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -50,7 +50,6 @@
|
|||
var resultData;
|
||||
var rawResult = parsedQuery.result;
|
||||
if (rawResult) {
|
||||
console.log(decodeURIComponent(rawResult));
|
||||
resultData = JSON.parse(decodeURIComponent(rawResult));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Creating Session - Web Platform Tests</title>
|
||||
<link rel="stylesheet" href="css/bulma-0.7.5/bulma.min.css" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<link rel="stylesheet" href="css/main.css" />
|
||||
<script src="lib/wave-service.js"></script>
|
||||
<style>
|
||||
#console {
|
||||
font-family: monospace;
|
||||
}
|
||||
.spinner {
|
||||
height: 0.7em;
|
||||
width: 0.7em;
|
||||
}
|
||||
.fa-pulse {
|
||||
-webkit-animation: fa-spin 1s steps(8) infinite;
|
||||
animation: fa-spin 1s steps(8) infinite;
|
||||
}
|
||||
@-webkit-keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(1turn);
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
@keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(1turn);
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<img src="res/wavelogo_2016.jpg" alt="WAVE Logo" class="site-logo" />
|
||||
|
||||
<h1 class="title is-spaced">
|
||||
<span>
|
||||
<img src="res/spinner-solid.svg" class="spinner fa-pulse" />
|
||||
</span>
|
||||
Creating Session
|
||||
</h1>
|
||||
<div id="content">
|
||||
<div id="details-wrapper">
|
||||
<div class="detail">
|
||||
<div>Reference Tokens:</div>
|
||||
<div id="reference-tokens"></div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div>Test Paths:</div>
|
||||
<div id="test-path"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="console"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
var screenConsole = document.getElementById("console");
|
||||
var log = function() {
|
||||
var text = "";
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
text += arguments[i] + " ";
|
||||
}
|
||||
text = text.replace(/ /gm, " ");
|
||||
text = text.replace(/\n/gm, "<br/>");
|
||||
screenConsole.innerHTML += "<br/>" + text;
|
||||
};
|
||||
|
||||
window.onerror = function(error) {
|
||||
log(error);
|
||||
};
|
||||
|
||||
var HOSTNAME = location.hostname;
|
||||
var PORT = location.port;
|
||||
var PROTOCOL = location.protocol.replace(/:/, "");
|
||||
var QUERY = decodeURIComponent(location.search.replace(/\?/, ""));
|
||||
var match = QUERY.match(/token=([^&]+)/);
|
||||
var TOKEN = match ? match[1] : null;
|
||||
QUERY += /[\?&]path=/.test(location.search) ? "" : "&resume=1";
|
||||
|
||||
var parsedQuery = {};
|
||||
|
||||
var parts = QUERY.split("&");
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i];
|
||||
var key = part.split("=")[0];
|
||||
var value = part.split("=")[1];
|
||||
parsedQuery[key] = value;
|
||||
}
|
||||
|
||||
var includedTests = [];
|
||||
var paths = parsedQuery["path"].split(",");
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
includedTests.push(path.trim());
|
||||
}
|
||||
|
||||
var excludedTests = [
|
||||
"/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-manual-classic.html",
|
||||
"/html/semantics/scripting-1/the-script-element/module/dynamic-import/no-active-script-manual-module.html"
|
||||
];
|
||||
|
||||
var referenceTokens = [];
|
||||
if (parsedQuery["reftoken"]) {
|
||||
var paths = parsedQuery["reftoken"].split(",");
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
referenceTokens.push(path.trim());
|
||||
}
|
||||
}
|
||||
|
||||
var testPath = document.getElementById("test-path");
|
||||
var paths = includedTests;
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
testPath.innerText += path + "\n";
|
||||
}
|
||||
var referenceTokensElement = document.getElementById("reference-tokens");
|
||||
if (referenceTokens.length === 0) {
|
||||
referenceTokensElement.innerText = "none";
|
||||
} else {
|
||||
for (var i = 0; i < referenceTokens.length; i++) {
|
||||
var token = referenceTokens[i];
|
||||
referenceTokensElement.innerText += token + "\n";
|
||||
}
|
||||
}
|
||||
log("Please wait ...");
|
||||
WaveService.createSession(
|
||||
{
|
||||
tests: { include: includedTests, exclude: excludedTests },
|
||||
referenceTokens: referenceTokens
|
||||
},
|
||||
function(token) {
|
||||
log("Session created successfully! Token: " + token);
|
||||
log("Redirecting ...");
|
||||
location.href = "/newsession.html?token=" + token;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче