├── src ├── Emakefile ├── webdrv_cap.erl ├── json.erl ├── webdrv_wire.erl └── webdrv_session.erl ├── test ├── Emakefile ├── webdrv_http_srv.erl ├── webdrv_misc_test.erl └── webdrv_eqc.erl ├── ebin ├── Emakefile └── webdrv.app ├── .gitignore ├── data ├── page0.html ├── page1.html ├── page2.html ├── windows.html └── elements.html ├── Makefile.in ├── configure.in ├── LICENSE ├── README.md ├── doc └── overview.edoc └── include └── webdrv.hrl /src/Emakefile: -------------------------------------------------------------------------------- 1 | {'*', [{outdir, "../ebin"}]}. 2 | -------------------------------------------------------------------------------- /test/Emakefile: -------------------------------------------------------------------------------- 1 | {'*', [{outdir, "../ebin"}]}. 2 | -------------------------------------------------------------------------------- /ebin/Emakefile: -------------------------------------------------------------------------------- 1 | {'../src/*', []}. 2 | {'../test/*', []}. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | .eqc-info 7 | current_counterexample.eqc 8 | -------------------------------------------------------------------------------- /data/page0.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |37 | Permission is hereby granted, free of charge, to any person 38 | obtaining a copy of this software and associated documentation 39 | files (the "Software"), to deal in the Software without 40 | restriction, including without limitation the rights to use, copy, 41 | modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be 46 | included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 49 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 50 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 51 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 52 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 53 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 54 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE.56 | @end 57 | ------------------------------------------------------------------ 58 | -------------------------------------------------------------------------------- /include/webdrv.hrl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Hans Svensson
10 | %%% Permission is hereby granted, free of charge, to any person 11 | %%% obtaining a copy of this software and associated documentation 12 | %%% files (the "Software"), to deal in the Software without 13 | %%% restriction, including without limitation the rights to use, copy, 14 | %%% modify, merge, publish, distribute, sublicense, and/or sell copies 15 | %%% of the Software, and to permit persons to whom the Software is 16 | %%% furnished to do so, subject to the following conditions: 17 | %%% 18 | %%% The above copyright notice and this permission notice shall be 19 | %%% included in all copies or substantial portions of the Software. 20 | %%% 21 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | %%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 25 | %%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 26 | %%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 27 | %%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | %%% SOFTWARE.29 | %%% @end 30 | %%%------------------------------------------------------------------- 31 | 32 | -module(webdrv_cap). 33 | 34 | -include("../include/webdrv.hrl"). 35 | 36 | -export([default/0, default_browser/1, 37 | to_json/1, 38 | default_firefox/0, default_chrome/0, default_htmlunit/0, 39 | default_safari/1]). 40 | 41 | %% @doc Return the default capability record. 42 | -spec default() -> #capability{}. 43 | default() -> 44 | #capability{ }. 45 | 46 | %% @hidden Just for the tests 47 | -spec default_browser(string() | atom()) -> #capability{}. 48 | default_browser(Browser) when is_atom(Browser) -> 49 | (default())#capability{ browserName = list_to_binary(atom_to_list(Browser))}; 50 | default_browser(Browser) when is_list(Browser) -> 51 | (default())#capability{ browserName = list_to_binary(Browser)}. 52 | 53 | %% @doc Return the default capability with browser set to chrome. 54 | -spec default_chrome() -> #capability{}. 55 | default_chrome() -> 56 | (default())#capability{ browserName = <<"chrome">>, version = <<"">> }. 57 | 58 | %% @doc Return the default capability with browser set to htmlunit. 59 | -spec default_htmlunit() -> #capability{}. 60 | default_htmlunit() -> 61 | (default())#capability{ browserName = <<"htmlunit">>, version = <<"">> }. 62 | 63 | %% @doc Return the default capability with browser set to firefox. 64 | -spec default_firefox() -> #capability{}. 65 | default_firefox() -> 66 | (default())#capability{ browserName = <<"firefox">> }. 67 | 68 | %% @doc Return the default capability with browser set to firefox. 69 | -spec default_safari(string()) -> #capability{}. 70 | default_safari(Device) -> 71 | (default())#capability{ browserName = <<"Safari">>, device = Device }. 72 | 73 | %% @doc Convert a capability (possibly null) to JSON format. Function is 74 | %% idempotent, i.e. converting an already converted object is fine. 75 | -spec to_json(capability()) -> jsonobj() | null. 76 | to_json(C = #capability{}) -> 77 | {obj, 78 | [ {Field, Arg1} || {Field, Arg1, Arg2} <- lists:zip3(record_info(fields, capability), 79 | tl(tuple_to_list(C)), 80 | tl(tuple_to_list(#capability{}))), 81 | (Arg1 =/= Arg2 orelse Field == javascriptEnabled)]}; 82 | to_json(Obj = {obj, _Any}) -> Obj; 83 | to_json(null) -> null. 84 | 85 | -------------------------------------------------------------------------------- /test/webdrv_misc_test.erl: -------------------------------------------------------------------------------- 1 | %%% @author Hans Svensson <> 2 | %%% @copyright (C) 2012, Hans Svensson 3 | %%% @doc 4 | %%% Webdriver implementation in Erlang 5 | %%% @end 6 | %%% Created : 20 Dec 2012 by Hans Svensson <> 7 | 8 | -module(webdrv_misc_test). 9 | 10 | -compile(export_all). 11 | -include("../include/webdrv.hrl"). 12 | -define(SELENIUM, "http://localhost:4444/wd/hub/"). 13 | -define(CHROMEDRIVER, "http://localhost:9515/"). 14 | 15 | relative_mess() -> 16 | {ok, _Pid} = webdrv_session:start_session(test, ?SELENIUM, webdrv_cap:default_htmlunit(), 10000), 17 | webdrv_session:set_url(test, "http://localhost:8088/elements.html"), 18 | ElemSelects = [ X || {X, _} <- all_elements() ], 19 | ElemSelects2 = [ X || {X, _} <- all_elements2() ], 20 | %% Test that we can really find all of these 21 | Elems = lists:map(fun({St, V}) -> {ok, E} = webdrv_session:find_element(test, St, V), E end, 22 | ElemSelects), 23 | io:format("Elems: ~p\n", [lists:zip(ElemSelects, Elems)]), 24 | Res = [ {E1, E2, find_rel(E,E2)} || {E, E1} <- lists:zip(Elems, ElemSelects), 25 | E2 <- ElemSelects2], 26 | io:format("\nRes:\n~p\n", [Res]), 27 | webdrv_session:stop_session(test). 28 | 29 | find_rel(E, {St, V}) -> 30 | case webdrv_session:find_element_rel(test, E, St, V) of 31 | {ok, _} -> io:format("."), ok; 32 | _ -> io:format("x"), fail 33 | end. 34 | 35 | open_window(Session, Window) -> 36 | webdrv_session:set_url(Session, "http://localhost:8088/windows.html"), 37 | {ok, E} = webdrv_session:find_element(Session, "name", Window), 38 | webdrv_session:click_element(Session, E), 39 | webdrv_session:set_window_focus(Session, Window). 40 | 41 | test5() -> 42 | R1 = webdrv_wire:get_status(#webdrv_opts{ url = ?SELENIUM }), 43 | io:format("Status: ~p\n", [R1]), 44 | 45 | {ok, _Pid} = webdrv_session:start_session(test, ?SELENIUM, 46 | webdrv_cap:default_htmlunit(), 10000), 47 | R = webdrv_session:get_status(test), 48 | io:format("Status: ~p\n", [R]), 49 | 50 | webdrv_session:stop_session(test). 51 | 52 | 53 | test4() -> 54 | {ok, _Pid} = webdrv_session:start_session(test, ?CHROMEDRIVER, 55 | webdrv_cap:default_chrome(), 10000), 56 | webdrv_session:execute(test, <<"window.name = 'WINDOW-0';">>, []), 57 | open_window(test, "WINDOW-1"), 58 | %% webdrv_session:close_window(test), 59 | webdrv_session:set_window_focus(test, "WINDOW-0"), 60 | 61 | Res = webdrv_session:get_screenshot(test), 62 | io:format("Res: ~p\n", [Res]), 63 | 64 | webdrv_session:stop_session(test). 65 | 66 | 67 | test3() -> 68 | {ok, _Pid} = webdrv_session:start_session(test, ?SELENIUM, webdrv_cap:default_htmlunit(), 10000), 69 | webdrv_session:set_url(test, "http://localhost:8088/elements.html"), 70 | Code = webdrv_session:get_page_source(test), 71 | io:format("CODE: ~p\n", [Code]), 72 | 73 | Res = webdrv_session:find_element(test, "class name", "class1"), 74 | io:format("RES: ~p\n", [Res]), 75 | 76 | webdrv_session:stop_session(test). 77 | 78 | 79 | test2() -> 80 | {ok, _Pid} = webdrv_session:start_session(test, ?CHROMEDRIVER, webdrv_cap:default_chrome(), 10000), 81 | %% {ok, _Pid} = webdrv_session:start_session(test, ?SELENIUM, webdrv_cap:default_chrome(), 10000), 82 | 83 | R = webdrv_session:get_cache_status(test), 84 | io:format("~p\n", [R]), 85 | 86 | %% [ io:format("~p\n", [{element(1, webdrv_session:get_log(test, L)), L}]) 87 | %% [ io:format("~p\n", [webdrv_session:get_log(test, L)]) 88 | %% || L <- ["browser","driver","client","server"] ], 89 | 90 | webdrv_session:stop_session(test). 91 | 92 | test() -> 93 | {ok, _Pid} = webdrv_session:start_session(test, ?SELENIUM, webdrv_cap:default_firefox(), 10000), 94 | 95 | Res = webdrv_session:execute(test, <<"window.name = 'WINDOW0';">>, []), 96 | io:format("Res: ~p\n", [Res]), 97 | 98 | Res2 = webdrv_session:set_window_maximize(test, "DFSJFLKSDJKLFJLKJF"), 99 | io:format("Res: ~p\n", [Res2]), 100 | 101 | webdrv_session:set_url(test, "http://80.252.210.134:8001/webtest/windows.html"), 102 | 103 | {ok, E0} = webdrv_session:find_element(test, "name", "WINDOW0"), 104 | webdrv_session:click_element(test, E0), 105 | 106 | {ok, E1} = webdrv_session:find_element(test, "name", "WINDOW1"), 107 | webdrv_session:click_element(test, E1), 108 | 109 | {ok, E2} = webdrv_session:find_element(test, "name", "WINDOW2"), 110 | webdrv_session:click_element(test, E2), 111 | 112 | webdrv_session:set_window_focus(test, "WINDOW0"), 113 | webdrv_session:set_url(test, "http://80.252.210.134:8001/webtest/page0.html"), 114 | 115 | webdrv_session:set_window_focus(test, "WINDOW1"), 116 | webdrv_session:set_url(test, "http://80.252.210.134:8001/webtest/page2.html"), 117 | 118 | webdrv_session:set_window_focus(test, "WINDOW2"), 119 | webdrv_session:set_url(test, "http://80.252.210.134:8001/webtest/page1.html"), 120 | 121 | webdrv_session:set_window_focus(test, "WINDOW2"), 122 | io:format("~p\n", [webdrv_session:get_page_source(test)]), 123 | 124 | webdrv_session:set_window_focus(test, "WINDOW1"), 125 | io:format("~p\n", [webdrv_session:get_page_source(test)]), 126 | 127 | webdrv_session:set_window_focus(test, "WINDOW0"), 128 | io:format("~p\n", [webdrv_session:get_page_source(test)]), 129 | 130 | 131 | webdrv_session:stop_session(test). 132 | 133 | 134 | links_w_text(SessName) -> 135 | {ok, Elements} = webdrv_session:find_elements(test, "tag name", "a"), 136 | %% [ io:format("~p\n", [E2]) || E <- Elements, E2 <- webdrv_session:get_element_info(SessName, E) ], 137 | Hrefs = [ Href || E <- Elements, {ok, Href} <- [webdrv_session:element_attribute(SessName, E, "href")] ], 138 | Texts = [ Txt || E <- Elements, {ok, Txt} <- [webdrv_session:get_text(SessName, E)] ], 139 | io:format("Elements: ~p\n", [lists:zip3(Elements, Hrefs, Texts)]). 140 | 141 | all_elements() -> 142 | [ 143 | {{"id", "id1"}, 3}, 144 | %% {{"id", "id2"}, 5}, 145 | 146 | {{"name", "name1"}, 3}, 147 | %% {{"name", "name2"}, 5}, 148 | 149 | {{"css selector", "ul"}, 2}, 150 | 151 | {{"class name", "class2"}, 5}, 152 | 153 | {{"tag name", "ul"}, 2}, 154 | 155 | {{"link text", "Link1"}, 18}, 156 | 157 | {{"partial link text", "nk1"}, 18}, 158 | 159 | {{"xpath", "//html"}, 1}, 160 | {{"xpath", "/html/body/ul"}, 2} 161 | ]. 162 | 163 | all_elements2() -> 164 | [ 165 | {{"id", "id1"}, 3}, 166 | %% {{"id", "id2"}, 5}, 167 | 168 | {{"name", "name1"}, 3}, 169 | %% {{"name", "name2"}, 5}, 170 | 171 | {{"css selector", "ul"}, 2}, 172 | 173 | {{"class name", "class2"}, 5}, 174 | 175 | {{"tag name", "ul"}, 2}, 176 | 177 | {{"link text", "Link1"}, 18}, 178 | 179 | {{"partial link text", "nk1"}, 18}, 180 | 181 | {{"xpath", "//html"}, 1}, 182 | {{"xpath", "/html/body/ul"}, 2}, 183 | {{"xpath", "li"}, 20} 184 | ]. 185 | 186 | 187 | res_sel_html() -> 188 | [{{"id","id1"},{"id","id1"},fail}, 189 | {{"id","id1"},{"name","name1"},fail}, 190 | {{"id","id1"},{"css selector","ul"},fail}, 191 | {{"id","id1"},{"class name","class2"},fail}, 192 | {{"id","id1"},{"tag name","ul"},fail}, 193 | {{"id","id1"},{"link text","Link2"},fail}, 194 | {{"id","id1"},{"partial link text","nk2"},fail}, 195 | {{"id","id1"},{"xpath","html"},fail}, 196 | {{"id","id1"},{"xpath","/html/body/ul"},ok}, 197 | {{"id","id1"},{"xpath","/html/body/a[last()]"},ok}, 198 | {{"name","name1"},{"id","id1"},fail}, 199 | {{"name","name1"},{"name","name1"},fail}, 200 | {{"name","name1"},{"css selector","ul"},fail}, 201 | {{"name","name1"},{"class name","class2"},fail}, 202 | {{"name","name1"},{"tag name","ul"},fail}, 203 | {{"name","name1"},{"link text","Link2"},fail}, 204 | {{"name","name1"},{"partial link text","nk2"},fail}, 205 | {{"name","name1"},{"xpath","html"},fail}, 206 | {{"name","name1"},{"xpath","/html/body/ul"},ok}, 207 | {{"name","name1"},{"xpath","/html/body/a[last()]"},ok}, 208 | {{"css selector","ul"},{"id","id1"},ok}, 209 | {{"css selector","ul"},{"name","name1"},ok}, 210 | {{"css selector","ul"},{"css selector","ul"},fail}, 211 | {{"css selector","ul"},{"class name","class2"},ok}, 212 | {{"css selector","ul"},{"tag name","ul"},fail}, 213 | {{"css selector","ul"},{"link text","Link2"},fail}, 214 | {{"css selector","ul"},{"partial link text","nk2"},fail}, 215 | {{"css selector","ul"},{"xpath","html"},fail}, 216 | {{"css selector","ul"},{"xpath","/html/body/ul"},ok}, 217 | {{"css selector","ul"},{"xpath","/html/body/a[last()]"},ok}, 218 | {{"class name","class2"},{"id","id1"},fail}, 219 | {{"class name","class2"},{"name","name1"},fail}, 220 | {{"class name","class2"},{"css selector","ul"},fail}, 221 | {{"class name","class2"},{"class name","class2"},fail}, 222 | {{"class name","class2"},{"tag name","ul"},fail}, 223 | {{"class name","class2"},{"link text","Link2"},fail}, 224 | {{"class name","class2"},{"partial link text","nk2"},fail}, 225 | {{"class name","class2"},{"xpath","html"},fail}, 226 | {{"class name","class2"},{"xpath","/html/body/ul"},ok}, 227 | {{"class name","class2"},{"xpath","/html/body/a[last()]"},ok}, 228 | {{"tag name","ul"},{"id","id1"},ok}, 229 | {{"tag name","ul"},{"name","name1"},ok}, 230 | {{"tag name","ul"},{"css selector","ul"},fail}, 231 | {{"tag name","ul"},{"class name","class2"},ok}, 232 | {{"tag name","ul"},{"tag name","ul"},fail}, 233 | {{"tag name","ul"},{"link text","Link2"},fail}, 234 | {{"tag name","ul"},{"partial link text","nk2"},fail}, 235 | {{"tag name","ul"},{"xpath","html"},fail}, 236 | {{"tag name","ul"},{"xpath","/html/body/ul"},ok}, 237 | {{"tag name","ul"},{"xpath","/html/body/a[last()]"},ok}, 238 | {{"link text","Link2"},{"id","id1"},fail}, 239 | {{"link text","Link2"},{"name","name1"},fail}, 240 | {{"link text","Link2"},{"css selector","ul"},fail}, 241 | {{"link text","Link2"},{"class name","class2"},fail}, 242 | {{"link text","Link2"},{"tag name","ul"},fail}, 243 | {{"link text","Link2"},{"link text","Link2"},fail}, 244 | {{"link text","Link2"},{"partial link text","nk2"},fail}, 245 | {{"link text","Link2"},{"xpath","html"},fail}, 246 | {{"link text","Link2"},{"xpath","/html/body/ul"},ok}, 247 | {{"link text","Link2"},{"xpath","/html/body/a[last()]"},ok}, 248 | {{"partial link text","nk2"},{"id","id1"},fail}, 249 | {{"partial link text","nk2"},{"name","name1"},fail}, 250 | {{"partial link text","nk2"},{"css selector","ul"},fail}, 251 | {{"partial link text","nk2"},{"class name","class2"},fail}, 252 | {{"partial link text","nk2"},{"tag name","ul"},fail}, 253 | {{"partial link text","nk2"},{"link text","Link2"},fail}, 254 | {{"partial link text","nk2"},{"partial link text","nk2"},fail}, 255 | {{"partial link text","nk2"},{"xpath","html"},fail}, 256 | {{"partial link text","nk2"},{"xpath","/html/body/ul"},ok}, 257 | {{"partial link text","nk2"},{"xpath","/html/body/a[last()]"},ok}, 258 | {{"xpath","html"},{"id","id1"},ok}, 259 | {{"xpath","html"},{"name","name1"},ok}, 260 | {{"xpath","html"},{"css selector","ul"},ok}, 261 | {{"xpath","html"},{"class name","class2"},ok}, 262 | {{"xpath","html"},{"tag name","ul"},ok}, 263 | {{"xpath","html"},{"link text","Link2"},ok}, 264 | {{"xpath","html"},{"partial link text","nk2"},ok}, 265 | {{"xpath","html"},{"xpath","html"},fail}, 266 | {{"xpath","html"},{"xpath","/html/body/ul"},ok}, 267 | {{"xpath","html"},{"xpath","/html/body/a[last()]"},ok}, 268 | {{"xpath","/html/body/ul"},{"id","id1"},ok}, 269 | {{"xpath","/html/body/ul"},{"name","name1"},ok}, 270 | {{"xpath","/html/body/ul"},{"css selector","ul"},fail}, 271 | {{"xpath","/html/body/ul"},{"class name","class2"},ok}, 272 | {{"xpath","/html/body/ul"},{"tag name","ul"},fail}, 273 | {{"xpath","/html/body/ul"},{"link text","Link2"},fail}, 274 | {{"xpath","/html/body/ul"},{"partial link text","nk2"},fail}, 275 | {{"xpath","/html/body/ul"},{"xpath","html"},fail}, 276 | {{"xpath","/html/body/ul"},{"xpath","/html/body/ul"},ok}, 277 | {{"xpath","/html/body/ul"},{"xpath","/html/body/a[last()]"},ok}, 278 | {{"xpath","/html/body/a[last()]"},{"id","id1"},fail}, 279 | {{"xpath","/html/body/a[last()]"},{"name","name1"},fail}, 280 | {{"xpath","/html/body/a[last()]"},{"css selector","ul"},fail}, 281 | {{"xpath","/html/body/a[last()]"},{"class name","class2"},fail}, 282 | {{"xpath","/html/body/a[last()]"},{"tag name","ul"},fail}, 283 | {{"xpath","/html/body/a[last()]"},{"link text","Link2"},fail}, 284 | {{"xpath","/html/body/a[last()]"},{"partial link text","nk2"},fail}, 285 | {{"xpath","/html/body/a[last()]"},{"xpath","html"},fail}, 286 | {{"xpath","/html/body/a[last()]"},{"xpath","/html/body/ul"},ok}, 287 | {{"xpath","/html/body/a[last()]"},{"xpath","/html/body/a[last()]"},ok}] 288 | . 289 | -------------------------------------------------------------------------------- /src/json.erl: -------------------------------------------------------------------------------- 1 | %% JSON - RFC 4627 - for Erlang 2 | %%--------------------------------------------------------------------------- 3 | %% @author Tony Garnock-Jones
Permission is hereby granted, free of charge, to any person 62 | %% obtaining a copy of this software and associated documentation 63 | %% files (the "Software"), to deal in the Software without 64 | %% restriction, including without limitation the rights to use, copy, 65 | %% modify, merge, publish, distribute, sublicense, and/or sell copies 66 | %% of the Software, and to permit persons to whom the Software is 67 | %% furnished to do so, subject to the following conditions: 68 | %% 69 | %% The above copyright notice and this permission notice shall be 70 | %% included in all copies or substantial portions of the Software. 71 | %% 72 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 73 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 74 | %% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 75 | %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 76 | %% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 77 | %% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 78 | %% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 79 | %% SOFTWARE.80 | 81 | %% @type json() = jsonobj() | jsonarray() | jsonnum() | jsonstr() | true | false | null. An Erlang representation of a general JSON value. 82 | %% @type jsonobj() = {obj, [{jsonkey(), json()}]}. A JSON "object" or "struct". 83 | %% @type jsonkey() = string(). A field-name within a JSON "object". 84 | %% @type jsonarray() = [json()]. A JSON array value. 85 | %% @type jsonnum() = integer() | float(). A JSON numeric value. 86 | %% @type jsonstr() = binary(). A JSON string value. 87 | 88 | -module(json). 89 | 90 | -export([mime_type/0, encode/1, decode/1]). 91 | -export([encode_noauto/1, encode_noauto/2, decode_noauto/1]). 92 | -export([unicode_decode/1, unicode_encode/1]). 93 | -export([from_record/3, to_record/3]). 94 | -export([hex_digit/1, digit_hex/1]). 95 | -export([get_field/2, get_field/3, set_field/3, exclude_field/2]). 96 | -export([equiv/2]). 97 | 98 | %% @spec () -> string() 99 | %% @doc Returns the IANA-registered MIME type for JSON data. 100 | mime_type() -> 101 | "application/json; charset=utf-8". 102 | 103 | %% @spec (json()) -> [byte()] 104 | %% 105 | %% @doc Encodes the JSON value supplied, first into Unicode 106 | %% codepoints, and then into UTF-8. 107 | %% 108 | %% The resulting string is a list of byte values that should be 109 | %% interpreted as UTF-8 encoded text. 110 | %% 111 | %% During encoding, atoms and binaries are accepted as keys of JSON 112 | %% objects (type {@link jsonkey()}) as well as the usual strings 113 | %% (lists of character codepoints). 114 | encode(X) -> 115 | unicode_encode({'utf-8', encode_noauto(X)}). 116 | 117 | %% @spec (json()) -> string() 118 | %% 119 | %% @doc Encodes the JSON value supplied into raw Unicode codepoints. 120 | %% 121 | %% The resulting string may contain codepoints with value >=128. You 122 | %% can use {@link unicode_encode/1} to UTF-encode the results, if 123 | %% that's appropriate for your application. 124 | %% 125 | %% During encoding, atoms and binaries are accepted as keys of JSON 126 | %% objects (type {@link jsonkey()}) as well as the usual strings 127 | %% (lists of character codepoints). 128 | encode_noauto(X) -> 129 | lists:reverse(encode_noauto(X, [])). 130 | 131 | %% @spec (json(), string()) -> string() 132 | %% 133 | %% @doc As {@link encode_noauto/1}, but prepends reversed text 134 | %% to the supplied accumulator string. 135 | encode_noauto(true, Acc) -> 136 | "eurt" ++ Acc; 137 | encode_noauto(false, Acc) -> 138 | "eslaf" ++ Acc; 139 | encode_noauto(null, Acc) -> 140 | "llun" ++ Acc; 141 | encode_noauto(Str, Acc) when is_binary(Str) -> 142 | Codepoints = xmerl_ucs:from_utf8(Str), 143 | quote_and_encode_string(Codepoints, Acc); 144 | encode_noauto(Str, Acc) when is_atom(Str) -> 145 | quote_and_encode_string(atom_to_list(Str), Acc); 146 | encode_noauto(Num, Acc) when is_number(Num) -> 147 | encode_number(Num, Acc); 148 | encode_noauto({obj, Fields}, Acc) -> 149 | "}" ++ encode_object(Fields, "{" ++ Acc); 150 | encode_noauto(Dict, Acc) when element(1, Dict) =:= dict -> 151 | "}" ++ encode_object(dict:to_list(Dict), "{" ++ Acc); 152 | encode_noauto(Arr, Acc) when is_list(Arr) -> 153 | "]" ++ encode_array(Arr, "[" ++ Acc). 154 | 155 | encode_object([], Acc) -> 156 | Acc; 157 | encode_object([{Key, Value}], Acc) -> 158 | encode_field(Key, Value, Acc); 159 | encode_object([{Key, Value} | Rest], Acc) -> 160 | encode_object(Rest, "," ++ encode_field(Key, Value, Acc)). 161 | 162 | encode_field(Key, Value, Acc) when is_binary(Key) -> 163 | Codepoints = xmerl_ucs:from_utf8(Key), 164 | encode_noauto(Value, ":" ++ quote_and_encode_string(Codepoints, Acc)); 165 | encode_field(Key, Value, Acc) when is_atom(Key) -> 166 | encode_noauto(Value, ":" ++ quote_and_encode_string(atom_to_list(Key), Acc)); 167 | encode_field(Key, Value, Acc) when is_list(Key) -> 168 | encode_noauto(Value, ":" ++ quote_and_encode_string(Key, Acc)). 169 | 170 | encode_array([], Acc) -> 171 | Acc; 172 | encode_array([X], Acc) -> 173 | encode_noauto(X, Acc); 174 | encode_array([X | Rest], Acc) -> 175 | encode_array(Rest, "," ++ encode_noauto(X, Acc)). 176 | 177 | quote_and_encode_string(Str, Acc) -> 178 | "\"" ++ encode_string(Str, "\"" ++ Acc). 179 | 180 | encode_string([], Acc) -> 181 | Acc; 182 | encode_string([$" | Rest], Acc) -> 183 | encode_string(Rest, [$", $\\ | Acc]); 184 | encode_string([$\\ | Rest], Acc) -> 185 | encode_string(Rest, [$\\, $\\ | Acc]); 186 | encode_string([X | Rest], Acc) when X < 32 orelse X > 127 -> 187 | encode_string(Rest, encode_general_char(X, Acc)); 188 | encode_string([X | Rest], Acc) -> 189 | encode_string(Rest, [X | Acc]). 190 | 191 | encode_general_char(8, Acc) -> [$b, $\\ | Acc]; 192 | encode_general_char(9, Acc) -> [$t, $\\ | Acc]; 193 | encode_general_char(10, Acc) -> [$n, $\\ | Acc]; 194 | encode_general_char(12, Acc) -> [$f, $\\ | Acc]; 195 | encode_general_char(13, Acc) -> [$r, $\\ | Acc]; 196 | encode_general_char(X, Acc) when X > 127 -> [X | Acc]; 197 | encode_general_char(X, Acc) -> 198 | %% FIXME currently this branch never runs. 199 | %% We could make it configurable, maybe? 200 | Utf16Bytes = xmerl_ucs:to_utf16be(X), 201 | encode_utf16be_chars(Utf16Bytes, Acc). 202 | 203 | encode_utf16be_chars([], Acc) -> 204 | Acc; 205 | encode_utf16be_chars([B1, B2 | Rest], Acc) -> 206 | encode_utf16be_chars(Rest, [hex_digit((B2) band 16#F), 207 | hex_digit((B2 bsr 4) band 16#F), 208 | hex_digit((B1) band 16#F), 209 | hex_digit((B1 bsr 4) band 16#F), 210 | $u, 211 | $\\ | Acc]). 212 | 213 | %% @spec (Nibble::integer()) -> char() 214 | %% @doc Returns the character code corresponding to Nibble. 215 | %% 216 | %% Nibble must be >=0 and =<15. 217 | hex_digit(N) when is_integer(N), N >= 0, N =< 9 -> $0 + N; 218 | hex_digit(N) when is_integer(N), N >= 10, N =< 15 -> $A + N - 10. 219 | 220 | encode_number(Num, Acc) when is_integer(Num) -> 221 | lists:reverse(integer_to_list(Num), Acc); 222 | encode_number(Num, Acc) when is_float(Num) -> 223 | lists:reverse(float_to_list(Num), Acc). 224 | 225 | %% @spec (Input::(binary() | [byte()])) -> ({ok, json(), Remainder} | {error, Reason}) 226 | %% where Remainder = string() 227 | %% Reason = any() 228 | %% 229 | %% @doc Decodes a JSON value from an input binary or string of 230 | %% Unicode-encoded text. 231 | %% 232 | %% Given a binary, converts it to a list of bytes. Given a 233 | %% list/string, interprets it as a list of bytes. 234 | %% 235 | %% Uses {@link unicode_decode/1} on its input, which results in a list 236 | %% of codepoints, and then decodes a JSON value from that list of 237 | %% codepoints. 238 | %% 239 | %% Returns either `{ok, Result, Remainder}', where Remainder is the 240 | %% remaining portion of the input that was not consumed in the process 241 | %% of decoding Result, or `{error, Reason}'. 242 | decode(Bin) when is_binary(Bin) -> 243 | decode(binary_to_list(Bin)); 244 | decode(Bytes) -> 245 | {_Charset, Codepoints} = unicode_decode(Bytes), 246 | decode_noauto(Codepoints). 247 | 248 | %% @spec (Input::string()) -> ({ok, json(), string()} | {error, any()}) 249 | %% 250 | %% @doc As {@link decode/1}, but does not perform Unicode decoding on its input. 251 | %% 252 | %% Expects a list of codepoints - an ordinary Erlang string - rather 253 | %% than a list of Unicode-encoded bytes. 254 | decode_noauto(Bin) when is_binary(Bin) -> 255 | decode_noauto(binary_to_list(Bin)); 256 | decode_noauto(Chars) -> 257 | case catch parse(skipws(Chars)) of 258 | {'EXIT', Reason} -> 259 | %% Reason is usually far too much information, but helps 260 | %% if needing to debug this module. 261 | {error, Reason}; 262 | {Value, Remaining} -> 263 | {ok, Value, skipws(Remaining)} 264 | end. 265 | 266 | %% @spec ([byte()]) -> [char()] 267 | %% 268 | %% @doc Autodetects and decodes using the Unicode encoding of its input. 269 | %% 270 | %% From RFC4627, section 3, "Encoding": 271 | %% 272 | %%
273 | %% JSON text SHALL be encoded in Unicode. The default encoding is 274 | %% UTF-8. 275 | %% 276 | %% Since the first two characters of a JSON text will always be ASCII 277 | %% characters [RFC0020], it is possible to determine whether an octet 278 | %% stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking 279 | %% at the pattern of nulls in the first four octets. 280 | %% 281 | %% 00 00 00 xx UTF-32BE 282 | %% 00 xx 00 xx UTF-16BE 283 | %% xx 00 00 00 UTF-32LE 284 | %% xx 00 xx 00 UTF-16LE 285 | %% xx xx xx xx UTF-8 286 | %%287 | %% 288 | %% Interestingly, the BOM (byte-order mark) is not mentioned. We 289 | %% support it here by using it to detect our encoding, discarding it 290 | %% if present, even though RFC4627 explicitly notes that the first two 291 | %% characters of a JSON text will be ASCII. 292 | %% 293 | %% If a BOM ([http://unicode.org/faq/utf_bom.html]) is present, we use 294 | %% that; if not, we use RFC4627's rules (as above). Note that UTF-32 295 | %% is the same as UCS-4 for our purposes (but see also 296 | %% [http://unicode.org/reports/tr19/tr19-9.html]). Note that UTF-16 is 297 | %% not the same as UCS-2! 298 | %% 299 | %% Note that I'm using xmerl's UCS/UTF support here. There's another 300 | %% UTF-8 codec in asn1rt, which works on binaries instead of lists. 301 | %% 302 | unicode_decode([0,0,254,255|C]) -> {'utf-32', xmerl_ucs:from_ucs4be(C)}; 303 | unicode_decode([255,254,0,0|C]) -> {'utf-32', xmerl_ucs:from_ucs4le(C)}; 304 | unicode_decode([254,255|C]) -> {'utf-16', xmerl_ucs:from_utf16be(C)}; 305 | unicode_decode([239,187,191|C]) -> {'utf-8', xmerl_ucs:from_utf8(C)}; 306 | unicode_decode(C=[0,0,_,_|_]) -> {'utf-32be', xmerl_ucs:from_ucs4be(C)}; 307 | unicode_decode(C=[_,_,0,0|_]) -> {'utf-32le', xmerl_ucs:from_ucs4le(C)}; 308 | unicode_decode(C=[0,_|_]) -> {'utf-16be', xmerl_ucs:from_utf16be(C)}; 309 | unicode_decode(C=[_,0|_]) -> {'utf-16le', xmerl_ucs:from_utf16le(C)}; 310 | unicode_decode(C=_) -> {'utf-8', xmerl_ucs:from_utf8(C)}. 311 | 312 | %% @spec (EncodingAndCharacters::{Encoding, [char()]}) -> [byte()] 313 | %% where Encoding = 'utf-32' | 'utf-32be' | 'utf-32le' | 'utf-16' | 314 | %% 'utf-16be' | 'utf-16le' | 'utf-8' 315 | %% 316 | %% @doc Encodes the given characters to bytes, using the given Unicode encoding. 317 | %% 318 | %% For convenience, we supply a partial inverse of unicode_decode; If 319 | %% a BOM is requested, we more-or-less arbitrarily pick the big-endian 320 | %% variant of the encoding, since big-endian is network-order. We 321 | %% don't support UTF-8 with BOM here. 322 | unicode_encode({'utf-32', C}) -> [0,0,254,255|xmerl_ucs:to_ucs4be(C)]; 323 | unicode_encode({'utf-32be', C}) -> xmerl_ucs:to_ucs4be(C); 324 | unicode_encode({'utf-32le', C}) -> xmerl_ucs:to_ucs4le(C); 325 | unicode_encode({'utf-16', C}) -> [254,255|xmerl_ucs:to_utf16be(C)]; 326 | unicode_encode({'utf-16be', C}) -> xmerl_ucs:to_utf16be(C); 327 | unicode_encode({'utf-16le', C}) -> xmerl_ucs:to_utf16le(C); 328 | unicode_encode({'utf-8', C}) -> xmerl_ucs:to_utf8(C). 329 | 330 | parse([$" | Rest]) -> %% " emacs balancing 331 | {Codepoints, Rest1} = parse_string(Rest, []), 332 | {list_to_binary(xmerl_ucs:to_utf8(Codepoints)), Rest1}; 333 | parse("true" ++ Rest) -> {true, Rest}; 334 | parse("false" ++ Rest) -> {false, Rest}; 335 | parse("null" ++ Rest) -> {null, Rest}; 336 | parse([${ | Rest]) -> parse_object(skipws(Rest), []); 337 | parse([$[ | Rest]) -> parse_array(skipws(Rest), []); 338 | parse([]) -> exit(unexpected_end_of_input); 339 | parse(Chars) -> parse_number(Chars, []). 340 | 341 | skipws([X | Rest]) when X =< 32 -> 342 | skipws(Rest); 343 | skipws(Chars) -> 344 | Chars. 345 | 346 | parse_string(Chars, Acc) -> 347 | case parse_codepoint(Chars) of 348 | {done, Rest} -> 349 | {lists:reverse(Acc), Rest}; 350 | {ok, Codepoint, Rest} -> 351 | parse_string(Rest, [Codepoint | Acc]) 352 | end. 353 | 354 | parse_codepoint([$" | Rest]) -> %% " emacs balancing 355 | {done, Rest}; 356 | parse_codepoint([$\\, Key | Rest]) -> 357 | parse_general_char(Key, Rest); 358 | parse_codepoint([X | Rest]) -> 359 | {ok, X, Rest}. 360 | 361 | parse_general_char($b, Rest) -> {ok, 8, Rest}; 362 | parse_general_char($t, Rest) -> {ok, 9, Rest}; 363 | parse_general_char($n, Rest) -> {ok, 10, Rest}; 364 | parse_general_char($f, Rest) -> {ok, 12, Rest}; 365 | parse_general_char($r, Rest) -> {ok, 13, Rest}; 366 | parse_general_char($/, Rest) -> {ok, $/, Rest}; 367 | parse_general_char($\\, Rest) -> {ok, $\\, Rest}; 368 | parse_general_char($", Rest) -> {ok, $", Rest}; 369 | parse_general_char($u, [D0, D1, D2, D3 | Rest]) -> 370 | Codepoint = 371 | (digit_hex(D0) bsl 12) + 372 | (digit_hex(D1) bsl 8) + 373 | (digit_hex(D2) bsl 4) + 374 | (digit_hex(D3)), 375 | if 376 | Codepoint >= 16#D800 andalso Codepoint < 16#DC00 -> 377 | % High half of surrogate pair 378 | case parse_codepoint(Rest) of 379 | {low_surrogate_pair, Codepoint2, Rest1} -> 380 | [FinalCodepoint] = 381 | xmerl_ucs:from_utf16be(<
17 | %%% Permission is hereby granted, free of charge, to any person 18 | %%% obtaining a copy of this software and associated documentation 19 | %%% files (the "Software"), to deal in the Software without 20 | %%% restriction, including without limitation the rights to use, copy, 21 | %%% modify, merge, publish, distribute, sublicense, and/or sell copies 22 | %%% of the Software, and to permit persons to whom the Software is 23 | %%% furnished to do so, subject to the following conditions: 24 | %%% 25 | %%% The above copyright notice and this permission notice shall be 26 | %%% included in all copies or substantial portions of the Software. 27 | %%% 28 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 29 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 30 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 31 | %%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 32 | %%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 33 | %%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 34 | %%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | %%% SOFTWARE.36 | %%% @end 37 | %%%------------------------------------------------------------------- 38 | 39 | -module(webdrv_wire). 40 | 41 | -export([get_status/1, start_session/2, start_session/3, 42 | get_sessions/1, session/1, stop_session/1, 43 | set_timeout/3, set_async_script_timeout/2, set_implicit_wait_timeout/2, 44 | get_window_handle/1, get_window_handles/1, get_url/1, 45 | set_url/2, forward/1, back/1, 46 | refresh/1, execute/3, execute_async/3, 47 | get_screenshot/1, get_ime_available_engines/1, get_ime_active_engine/1, 48 | get_ime_activated/1, ime_deactivate/1, ime_activate/2, 49 | set_frame/2, set_window_focus/2, close_window/1, 50 | set_window_size/3, set_window_size/4, get_window_size/1, 51 | get_window_size/2, set_window_position/3, set_window_position/4, 52 | get_window_position/1, get_window_position/2, set_window_maximize/1, 53 | set_window_maximize/2, get_cookies/1, add_cookie/2, 54 | delete_cookies/1, delete_cookie/2, get_page_source/1, 55 | get_page_title/1, find_element/3, find_elements/3, 56 | get_active_element/1, get_element_info/2, find_element_rel/4, 57 | find_elements_rel/4, click_element/2, submit/2, 58 | get_text/2, send_value/3, send_keys/2, 59 | element_name/2, clear_element/2, is_selected_element/2, 60 | is_enabled_element/2, element_attribute/3, are_elements_equal/3, 61 | is_displayed_element/2, element_location/2, is_element_location_in_view/2, 62 | element_size/2, element_css_property/3, get_browser_orientation/1, 63 | set_browser_orientation/2, get_alert_text/1, set_alert_text/2, 64 | accept_alert/1, dismiss_alert/1, moveto/4, 65 | click/2, buttondown/2, buttonup/2, 66 | doubleclick/1, get_log/2, get_log_types/1, get_cache_status/1]). 67 | 68 | -include("../include/webdrv.hrl"). 69 | 70 | %% Helper functions for options 71 | session_id(#webdrv_opts{ session_id = SId }) -> SId. 72 | url(#webdrv_opts{ url = Url }) -> Url. 73 | timeout(#webdrv_opts{ timeout = Timeout }) -> Timeout. 74 | 75 | %% API 76 | get_status(Opts) -> 77 | do_get_cmd(Opts, "status"). 78 | 79 | -spec start_session(#webdrv_opts{}, capability()) -> request_res(). 80 | start_session(Opts, Capability) -> 81 | start_session(Opts, Capability, null). 82 | 83 | -spec start_session(#webdrv_opts{}, capability(), capability()) -> request_res(). 84 | start_session(Opts, Desired, Required) -> 85 | Params = [{desiredCapabilities, webdrv_cap:to_json(Desired)}, 86 | {requiredCapabilities, webdrv_cap:to_json(Required)}], 87 | do_post_cmd(Opts, "session", Params). 88 | 89 | -spec get_sessions(#webdrv_opts{}) -> request_res(). 90 | get_sessions(Opts) -> 91 | do_get_cmd(Opts, "sessions"). 92 | 93 | -spec session(#webdrv_opts{}) -> request_res(). 94 | session(Opts) -> 95 | do_get_cmd(Opts, "session/" ++ session_id(Opts)). 96 | 97 | -spec stop_session(#webdrv_opts{}) -> request_res(). 98 | stop_session(Opts) -> 99 | do_delete_cmd(Opts, "session/" ++ session_id(Opts)). 100 | 101 | -spec set_timeout(#webdrv_opts{}, term(), term()) -> request_res(). 102 | set_timeout(Opts, TimeoutType, Timeout) -> 103 | Params = [{type, TimeoutType}, {ms, Timeout}], 104 | do_post_scmd(Opts, "timeouts", Params). 105 | 106 | -spec set_async_script_timeout(#webdrv_opts{}, number()) -> request_res(). 107 | set_async_script_timeout(Opts, Timeout) -> 108 | do_post_scmd(Opts, "timeouts/async_script", [{ms, Timeout}]). 109 | 110 | -spec set_implicit_wait_timeout(#webdrv_opts{}, number()) -> request_res(). 111 | set_implicit_wait_timeout(Opts, Timeout) -> 112 | do_post_scmd(Opts, "timeouts/implicit_wait", [{ms, Timeout}]). 113 | 114 | -spec get_window_handle(#webdrv_opts{}) -> request_res(). 115 | get_window_handle(Opts) -> 116 | do_get_scmd(Opts, "window_handle"). 117 | 118 | -spec get_window_handles(#webdrv_opts{}) -> request_res(). 119 | get_window_handles(Opts) -> 120 | do_get_scmd(Opts, "window_handles"). 121 | 122 | -spec get_url(#webdrv_opts{}) -> request_res(). 123 | get_url(Opts) -> 124 | do_get_scmd(Opts, "url"). 125 | 126 | -spec set_url(#webdrv_opts{}, jsonstr()) -> request_res(). 127 | set_url(Opts, Url) -> 128 | do_post_scmd(Opts, "url", [{url, Url}]). 129 | 130 | -spec forward(#webdrv_opts{}) -> request_res(). 131 | forward(Opts) -> 132 | do_post_scmd(Opts, "forward", []). 133 | 134 | -spec back(#webdrv_opts{}) -> request_res(). 135 | back(Opts) -> 136 | do_post_scmd(Opts, "back", []). 137 | 138 | -spec refresh(#webdrv_opts{}) -> request_res(). 139 | refresh(Opts) -> 140 | do_post_scmd(Opts, "refresh", []). 141 | 142 | -spec execute(#webdrv_opts{}, jsonstr(), jsonlist()) -> request_res(). 143 | execute(Opts, Script, Args) -> 144 | do_post_scmd(Opts, "execute", [{script, Script}, {args, Args}]). 145 | 146 | -spec execute_async(#webdrv_opts{}, jsonstr(), jsonlist()) -> request_res(). 147 | execute_async(Opts, Script, Args) -> 148 | do_post_scmd(Opts, "execute_async", [{script, Script}, {args, Args}]). 149 | 150 | -spec get_screenshot(#webdrv_opts{}) -> request_res(). 151 | get_screenshot(Opts) -> 152 | do_get_scmd(Opts, "screenshot"). 153 | 154 | -spec get_ime_available_engines(#webdrv_opts{}) -> request_res(). 155 | get_ime_available_engines(Opts) -> 156 | do_get_scmd(Opts, "ime/available_engines"). 157 | 158 | -spec get_ime_active_engine(#webdrv_opts{}) -> request_res(). 159 | get_ime_active_engine(Opts) -> 160 | do_get_scmd(Opts, "ime/active_engine"). 161 | 162 | -spec get_ime_activated(#webdrv_opts{}) -> request_res(). 163 | get_ime_activated(Opts) -> 164 | do_get_scmd(Opts, "ime/activated"). 165 | 166 | -spec ime_deactivate(#webdrv_opts{}) -> request_res(). 167 | ime_deactivate(Opts) -> 168 | do_post_scmd(Opts, "ime/deactivate", []). 169 | 170 | -spec ime_activate(#webdrv_opts{}, jsonstr()) -> request_res(). 171 | ime_activate(Opts, Engine) -> 172 | do_post_scmd(Opts, "ime/activate", [{engine, Engine}]). 173 | 174 | -spec set_frame(#webdrv_opts{}, frame_id()) -> request_res(). 175 | set_frame(Opts, Frame) -> 176 | do_post_scmd(Opts, "frame", [{id, Frame}]). 177 | 178 | -spec set_window_focus(#webdrv_opts{}, jsonstr()) -> request_res(). 179 | set_window_focus(Opts, Window) -> 180 | do_post_scmd(Opts, "window", [{name, Window}]). 181 | 182 | -spec close_window(#webdrv_opts{}) -> request_res(). 183 | close_window(Opts) -> 184 | do_delete_scmd(Opts, "window"). 185 | 186 | -spec set_window_size(#webdrv_opts{}, number(), number()) -> request_res(). 187 | set_window_size(Opts, Width, Height) -> 188 | set_window_size(Opts, "current", Width, Height). 189 | 190 | -spec set_window_size(#webdrv_opts{}, jsonstr(), number(), number()) -> request_res(). 191 | set_window_size(Opts, WindowHandle, Width, Height) -> 192 | do_post_scmd(Opts, "window/" ++ WindowHandle ++ "/size", 193 | [{width, Width}, {height, Height}]). 194 | 195 | -spec get_window_size(#webdrv_opts{}) -> request_res(). 196 | get_window_size(Opts) -> 197 | get_window_size(Opts, "current"). 198 | 199 | -spec get_window_size(#webdrv_opts{}, jsonstr()) -> request_res(). 200 | get_window_size(Opts, WindowHandle) -> 201 | do_get_scmd(Opts, "window/" ++ WindowHandle ++ "/size"). 202 | 203 | -spec set_window_position(#webdrv_opts{}, number(), number()) -> request_res(). 204 | set_window_position(Opts, X, Y) -> 205 | set_window_position(Opts, "current", X, Y). 206 | 207 | -spec set_window_position(#webdrv_opts{}, jsonstr(), number(), number()) -> request_res(). 208 | set_window_position(Opts, WindowHandle, X, Y) -> 209 | do_post_scmd(Opts, "window/" ++ WindowHandle ++ "/position", [{x, X}, {y, Y}]). 210 | 211 | -spec get_window_position(#webdrv_opts{}) -> request_res(). 212 | get_window_position(Opts) -> 213 | get_window_position(Opts, "current"). 214 | 215 | -spec get_window_position(#webdrv_opts{}, jsonstr()) -> request_res(). 216 | get_window_position(Opts, WindowHandle) -> 217 | do_get_scmd(Opts, "window/" ++ WindowHandle ++ "/position"). 218 | 219 | -spec set_window_maximize(#webdrv_opts{}) -> request_res(). 220 | set_window_maximize(Opts) -> 221 | set_window_maximize(Opts, "current"). 222 | 223 | -spec set_window_maximize(#webdrv_opts{}, jsonstr()) -> request_res(). 224 | set_window_maximize(Opts, WindowHandle) -> 225 | do_post_scmd(Opts, "window/" ++ WindowHandle ++ "/maximize", []). 226 | 227 | -spec get_cookies(#webdrv_opts{}) -> request_res(). 228 | get_cookies(Opts) -> 229 | do_get_scmd(Opts, "cookie"). 230 | 231 | -spec add_cookie(#webdrv_opts{}, jsonobj()) -> request_res(). 232 | add_cookie(Opts, Cookie) -> 233 | do_post_scmd(Opts, "cookie", [{cookie, Cookie}]). 234 | 235 | -spec delete_cookies(#webdrv_opts{}) -> request_res(). 236 | delete_cookies(Opts) -> 237 | do_delete_scmd(Opts, "cookie"). 238 | 239 | -spec delete_cookie(#webdrv_opts{}, jsonstr()) -> request_res(). 240 | delete_cookie(Opts, Name) -> 241 | do_delete_scmd(Opts, "cookie/" ++ Name). 242 | 243 | -spec get_page_source(#webdrv_opts{}) -> request_res(). 244 | get_page_source(Opts) -> 245 | do_get_scmd(Opts, "source"). 246 | 247 | -spec get_page_title(#webdrv_opts{}) -> request_res(). 248 | get_page_title(Opts) -> 249 | do_get_scmd(Opts, "title"). 250 | 251 | -spec find_element(#webdrv_opts{}, jsonstr(), jsonstr()) -> request_res(). 252 | find_element(Opts, Strategy, Value) -> 253 | do_post_scmd(Opts, "element", [{using, Strategy}, {value, Value}]). 254 | 255 | -spec find_elements(#webdrv_opts{}, jsonstr(), jsonstr()) -> request_res(). 256 | find_elements(Opts, Strategy, Value) -> 257 | do_post_scmd(Opts, "elements", [{using, Strategy}, {value, Value}]). 258 | 259 | -spec get_active_element(#webdrv_opts{}) -> request_res(). 260 | get_active_element(Opts) -> 261 | do_post_scmd(Opts, "element/active", []). 262 | 263 | %% Currently undefined 264 | -spec get_element_info(#webdrv_opts{}, jsonstr()) -> request_res(). 265 | get_element_info(Opts, ElementId) -> 266 | do_get_scmd(Opts, "element/" ++ ElementId). 267 | 268 | -spec find_element_rel(#webdrv_opts{}, jsonstr(), jsonstr(), jsonstr()) -> request_res(). 269 | find_element_rel(Opts, ElementId, Strategy, Value) -> 270 | do_post_ecmd(Opts, ElementId, "element", [{using, Strategy}, {value, Value}]). 271 | 272 | -spec find_elements_rel(#webdrv_opts{}, jsonstr(), jsonstr(), jsonstr()) -> request_res(). 273 | find_elements_rel(Opts, ElementId, Strategy, Value) -> 274 | do_post_ecmd(Opts, ElementId, "elements", [{using, Strategy}, {value, Value}]). 275 | 276 | -spec click_element(#webdrv_opts{}, jsonstr()) -> request_res(). 277 | click_element(Opts, ElementId) -> 278 | do_post_ecmd(Opts, ElementId, "click", []). 279 | 280 | -spec submit(#webdrv_opts{}, jsonstr()) -> request_res(). 281 | submit(Opts, ElementId) -> 282 | do_post_ecmd(Opts, ElementId, "submit", []). 283 | 284 | -spec get_text(#webdrv_opts{}, jsonstr()) -> request_res(). 285 | get_text(Opts, ElementId) -> 286 | do_get_ecmd(Opts, ElementId, "text"). 287 | 288 | -spec send_value(#webdrv_opts{}, jsonstr(), jsonstr()) -> request_res(). 289 | send_value(Opts, ElementId, Value) -> 290 | do_post_ecmd(Opts, ElementId, "value", [{value, [Value]}]). 291 | 292 | -spec send_keys(#webdrv_opts{}, jsonstr()) -> request_res(). 293 | send_keys(Opts, Value) -> 294 | do_post_scmd(Opts, "keys", [{value, Value}]). 295 | 296 | -spec element_name(#webdrv_opts{}, jsonstr()) -> request_res(). 297 | element_name(Opts, ElementId) -> 298 | do_get_ecmd(Opts, ElementId, "name"). 299 | 300 | -spec clear_element(#webdrv_opts{}, jsonstr()) -> request_res(). 301 | clear_element(Opts, ElementId) -> 302 | do_post_ecmd(Opts, ElementId, "clear", []). 303 | 304 | -spec is_selected_element(#webdrv_opts{}, jsonstr()) -> request_res(). 305 | is_selected_element(Opts, ElementId) -> 306 | do_get_ecmd(Opts, ElementId, "selected"). 307 | 308 | -spec is_enabled_element(#webdrv_opts{}, jsonstr()) -> request_res(). 309 | is_enabled_element(Opts, ElementId) -> 310 | do_get_ecmd(Opts, ElementId, "enabled"). 311 | 312 | -spec element_attribute(#webdrv_opts{}, jsonstr(), jsonstr()) -> request_res(). 313 | element_attribute(Opts, ElementId, Name) -> 314 | do_get_ecmd(Opts, ElementId, "attribute/" ++ Name). 315 | 316 | -spec are_elements_equal(#webdrv_opts{}, jsonstr(), jsonstr()) -> request_res(). 317 | are_elements_equal(Opts, ElementId1, ElementId2) -> 318 | do_get_ecmd(Opts, ElementId1, "equals/" ++ ElementId2). 319 | 320 | -spec is_displayed_element(#webdrv_opts{}, jsonstr()) -> request_res(). 321 | is_displayed_element(Opts, ElementId) -> 322 | do_get_ecmd(Opts, ElementId, "displayed"). 323 | 324 | -spec element_location(#webdrv_opts{}, jsonstr()) -> request_res(). 325 | element_location(Opts, ElementId) -> 326 | do_get_ecmd(Opts, ElementId, "location"). 327 | 328 | -spec is_element_location_in_view(#webdrv_opts{}, jsonstr()) -> request_res(). 329 | is_element_location_in_view(Opts, ElementId) -> 330 | do_get_ecmd(Opts, ElementId, "location_in_view"). 331 | 332 | -spec element_size(#webdrv_opts{}, jsonstr()) -> request_res(). 333 | element_size(Opts, ElementId) -> 334 | do_get_ecmd(Opts, ElementId, "size"). 335 | 336 | -spec element_css_property(#webdrv_opts{}, jsonstr(), jsonstr()) -> request_res(). 337 | element_css_property(Opts, ElementId, Prop) -> 338 | do_get_ecmd(Opts, ElementId, "css/" ++ Prop). 339 | 340 | -spec get_browser_orientation(#webdrv_opts{}) -> request_res(). 341 | get_browser_orientation(Opts) -> 342 | do_get_scmd(Opts, "orientation"). 343 | 344 | -spec set_browser_orientation(#webdrv_opts{}, jsonstr()) -> request_res(). 345 | set_browser_orientation(Opts, Dir) -> 346 | do_post_scmd(Opts, "orientation", [{orientation, Dir}]). 347 | 348 | -spec get_alert_text(#webdrv_opts{}) -> request_res(). 349 | get_alert_text(Opts) -> 350 | do_get_scmd(Opts, "alert_text"). 351 | 352 | -spec set_alert_text(#webdrv_opts{}, jsonstr()) -> request_res(). 353 | set_alert_text(Opts, Str) -> 354 | do_post_scmd(Opts, "alert_text", [{text, Str}]). 355 | 356 | -spec accept_alert(#webdrv_opts{}) -> request_res(). 357 | accept_alert(Opts) -> 358 | do_post_scmd(Opts, "accept_alert", []). 359 | 360 | -spec dismiss_alert(#webdrv_opts{}) -> request_res(). 361 | dismiss_alert(Opts) -> 362 | do_post_scmd(Opts, "dismiss_alert", []). 363 | 364 | -spec moveto(#webdrv_opts{}, jsonstr(), number(), number()) -> request_res(). 365 | moveto(Opts, Elem, XOffSet, YOffSet) -> 366 | do_post_scmd(Opts, "moveto", 367 | [{element, Elem}, {xoffset, XOffSet}, {yoffset, YOffSet}]). 368 | 369 | -spec click(#webdrv_opts{}, number()) -> request_res(). 370 | click(Opts, Button) -> 371 | do_post_scmd(Opts, "click", [{button, Button}]). 372 | 373 | -spec buttondown(#webdrv_opts{}, number()) -> request_res(). 374 | buttondown(Opts, Button) -> 375 | do_post_scmd(Opts, "buttondown", [{button, Button}]). 376 | 377 | -spec buttonup(#webdrv_opts{}, number()) -> request_res(). 378 | buttonup(Opts, Button) -> 379 | do_post_scmd(Opts, "buttonup", [{button, Button}]). 380 | 381 | -spec doubleclick(#webdrv_opts{}) -> request_res(). 382 | doubleclick(Opts) -> 383 | do_post_scmd(Opts, "doubleclick", []). 384 | 385 | %% SKIP Touch, Geo and Local storage for now... 386 | 387 | -spec get_log(#webdrv_opts{}, jsonstr()) -> request_res(). 388 | get_log(Opts, LogType) -> 389 | do_post_scmd(Opts, "log", [{type, LogType}]). 390 | 391 | -spec get_log_types(#webdrv_opts{}) -> request_res(). 392 | get_log_types(Opts) -> 393 | do_get_scmd(Opts, "log/types"). 394 | 395 | -spec get_cache_status(#webdrv_opts{}) -> request_res(). 396 | get_cache_status(Opts) -> 397 | do_get_scmd(Opts, "application_cache/status"). 398 | 399 | 400 | %% ------------- 401 | do_post_ecmd(Opts, ElementId, Cmd, Params) -> 402 | do_post_scmd(Opts, "element/" ++ ElementId ++ "/" ++ Cmd, Params). 403 | 404 | do_get_ecmd(Opts, ElementId, Cmd) -> 405 | do_get_scmd(Opts, "element/" ++ ElementId ++ "/" ++ Cmd). 406 | 407 | do_post_scmd(Opts, Cmd, Params) -> 408 | do_post_cmd(Opts, "session/" ++ session_id(Opts) ++ "/" ++ Cmd, Params). 409 | 410 | do_get_scmd(Opts, Cmd) -> 411 | do_get_cmd(Opts, "session/" ++ session_id(Opts) ++ "/" ++ Cmd). 412 | 413 | do_post_cmd(Opts, Cmd, Params) -> 414 | URL = url(Opts) ++ Cmd, 415 | do_post(Opts, URL, {obj, Params}). 416 | 417 | do_get_cmd(Opts, Cmd) -> 418 | do_get(Opts, url(Opts) ++ Cmd). 419 | 420 | do_delete_scmd(Opts, Cmd) -> 421 | do_delete_cmd(Opts, "session/" ++ session_id(Opts) ++ "/" ++ Cmd). 422 | 423 | do_delete_cmd(Opts, Cmd) -> 424 | do_delete(Opts, url(Opts) ++ Cmd). 425 | 426 | %% HTML / HTTP functions 427 | do_post(Opts, Url, JSONParams) -> 428 | {ok, {_, _, Host, Port, _, _}} = http_uri:parse(Url), 429 | JSON = json:encode(JSONParams), 430 | Len = length(JSON), 431 | request(Opts, post, 432 | {Url, 433 | [{"Content-Length", integer_to_list(Len)}, 434 | {"Content-Type", json:mime_type()}, 435 | {"host", Host ++ ":" ++ integer_to_list(Port)}, 436 | {"connection", "keep-alive"} 437 | ], 438 | json:mime_type(), 439 | JSON}). 440 | 441 | do_get(Opts, Url) -> 442 | request(Opts, get, {Url, [{"Accept", "application/json"}]}). 443 | 444 | do_delete(Opts, URL) -> 445 | request(Opts, delete, {URL, []}). 446 | 447 | -spec request(#webdrv_opts{}, httpc:method(), httpc:request()) -> 448 | {ok, session_id(), jsonobj()} | request_error(). 449 | request(Opts, Method, Request) -> 450 | %% io:format("REQ: ~p\n", [{Method, Request}]), 451 | Res = httpc_request_bug_fix(Opts, Method, Request), 452 | case parse_httpc_result(Res) of 453 | {error, {txt, Err}} -> 454 | {html_error, Err}; 455 | {error, {json, JErr}} -> 456 | {json_error, JErr}; 457 | {cmd_fail, {ok, JSON}} -> 458 | {cmd_error, JSON}; 459 | {cmd_fail, {error, {json, JErr}}} -> 460 | {json_error, JErr}; %% This one could be discussed 461 | ok -> {ok, null, {obj, []}}; 462 | {ok, JsonTerm} -> check_json_response(JsonTerm) 463 | end. 464 | 465 | check_json_response(JsonTerm) -> 466 | case parse_response_json(JsonTerm) of 467 | {0, SessId, Value} -> 468 | {ok, SessId, Value}; 469 | {N, SessId, _Value} -> 470 | {error, wire_error(N), SessId}; 471 | {error, JErr} -> 472 | {json_error, JErr} 473 | end. 474 | 475 | parse_httpc_result({ok, Result}) -> 476 | %% io:format("Res: ~p\n", [Result]), 477 | case Result of 478 | {{_Vsn, Code, Reason}, Hdrs, Body} -> 479 | if Code == 200 -> 480 | json_decode(Body); 481 | Code == 204 -> %% No Content 482 | ok; 483 | (Code >= 400 andalso Code < 500) orelse Code == 501 -> 484 | {error, {txt, Body}}; 485 | Code == 500 -> json_decode(Body); 486 | true -> 487 | case proplists:get_value("content-type", Hdrs, undefined) 488 | == json:mime_type() of 489 | true -> {cmd_fail, json_decode(Body)}; 490 | false -> {error, {txt, lists:concat( 491 | ["Incorrect response ", Code, " - ", Reason])}} 492 | end 493 | end; 494 | {Code, Body} -> 495 | {error, {txt, lists:concat(["Illformed response ", Code, " - ", Body])}}; 496 | _ -> 497 | {error, {txt, "Illformed response, expected normal response got just request_id"}} 498 | end; 499 | parse_httpc_result({error, Reason}) -> 500 | {error, {txt, Reason}}. 501 | 502 | json_decode(Body) -> 503 | case json:decode(Body) of 504 | {ok, Json, []} -> {ok, Json}; 505 | {ok, _PJson, Rest} -> 506 | {error, {json, "Only partial decode possible, remaining: " ++ Rest}}; 507 | {error, Reason} -> {error, {json, Reason}} 508 | end. 509 | 510 | parse_response_json(JSON) -> 511 | case JSON of 512 | {obj, Dict} -> 513 | SessId = proplists:get_value("sessionId", Dict, null), 514 | Status = proplists:get_value("status", Dict, -1), 515 | Value = proplists:get_value("value", Dict, none), 516 | if Status < 0 -> 517 | {error, "JSON object contained no status field"}; 518 | Value == none -> 519 | {error, "JSON object contained no value"}; 520 | true -> 521 | {Status, SessId, Value} 522 | end; 523 | _ -> 524 | {error, "JSON response is not of object type"} 525 | end. 526 | 527 | %% WIRE Protocol Errors 528 | -spec wire_error(integer()) -> {atom(), string()}. 529 | wire_error(6) -> {'NoSuchDriver' , "A session is either terminated or not started"}; 530 | wire_error(7) -> {'NoSuchElement' , "An element could not be located on the page using the given search parameters."}; 531 | wire_error(8) -> {'NoSuchFrame' , "A request to switch to a frame could not be satisfied because the frame could not be found."}; 532 | wire_error(9) -> {'UnknownCommand' , "The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource."}; 533 | wire_error(10) -> {'StaleElementReference' , "An element command failed because the referenced element is no longer attached to the DOM."}; 534 | wire_error(11) -> {'ElementNotVisible' , "An element command could not be completed because the element is not visible on the page."}; 535 | wire_error(12) -> {'InvalidElementState' , "An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element)."}; 536 | wire_error(13) -> {'UnknownError' , "An unknown server-side error occurred while processing the command."}; 537 | wire_error(15) -> {'ElementIsNotSelectable' , "An attempt was made to select an element that cannot be selected."}; 538 | wire_error(17) -> {'JavaScriptError' , "An error occurred while executing user supplied JavaScript."}; 539 | wire_error(19) -> {'XPathLookupError' , "An error occurred while searching for an element by XPath."}; 540 | wire_error(21) -> {'Timeout' , "An operation did not complete before its timeout expired."}; 541 | wire_error(23) -> {'NoSuchWindow' , "A request to switch to a different window could not be satisfied because the window could not be found."}; 542 | wire_error(24) -> {'InvalidCookieDomain' , "An illegal attempt was made to set a cookie under a different domain than the current page."}; 543 | wire_error(25) -> {'UnableToSetCookie' , "A request to set a cookie's value could not be satisfied."}; 544 | wire_error(26) -> {'UnexpectedAlertOpen' , "A modal dialog was open, blocking this operation."}; 545 | wire_error(27) -> {'NoAlertOpenError' , "An attempt was made to operate on a modal dialog when one was not open."}; 546 | wire_error(28) -> {'ScriptTimeout' , "A script did not complete before its timeout expired."}; 547 | wire_error(29) -> {'InvalidElementCoordinates' , "The coordinates provided to an interactions operation are invalid."}; 548 | wire_error(30) -> {'IMENotAvailable' , "IME was not available."}; 549 | wire_error(31) -> {'IMEEngineActivationFailed' , "An IME engine could not be started."}; 550 | wire_error(32) -> {'InvalidSelector' , "Argument was an invalid selector (e.g. XPath/CSS)."}; 551 | wire_error(33) -> {'SessionNotCreatedException', "A new session could not be created."}; 552 | wire_error(34) -> {'MoveTargetOutOfBounds' , "Target provided for a move action is out of bounds."}. 553 | 554 | 555 | %% BUG in httpc:request, does not follow 303 when POST:ing 556 | %% in R16 it correctly follows 303 redirects, but fails to 557 | %% get the relative location correct... Sigh... 558 | %% TODO: Make the fix more general?? 559 | %% (non-relative location etc...) 560 | httpc_request_bug_fix(Opts, post, Request={_Url, Headers, _, _}) -> 561 | Url = "http://" ++ proplists:get_value("host", Headers) ++ "/", 562 | Timeout = {timeout, timeout(Opts)}, 563 | case httpc:request(post, Request, 564 | [Timeout, {autoredirect, false}], 565 | [{headers_as_is, true}]) of 566 | _Res = {ok, {{_, 303, _}, OutHdrs, _Body}} -> 567 | NewLoc = proplists:get_value("location", OutHdrs, " "), 568 | Res = httpc_request_bug_fix(Opts, get, {Url ++ tl(NewLoc), []}), 569 | Res; 570 | % Fix selenium 571 | _Res = {ok, {{_, 302, _}, OutHdrs, _Body}} -> 572 | Redirect = proplists:get_value("location", OutHdrs, ""), 573 | httpc:request(get, {Redirect, [{"Accept", "application/json"}]}, [Timeout], []); 574 | % Fix ios-driver 575 | _Res = {ok, {{_, 301, _}, OutHdrs, _Body}} -> 576 | Redirect = proplists:get_value("location", OutHdrs, ""), 577 | httpc:request(get, {Redirect, [{"Accept", "application/json"}]}, [Timeout], []); 578 | Res -> 579 | Res 580 | end; 581 | httpc_request_bug_fix(Opts, Method, Request) -> 582 | httpc:request(Method, Request, [{timeout, timeout(Opts)}, {autoredirect, true}], []). 583 | 584 | -------------------------------------------------------------------------------- /src/webdrv_session.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Hans Svensson
16 | %%% webdrv_session:start_session(test, Selenium, webdrv_cap:default_htmlunit(), 10000),
17 | %%% webdrv_session:set_url(test, SomeUrl),
18 | %%% PageSource = webdrv_session:get_page_source(test),
19 | %%% io:format("Source: ~p\n", [PageSource]),
20 | %%% webdrv_session:stop_session(test).
21 | %%%
22 | %%% The individual functions are not documented. They have type annotations, for more
23 | %%% information on the particular functions, we refer to (The WebDriver
25 | %%% Wire Protocol).
26 | %%%
27 | %%% 29 | %%% Permission is hereby granted, free of charge, to any person 30 | %%% obtaining a copy of this software and associated documentation 31 | %%% files (the "Software"), to deal in the Software without 32 | %%% restriction, including without limitation the rights to use, copy, 33 | %%% modify, merge, publish, distribute, sublicense, and/or sell copies 34 | %%% of the Software, and to permit persons to whom the Software is 35 | %%% furnished to do so, subject to the following conditions: 36 | %%% 37 | %%% The above copyright notice and this permission notice shall be 38 | %%% included in all copies or substantial portions of the Software. 39 | %%% 40 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 41 | %%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 42 | %%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 43 | %%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 44 | %%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 45 | %%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 46 | %%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 47 | %%% SOFTWARE.48 | %%% @end 49 | %%%------------------------------------------------------------------- 50 | -module(webdrv_session). 51 | 52 | -include("../include/webdrv.hrl"). 53 | 54 | -behaviour(gen_server). 55 | 56 | -define(TIMEOUT, 30000). 57 | 58 | %% API 59 | -export([execute/3, execute_async/3, get_screenshot/1, get_status/1, 60 | get_window_handle/1, get_window_handles/1, 61 | get_ime_available_engines/1, get_ime_active_engine/1, get_ime_activated/1, 62 | ime_deactivate/1, ime_activate/2, 63 | set_frame/2, set_window_focus/2, close_window/1, set_window_size/3, 64 | set_window_size/4, get_window_size/1, get_window_size/2, set_window_position/3, 65 | set_window_position/4, get_window_position/1, get_window_position/2, 66 | set_window_maximize/1, set_window_maximize/2, get_cookies/1, add_cookie/2, 67 | delete_cookies/1, delete_cookie/2, get_page_source/1, get_page_title/1, 68 | find_element/3, find_elements/3, get_active_element/1, find_element_rel/4, 69 | find_elements_rel/4, click_element/2, submit/2, get_text/2, send_value/3, 70 | send_keys/2, element_name/2, clear_element/2, is_selected_element/2, 71 | is_enabled_element/2, element_attribute/3, are_elements_equal/3, 72 | is_displayed_element/2, get_element_info/2, 73 | element_location/2, is_element_location_in_view/2, element_size/2, 74 | element_css_property/3, get_browser_orientation/1, set_browser_orientation/2, 75 | get_alert_text/1, set_alert_text/2, accept_alert/1, dismiss_alert/1, moveto/4, 76 | click/2, buttondown/2, buttonup/2, doubleclick/1, get_log/2, 77 | get_log_types/1, get_cache_status/1, set_timeout/3, set_async_script_timeout/2, 78 | set_implicit_wait_timeout/2, forward/1, back/1, refresh/1, set_url/2, get_url/1, 79 | start_session/3, start_session/4, stop_session/1, stop/1 80 | ]). 81 | 82 | %% gen_server callbacks 83 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 84 | terminate/2, code_change/3]). 85 | 86 | -record(state, { url 87 | , capabilities 88 | , session_id 89 | , timeout}). 90 | 91 | %%%=================================================================== 92 | %%% API 93 | %%%=================================================================== 94 | 95 | %% @equiv start_session(Name, Url, DesiredCapabilities, null, 5000) 96 | -spec start_session(atom(), url(), capability()) -> 97 | {ok, pid()} | request_error(). 98 | start_session(Name, Url, DesiredCapabilities) -> 99 | start_session(Name, Url, DesiredCapabilities, null, 10000). 100 | 101 | %% @equiv start_session(Name, Url, DesiredCapabilities, null, ConnTimeout) 102 | -spec start_session(atom(), url(), capability(), number()) -> 103 | {ok, pid()} | request_error(). 104 | start_session(Name, Url, DesiredCapabilities, ConnTimeout) -> 105 | start_session(Name, Url, DesiredCapabilities, null, ConnTimeout). 106 | 107 | %% @doc 108 | %% Starts the server (initializing a session) 109 | %% 110 | %% @end 111 | -spec start_session(atom(), url(), capability(), capability(), number()) -> 112 | {ok, pid()} | request_error(). 113 | start_session(Name, Url, DesiredCapabilities, RequiredCapabilities, ConnTimeout) -> 114 | inets:start(), 115 | case webdrv_wire:start_session(#webdrv_opts{ url = Url, timeout = ConnTimeout }, 116 | DesiredCapabilities, RequiredCapabilities) of 117 | {ok, SessionId, Capabilities} -> 118 | gen_server:start_link({local, Name}, ?MODULE, [binary_to_list(SessionId), Url, 119 | Capabilities, ConnTimeout], []); 120 | 121 | Err -> {error, Err} 122 | end. 123 | 124 | -spec stop_session(atom()) -> ok. 125 | stop_session(Name) -> 126 | _Res = gen_server:call(Name, stop_session), 127 | ok. 128 | 129 | -spec stop(atom()) -> ok. 130 | stop(Name) -> 131 | _Res = gen_server:call(Name, stop_session_no_cleanup), 132 | ok. 133 | 134 | -spec get_status(atom()) -> {ok, json()} | request_error(). 135 | get_status(Name) -> 136 | mkRes(gen_server:call(Name, {get_status, sid}, ?TIMEOUT), fun(X) -> X end). 137 | 138 | -spec set_timeout(atom(), string(), number()) -> ok | request_error(). 139 | set_timeout(Name, Type, Timeout) -> 140 | mkRes(gen_server:call(Name, {set_timeout, sid, bs(Type), Timeout}, ?TIMEOUT), ok). 141 | 142 | -spec set_async_script_timeout(atom(), number()) -> ok | request_error(). 143 | set_async_script_timeout(Name, Timeout) -> 144 | mkRes(gen_server:call(Name, {set_async_script_timeout, sid, Timeout}, ?TIMEOUT), ok). 145 | 146 | -spec set_implicit_wait_timeout(atom(), number()) -> ok | request_error(). 147 | set_implicit_wait_timeout(Name, Timeout) -> 148 | mkRes(gen_server:call(Name, {set_implicit_wait_timeout, sid, Timeout}, ?TIMEOUT), ok). 149 | 150 | -spec get_window_handle(atom()) -> {ok, string()} | request_error(). 151 | get_window_handle(Name) -> 152 | mkRes(gen_server:call(Name, {get_window_handle, sid}, ?TIMEOUT), fun check_str/1). 153 | 154 | -spec get_window_handles(atom()) -> {ok, [string()]} | request_error(). 155 | get_window_handles(Name) -> 156 | mkRes(gen_server:call(Name, {get_window_handles, sid}, ?TIMEOUT), 157 | fun(WHs) -> check_list(WHs, fun check_str/1) end). 158 | 159 | -spec set_url(atom(), string()) -> ok | request_error(). 160 | set_url(Name, Url) -> 161 | mkRes(gen_server:call(Name, {set_url, sid, bs(Url)}, ?TIMEOUT), ok). 162 | 163 | -spec get_url(atom()) -> {ok, string()} | request_error(). 164 | get_url(Name) -> 165 | mkRes(gen_server:call(Name, {get_url, sid}, ?TIMEOUT), fun check_str/1). 166 | 167 | -spec forward(atom()) -> ok | request_error(). 168 | forward(Name) -> 169 | mkRes(gen_server:call(Name, {forward, sid}, ?TIMEOUT), ok). 170 | 171 | -spec back(atom()) -> ok | request_error(). 172 | back(Name) -> 173 | mkRes(gen_server:call(Name, {back, sid}, ?TIMEOUT), ok). 174 | 175 | -spec refresh(atom()) -> ok | request_error(). 176 | refresh(Name) -> 177 | mkRes(gen_server:call(Name, {refresh, sid}, ?TIMEOUT), ok). 178 | 179 | %% Better for this function to work with pure json data! 180 | -spec execute(atom(), jsonstr(), jsonlist()) -> {ok, json()} | request_error(). 181 | execute(Name, Script, Args) -> 182 | mkRes(gen_server:call(Name, {execute, sid, Script, Args}, ?TIMEOUT), fun(X) -> X end). 183 | 184 | -spec execute_async(atom(), jsonstr(), jsonlist()) -> {ok, json()} | request_error(). 185 | execute_async(Name, Script, Args) -> 186 | mkRes(gen_server:call(Name, {execute_async, sid, Script, Args}, ?TIMEOUT), fun(X) -> X end). 187 | 188 | -spec get_screenshot(atom()) -> {ok, [char()]} | request_error(). 189 | get_screenshot(Name) -> 190 | mkRes(gen_server:call(Name, {get_screenshot, sid}, ?TIMEOUT), fun check_str/1). 191 | 192 | -spec get_ime_available_engines(atom()) -> {ok, [string()]} | request_error(). 193 | get_ime_available_engines(Name) -> 194 | mkRes(gen_server:call(Name, {get_ime_available_engines, sid}, ?TIMEOUT), 195 | fun(Es) -> check_list(Es, fun check_str/1) end). 196 | 197 | -spec get_ime_active_engine(atom()) -> {ok, string()} | request_error(). 198 | get_ime_active_engine(Name) -> 199 | mkRes(gen_server:call(Name, {get_ime_active_engine, sid}, ?TIMEOUT), fun check_str/1). 200 | 201 | -spec get_ime_activated(atom()) -> {ok, boolean()} | request_error(). 202 | get_ime_activated(Name) -> 203 | mkRes(gen_server:call(Name, {get_ime_activated, sid}, ?TIMEOUT), 204 | fun(B) -> check_bool(B) end). 205 | 206 | -spec ime_deactivate(atom()) -> ok | request_error(). 207 | ime_deactivate(Name) -> 208 | mkRes(gen_server:call(Name, {ime_deactivate, sid}, ?TIMEOUT), ok). 209 | 210 | -spec ime_activate(atom(), string()) -> ok | request_error(). 211 | ime_activate(Name, Engine) -> 212 | mkRes(gen_server:call(Name, {ime_activate, sid, bs(Engine)}, ?TIMEOUT), ok). 213 | 214 | %% Uses raw json!? 215 | -spec set_frame(atom(), json()) -> ok | request_error(). 216 | set_frame(Name, Frame) -> 217 | mkRes(gen_server:call(Name, {set_frame, sid, Frame}, ?TIMEOUT), ok). 218 | 219 | -spec set_window_focus(atom(), string()) -> ok | request_error(). 220 | set_window_focus(Name, Window) -> 221 | mkRes(gen_server:call(Name, {set_window_focus, sid, bs(Window)}, ?TIMEOUT), ok). 222 | 223 | -spec close_window(atom()) -> ok | request_error(). 224 | close_window(Name) -> 225 | mkRes(gen_server:call(Name, {close_window, sid}, ?TIMEOUT), ok). 226 | 227 | -spec set_window_size(atom(), number(), number()) -> ok | request_error(). 228 | set_window_size(Name, Width, Height) -> 229 | mkRes(gen_server:call(Name, {set_window_size, sid, "current", Width, Height}, ?TIMEOUT), ok). 230 | 231 | -spec set_window_size(atom(), string(), number(), number()) -> ok | request_error(). 232 | set_window_size(Name, WindowHandle, Width, Height) -> 233 | mkRes(gen_server:call(Name, {set_window_size, sid, WindowHandle, Width, Height}, ?TIMEOUT), ok). 234 | 235 | -spec get_window_size(atom()) -> {ok, {number(), number()}} | request_error(). 236 | get_window_size(Name) -> 237 | get_window_size(Name, "current"). 238 | 239 | -spec get_window_size(atom(), string()) -> {ok, {number(), number()}} | request_error(). 240 | get_window_size(Name, WindowHandle) -> 241 | mkRes(gen_server:call(Name, {get_window_size, sid, WindowHandle}, ?TIMEOUT), 242 | fun({obj, Dict}) -> 243 | Width = proplists:get_value("width", Dict, 0), 244 | Height = proplists:get_value("height", Dict, 0), 245 | {Width, Height}; 246 | (X) -> {type_error, exp_obj, X} 247 | end). 248 | 249 | -spec set_window_position(atom(), number(), number()) -> ok | request_error(). 250 | set_window_position(Name, X, Y) -> 251 | mkRes(gen_server:call(Name, {set_window_position, sid, X, Y}, ?TIMEOUT), ok). 252 | 253 | -spec set_window_position(atom(), string(), number(), number()) -> ok | request_error(). 254 | set_window_position(Name, WindowHandle, X, Y) -> 255 | mkRes(gen_server:call(Name, {set_window_position, sid, WindowHandle, X, Y}, ?TIMEOUT), ok). 256 | 257 | -spec get_window_position(atom()) -> {ok, {number(), number()}} | request_error(). 258 | get_window_position(Name) -> 259 | get_window_position(Name, "current"). 260 | 261 | -spec get_window_position(atom(), string()) -> {ok, {number(), number()}} | request_error(). 262 | get_window_position(Name, WindowHandle) -> 263 | mkRes(gen_server:call(Name, {get_window_position, sid, WindowHandle}, ?TIMEOUT), 264 | fun({obj, Dict}) -> 265 | X = proplists:get_value("x", Dict, 0), 266 | Y = proplists:get_value("y", Dict, 0), 267 | {X, Y}; 268 | (X) -> {type_error, exp_obj, X} 269 | end). 270 | 271 | -spec set_window_maximize(atom()) -> ok | request_error(). 272 | set_window_maximize(Name) -> 273 | mkRes(gen_server:call(Name, {set_window_maximize, sid}, ?TIMEOUT), ok). 274 | 275 | -spec set_window_maximize(atom(), string()) -> ok | request_error(). 276 | set_window_maximize(Name, WindowHandle) -> 277 | mkRes(gen_server:call(Name, {set_window_maximize, sid, WindowHandle}, ?TIMEOUT), ok). 278 | 279 | -spec get_cookies(atom()) -> {ok, [cookie()]} | request_error(). 280 | get_cookies(Name) -> 281 | mkRes(gen_server:call(Name, {get_cookies, sid}, ?TIMEOUT), fun(X) -> X end). 282 | 283 | -spec add_cookie(atom(), cookie()) -> ok | request_error(). 284 | add_cookie(Name, Cookie) -> 285 | mkRes(gen_server:call(Name, {add_cookie, sid, Cookie}, ?TIMEOUT), ok). 286 | 287 | -spec delete_cookies(atom()) -> ok | request_error(). 288 | delete_cookies(Name) -> 289 | mkRes(gen_server:call(Name, {delete_cookies, sid}, ?TIMEOUT), ok). 290 | 291 | -spec delete_cookie(atom(), string()) -> ok | request_error(). 292 | delete_cookie(Name, CookieName) -> 293 | mkRes(gen_server:call(Name, {delete_cookie, sid, CookieName}, ?TIMEOUT), ok). 294 | 295 | -spec get_page_source(atom()) -> {ok, string()} | request_error(). 296 | get_page_source(Name) -> 297 | mkRes(gen_server:call(Name, {get_page_source, sid}, ?TIMEOUT), fun check_str/1). 298 | 299 | -spec get_page_title(atom()) -> {ok, string()} | request_error(). 300 | get_page_title(Name) -> 301 | mkRes(gen_server:call(Name, {get_page_title, sid}, ?TIMEOUT), fun check_str/1). 302 | 303 | -spec find_element(atom(), string(), string()) -> {ok, string()} | request_error(). 304 | find_element(Name, Strategy, Value) -> 305 | V = unicode:characters_to_binary(Value, unicode, utf8), 306 | mkRes(gen_server:call(Name, {find_element, sid, bs(Strategy), V}, ?TIMEOUT), 307 | fun parse_web_element/1). 308 | 309 | -spec find_elements(atom(), string(), string()) -> {ok, [string()]} | request_error(). 310 | find_elements(Name, Strategy, Value) -> 311 | V = unicode:characters_to_binary(Value, unicode, utf8), 312 | mkRes(gen_server:call(Name, {find_elements, sid, bs(Strategy), V}, ?TIMEOUT), 313 | fun(Es) -> check_list(Es, fun parse_web_element/1) end). 314 | 315 | 316 | -spec get_active_element(atom()) -> {ok, string()} | request_error(). 317 | get_active_element(Name) -> 318 | mkRes(gen_server:call(Name, {get_active_element, sid}, ?TIMEOUT), 319 | fun parse_web_element/1). 320 | 321 | %% Currently undefined return value 322 | -spec get_element_info(atom(), string()) -> {ok, json()} | request_error(). 323 | get_element_info(Name, ElementId) -> 324 | mkRes(gen_server:call(Name, {get_element_info, sid, ElementId}, ?TIMEOUT), fun(X) -> X end). 325 | 326 | -spec find_element_rel(atom(), string(), string(), string()) -> 327 | {ok, string()} | request_error(). 328 | find_element_rel(Name, ElementId, Strategy, Value) -> 329 | mkRes(gen_server:call(Name, {find_element_rel, sid, ElementId, bs(Strategy), bs(Value)}, ?TIMEOUT), 330 | fun parse_web_element/1). 331 | 332 | -spec find_elements_rel(atom(), string(), string(), string()) -> 333 | {ok, [string()]} | request_error(). 334 | find_elements_rel(Name, ElementId, Strategy, Value) -> 335 | mkRes(gen_server:call(Name, {find_elements_rel, sid, ElementId, bs(Strategy), bs(Value)}, ?TIMEOUT), 336 | fun(Es) -> check_list(Es, fun parse_web_element/1) end). 337 | 338 | -spec click_element(atom(), string()) -> ok | request_error(). 339 | click_element(Name, ElementId) -> 340 | mkRes(gen_server:call(Name, {click_element, sid, ElementId}, ?TIMEOUT), ok). 341 | 342 | -spec submit(atom(), string()) -> ok | request_error(). 343 | submit(Name, ElementId) -> 344 | mkRes(gen_server:call(Name, {submit, sid, ElementId}, ?TIMEOUT), ok). 345 | 346 | %% Return type missing in spec!? 347 | -spec get_text(atom(), string()) -> {ok, string()} | request_error(). 348 | get_text(Name, ElementId) -> 349 | mkRes(gen_server:call(Name, {get_text, sid, ElementId}, ?TIMEOUT), fun check_str/1). 350 | 351 | -spec send_value(atom(), string(), string()) -> ok | request_error(). 352 | send_value(Name, ElementId, Value) -> 353 | mkRes(gen_server:call(Name, {send_value, sid, ElementId, bs(Value)}, ?TIMEOUT), ok). 354 | 355 | -spec send_keys(atom(), string()) -> ok | request_error(). 356 | send_keys(Name, Value) -> 357 | mkRes(gen_server:call(Name, {send_keys, sid, bs(Value)}, ?TIMEOUT), ok). 358 | 359 | -spec element_name(atom(), string()) -> {ok, string()} | request_error(). 360 | element_name(Name, ElementId) -> 361 | mkRes(gen_server:call(Name, {element_name, sid, ElementId}, ?TIMEOUT), fun check_str/1). 362 | 363 | -spec clear_element(atom(), string()) -> ok | request_error(). 364 | clear_element(Name, ElementId) -> 365 | mkRes(gen_server:call(Name, {clear_element, sid, ElementId}, ?TIMEOUT), ok). 366 | 367 | -spec is_selected_element(atom(), string()) -> {ok, boolean()} | request_error(). 368 | is_selected_element(Name, ElementId) -> 369 | mkRes(gen_server:call(Name, {is_selected_element, sid, ElementId}, ?TIMEOUT), 370 | fun(X) -> check_bool(X) end). 371 | 372 | -spec is_enabled_element(atom(), string()) -> {ok, boolean()} | request_error(). 373 | is_enabled_element(Name, ElementId) -> 374 | mkRes(gen_server:call(Name, {is_enabled_element, sid, ElementId}, ?TIMEOUT), 375 | fun(X) -> check_bool(X) end). 376 | 377 | -spec element_attribute(atom(), string(), string()) -> 378 | {ok, string() | null} | request_error(). 379 | element_attribute(Name, ElementId, EName) -> 380 | mkRes(gen_server:call(Name, {element_attribute, sid, ElementId, EName}, ?TIMEOUT), 381 | fun(X) -> type_or(check_str(X), check_null(X)) end). 382 | 383 | -spec are_elements_equal(atom(), string(), string()) -> {ok, boolean()} | request_error(). 384 | are_elements_equal(Name, ElementId1, ElementId2) -> 385 | mkRes(gen_server:call(Name, {element_attribute, sid, ElementId1, ElementId2}, ?TIMEOUT), 386 | fun(X) -> check_bool(X) end). 387 | 388 | -spec is_displayed_element(atom(), string()) -> {ok, boolean()} | request_error(). 389 | is_displayed_element(Name, ElementId) -> 390 | mkRes(gen_server:call(Name, {is_displayed_element, sid, ElementId}, ?TIMEOUT), 391 | fun(X) -> check_bool(X) end). 392 | 393 | -spec element_location(atom(), string()) -> {ok, {number(), number()}} | request_error(). 394 | element_location(Name, ElementId) -> 395 | mkRes(gen_server:call(Name, {element_location, sid, ElementId}, ?TIMEOUT), 396 | fun({obj, Dict}) -> 397 | X = proplists:get_value("x", Dict, 0), 398 | Y = proplists:get_value("y", Dict, 0), 399 | {X, Y}; 400 | (X) -> {type_error, exp_obj, X} 401 | end). 402 | 403 | -spec is_element_location_in_view(atom(), string()) -> 404 | {ok, {number(), number()}} | request_error(). 405 | is_element_location_in_view(Name, ElementId) -> 406 | mkRes(gen_server:call(Name, {is_element_location_in_view, sid, ElementId}, ?TIMEOUT), 407 | fun({obj, Dict}) -> 408 | X = proplists:get_value("x", Dict, 0), 409 | Y = proplists:get_value("y", Dict, 0), 410 | {X, Y}; 411 | (X) -> {type_error, exp_obj, X} 412 | end). 413 | 414 | -spec element_size(atom(), string()) -> {ok, {number(), number()}} | request_error(). 415 | element_size(Name, ElementId) -> 416 | mkRes(gen_server:call(Name, {element_size, sid, ElementId}, ?TIMEOUT), 417 | fun({obj, Dict}) -> 418 | Width = proplists:get_value("width", Dict, 0), 419 | Height = proplists:get_value("height", Dict, 0), 420 | {Width, Height}; 421 | (X) -> {type_error, exp_obj, X} 422 | end). 423 | 424 | -spec element_css_property(atom(), string(), string()) -> {ok, string()} | request_error(). 425 | element_css_property(Name, ElementId, Prop) -> 426 | mkRes(gen_server:call(Name, {element_css_property, sid, ElementId, Prop}, ?TIMEOUT), fun check_str/1). 427 | 428 | -spec get_browser_orientation(atom()) -> {ok, orientation()} | request_error(). 429 | get_browser_orientation(Name) -> 430 | mkRes(gen_server:call(Name, {get_browser_orientation, sid}, ?TIMEOUT), fun parse_orientation/1). 431 | 432 | -spec set_browser_orientation(atom(), orientation()) -> ok | request_error(). 433 | set_browser_orientation(Name, Dir) -> 434 | mkRes(gen_server:call(Name, {set_browser_orientation, sid, js_orientation(Dir)}, ?TIMEOUT), ok). 435 | 436 | -spec get_alert_text(atom()) -> {ok, string()} | request_error(). 437 | get_alert_text(Name) -> 438 | mkRes(gen_server:call(Name, {get_alert_text, sid}, ?TIMEOUT), fun check_str/1). 439 | 440 | -spec set_alert_text(atom(), string()) -> ok | request_error(). 441 | set_alert_text(Name, Str) -> 442 | mkRes(gen_server:call(Name, {set_alert_text, sid, bs(Str)}, ?TIMEOUT), ok). 443 | 444 | -spec accept_alert(atom()) -> ok | request_error(). 445 | accept_alert(Name) -> 446 | mkRes(gen_server:call(Name, {accept_alert, sid}, ?TIMEOUT), ok). 447 | 448 | -spec dismiss_alert(atom()) -> ok | request_error(). 449 | dismiss_alert(Name) -> 450 | mkRes(gen_server:call(Name, {dismiss_alert, sid}, ?TIMEOUT), ok). 451 | 452 | -spec moveto(atom(), string(), number(), number()) -> ok | request_error(). 453 | moveto(Name, Elem, XOffSet, YOffSet) -> 454 | mkRes(gen_server:call(Name, {moveto, sid, bs(Elem), XOffSet, YOffSet}, ?TIMEOUT), ok). 455 | 456 | -spec click(atom(), button()) -> ok | request_error(). 457 | click(Name, Button) -> 458 | mkRes(gen_server:call(Name, {click, sid, js_button(Button)}, ?TIMEOUT), ok). 459 | 460 | -spec buttondown(atom(), button()) -> ok | request_error(). 461 | buttondown(Name, Button) -> 462 | mkRes(gen_server:call(Name, {buttondown, sid, js_button(Button)}, ?TIMEOUT), ok). 463 | 464 | -spec buttonup(atom(), button()) -> ok | request_error(). 465 | buttonup(Name, Button) -> 466 | mkRes(gen_server:call(Name, {buttonup, sid, js_button(Button)}, ?TIMEOUT), ok). 467 | 468 | -spec doubleclick(atom()) -> ok | request_error(). 469 | doubleclick(Name) -> 470 | mkRes(gen_server:call(Name, {doubleclick, sid}, ?TIMEOUT), ok). 471 | 472 | %% SKIP Touch, Geo and Local storage for now... 473 | 474 | -spec get_log(atom(), string()) -> {ok, log()} | request_error(). 475 | get_log(Name, LogType) -> 476 | mkRes(gen_server:call(Name, {get_log, sid, bs(LogType)}, ?TIMEOUT), 477 | fun(LogEntries) -> check_list(LogEntries, fun parse_log_entry/1) end). 478 | 479 | -spec get_log_types(atom()) -> {ok, [string()]} | request_error(). 480 | get_log_types(Name) -> 481 | mkRes(gen_server:call(Name, {get_log_types, sid}, ?TIMEOUT), 482 | fun(Types) -> check_list(Types, fun check_str/1) end). 483 | 484 | -spec get_cache_status(atom()) -> {ok, cache_status()} | request_error(). 485 | get_cache_status(Name) -> 486 | mkRes(gen_server:call(Name, {get_cache_status, sid}, ?TIMEOUT), 487 | fun parse_cache_status/1). 488 | 489 | %%%=================================================================== 490 | %%% gen_server callbacks 491 | %%%=================================================================== 492 | 493 | %%-------------------------------------------------------------------- 494 | %% @private 495 | %% @doc 496 | %% Initializes the server 497 | %% 498 | %% @spec init(Args) -> {ok, State} | 499 | %% {ok, State, Timeout} | 500 | %% ignore | 501 | %% {stop, Reason} 502 | %% @end 503 | %%-------------------------------------------------------------------- 504 | init([SessionId, Url, Capabilities, ConnTimeout]) -> 505 | {ok, #state{ url = Url, capabilities = Capabilities, 506 | timeout = ConnTimeout, session_id = SessionId } }. 507 | 508 | %%-------------------------------------------------------------------- 509 | %% @private 510 | %% @doc 511 | %% Handling call messages 512 | %% 513 | %% @spec handle_call(Request, From, State) -> 514 | %% {reply, Reply, State} | 515 | %% {reply, Reply, State, Timeout} | 516 | %% {noreply, State} | 517 | %% {noreply, State, Timeout} | 518 | %% {stop, Reason, Reply, State} | 519 | %% {stop, Reason, State} 520 | %% @end 521 | %%-------------------------------------------------------------------- 522 | handle_call({Fun, sid}, _From, S) -> 523 | Reply = webdrv_wire:Fun(mkOpts(S)), 524 | {reply, Reply, S}; 525 | handle_call({Fun, sid, Arg1}, _From, S) -> 526 | Reply = webdrv_wire:Fun(mkOpts(S), Arg1), 527 | {reply, Reply, S}; 528 | handle_call({Fun, sid, Arg1, Arg2}, _From, S) -> 529 | Reply = webdrv_wire:Fun(mkOpts(S), Arg1, Arg2), 530 | {reply, Reply, S}; 531 | handle_call({Fun, sid, Arg1, Arg2, Arg3}, _From, S) -> 532 | Reply = webdrv_wire:Fun(mkOpts(S), Arg1, Arg2, Arg3), 533 | {reply, Reply, S}; 534 | handle_call(stop_session, _From, S) -> 535 | Reply = webdrv_wire:stop_session(mkOpts(S)), 536 | {stop, normal, Reply, S#state{ session_id = undefined, url = undefined }}; 537 | handle_call(stop_session_no_cleanup, _From, S) -> 538 | {stop, normal, ok, S}; 539 | handle_call(_Request, _From, State) -> 540 | Reply = ok, 541 | {reply, Reply, State}. 542 | 543 | %%-------------------------------------------------------------------- 544 | %% @private 545 | %% @doc 546 | %% Handling cast messages 547 | %% 548 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 549 | %% {noreply, State, Timeout} | 550 | %% {stop, Reason, State} 551 | %% @end 552 | %%-------------------------------------------------------------------- 553 | handle_cast(_Msg, State) -> 554 | {noreply, State}. 555 | 556 | %%-------------------------------------------------------------------- 557 | %% @private 558 | %% @doc 559 | %% Handling all non call/cast messages 560 | %% 561 | %% @spec handle_info(Info, State) -> {noreply, State} | 562 | %% {noreply, State, Timeout} | 563 | %% {stop, Reason, State} 564 | %% @end 565 | %%-------------------------------------------------------------------- 566 | handle_info(_Info, State) -> 567 | {noreply, State}. 568 | 569 | %%-------------------------------------------------------------------- 570 | %% @private 571 | %% @doc 572 | %% This function is called by a gen_server when it is about to 573 | %% terminate. It should be the opposite of Module:init/1 and do any 574 | %% necessary cleaning up. When it returns, the gen_server terminates 575 | %% with Reason. The return value is ignored. 576 | %% 577 | %% @spec terminate(Reason, State) -> void() 578 | %% @end 579 | %%-------------------------------------------------------------------- 580 | terminate(_Reason, _State) -> 581 | ok. 582 | 583 | %%-------------------------------------------------------------------- 584 | %% @private 585 | %% @doc 586 | %% Convert process state when code is changed 587 | %% 588 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 589 | %% @end 590 | %%-------------------------------------------------------------------- 591 | code_change(_OldVsn, State, _Extra) -> 592 | {ok, State}. 593 | 594 | %%%=================================================================== 595 | %%% Internal functions 596 | %%%=================================================================== 597 | mkOpts(#state{ session_id = SId, timeout = T, url = Url }) -> 598 | #webdrv_opts{ url = Url, timeout = T, session_id = SId }. 599 | 600 | parse_web_element({obj, [{"ELEMENT", BElem}]}) -> 601 | binary_to_list(BElem); 602 | parse_web_element(X) -> {type_error, exp_element_obj, X}. 603 | 604 | 605 | -spec parse_log_entry(jsonobj()) -> log_entry(). 606 | parse_log_entry({obj, Dict}) -> 607 | Timestamp = proplists:get_value("timestamp", Dict, -1), 608 | Level = proplists:get_value("level", Dict, ""), 609 | Msg = proplists:get_value("message", Dict, ""), 610 | {Timestamp, Level, Msg}; 611 | parse_log_entry(X) -> {type_error, exp_logentry_obj, X}. 612 | 613 | 614 | -spec parse_cache_status(number()) -> cache_status(). 615 | parse_cache_status(0) -> uncached; 616 | parse_cache_status(1) -> idle; 617 | parse_cache_status(2) -> checking; 618 | parse_cache_status(3) -> downloading; 619 | parse_cache_status(4) -> update_ready; 620 | parse_cache_status(5) -> obsolete; 621 | parse_cache_status(X) -> {type_error, exp_cache_status, X}. 622 | 623 | -spec parse_orientation(binary()) -> orientation(). 624 | parse_orientation(<<"PORTRAIT">>) -> portrait; 625 | parse_orientation(<<"LANDSCAPE">>) -> landscape; 626 | parse_orientation(X) -> {type_error, exp_orientation, X}. 627 | 628 | bs(String) when is_list(String) -> 629 | % unicode:characters_to_binary(String, unicode, utf8); 630 | list_to_binary(String); 631 | bs(BinString) when is_binary(BinString) -> 632 | BinString; 633 | %% bs(X) when is_number(X) -> X; 634 | %% bs(true) -> true; 635 | %% bs(false) -> false; 636 | bs(null) -> null. 637 | 638 | -spec js_button(button()) -> number(). 639 | js_button(left) -> 0; 640 | js_button(middle) -> 1; 641 | js_button(right) -> 2; 642 | js_button(N) -> N. 643 | 644 | -spec js_orientation(orientation()) -> binary(). 645 | js_orientation(portrait) -> <<"PORTRAIT">>; 646 | js_orientation(landscape) -> <<"LANDSCAPE">>. 647 | 648 | check_str(BinString) when is_binary(BinString) -> 649 | unicode:characters_to_list(BinString); 650 | check_str(X) -> 651 | {type_error, exp_binstring, X}. 652 | 653 | check_bool(X) when is_boolean(X) -> X; 654 | check_bool(X) -> {type_error, exp_boolean, X}. 655 | 656 | check_null(null) -> null; 657 | check_null(X) -> {type_error, exp_null, X}. 658 | 659 | check_list(X, F) when is_list(X) -> mkList(X, F, []); 660 | check_list(X, _) -> {type_error, exp_array, X}. 661 | 662 | mkList([], _, Xs) -> lists:reverse(Xs); 663 | mkList([X | Xs], F, Ys) -> 664 | case F(X) of 665 | E = {type_error, _E, _X} -> E; 666 | R -> mkList(Xs, F, [R | Ys]) 667 | end. 668 | 669 | type_or({type_error, E1, X}, {type_error, E2, _X}) -> 670 | {type_error, list_to_atom(lists:concat([E1, "_or_", E2])), X}; 671 | type_or({type_error, _, _}, X) -> X; 672 | type_or(X, _) -> X. 673 | 674 | mkRes({ok, _SId, _Res}, ok) -> ok; 675 | mkRes({ok, _SId, Res}, Fun) -> 676 | case Fun(Res) of 677 | E = {type_error, _E, _X} -> E; 678 | X -> {ok, X} 679 | end; 680 | mkRes(Res, _Fun) -> Res. 681 | -------------------------------------------------------------------------------- /test/webdrv_eqc.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Hans Svensson