├── LICENSE ├── README.md ├── default.nix └── wd /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mikael Brockman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wd -- WebDriver command line tool 2 | 3 | `wd` is a simple tool for interacting with servers that implement the 4 | W3C WebDriver API. 5 | 6 | It can be used for web automation tasks such as testing and scraping. 7 | 8 | You can use [Selenium](http://www.seleniumhq.org/) as the WebDriver server 9 | to control browsers on your own machine. 10 | 11 | There are commercial services that offer the WebDriver API remotely; see 12 | "Functional Test Services" [here](http://www.seleniumhq.org/ecosystem/). 13 | 14 | See [the WebDriver spec](https://w3c.github.io/webdriver/webdriver-spec.html) 15 | for details about the protocol and behavior. 16 | 17 | ## Dependencies 18 | 19 | - `bash` 20 | - `perl` (5.14 or greater) 21 | - `curl` 22 | 23 | ## Example session 24 | 25 | $ export WEBDRIVER_BROWSER=chrome 26 | $ export WEBDRIVER_SESSION="$(wd new-session)" 27 | $ wd go https://github.com/mbrock/wd 28 | $ wd screenshot > /tmp/wd.png 29 | $ wd click "$(wd find css .user-mention)" 30 | $ wd exec 'return document.title' 31 | 32 | ## Configuration 33 | 34 | - `WEBDRIVER_URL`: WebDriver API URL (default `http://127.0.0.1:4444/wd/hub`) 35 | 36 | ## Command reference 37 | 38 | ### Managing sessions 39 | 40 | #### `wd new-session` 41 | 42 | Prints the new session ID. 43 | 44 | All other commands expect this ID to be in `WEBDRIVER_SESSION`, so 45 | 46 | export WEBDRIVER_SESSION="$(wd new-session)" 47 | 48 | is a useful pattern. 49 | 50 | You must configure desired capabilities by setting either 51 | 52 | - `WEBDRIVER_CAPABILITIES` to a stringified JSON object, or 53 | - `WEBDRIVER_BROWSER` to a browser name (`chrome`, `firefox`, etc). 54 | 55 | #### `wd delete-session` 56 | 57 | Deletes the current session. 58 | 59 | ### Navigation 60 | 61 | #### `wd go ` 62 | 63 | Opens `` in the current window. 64 | 65 | #### `wd back` 66 | 67 | Navigates back in the current window. 68 | 69 | #### `wd forward` 70 | 71 | Navigates forward in the current window. 72 | 73 | #### `wd refresh` 74 | 75 | Refreshes the page of the current window. 76 | 77 | ### Element finding 78 | 79 | #### `wd find ...` 80 | 81 | Finds one matching element and prints its element ID. 82 | 83 | The `` can be one of: 84 | 85 | - `css` (CSS selector) 86 | - `xpath` (XPath selector) 87 | - `id` (element ID) 88 | - `name` (element name) 89 | - `class` (element class name) 90 | - `tag` (element tag name) 91 | - `link` (element link text) 92 | - `partial` (partial element link text) 93 | 94 | The `` values are concatenated for convenience. 95 | 96 | Example: 97 | 98 | $ wd find css article header img.avatar 99 | 100 | #### `wd find-all ...` 101 | 102 | See `wd find`; finds all matching elements. 103 | 104 | #### `wd find-in ...` 105 | 106 | See `wd find`; finds one matching sub-element. 107 | 108 | #### `wd find-all-in ...` 109 | 110 | See `wd find`; finds all matching sub-elements. 111 | 112 | ### Element information 113 | 114 | #### `wd is-selected ` 115 | 116 | Exits with a non-zero status if the element is not a selected or 117 | checked `input` or `option`. 118 | 119 | #### `wd is-enabled ` 120 | 121 | Exits with a non-zero status if the element is not an enabled 122 | form control. 123 | 124 | #### `wd attribute ` 125 | 126 | Prints an element attribute value. 127 | 128 | Exits with non-zero status if the given attribute does not exist. 129 | 130 | #### `wd css-value ` 131 | 132 | Prints an element CSS property value. 133 | 134 | Exits with non-zero status if the given style property does not exist. 135 | 136 | #### `wd text ` 137 | 138 | Prints an element's `innerText`. 139 | 140 | #### `wd tag-name ` 141 | 142 | Prints the tag name of an element. 143 | 144 | ### Element actions 145 | 146 | #### `wd click ` 147 | 148 | Clicks an element. 149 | 150 | #### `wd clear ` 151 | 152 | Clears the value, checkedness, or text content of an element. 153 | 154 | #### `wd send-keys [keys] ...` 155 | 156 | Sends keys to an element. 157 | 158 | Key arguments are concatenated for convenience. 159 | 160 | Example: 161 | 162 | $ wd send-keys "$(wd find id search)" webdriver json api 163 | 164 | ### JavaScript execution 165 | 166 | #### `wd execute [argument] ...` 167 | 168 | Evaluates the JavaScript code `` as a function called with the 169 | given arguments. 170 | 171 | Prints the return value of the specified function. 172 | 173 | #### `wd execute-async [argument] ...` 174 | 175 | Evaluates as in `wd execute` but waiting for the script to invoke a callback 176 | which is passed as an additional final argument to the specified function. 177 | 178 | Prints the value finally passed to the callback. 179 | 180 | ### Page information 181 | 182 | #### `wd get-current-url` 183 | 184 | Prints the URL of the page in the current window. 185 | 186 | #### `wd get-title` 187 | 188 | Prints the title of the page in the current window. 189 | 190 | #### `wd get-page-source` 191 | 192 | Prints the raw HTML source of the page in the current window. 193 | 194 | ### Windows 195 | 196 | #### `wd get-window-size` 197 | 198 | Prints the current window's width and height on separate lines. 199 | 200 | #### `wd set-window-size ` 201 | 202 | Changes the size of the current window. 203 | 204 | #### `wd maximize` 205 | 206 | Maximizes the current window. 207 | 208 | #### `wd get-window-handle` 209 | 210 | Prints the window handle of the current window. 211 | 212 | #### `wd get-window-handles` 213 | 214 | Prints a list of all window handles in the current session. 215 | 216 | #### `wd close-window` 217 | 218 | Close the current window. 219 | 220 | #### `wd switch-to-window ` 221 | 222 | Changes which window is the current window. 223 | 224 | ### Frames 225 | 226 | #### `wd switch-to-frame ` 227 | 228 | Changes the current frame. 229 | 230 | `` can be either a number or an element ID. 231 | 232 | See [the specification](https://www.w3.org/TR/webdriver/#switch-to-frame) 233 | for exact details. 234 | 235 | #### `wd switch-to-top-level-frame` 236 | 237 | Resets the current frame to the top level. 238 | 239 | #### `wd switch-to-parent-frame` 240 | 241 | Sets the current frame to the parent of the current frame. 242 | 243 | ### Cookies 244 | 245 | See [the spec](https://w3c.github.io/webdriver/webdriver-spec.html#cookies) 246 | for details on cookie JSON serialization. 247 | 248 | #### `wd cookies` 249 | 250 | Prints the currently set cookies as a JSON array. 251 | 252 | #### `wd cookie ` 253 | 254 | Prints the cookie named `` as JSON. 255 | 256 | #### `wd add-cookie ...` 257 | 258 | Adds a cookie according to the given keys/values. 259 | 260 | Example: `wd add-cookie name '"user"' value '"mbrock"'` 261 | 262 | #### `wd delete-cookie ` 263 | 264 | Deletes the cookie whose name is ``. 265 | 266 | #### `wd delete-cookies` 267 | 268 | Deletes all cookies. 269 | 270 | ### Screenshots 271 | 272 | #### `wd screenshot` 273 | 274 | Prints a binary PNG screenshot to stdout. 275 | 276 | #### `wd element-screenshot ` 277 | 278 | Prints a binary PNG screenshot of a specific element to stdout. 279 | 280 | (Not supported by Chrome.) 281 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, stdenv, lib, ... }: 2 | 3 | let deps = [pkgs.perl pkgs.curl]; in 4 | 5 | stdenv.mkDerivation rec { 6 | name = "wd-${version}"; 7 | version = "0.11"; 8 | meta = { 9 | description = "Command-line tool for WebDriver API"; 10 | homepage = https://github.com/mbrock/wd; 11 | license = lib.licenses.mit; 12 | }; 13 | 14 | src = ./.; 15 | buildInputs = [pkgs.makeWrapper]; 16 | buildPhase = "true"; 17 | installPhase = 18 | let path = lib.makeBinPath deps; in '' 19 | mkdir -p $out/bin && cp $src/wd $out/bin; 20 | wrapProgram "$out/bin/wd" --prefix PATH : "${path}" 21 | ''; 22 | } 23 | -------------------------------------------------------------------------------- /wd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # wd -- the WebDriver command line interface 3 | # 4 | # Copyright 2016 Mikael Brockman 5 | # 6 | # MIT license: 7 | # 8 | # Permission is hereby granted, free of charge, to any person 9 | # obtaining a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | set -o pipefail 28 | #set -x 29 | 30 | shopt -s extglob # extended case patterns 31 | 32 | main() { 33 | : "${WEBDRIVER_URL:=http://127.0.0.1:4444/wd/hub}" 34 | 35 | cmd=$1; shift 36 | case $cmd in 37 | new-session) 38 | wd-new-session ;; 39 | delete-session) 40 | wd-delete-session ;; 41 | 42 | go) 43 | wd-go "$@" ;; 44 | back) 45 | wd-post-session /back ;; 46 | forward) 47 | wd-post-session /forward ;; 48 | refresh) 49 | wd-post-session /refresh ;; 50 | 51 | get-current-url) 52 | wd-get-current-url ;; 53 | get-title) 54 | wd-get-title ;; 55 | get-page-source | page-source) 56 | wd-get-page-source "$@" ;; 57 | 58 | get-window-handle) 59 | wd-get-window-handle ;; 60 | get-window-handles) 61 | wd-get-window-handles ;; 62 | 63 | close-window) 64 | wd-close-window ;; 65 | 66 | switch-to-window) 67 | wd-switch-to-window "$@" ;; 68 | switch-to-frame) 69 | wd-switch-to-frame "$@" ;; 70 | switch-to-parent-frame) 71 | wd-post-session /frame/parent ;; 72 | 73 | get-window-size) 74 | wd-get-window-size ;; 75 | set-window-size) 76 | wd-set-window-size "$@" ;; 77 | maximize) 78 | wd-maximize ;; 79 | 80 | find-element | find) 81 | wd-find-element "$@" ;; 82 | find-elements | find-all) 83 | wd-find-elements "$@" ;; 84 | find-element-in | find-in | find-element-from-element) 85 | wd-find-element-from-element "$@" ;; 86 | find-elements-in | find-all-in | find-elements-from-element) 87 | wd-find-elements-from-element "$@" ;; 88 | 89 | get-element-attribute | attr | attribute) 90 | wd-get-element-attribute "$@" ;; 91 | get-element-css-value | css-value) 92 | wd-get-element-css-value "$@" ;; 93 | get-element-text | text) 94 | wd-get-element-text "$@" ;; 95 | get-element-tag-name | tag-name) 96 | wd-get-element-tag-name "$@" ;; 97 | 98 | is-element-selected | is-selected) 99 | wd-is-element-selected "$@" ;; 100 | is-element-enabled | is-enabled) 101 | wd-is-element-enabled "$@" ;; 102 | 103 | element-click | click) 104 | wd-element-click "$@" ;; 105 | element-clear | clear) 106 | wd-element-clear "$@" ;; 107 | element-send-keys | send-keys) 108 | wd-element-send-keys "$@" ;; 109 | 110 | execute-script | execute | exec) 111 | wd-execute-script-sync "$@" ;; 112 | execute-script-async | execute-async | exec-async) 113 | wd-execute-script-async "$@" ;; 114 | 115 | get-all-cookies | cookies) 116 | wd-get-all-cookies ;; 117 | get-named-cookie | cookie) 118 | wd-get-named-cookie "$@" ;; 119 | add-cookie) 120 | wd-add-cookie "$@" ;; 121 | delete-cookie) 122 | wd-delete-cookie "$@" ;; 123 | delete-cookies) 124 | wd-delete-cookies ;; 125 | 126 | take-screenshot | screenshot) 127 | wd-take-screenshot ;; 128 | take-element-screenshot | element-screenshot) 129 | wd-take-element-screenshot "$@" ;; 130 | 131 | help) 132 | print-help ;; 133 | 134 | *) 135 | cat >&2 <<. 136 | Usage: wd [arguments] 137 | Help: wd help 138 | . 139 | exit 1 140 | ;; 141 | esac 142 | } 143 | 144 | die() { 145 | rc=$1; shift; echo "$0:" "$@" >&2; exit "$rc" 146 | } 147 | 148 | json-obj() { 149 | perl -mJSON::PP -e ' 150 | use List::Util qw(pairmap); 151 | $j = JSON::PP->new->allow_nonref; 152 | print "{", join(",", pairmap { $j->encode($a) .":". $b } @ARGV), "}" 153 | ' "$@" 154 | } 155 | 156 | json-arr() { 157 | perl -e ' 158 | print "[", join(",", @ARGV), "]" 159 | ' "$@" 160 | } 161 | 162 | json-keyarr() { 163 | perl -mJSON::PP -e ' 164 | print 165 | "[", 166 | (join ",\" \",", 167 | map { 168 | join ",", 169 | map { JSON::PP->new->allow_nonref->encode($_) } 170 | split(//, $_) 171 | } @ARGV), 172 | "]" 173 | ' "$@" 174 | } 175 | 176 | json-str() { 177 | perl -mJSON::PP -e 'print JSON::PP->new->allow_nonref->encode($ARGV[0])' "$1" 178 | } 179 | 180 | json-get() { 181 | perl -mJSON::PP -e ' 182 | local $/; my $s = || exit 1; 183 | my $j = JSON::PP->new->allow_nonref; 184 | my $data = $j->decode($s); 185 | my $x = $data; 186 | foreach (@ARGV) { 187 | if (ref($x) eq "ARRAY") { 188 | # convert array to k=>v hash 189 | my %h = map { $_ => $x->[$_] } 0..$#$x; 190 | $x = \%h; 191 | } 192 | exists $x->{$_} or die "invalid JSON index"; 193 | $x = $x->{$_}; 194 | } 195 | print $j->encode($x), "\n"; 196 | ' "$@" 197 | } 198 | 199 | json-raw() { 200 | perl -mJSON::PP -e ' 201 | local $/; my $s = || exit 1; 202 | my $j = JSON::PP->new->allow_nonref; 203 | sub flat { 204 | my $x = shift; 205 | if (ref($x) eq "ARRAY") { 206 | flat($_) foreach @$x; 207 | } elsif (ref($x) eq "HASH") { 208 | flat($_) foreach values %$x; 209 | } else { 210 | print $x, "\n"; 211 | } 212 | } 213 | flat $j->decode($s); 214 | ' 215 | } 216 | 217 | json-raw-nonnull() { 218 | local x 219 | x=$(cat) 220 | case $x in 221 | null) false ;; 222 | *) json-raw <<<"$x" ;; 223 | esac 224 | } 225 | 226 | json-get-raw() { json-get "$@" | json-raw; } 227 | json-get-raw-nonnull() { json-get "$@" | json-raw-nonnull; } 228 | 229 | decode-base64() { 230 | perl -MMIME::Base64 -e 'print decode_base64()' 231 | } 232 | 233 | hush() { "$@" >/dev/null; } 234 | 235 | wd-post() { 236 | if [[ $# -eq 2 ]]; then 237 | curl -sSL -H "Content-Type: application/json" --data-raw "$2" \ 238 | "$WEBDRIVER_URL""$1" 239 | elif [[ $# -eq 1 ]]; then 240 | curl -sSL -XPOST "$WEBDRIVER_URL""$1" 241 | else 242 | die 127 internal error 243 | fi 244 | } 245 | 246 | wd-new-session() { 247 | local capabilities 248 | if [[ -n "$WEBDRIVER_CAPABILITIES" ]]; then 249 | capabilities="$WEBDRIVER_CAPABILITIES" 250 | elif [[ -n "$WEBDRIVER_BROWSER" ]]; then 251 | capabilities="$(json-obj browserName "$(json-str "$WEBDRIVER_BROWSER")")" 252 | else 253 | die 1 "neither WEBDRIVER_CAPABILITIES nor WEBDRIVER_BROWSER set" 254 | fi 255 | wd-post /session "$(json-obj capabilities "$capabilities")" | 256 | json-get-raw-nonnull value sessionId 257 | } 258 | 259 | wd-assert-session() { 260 | if [[ -z "$WEBDRIVER_SESSION" ]]; then 261 | die 1 WEBDRIVER_SESSION not defined 262 | fi 263 | } 264 | 265 | wd-get-session() { 266 | wd-assert-session 267 | curl -sSL "$WEBDRIVER_URL"/session/"$WEBDRIVER_SESSION""$1" 268 | } 269 | 270 | wd-checked-result() { 271 | local result 272 | result=$(cat) 273 | json-get value <<<"$result" 274 | } 275 | 276 | wd-post-session-value() { wd-post-session "$@" | wd-checked-result; } 277 | wd-get-session-simple() { wd-get-session "$@" | json-get-raw-nonnull value; } 278 | wd-get-session-value() { wd-get-session "$@" | wd-checked-result; } 279 | wd-get-session-value-raw() { wd-get-session-value "$@" | json-raw-nonnull; } 280 | wd-get-element-value() { wd-get-session-value-raw /element/"$1"/"$2"; } 281 | 282 | wd-get-current-url() { wd-get-session-simple /url; } 283 | wd-get-title() { wd-get-session-simple /title; } 284 | wd-get-window-handle() { wd-get-session-simple /window; } 285 | wd-get-page-source() { wd-get-session-value /source | json-raw; } 286 | 287 | wd-delete-session() { 288 | wd-assert-session 289 | curl -sSL -XDELETE "$WEBDRIVER_URL"/session/"$WEBDRIVER_SESSION""$1" 290 | } 291 | 292 | wd-post-session() { 293 | wd-assert-session 294 | url=$1; shift 295 | wd-post /session/"$WEBDRIVER_SESSION""$url" "$@" | wd-checked-result 296 | } 297 | 298 | wd-go() { 299 | hush wd-post-session /url "$(json-obj url "$(json-str "$1")")" 300 | } 301 | 302 | wd-close-window() { 303 | hush wd-delete-session /window 304 | } 305 | 306 | wd-switch-to-window() { 307 | hush wd-post-session /window "$(json-obj handle "$(json-str "$1")")" 308 | } 309 | 310 | wd-get-window-handles() { 311 | wd-get-session /window/handles | json-get-raw value 312 | } 313 | 314 | wd-switch-to-frame() { 315 | local id 316 | case $1 in 317 | +([0-9]) ) id=$1 ;; 318 | * ) id="$(json-str "$1")" ;; 319 | esac 320 | hush wd-post-session /frame "$(json-obj id "$id")" 321 | } 322 | 323 | wd-switch-to-top-level-frame() { 324 | hush wd-post-session frame "$(json-obj id null)" 325 | } 326 | 327 | wd-get-window-size() { 328 | local result 329 | result=$(wd-get-session /window/size) 330 | echo "$result" | json-get-raw value width 331 | echo "$result" | json-get-raw value height 332 | } 333 | 334 | wd-set-window-size() { 335 | hush wd-post-session /window/size "$(json-obj width "$1" height "$2")" 336 | } 337 | 338 | wd-maximize() { 339 | hush wd-post-session /window/maximize 340 | } 341 | 342 | wd-parse-strategy() { 343 | case $1 in 344 | id) json-str "id" ;; 345 | name) json-str "name" ;; 346 | class*) json-str "class name" ;; 347 | tag*) json-str "tag name" ;; 348 | css*) json-str "css selector" ;; 349 | link*) json-str "link text" ;; 350 | partial*) json-str "partial link text" ;; 351 | xpath*) json-str xpath ;; 352 | *) die 1 unknown element location strategy: "$1" 353 | esac 354 | } 355 | 356 | wd-general-element-find() { 357 | local path="$1"; shift 358 | local strategy 359 | strategy="$(wd-parse-strategy "$1")" 360 | [[ $? -eq 0 ]] || exit $? 361 | shift 362 | local value 363 | value=$(json-str "$*") 364 | wd-post-session "$path" "$(json-obj using "$strategy" value "$value")" | 365 | json-raw 366 | } 367 | 368 | wd-find-element() { wd-general-element-find /element "$@"; } 369 | wd-find-elements() { wd-general-element-find /elements "$@"; } 370 | 371 | wd-find-element-from-element() { 372 | local element=$1; shift 373 | wd-general-element-find /element/"$element"/element "$@" 374 | } 375 | 376 | wd-find-elements-from-element() { 377 | local element=$1; shift 378 | wd-general-element-find /element/"$element"/elements "$@" 379 | } 380 | 381 | wd-get-element-attribute() { 382 | local x 383 | x=$(wd-get-session-value-raw /element/"$1"/attribute/"$2") 384 | [[ $? -eq 0 ]] || die 1 "attribute not found" 385 | echo "$x" 386 | } 387 | 388 | wd-get-element-css-value() { 389 | local x 390 | x=$(wd-get-session-value-raw /element/"$1"/css/"$2") 391 | [[ $? -eq 0 ]] || die 1 "CSS property not found" 392 | echo "$x" 393 | } 394 | 395 | wd-get-element-text() { wd-get-element-value "$1" text; } 396 | wd-get-element-tag-name() { wd-get-element-value "$1" name; } 397 | 398 | wd-is-element-enabled() { [[ 1 -eq $(wd-get-element-value "$1" enabled) ]]; } 399 | wd-is-element-selected() { [[ 1 -eq $(wd-get-element-value "$1" selected) ]]; } 400 | 401 | wd-element-simple-action() { 402 | local x y 403 | x=$1; shift; y=$1; shift 404 | hush wd-post-session /element/"$x"/"$y" "$@" 405 | } 406 | 407 | wd-element-click() { wd-element-simple-action "$1" click; } 408 | wd-element-clear() { wd-element-simple-action "$1" clear; } 409 | 410 | wd-element-send-keys() { 411 | local element keys 412 | element=$1; shift 413 | # keys=$(json-keyarr "$*") 414 | wd-element-simple-action "$element" value "$(json-obj text "$(json-str "$*")")" 415 | } 416 | 417 | wd-execute-script-sync() { 418 | local script="$1"; shift 419 | wd-post-session /execute/sync "$(json-obj \ 420 | script "$(json-str "$script")" \ 421 | args "$(json-arr "$@")" \ 422 | )" 423 | } 424 | 425 | wd-execute-script-async() { 426 | local script="$1"; shift 427 | wd-post-session /execute/async "$(json-obj \ 428 | script "$(json-str "$script")" \ 429 | args "$(json-arr "$@")" \ 430 | )" 431 | } 432 | 433 | wd-get-all-cookies() { wd-get-session-value /cookie; } 434 | wd-get-named-cookie() { wd-get-session-value /cookie/"$1"; } 435 | 436 | wd-add-cookie() { 437 | hush wd-post-session /cookie "$(json-obj cookie "$(json-obj "$@")")" 438 | } 439 | 440 | wd-delete-cookie() { wd-delete-session /cookie/"$1"; } 441 | wd-delete-cookies() { wd-delete-session /cookie; } 442 | 443 | wd-take-screenshot() { 444 | wd-get-session-value /screenshot | json-raw | decode-base64 445 | } 446 | 447 | wd-take-element-screenshot() { 448 | wd-get-session-value /element/"$1"/screenshot | json-raw | decode-base64 449 | } 450 | 451 | print-help() { 452 | cat <<'.' 453 | # wd -- WebDriver command line tool 454 | 455 | `wd` is a simple tool for interacting with servers that implement the 456 | W3C WebDriver API. 457 | 458 | It can be used for web automation tasks such as testing and scraping. 459 | 460 | You can use [Selenium](http://www.seleniumhq.org/) as the WebDriver server 461 | to control browsers on your own machine. 462 | 463 | There are commercial services that offer the WebDriver API remotely; see 464 | "Functional Test Services" [here](http://www.seleniumhq.org/ecosystem/). 465 | 466 | See [the WebDriver spec](https://w3c.github.io/webdriver/webdriver-spec.html) 467 | for details about the protocol and behavior. 468 | 469 | ## Dependencies 470 | 471 | - `bash` 472 | - `perl` (5.14 or greater) 473 | - `curl` 474 | 475 | ## Example session 476 | 477 | $ export WEBDRIVER_BROWSER=chrome 478 | $ export WEBDRIVER_SESSION="$(wd new-session)" 479 | $ wd go https://github.com/mbrock/wd 480 | $ wd screenshot > /tmp/wd.png 481 | $ wd click "$(wd find css .user-mention)" 482 | $ wd exec 'return document.title' 483 | 484 | ## Configuration 485 | 486 | - `WEBDRIVER_URL`: WebDriver API URL (default `http://127.0.0.1:4444/wd/hub`) 487 | 488 | ## Command reference 489 | 490 | ### Managing sessions 491 | 492 | #### `wd new-session` 493 | 494 | Prints the new session ID. 495 | 496 | All other commands expect this ID to be in `WEBDRIVER_SESSION`, so 497 | 498 | export WEBDRIVER_SESSION="$(wd new-session)" 499 | 500 | is a useful pattern. 501 | 502 | You must configure desired capabilities by setting either 503 | 504 | - `WEBDRIVER_CAPABILITIES` to a stringified JSON object, or 505 | - `WEBDRIVER_BROWSER` to a browser name (`chrome`, `firefox`, etc). 506 | 507 | #### `wd delete-session` 508 | 509 | Deletes the current session. 510 | 511 | ### Navigation 512 | 513 | #### `wd go ` 514 | 515 | Opens `` in the current window. 516 | 517 | #### `wd back` 518 | 519 | Navigates back in the current window. 520 | 521 | #### `wd forward` 522 | 523 | Navigates forward in the current window. 524 | 525 | #### `wd refresh` 526 | 527 | Refreshes the page of the current window. 528 | 529 | ### Element finding 530 | 531 | #### `wd find ...` 532 | 533 | Finds one matching element and prints its element ID. 534 | 535 | The `` can be one of: 536 | 537 | - `css` (CSS selector) 538 | - `xpath` (XPath selector) 539 | - `id` (element ID) 540 | - `name` (element name) 541 | - `class` (element class name) 542 | - `tag` (element tag name) 543 | - `link` (element link text) 544 | - `partial` (partial element link text) 545 | 546 | The `` values are concatenated for convenience. 547 | 548 | Example: 549 | 550 | $ wd find css article header img.avatar 551 | 552 | #### `wd find-all ...` 553 | 554 | See `wd find`; finds all matching elements. 555 | 556 | #### `wd find-in ...` 557 | 558 | See `wd find`; finds one matching sub-element. 559 | 560 | #### `wd find-all-in ...` 561 | 562 | See `wd find`; finds all matching sub-elements. 563 | 564 | ### Element information 565 | 566 | #### `wd is-selected ` 567 | 568 | Exits with a non-zero status if the element is not a selected or 569 | checked `input` or `option`. 570 | 571 | #### `wd is-enabled ` 572 | 573 | Exits with a non-zero status if the element is not an enabled 574 | form control. 575 | 576 | #### `wd attribute ` 577 | 578 | Prints an element attribute value. 579 | 580 | Exits with non-zero status if the given attribute does not exist. 581 | 582 | #### `wd css-value ` 583 | 584 | Prints an element CSS property value. 585 | 586 | Exits with non-zero status if the given style property does not exist. 587 | 588 | #### `wd text ` 589 | 590 | Prints an element's `innerText`. 591 | 592 | #### `wd tag-name ` 593 | 594 | Prints the tag name of an element. 595 | 596 | ### Element actions 597 | 598 | #### `wd click ` 599 | 600 | Clicks an element. 601 | 602 | #### `wd clear ` 603 | 604 | Clears the value, checkedness, or text content of an element. 605 | 606 | #### `wd send-keys [keys] ...` 607 | 608 | Sends keys to an element. 609 | 610 | Key arguments are concatenated for convenience. 611 | 612 | Example: 613 | 614 | $ wd send-keys "$(wd find id search)" webdriver json api 615 | 616 | ### JavaScript execution 617 | 618 | #### `wd execute [argument] ...` 619 | 620 | Evaluates the JavaScript code `` as a function called with the 621 | given arguments. 622 | 623 | Prints the return value of the specified function. 624 | 625 | #### `wd execute-async [argument] ...` 626 | 627 | Evaluates as in `wd execute` but waiting for the script to invoke a callback 628 | which is passed as an additional final argument to the specified function. 629 | 630 | Prints the value finally passed to the callback. 631 | 632 | ### Page information 633 | 634 | #### `wd get-current-url` 635 | 636 | Prints the URL of the page in the current window. 637 | 638 | #### `wd get-title` 639 | 640 | Prints the title of the page in the current window. 641 | 642 | #### `wd get-page-source` 643 | 644 | Prints the raw HTML source of the page in the current window. 645 | 646 | ### Windows 647 | 648 | #### `wd get-window-size` 649 | 650 | Prints the current window's width and height on separate lines. 651 | 652 | #### `wd set-window-size ` 653 | 654 | Changes the size of the current window. 655 | 656 | #### `wd maximize` 657 | 658 | Maximizes the current window. 659 | 660 | #### `wd get-window-handle` 661 | 662 | Prints the window handle of the current window. 663 | 664 | #### `wd get-window-handles` 665 | 666 | Prints a list of all window handles in the current session. 667 | 668 | #### `wd switch-to-window ` 669 | 670 | Changes which window is the current window. 671 | 672 | ### Frames 673 | 674 | #### `wd switch-to-frame ` 675 | 676 | Changes the current frame. 677 | 678 | `` can be either a number or an element ID. 679 | 680 | See [the specification](https://www.w3.org/TR/webdriver/#switch-to-frame) 681 | for exact details. 682 | 683 | #### `wd switch-to-top-level-frame` 684 | 685 | Resets the current frame to the top level. 686 | 687 | #### `wd switch-to-parent-frame` 688 | 689 | Sets the current frame to the parent of the current frame. 690 | 691 | ### Cookies 692 | 693 | See [the spec](https://w3c.github.io/webdriver/webdriver-spec.html#cookies) 694 | for details on cookie JSON serialization. 695 | 696 | #### `wd cookies` 697 | 698 | Prints the currently set cookies as a JSON array. 699 | 700 | #### `wd cookie ` 701 | 702 | Prints the cookie named `` as JSON. 703 | 704 | #### `wd add-cookie ...` 705 | 706 | Adds a cookie according to the given keys/values. 707 | 708 | Example: `wd add-cookie name '"user"' value '"mbrock"'` 709 | 710 | #### `wd delete-cookie ` 711 | 712 | Deletes the cookie whose name is ``. 713 | 714 | #### `wd delete-cookies` 715 | 716 | Deletes all cookies. 717 | 718 | ### Screenshots 719 | 720 | #### `wd screenshot` 721 | 722 | Prints a binary PNG screenshot to stdout. 723 | 724 | #### `wd element-screenshot ` 725 | 726 | Prints a binary PNG screenshot of a specific element to stdout. 727 | 728 | (Not supported by Chrome.) 729 | . 730 | } 731 | 732 | main "$@" 733 | --------------------------------------------------------------------------------