Unreviewed. Update W3C WebDriver imported tests.
[WebKit-https.git] / WebDriverTests / imported / w3c / tools / webdriver / webdriver / transport.py
1 import httplib
2 import json
3 import urlparse
4
5 import error
6
7
8 """Implements HTTP transport for the WebDriver wire protocol."""
9
10
11 class Response(object):
12     """
13     Describes an HTTP response received from a remote end whose
14     body has been read and parsed as appropriate.
15     """
16
17     def __init__(self, status, body):
18         self.status = status
19         self.body = body
20
21     def __repr__(self):
22         cls_name = self.__class__.__name__
23         if self.error:
24             return "<%s status=%s error=%s>" % (cls_name, self.status, repr(self.error))
25         return "<% status=%s body=%s>" % (cls_name, self.status, json.dumps(self.body))
26
27     def __str__(self):
28         return json.dumps(self.body, indent=2)
29
30     @property
31     def error(self):
32         if self.status != 200:
33             return error.from_response(self)
34         return None
35
36     @classmethod
37     def from_http(cls, http_response, decoder=json.JSONDecoder, **kwargs):
38         try:
39             body = json.load(http_response, cls=decoder, **kwargs)
40         except ValueError:
41             raise ValueError("Failed to decode response body as JSON:\n"
42                 "%s" % json.dumps(body, indent=2))
43
44         return cls(http_response.status, body)
45
46
47 class HTTPWireProtocol(object):
48     """
49     Transports messages (commands and responses) over the WebDriver
50     wire protocol.
51
52     Complex objects, such as ``webdriver.Element``, are by default
53     not marshaled to enable use of `session.transport.send` in WPT tests::
54
55         session = webdriver.Session("127.0.0.1", 4444)
56         response = transport.send("GET", "element/active", None)
57         print response.body["value"]
58         # => {u'element-6066-11e4-a52e-4f735466cecf': u'<uuid>'}
59
60     Automatic marshaling is provided by ``webdriver.protocol.Encoder``
61     and ``webdriver.protocol.Decoder``, which can be passed in to
62     ``HTTPWireProtocol.send`` along with a reference to the current
63     ``webdriver.Session``::
64
65         session = webdriver.Session("127.0.0.1", 4444)
66         response = transport.send("GET", "element/active", None,
67             encoder=protocol.Encoder, decoder=protocol.Decoder,
68             session=session)
69         print response.body["value"]
70         # => webdriver.Element
71     """
72
73     def __init__(self, host, port, url_prefix="/", timeout=None):
74         """
75         Construct interface for communicating with the remote server.
76
77         :param url: URL of remote WebDriver server.
78         :param wait: Duration to wait for remote to appear.
79         """
80         self.host = host
81         self.port = port
82         self.url_prefix = url_prefix
83
84         self._timeout = timeout
85
86     def url(self, suffix):
87         return urlparse.urljoin(self.url_prefix, suffix)
88
89     def send(self,
90              method,
91              uri,
92              body=None,
93              headers=None,
94              encoder=json.JSONEncoder,
95              decoder=json.JSONDecoder,
96              **codec_kwargs):
97         """
98         Send a command to the remote.
99
100         The request `body` must be JSON serialisable unless a
101         custom `encoder` has been provided.  This means complex
102         objects such as ``webdriver.Element`` are not automatically
103         made into JSON.  This behaviour is, however, provided by
104         ``webdriver.protocol.Encoder``, should you want it.
105
106         Similarly, the response body is returned au natural
107         as plain JSON unless a `decoder` that converts web
108         element references to ``webdriver.Element`` is provided.
109         Use ``webdriver.protocol.Decoder`` to achieve this behaviour.
110
111         :param method: `GET`, `POST`, or `DELETE`.
112         :param uri: Relative endpoint of the requests URL path.
113         :param body: Body of the request.  Defaults to an empty
114             dictionary if ``method`` is `POST`.
115         :param headers: Additional dictionary of headers to include
116             in the request.
117         :param encoder: JSON encoder class, which defaults to
118             ``json.JSONEncoder`` unless specified.
119         :param decoder: JSON decoder class, which defaults to
120             ``json.JSONDecoder`` unless specified.
121         :param codec_kwargs: Surplus arguments passed on to `encoder`
122             and `decoder` on construction.
123
124         :return: Instance of ``webdriver.transport.Response``
125             describing the HTTP response received from the remote end.
126
127         :raises ValueError: If `body` or the response body are not
128             JSON serialisable.
129         """
130         if body is None and method == "POST":
131             body = {}
132
133         try:
134             payload = json.dumps(body, cls=encoder, **codec_kwargs)
135         except ValueError:
136             raise ValueError("Failed to encode request body as JSON:\n"
137                 "%s" % json.dumps(body, indent=2))
138         if isinstance(payload, unicode):
139             payload = body.encode("utf-8")
140
141         if headers is None:
142             headers = {}
143
144         url = self.url(uri)
145
146         conn_kwargs = {}
147         if self._timeout is not None:
148             conn_kwargs["timeout"] = self._timeout
149
150         conn = httplib.HTTPConnection(
151             self.host, self.port, strict=True, **conn_kwargs)
152         conn.request(method, url, payload, headers)
153
154         try:
155             response = conn.getresponse()
156             return Response.from_http(
157                 response, decoder=decoder, **codec_kwargs)
158         finally:
159             conn.close()