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:
Fritz Heiden 2021-11-17 13:24:23 +00:00 коммит произвёл moz-wptsync-bot
Родитель e80bd061fe
Коммит 3ab7df3afe
73 изменённых файлов: 16543 добавлений и 2832 удалений

3
testing/web-platform/tests/tools/wave/.gitignore поставляемый Normal file
Просмотреть файл

@ -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 = {

35
testing/web-platform/tests/tools/wave/package-lock.json сгенерированный Normal file
Просмотреть файл

@ -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, "&nbsp;");
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, "&nbsp;");
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>

15
testing/web-platform/tests/tools/wave/www/lib/jszip.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -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, "&nbsp;");
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, "&nbsp;");
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>