1 module selenium.api;
2 
3 import core.vararg;
4 import std.stdio;
5 
6 import vibe.d;
7 
8 import vibe.http.client;
9 import vibe.data.json;
10 import std.typecons;
11 
12 //hack for development dub
13 alias Nint = Nullable!int;
14 Nint a;
15 
16 /// https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/url
17 
18 /**
19  * Aggregates all information about a model error status.
20  */
21 class SeleniumException : Exception {
22 	/**
23 	 * Create the exception
24 	 */
25 	this(string msg, string file = __FILE__, ulong line = cast(ulong)__LINE__, Throwable next = null) {
26 		super(msg);
27 	}
28 
29 	this(Json data, string file = __FILE__, ulong line = cast(ulong)__LINE__, Throwable next = null) {
30 		super("Selenium server error: " ~ data["value"]["message"].to!string);
31 	}
32 }
33 
34 enum string[int] StatusDescription = [
35 	0  : "The command executed successfully.",
36 	6  : "A session is either terminated or not started",
37 	7  : "An element could not be located on the page using the given search parameters.",
38 	8  : "A request to switch to a frame could not be satisfied because the frame could not be found.",
39 	9  : "The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.",
40 	10 : "An element command failed because the referenced element is no longer attached to the DOM.",
41 	11 : "An element command could not be completed because the element is not visible on the page.",
42 	12 : "An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).",
43 	13 : "An unknown server-side error occurred while processing the command.",
44 	15 : "An attempt was made to select an element that cannot be selected.",
45 	17 : "An error occurred while executing user supplied JavaScript.",
46 	19 : "An error occurred while searching for an element by XPath.",
47 	21 : "An operation did not complete before its timeout expired.",
48 	23 : "A request to switch to a different window could not be satisfied because the window could not be found.",
49 	24 : "An illegal attempt was made to set a cookie under a different domain than the current page.",
50 	25 : "A request to set a cookie's value could not be satisfied.",
51 	26 : "A modal dialog was open, blocking this operation",
52 	27 : "An attempt was made to operate on a modal dialog when one was not open.",
53 	28 : "A script did not complete before its timeout expired.",
54 	29 : "The coordinates provided to an interactions operation are invalid.",
55 	30 : "IME was not available.",
56 	31 : "An IME engine could not be started.",
57 	32 : "Argument was an invalid selector (e.g. XPath/CSS).",
58 	33 : "A new session could not be created.",
59 	34 : "Target provided for a move action is out of bounds."
60 ];
61 
62 enum LocatorStrategy : string {
63 	ClassName = "class name",
64 	CssSelector = "css selector",
65 	Id = "id",
66 	Name = "name",
67 	LinkText = "link text",
68 	PartialLinkText = "partial link text",
69 	TagName = "tag name",
70 	XPath = "xpath"
71 }
72 
73 enum Browser: string {
74 	android = "android",
75 	chrome = "chrome",
76 	firefox = "firefox",
77 	htmlunit = "htmlunit",
78 	internetExplorer = "internet explorer",
79 	iPhone = "iPhone",
80 	iPad = "iPad",
81 	opera = "opera",
82 	safari = "safari"
83 }
84 
85 enum Platform: string {
86 	windows = "WINDOWS",
87 	xp = "XP",
88 	vista = "VISTA",
89 	mac = "MAC",
90 	linux = "LINUX",
91 	unix = "UNIX",
92 	android = "ANDROID"
93 }
94 
95 enum AlertBehaviour: string {
96 	accept = "accept",
97 	dismiss = "dismiss",
98 	ignore = "ignore"
99 }
100 
101 enum LogType: string {
102 	client = "client",
103 	driver = "driver",
104 	browser = "browser",
105 	server = "server"
106 }
107 
108 enum TimeoutType: string {
109 	script = "script",
110 	implicit = "implicit",
111 	pageLoad = "page load"
112 }
113 
114 enum Orientation: string {
115 	landscape = "LANDSCAPE",
116 	portrait = "PORTRAIT"
117 }
118 
119 enum MouseButton: int {
120 	left = 0,
121 	middle = 1,
122 	right = 2
123 }
124 
125 enum LogLevel: string {
126 	ALL = "ALL",
127 	DEBUG = "DEBUG",
128 	INFO = "INFO",
129 	WARNING = "WARNING",
130 	SEVERE = "SEVERE",
131 	OFF = "OFF"
132 }
133 
134 enum CacheStatus {
135 	uncached = 0,
136 	idle = 1,
137 	checking = 2,
138 	downloading = 3,
139 	update_ready = 4,
140 	obsolete = 5
141 }
142 
143 struct Capabilities {
144 	@optional {
145 		Browser browserName;
146 		string browserVersion;
147 		Platform platform;
148 
149 		bool takesScreenshot;
150 		bool handlesAlerts;
151 		bool cssSelectorsEnabled;
152 
153 		bool javascriptEnabled;
154 		bool databaseEnabled;
155 		bool locationContextEnabled;
156 		bool applicationCacheEnabled;
157 		bool browserConnectionEnabled;
158 		bool webStorageEnabled;
159 		bool acceptSslCerts;
160 		bool rotatable;
161 		bool nativeEvents;
162 		//ProxyObject proxy;
163 		AlertBehaviour unexpectedAlertBehaviour;
164 		int elementScrollBehavior;
165 
166 		@name("webdriver.remote.sessionid")
167 		string webdriver_remote_sessionid;
168 
169 		@name("webdriver.remote.quietExceptions")
170 		bool webdriver_remote_quietExceptions;
171 	}
172 
173 	static Capabilities chrome() {
174 		auto capabilities = Capabilities();
175 		capabilities.browserName = Browser.chrome;
176 
177 		return capabilities;
178 	}
179 }
180 
181 struct SessionResponse(T) {
182 
183 	@optional {
184 		string sessionId;
185 		long hCode;
186 		long status;
187 
188 		string state;
189 
190 		T value;
191 	}
192 }
193 
194 struct Size {
195 	long width;
196 	long height;
197 }
198 
199 struct Position {
200 	long x;
201 	long y;
202 }
203 
204 struct Cookie {
205 	string name;
206 	string value;
207 
208 	@optional {
209 		string path;
210 		string domain;
211 		bool secure;
212 		bool httpOnly;
213 		long expiry;
214 	}
215 }
216 
217 struct ElementLocator {
218 	LocatorStrategy using;
219 	string value;
220 }
221 
222 
223 ElementLocator classLocator(string name) {
224 	return ElementLocator(LocatorStrategy.ClassName, name);
225 }
226 
227 ElementLocator cssLocator(string name) {
228 	return ElementLocator(LocatorStrategy.CssSelector, name);
229 }
230 
231 ElementLocator idLocator(string name) {
232 	return ElementLocator(LocatorStrategy.Id, name);
233 }
234 
235 ElementLocator nameLocator(string name) {
236 	return ElementLocator(LocatorStrategy.Name, name);
237 }
238 
239 ElementLocator linkTextLocator(string name) {
240 	return ElementLocator(LocatorStrategy.LinkText, name);
241 }
242 
243 ElementLocator partialLinkTextLocator(string name) {
244 	return ElementLocator(LocatorStrategy.PartialLinkText, name);
245 }
246 
247 ElementLocator tagLocator(string name) {
248 	return ElementLocator(LocatorStrategy.TagName, name);
249 }
250 
251 ElementLocator xpathLocator(string name) {
252 	return ElementLocator(LocatorStrategy.XPath, name);
253 }
254 
255 struct WebElement {
256 	string ELEMENT;
257 }
258 
259 struct GeoLocation(T) {
260 	T latitude;
261 	T longitude;
262 	T altitude;
263 }
264 
265 struct LogEntry {
266 	long timestamp;
267 	LogLevel level;
268 	string message;
269 }
270 
271 class SeleniumApiConnector {
272 	const {
273 		string serverUrl;
274 
275 		Capabilities desiredCapabilities;
276 		Capabilities requiredCapabilities;
277 		Capabilities session;
278 		Capabilities responseSession;
279 	}
280 
281 	immutable {
282 		SeleniumApiConnection connection;
283 		SeleniumApi api;
284 	}
285 
286 	this(const string serverUrl,
287       const Capabilities desiredCapabilities,
288       const Capabilities requiredCapabilities = Capabilities(),
289       const Capabilities session = Capabilities()) {
290 
291 		this.serverUrl = serverUrl;
292 		this.desiredCapabilities = desiredCapabilities;
293 		this.requiredCapabilities = requiredCapabilities;
294 		this.session = session;
295 
296 		responseSession = makeRequest(HTTPMethod.POST, serverUrl ~ "/session", ["desiredCapabilities": desiredCapabilities])
297 								.deserializeJson!(SessionResponse!Capabilities).value;
298 
299 		connection = new immutable SeleniumApiConnection(serverUrl, responseSession.webdriver_remote_sessionid.idup);
300 		api = new immutable SeleniumApi(connection);
301 	}
302 }
303 
304 class SeleniumApiConnection {
305 	immutable {
306 		string sessionId;
307 		string serverUrl;
308 	}
309 
310 	this(immutable string serverUrl, string sessionId) immutable {
311 		this.sessionId = sessionId;
312 		this.serverUrl = serverUrl;
313 	}
314 
315 	inout {
316 		void disconnect() {
317 			makeRequest(HTTPMethod.DELETE,
318 									serverUrl ~ "/session/" ~ sessionId);
319 		}
320 
321 		void DELETE(T)(string path, T values = null) {
322 			makeRequest(HTTPMethod.DELETE,
323 									serverUrl ~ "/session/" ~ sessionId ~ path,
324 									values);
325 		}
326 
327 		void DELETE(string path) {
328 			makeRequest(HTTPMethod.DELETE,
329 									serverUrl ~ "/session/" ~ sessionId ~ path);
330 		}
331 
332 		void POST(T)(string path, T values) {
333 			makeRequest(HTTPMethod.POST,
334 									serverUrl ~ "/session/" ~ sessionId ~ path,
335 									values);
336 		}
337 
338 		void POST(string path) {
339 			makeRequest(HTTPMethod.POST,
340 									serverUrl ~ "/session/" ~ sessionId ~ path);
341 		}
342 
343 		auto POST(U, T)(string path, T values) {
344 			return makeRequest(HTTPMethod.POST,
345 									serverUrl ~ "/session/" ~ sessionId ~ path,
346 									values).deserializeJson!(SessionResponse!U).value;
347 		}
348 
349 		auto POST(U)(string path) {
350 			return makeRequest(HTTPMethod.POST,
351 									serverUrl ~ "/session/" ~ sessionId ~ path)
352 									.deserializeJson!(SessionResponse!U).value;
353 		}
354 
355 		T GET(T)(string path) {
356 			return makeRequest(HTTPMethod.GET, serverUrl ~ "/session/" ~ sessionId ~ path)
357 									.deserializeJson!(SessionResponse!T).value;
358 		}
359 	}
360 }
361 
362 class SeleniumApi {
363 	const SeleniumApiConnection connection;
364 
365 	this(inout SeleniumApiConnection connection) inout {
366 		this.connection = connection;
367 	}
368 
369 	inout {
370 		auto timeouts(TimeoutType type, long ms) {
371 			connection.POST("/timeouts", ["type": Json(type), "ms": Json(ms)]);
372 			return this;
373 		}
374 
375 		auto timeoutsAsyncScript(long ms) {
376 			connection.POST("/timeouts/async_script", ["ms": Json(ms)]);
377 			return this;
378 		}
379 
380 		auto timeoutsImplicitWait(long ms) {
381 			connection.POST("/timeouts/implicit_wait", ["ms": Json(ms)]);
382 			return this;
383 		}
384 
385 		auto windowHandle() {
386 			return connection.GET!string("/window_handle");
387 		}
388 
389 		auto windowHandles() {
390 			return connection.GET!(string[])("/window_handles");
391 		}
392 
393 		auto url(string url) {
394 			connection.POST("/url", ["url": Json(url)]);
395 			return this;
396 		}
397 
398 		auto url() {
399 			return connection.GET!string("/url");
400 		}
401 
402 		auto forward() {
403 			connection.POST("/forward");
404 			return this;
405 		}
406 
407 		auto back() {
408 			connection.POST("/back");
409 			return this;
410 		}
411 
412 		auto refresh() {
413 			connection.POST("/refresh");
414 			return this;
415 		}
416 
417 		auto execute(T = string)(string script, Json args = Json.emptyArray) {
418 			return connection.POST!T("/execute", [ "script": Json(script), "args": args ]);
419 		}
420 
421 		auto executeAsync(T = string)(string script, Json args = Json.emptyArray) {
422 			return connection.POST!T("/execute_async", [ "script": Json(script), "args": args ]);
423 		}
424 
425 		auto screenshot() {
426 			return connection.GET!string("/screenshot");
427 		}
428 
429 		auto imeAvailableEngines() {
430 			return connection.GET!string("/ime/available_engines");
431 		}
432 
433 		auto imeActiveEngine() {
434 			return connection.GET!string("/ime/active_engine");
435 		}
436 
437 		auto imeActivated() {
438 			return connection.GET!bool("/ime/activated");
439 		}
440 
441 		auto imeDeactivate() {
442 			connection.POST("/ime/deactivate");
443 			return this;
444 		}
445 
446 		auto imeActivate(string engine) {
447 			connection.POST("/ime/activate", ["engine": engine]);
448 			return this;
449 		}
450 
451 		auto frame(string id) {
452 			connection.POST("/frame", ["id": id]);
453 			return this;
454 		}
455 
456 		auto frame(long id) {
457 			connection.POST("/frame", ["id": id]);
458 			return this;
459 		}
460 
461 		auto frame(WebElement element) {
462 			connection.POST("/frame", element);
463 			return this;
464 		}
465 
466 		auto frameParent() {
467 			connection.POST("/frame/parent");
468 			return this;
469 		}
470 
471 		auto selectWindow(string name) {
472 			connection.POST("/window", ["name": name]);
473 			return this;
474 		}
475 
476 		auto windowClose() {
477 			connection.DELETE("/window");
478 			return this;
479 		}
480 
481 		auto windowSize(Size size) {
482 			connection.POST("/window/size", size);
483 			return this;
484 		}
485 
486 		auto windowSize() {
487 			return connection.GET!Size("/window/size");
488 		}
489 
490 		auto windowMaximize() {
491 			connection.POST("/window/maximize");
492 			return this;
493 		}
494 
495 		auto cookie() {
496 			return connection.GET!(Cookie[])("/cookie");
497 		}
498 
499 		auto setCookie(Cookie cookie) {
500 			struct Body {
501 				Cookie cookie;
502 			}
503 
504 			connection.POST("/cookie", Body(cookie));
505 			return this;
506 		}
507 
508 		auto deleteAllCookies() {
509 			connection.DELETE("/cookie");
510 			return this;
511 		}
512 
513 		auto deleteCookie(string name) {
514 			connection.DELETE("/cookie/" ~ name);
515 			return this;
516 		}
517 
518 		auto source() {
519 			return connection.GET!string("/source");
520 		}
521 
522 		auto title() {
523 			return connection.GET!string("/title");
524 		}
525 
526 		auto element(ElementLocator locator) {
527 			return connection.POST!WebElement("/element", locator);
528 		}
529 
530 		auto elements(ElementLocator locator) {
531 			return connection.POST!(WebElement[])("/elements", locator);
532 		}
533 
534 		auto activeElement() {
535 			return connection.POST!WebElement("/element/active");
536 		}
537 
538 		auto elementFromElement(string initialElemId, ElementLocator locator) {
539 			return connection.POST!WebElement("/element/" ~ initialElemId ~ "/element", locator);
540 		}
541 
542 		auto elementsFromElement(string initialElemId, ElementLocator locator) {
543 			return connection.POST!(WebElement[])("/element/" ~ initialElemId ~ "/elements", locator);
544 		}
545 
546 		auto clickElement(string elementId) {
547 			connection.POST("/element/" ~ elementId ~ "/click");
548 			return this;
549 		}
550 
551 		auto submitElement(string elementId) {
552 			connection.POST("/element/" ~ elementId ~ "/submit");
553 			return this;
554 		}
555 
556 		auto elementText(string elementId) {
557 			return connection.GET!string("/element/" ~ elementId ~ "/text");
558 		}
559 
560 		auto sendKeys(string elementId, string[] value) {
561 			struct Body {
562 				string[] value;
563 			}
564 
565 			connection.POST("/element/" ~ elementId ~ "/value", Body(value));
566 			return this;
567 		}
568 
569 		auto sendKeysToActiveElement(const(string[]) value) {
570 			struct Body {
571 				const(string[]) value;
572 			}
573 
574 			connection.POST("/keys", Body(value));
575 			return this;
576 		}
577 
578 		auto elementName(string elementId) {
579 			return connection.GET!string("/element/" ~ elementId ~ "/name");
580 		}
581 
582 		auto clearElementValue(string elementId) {
583 			connection.POST("/element/" ~ elementId ~ "/clear");
584 			return this;
585 		}
586 
587 		auto elementSelected(string elementId) {
588 			return connection.GET!bool("/element/" ~ elementId ~ "/selected");
589 		}
590 
591 		auto elementEnabled(string elementId) {
592 			return connection.GET!bool("/element/" ~ elementId ~ "/enabled");
593 		}
594 
595 		auto elementValue(string elementId, string attribute) {
596 			return connection.GET!string("/element/" ~ elementId ~ "/attribute/" ~ attribute);
597 		}
598 
599 		auto elementEqualsOther(string firstElementId, string secondElementId) {
600 			return connection.GET!bool("/element/" ~ firstElementId ~ "/equals/" ~ secondElementId);
601 		}
602 
603 		auto elementDisplayed(string elementId) {
604 			return connection.GET!bool("/element/" ~ elementId ~ "/displayed");
605 		}
606 
607 		auto elementLocation(string elementId) {
608 			return connection.GET!Position("/element/" ~ elementId ~ "/location");
609 		}
610 
611 		auto elementLocationInView(string elementId) {
612 			return connection.GET!Position("/element/" ~ elementId ~ "/location_in_view");
613 		}
614 
615 		auto elementSize(string elementId) {
616 			return connection.GET!Size("/element/" ~ elementId ~ "/size");
617 		}
618 
619 		auto elementCssPropertyName(string elementId, string propertyName) {
620 			return connection.GET!string("/element/" ~ elementId ~ "/css/" ~ propertyName);
621 		}
622 
623 		auto orientation() {
624 			return connection.GET!Orientation("/orientation");
625 		}
626 
627 		auto setOrientation(Orientation orientation) {
628 			struct Body {
629 				Orientation orientation;
630 			}
631 
632 			return connection.POST("/orientation", Body(orientation));
633 		}
634 
635 		auto alertText() {
636 			return connection.GET!string("/alert_text");
637 		}
638 
639 		auto setPromptText(string text) {
640 			connection.POST("/alert_text", ["text": text]);
641 			return this;
642 		}
643 
644 		auto acceptAlert() {
645 			connection.POST("/accept_alert");
646 			return this;
647 		}
648 
649 		auto dismissAlert() {
650 			connection.POST("/dismiss_alert");
651 			return this;
652 		}
653 
654 		auto moveTo(Position position) {
655 			connection.POST("/moveto", ["xoffset": position.x, "yoffset": position.y]);
656 			return this;
657 		}
658 
659 		auto moveTo(string elementId) {
660 			connection.POST("/moveto", ["element": elementId]);
661 			return this;
662 		}
663 
664 		auto moveTo(string elementId, Position position) {
665 			struct Body {
666 				string element;
667 				long xoffset;
668 				long yoffset;
669 			}
670 
671 			connection.POST("/moveto", Body(elementId, position.x, position.y));
672 			return this;
673 		}
674 
675 		auto click(MouseButton button = MouseButton.left) {
676 			connection.POST("/click", ["button": button]);
677 			return this;
678 		}
679 
680 		auto buttonDown(MouseButton button = MouseButton.left) {
681 			connection.POST("/buttondown", ["button": button]);
682 			return this;
683 		}
684 
685 		auto buttonUp(MouseButton button = MouseButton.left) {
686 			connection.POST("/buttonup", ["button": button]);
687 			return this;
688 		}
689 
690 		auto doubleClick() {
691 			connection.POST("/doubleclick");
692 			return this;
693 		}
694 
695 		auto touchClick(string elementId) {
696 			connection.POST("/touch/click", ["element": elementId]);
697 			return this;
698 		}
699 
700 		auto touchDown(Position position) {
701 			connection.POST("/touch/down", ["x": position.x, "y": position.y]);
702 			return this;
703 		}
704 
705 		auto touchUp(Position position) {
706 			connection.POST("/touch/up", ["x": position.x, "y": position.y]);
707 			return this;
708 		}
709 
710 		auto touchMove(Position position) {
711 			connection.POST("/touch/move", ["x": position.x, "y": position.y]);
712 			return this;
713 		}
714 
715 		auto touchScroll(string elementId, Position position) {
716 			struct Body {
717 				string element;
718 				long xoffset;
719 				long yoffset;
720 			}
721 
722 			connection.POST("/touch/scroll", Body(elementId, position.x, position.y));
723 			return this;
724 		}
725 
726 		auto touchScroll(Position position) {
727 			connection.POST("/touch/scroll", ["xoffset": position.x, "yoffset": position.y]);
728 			return this;
729 		}
730 
731 		auto touchDoubleClick(string elementId) {
732 			connection.POST("/touch/doubleclick", ["element": elementId]);
733 			return this;
734 		}
735 
736 		auto touchLongClick(string elementId) {
737 			connection.POST("/touch/longclick", ["element": elementId]);
738 			return this;
739 		}
740 
741 		auto touchFlick(string elementId, Position position, long speed) {
742 			struct Body {
743 				string element;
744 				long xoffset;
745 				long yoffset;
746 				long speed;
747 			}
748 
749 			connection.POST("/touch/flick", Body(elementId, position.x, position.y, speed));
750 			return this;
751 		}
752 
753 		auto touchFlick(long xSpeed, long ySpeed) {
754 			connection.POST("/touch/flick", [ "xspeed": xSpeed, "yspeed": ySpeed ]);
755 			return this;
756 		}
757 
758 		auto geoLocation() {
759 			return connection.GET!(GeoLocation!double)("/location");
760 		}
761 
762 		auto setGeoLocation(T)(T location) {
763 			connection.POST("/location", ["location": location]);
764 			return this;
765 		}
766 
767 		auto localStorage() {
768 			return connection.GET!(string[])("/local_storage");
769 		}
770 
771 		auto setLocalStorage(string key, string value) {
772 			connection.POST("/local_storage", ["key": key, "value": value]);
773 			return this;
774 		}
775 
776 		auto deleteLocalStorage() {
777 			connection.DELETE("/local_storage");
778 			return this;
779 		}
780 
781 		auto localStorage(string key) {
782 			return connection.GET!(string)("/local_storage/key/" ~ key);
783 		}
784 
785 		auto deleteLocalStorage(string key) {
786 			connection.DELETE("/local_storage/key/" ~ key);
787 			return this;
788 		}
789 
790 		auto localStorageSize() {
791 			return connection.GET!(long)("/local_storage/size");
792 		}
793 
794 		auto sessionStorage() {
795 			return connection.GET!(string[])("/session_storage");
796 		}
797 
798 		auto setSessionStorage(string key, string value) {
799 			connection.POST("/session_storage", ["key": key, "value": value]);
800 			return this;
801 		}
802 
803 		auto deleteSessionStorage() {
804 			connection.DELETE("/session_storage");
805 			return this;
806 		}
807 
808 		auto sessionStorage(string key) {
809 			return connection.GET!(string)("/session_storage/key/" ~ key);
810 		}
811 
812 		auto deleteSessionStorage(string key) {
813 			connection.DELETE("/session_storage/key/" ~ key);
814 			return this;
815 		}
816 
817 		auto sessionStorageSize() {
818 			return connection.GET!(long)("/session_storage/size");
819 		}
820 
821 		auto log(LogType logType) {
822 			return connection.POST!(LogEntry[])("/log", ["type": logType]);
823 		}
824 
825 		auto logTypes() {
826 			return connection.GET!(string[])("/log/types");
827 		}
828 
829 		auto applicationCacheStatus() {
830 			return connection.GET!CacheStatus("/application_cache/status");
831 		}
832 
833 		/*
834 	/session/:sessionId/element/:id - not yet implemented in Selenium
835 
836 	/session/:sessionId/log/types
837 	/session/:sessionId/application_cache/status*/
838 
839 
840 		auto wait(long ms) {
841 			sleep(ms.msecs);
842 			return this;
843 		}
844 	}
845 }
846 
847 private Json makeRequest(T)(HTTPMethod method, string path, T data) {
848 	import vibe.core.core : sleep;
849 	import core.time : msecs;
850 	import std.conv : to;
851 
852 	Json result;
853 	bool done = false;
854 
855 	requestHTTP(path,
856 		(scope req) {
857 			req.method = method;
858 			req.writeJsonBody(data);
859 		},
860 		(scope res) {
861 			result = res.readJson;
862 
863 			if(res.statusCode == 500) {
864 				throw new SeleniumException(result);
865 			}
866 			done = true;
867 		}
868 	);
869 
870 	return result;
871 }
872 
873 
874 private Json makeRequest(HTTPMethod method, string path) {
875 	import vibe.core.core : sleep;
876 	import core.time : msecs;
877 	import std.conv : to;
878 
879 	Json result;
880 	bool done = false;
881 
882 	requestHTTP(path,
883 		(scope req) {
884 			req.method = method;
885 		},
886 		(scope res) {
887 			result = res.readJson;
888 
889 			if(res.statusCode == 500) {
890 				throw new SeleniumException(result);
891 			}
892 			done = true;
893 		}
894 	);
895 	return result;
896 }